├── cloudeos ├── version.go ├── diff_suppress_funcs.go ├── arista │ └── clouddeploy.v1 │ │ ├── gen_proto │ │ └── gen.go ├── provider_test.go ├── validators.go ├── client_test.go ├── provider.go ├── resource_cloudeos_aws_vpn_test.go ├── resource_cloudeos_subnet.go ├── resource_cloudeos_wan.go ├── resource_cloudeos_clos.go ├── client_utils.go ├── awstgw.go ├── resource_cloudeos_aws_vpn.go ├── resource_cloudeos_clos_test.go ├── resource_cloudeos_topology.go ├── resource_cloudeos_wan_test.go ├── resource_cloudeos_vpc_status.go ├── resource_cloudeos_vpc_config_test.go ├── resource_cloudeos_subnet_test.go ├── resource_cloudeos_router_status.go └── utils.go ├── go.mod ├── cloudvision-apis ├── arista │ ├── time │ │ └── time.proto │ ├── subscriptions │ │ └── subscriptions.proto │ ├── redirector.v1 │ │ ├── redirector.proto │ │ └── services.gen.proto │ ├── endpointlocation.v1 │ │ ├── services.gen.proto │ │ └── endpointlocation.proto │ ├── tag.v1 │ │ └── tag.proto │ ├── tag.v2 │ │ └── tag.proto │ ├── event.v1 │ │ ├── event.proto │ │ └── services.gen.proto │ ├── identityprovider.v1 │ │ └── identityprovider.proto │ ├── workspace.v1 │ │ └── workspace.proto │ ├── dashboard.v1 │ │ └── dashboard.proto │ └── configstatus.v1 │ │ ├── configstatus.proto │ │ └── services.gen.proto └── fmp │ ├── yang.proto │ ├── extensions.proto │ ├── deletes.proto │ └── inet.proto ├── main.go ├── Jenkinsfile ├── GNUmakefile ├── .goreleaser.yml ├── docs ├── resources │ ├── cloudeos_clos.md │ ├── cloudeos_wan.md │ ├── cloudeos_subnet.md │ ├── cloudeos_router_config.md │ ├── cloudeos_vpc_config.md │ ├── cloudeos_topology.md │ ├── cloudeos_aws_vpn.md │ ├── cloudeos_vpc_status.md │ └── cloudeos_router_status.md └── index.md ├── README.md └── publish_terraform /cloudeos/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | 4 | package cloudeos 5 | 6 | var providerCloudEOSVersion = "1.2.2" 7 | -------------------------------------------------------------------------------- /cloudeos/diff_suppress_funcs.go: -------------------------------------------------------------------------------- 1 | package cloudeos 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 7 | ) 8 | 9 | func suppressAttributeChange(attribute, old, new string, d *schema.ResourceData) bool { 10 | if old != "" && old != new { 11 | log.Fatalf("Attribute change not supported for %s, old value: %s, new value: %s", 12 | attribute, old, new) 13 | return true 14 | } 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aristanetworks/terraform-provider-cloudeos 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aristanetworks/cloudvision-go v0.0.0-20230630150914-c775d7a35cbb 7 | github.com/golang/protobuf v1.5.2 8 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 9 | github.com/hashicorp/terraform-plugin-sdk v1.13.1 10 | github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 11 | google.golang.org/genproto v0.0.0-20221130183247-a2ec334bae6f 12 | google.golang.org/grpc v1.51.0 13 | google.golang.org/protobuf v1.28.1 14 | ) 15 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/time/time.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.time; 8 | //option go_package = "arista/resources/arista/time"; 9 | option go_package = "github.com/aristanetworks/cloudvision-go/api/arista/time"; 10 | 11 | import "google/protobuf/timestamp.proto"; 12 | 13 | message TimeBounds { 14 | google.protobuf.Timestamp start = 1; 15 | google.protobuf.Timestamp end = 2; 16 | } 17 | -------------------------------------------------------------------------------- /cloudvision-apis/fmp/yang.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | // Useful types that come from ietf-yang-types.yang 7 | 8 | syntax = "proto3"; 9 | 10 | package fmp; 11 | 12 | option go_package = "github.com/aristanetworks/cloudvision-go/api/fmp"; 13 | 14 | message MACAddress { 15 | string value = 1; 16 | } 17 | 18 | message RepeatedMACAddress { 19 | repeated MACAddress values = 1; 20 | } 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/aristanetworks/terraform-provider-cloudeos/cloudeos" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/plugin" 11 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 12 | ) 13 | 14 | func main() { 15 | plugin.Serve(&plugin.ServeOpts{ 16 | ProviderFunc: func() terraform.ResourceProvider { 17 | return cloudeos.Provider() 18 | }, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | environment { 3 | GOCACHE = "/var/cache/jenkins/.gocache" 4 | } 5 | agent { label 'jenkins-agent-cloud-caching' } 6 | stages { 7 | stage("setup"){ 8 | steps { 9 | sh 'mkdir -p $GOCACHE' 10 | } 11 | } 12 | stage("make check") { 13 | agent { docker reuseNode: true, image: 'golang:1.18.5-buster' } 14 | steps { 15 | script { 16 | sh 'make clean' 17 | sh 'make' 18 | sh 'make test' 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | BINARY=terraform-provider-cloudeos 2 | VERSION=$(shell awk '{ if ($$2=="providerCloudEOSVersion") print $$4 }' ./cloudeos/version.go | tr -d \") 3 | TEST=./cloudeos 4 | 5 | default: build-all 6 | 7 | build-all: linux darwin 8 | 9 | linux: 10 | GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o $(BINARY)_$(VERSION)_linux_amd64 11 | GOOS=linux CGO_ENABLED=0 GOARCH=386 go build -o $(BINARY)_$(VERSION)_linux_x86 12 | 13 | darwin: 14 | GOOS=darwin CGO_ENABLED=0 GOARCH=amd64 go build -o $(BINARY)_$(VERSION)_darwin_amd64 15 | 16 | test: 17 | go test $(TEST) -timeout=30s -parallel=4 18 | 19 | testacc: 20 | TF_ACC=1 go test $(TEST) -v -parallel 20 $(TESTARGS) -timeout 120m 21 | 22 | clean: 23 | rm -f $(BINARY)_* 24 | 25 | .PHONY: build test testacc 26 | -------------------------------------------------------------------------------- /cloudeos/arista/clouddeploy.v1/gen_proto: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to generate .pb.go files in this directory. Steps: 4 | # - Copy over .proto files form arista.git/resources/arista/clouddeploy.v1 . 5 | # - Invoke script. 6 | # 7 | # Usage: 8 | # ./gen_proto 9 | set -e 10 | cd $(dirname $0) 11 | 12 | # Fixup go_package in .proto files copied over from arista.git. 13 | find $PWD/*.proto -exec sed -i.orig 's/.*go_package.*/option go_package = "github.com\/aristanetworks\/terraform-provider-cloudeos\/cloudeos\/arista\/clouddeploy.v1;clouddeploy_v1";/' {} \; && rm *.orig 14 | 15 | # Invoke go generate -v. 16 | go generate -v $PWD 17 | 18 | # go generate creates files in directory corresponding to go_package, relative to go_out. Move the files to current dir. 19 | cp $PWD/github.com/aristanetworks/terraform-provider-cloudeos/cloudeos/arista/clouddeploy.v1/*.pb.go $PWD && rm -rf $PWD/github.com 20 | -------------------------------------------------------------------------------- /cloudvision-apis/fmp/extensions.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package fmp; 8 | 9 | option go_package = "github.com/aristanetworks/cloudvision-go/api/fmp"; 10 | 11 | import "google/protobuf/descriptor.proto"; 12 | 13 | extend google.protobuf.MessageOptions { 14 | // TODO: will need an official number from Google, just like gNMI extensions 15 | // this works for now, though. 16 | string model = 51423; 17 | bool model_key = 51424; 18 | string custom_filter = 51425; 19 | bool no_default_filter = 51426; 20 | bool require_set_key = 51427; 21 | string unkeyed_model = 51428; 22 | } 23 | 24 | extend google.protobuf.FieldOptions { 25 | string child_resource = 51449; 26 | } 27 | 28 | extend google.protobuf.FileOptions { 29 | string disable_yang = 51623; 30 | } 31 | -------------------------------------------------------------------------------- /cloudeos/provider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 12 | ) 13 | 14 | func TestProvider(t *testing.T) { 15 | if err := Provider().(*schema.Provider).InternalValidate(); err != nil { 16 | t.Fatalf("err: %s", err) 17 | } 18 | } 19 | 20 | var testProvider *schema.Provider 21 | var testProviders map[string]terraform.ResourceProvider 22 | 23 | func init() { 24 | testProvider = Provider().(*schema.Provider) 25 | testProviders = map[string]terraform.ResourceProvider{ 26 | "cloudeos": testProvider, 27 | } 28 | } 29 | 30 | func TestProvider_impl(t *testing.T) { 31 | var _ terraform.ResourceProvider = Provider() 32 | } 33 | 34 | func testAccPreCheck(t *testing.T) {} 35 | -------------------------------------------------------------------------------- /cloudvision-apis/fmp/deletes.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package fmp; 8 | option go_package = "github.com/aristanetworks/cloudvision-go/api/fmp"; 9 | 10 | // DeleteError defines the set of delete error types. 11 | enum DeleteError { 12 | // DELETE_ERROR_UNSPECIFIED indicates that the delete error 13 | // is not specified. 14 | DELETE_ERROR_UNSPECIFIED = 0; 15 | // DELETE_ERROR_UNAUTHORIZED indicates that the user is not authorized 16 | // to perform the specified delete. 17 | DELETE_ERROR_UNAUTHORIZED = 1; 18 | // DELETE_ERROR_INTERNAL indicates that the server encountered an 19 | // unrecoverable error on the specified delete. 20 | DELETE_ERROR_INTERNAL = 2; 21 | // DELETE_ERROR_UNDELETABLE_KEY indicates that the specified error is 22 | // not allowed by the service. 23 | DELETE_ERROR_UNDELETABLE_KEY = 3; 24 | } 25 | -------------------------------------------------------------------------------- /cloudvision-apis/fmp/inet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | // Useful types that come from ietf-inet-types.yang 7 | 8 | syntax = "proto3"; 9 | 10 | package fmp; 11 | 12 | option go_package = "github.com/aristanetworks/cloudvision-go/api/fmp"; 13 | 14 | message IPAddress { 15 | string value = 1; 16 | } 17 | 18 | message RepeatedIPAddress { 19 | repeated IPAddress values = 1; 20 | } 21 | 22 | message IPv4Address { 23 | string value = 1; 24 | } 25 | 26 | message RepeatedIPv4Address { 27 | repeated IPv4Address values = 1; 28 | } 29 | 30 | message IPv6Address { 31 | string value = 1; 32 | } 33 | 34 | message RepeatedIPv6Address { 35 | repeated IPv6Address values = 1; 36 | } 37 | 38 | message IPPrefix { 39 | string value = 1; 40 | } 41 | 42 | message IPv4Prefix { 43 | string value = 1; 44 | } 45 | 46 | message IPv6Prefix { 47 | string value = 1; 48 | } 49 | 50 | message Port { 51 | uint32 value = 1; 52 | } 53 | -------------------------------------------------------------------------------- /cloudeos/validators.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | 4 | package cloudeos 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | ) 10 | 11 | func validateCIDRBlock(val interface{}, key string) (warns []string, errors []error) { 12 | if err := validateCIDR(val.(string)); err != nil { 13 | errors = append(errors, err) 14 | } 15 | return 16 | } 17 | 18 | func validateCIDR(cidrString string) error { 19 | _, _, err := net.ParseCIDR(cidrString) 20 | if err != nil { 21 | return fmt.Errorf("%s is not a valid CIDR. %w", cidrString, err) 22 | } 23 | return nil 24 | } 25 | 26 | func validateIPList(val interface{}, key string) (warns []string, errors []error) { 27 | ipList := val.([]string) 28 | for _, ipStr := range ipList { 29 | if err := validateIP(ipStr); err != nil { 30 | errors = append(errors, err) 31 | return 32 | } 33 | } 34 | return 35 | } 36 | 37 | func validateIP(ipStr string) error { 38 | ip := net.ParseIP(ipStr) 39 | if ip == nil { 40 | return fmt.Errorf("%s is not a valid IP address", ipStr) 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cloudeos/arista/clouddeploy.v1/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | // Steps for making changes in proto models in clouddeploy.proto 6 | // 1. Amend the original proto and regenerate services.gen.proto in arista.git (via boomtown). The original proto can be found at resources/arista/clouddeploy.v1 7 | // 2. Replace the current proto and the services.gen.proto with the ones generated in step 1 into this repo (Don't forget to correct the license to MPL 2.0) 8 | // 3. Invoke go generate in this repo, which will regenerate all the bindings. 9 | 10 | package clouddeploy_v1 11 | 12 | //go:generate go install google.golang.org/protobuf/cmd/protoc-gen-go 13 | //go:generate go install google.golang.org/grpc/cmd/protoc-gen-go-grpc 14 | //go:generate protoc -I ../.. -I ../../../cloudvision-apis --go_out=./ --go-grpc_out=./ arista/clouddeploy.v1/clouddeploy.proto 15 | //go:generate protoc -I ../.. -I ../../../cloudvision-apis --go_out=./ --go-grpc_out=./ arista/clouddeploy.v1/services.gen.proto 16 | //go:generate goimports -w . 17 | -------------------------------------------------------------------------------- /cloudeos/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func ctProvider(t *testing.T) *CloudeosProvider { 14 | p := &CloudeosProvider{ 15 | // SA_TOKEN env variable should be set to a valid cvaas service 16 | // account token. 17 | srvcAcctToken: os.Getenv("SA_TOKEN"), 18 | server: "www.cv-dev.corp.arista.io", 19 | cvaasDomain: "", 20 | } 21 | if p.srvcAcctToken == "" { 22 | fmt.Fprintln(os.Stderr, "warning: no client tests can run, SA_TOKEN "+ 23 | "env variable is not set to a service account token") 24 | t.Skip() 25 | } 26 | return p 27 | } 28 | 29 | func TestGetAssignment(t *testing.T) { 30 | p := ctProvider(t) 31 | _, err := p.getAssignment(p.server) 32 | if err != nil { 33 | t.Fatalf("Failed to get assignment: %s", err) 34 | } 35 | } 36 | 37 | func TestGetEnrollmentToken(t *testing.T) { 38 | p := ctProvider(t) 39 | _, err := p.getDeviceEnrollmentToken() 40 | if err != nil { 41 | t.Fatalf("Failed to get enrollment token: %s", err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/subscriptions/subscriptions.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.subscriptions; 8 | 9 | //option go_package = "arista/resources/arista/subscriptions"; 10 | option go_package = "github.com/aristanetworks/cloudvision-go/api/arista/subscriptions"; 11 | 12 | 13 | enum Operation { 14 | UNSPECIFIED = 0; 15 | 16 | // INITIAL indicates the associated notification is that of the 17 | // current state and a fully-specified Resource is provided. 18 | INITIAL = 10; 19 | // INITIAL_SYNC_COMPLETE indicates all existing-state has been 20 | // streamed to the client. This status will be sent in an 21 | // otherwise-empty message and no subsequent INITIAL messages 22 | // should be expected. 23 | INITIAL_SYNC_COMPLETE = 11; 24 | 25 | // UPDATED indicates the associated notification carries 26 | // modification to the last-streamed state. This indicates 27 | // the contained Resource may be a partial diff, though, it 28 | // may contain a fully-specified Resource. 29 | UPDATED = 20; 30 | 31 | // DETLETED indicates the associated notification carries 32 | // a deletion. The Resource's key will always be set in this case, 33 | // but no other fields should be expected. 34 | DELETED = 30; 35 | }; 36 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | binary: '{{ .ProjectName }}_v{{ .Version }}' 32 | archives: 33 | - format: zip 34 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 35 | checksum: 36 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 37 | algorithm: sha256 38 | signs: 39 | - cmd: "bash" 40 | args: [ "/tmp/vault-terraform-sign.sh", "-o", "${signature}", "${artifact}" ] 41 | artifacts: checksum 42 | release: 43 | # If you want to manually examine the release before its live, uncomment this line: 44 | # draft: true 45 | github: 46 | owner: aristanetworks 47 | name: terraform-provider-cloudeos 48 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/redirector.v1/redirector.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.redirector.v1; 8 | 9 | option go_package = "arista/resources/arista/redirector.v1;redirector"; 10 | 11 | import "google/protobuf/wrappers.proto"; 12 | 13 | import "fmp/extensions.proto"; 14 | import "fmp/wrappers.proto"; 15 | 16 | // AssignmentKey allows to uniquely identify an assignment. 17 | message AssignmentKey { 18 | option (fmp.model_key) = true; 19 | 20 | // system_id is the unique identifier of a device. 21 | google.protobuf.StringValue system_id = 1; 22 | } 23 | 24 | // Assignment returns the information about the regional clusters that the 25 | // system is assigned to. Each cluster consists of a series of hosts, each of 26 | // which the client can use to connect. 27 | message Assignment { 28 | option (fmp.model) = "ro"; 29 | 30 | // key uniquely identifies the assignment of system_id to the cluster. 31 | AssignmentKey key = 1; 32 | // clusters that the system is assigned to. 33 | Clusters clusters = 2; 34 | } 35 | 36 | // Clusters wraps a cluster list which contain the information about the hosts. 37 | message Clusters { 38 | // values contains the list of clusters associated with the region 39 | repeated Cluster values = 2; 40 | } 41 | 42 | message Cluster { 43 | // name of the cluster. The name can change over time as new clusters 44 | // are added or removed. 45 | google.protobuf.StringValue name = 1; 46 | // hosts in the cluster that the devices can connect to. 47 | fmp.RepeatedString hosts = 2; 48 | } 49 | -------------------------------------------------------------------------------- /cloudeos/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 12 | ) 13 | 14 | //Provider function which defines the Terraform provider. 15 | func Provider() terraform.ResourceProvider { 16 | return &schema.Provider{ 17 | Schema: map[string]*schema.Schema{ 18 | "cvaas_server": { 19 | Type: schema.TypeString, 20 | Required: true, 21 | Description: "Cvp server hostname / ip address and port for terraform" + 22 | " client to authenticate. It must be in format of " + 23 | " or :", 24 | }, 25 | "service_account_web_token": { 26 | Type: schema.TypeString, 27 | Required: true, 28 | Description: "Service account web token", 29 | }, 30 | "cvaas_domain": { 31 | Type: schema.TypeString, 32 | Required: true, 33 | Description: "CVaaS Domain name", 34 | }, 35 | }, 36 | ResourcesMap: map[string]*schema.Resource{ 37 | "cloudeos_vpc_config": cloudeosVpcConfig(), 38 | "cloudeos_vpc_status": cloudeosVpcStatus(), 39 | "cloudeos_router_config": cloudeosRouterConfig(), 40 | "cloudeos_router_status": cloudeosRouterStatus(), 41 | "cloudeos_subnet": cloudeosSubnet(), 42 | "cloudeos_topology": cloudeosTopology(), 43 | "cloudeos_clos": cloudeosClos(), 44 | "cloudeos_wan": cloudeosWan(), 45 | "cloudeos_aws_vpn": cloudeosAwsVpn(), 46 | }, 47 | 48 | ConfigureFunc: configureCloudEOSProvider, 49 | } 50 | } 51 | 52 | func configureCloudEOSProvider(d *schema.ResourceData) (interface{}, error) { 53 | var cfg CloudeosProvider 54 | cfg.server = d.Get("cvaas_server").(string) 55 | cfg.srvcAcctToken = d.Get("service_account_web_token").(string) 56 | cfg.cvaasDomain = d.Get("cvaas_domain").(string) 57 | if cfg.server == "" || cfg.srvcAcctToken == "" || cfg.cvaasDomain == "" { 58 | return nil, errors.New("Provider not configured correctly") 59 | } 60 | 61 | return cfg, nil 62 | } 63 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_clos.md: -------------------------------------------------------------------------------- 1 | # `cloudeos_clos` 2 | 3 | The `cloudeos_clos` resource is dependent on the `cloudeos_topology` resource and is used to provide attributes 4 | for the underlay and overlay connectivity for inter-vpc communication between Leaf and Edge routers in the same region. 5 | A `cloudeos_topology` can have multiple `cloudeos_clos` resources, depending on the number of 6 | Leaf-Edge CLOS networks in the entire network topology. 7 | 8 | For example, if you want to deploy a Leaf-Edge topology in two AWS regions ( us-east-1 and us-west-1) and one in an Azure region 9 | ( westus2 ). You would have to create three `cloudeos_clos` resources, one each for the CLOS network in that region. 10 | 11 | To refer to attributes defined in the CLOS resource, leaf VPC and leaf CloudEOS use 12 | the `cloudeos_clos` name in their resource definition. 13 | 14 | ## Example Usage 15 | 16 | ```hcl 17 | resource "cloudeos_topology" "topology1" { 18 | topology_name = "topo-test" 19 | bgp_asn = "65000-65100" 20 | vtep_ip_cidr = "1.0.0.0/16" 21 | terminattr_ip_cidr = "4.0.0.0/16" 22 | dps_controlplane_cidr = "3.0.0.0/16" 23 | } 24 | resource "cloudeos_clos" "clos" { 25 | name = "clos-test" 26 | topology_name = cloudeos_topology.topology1.topology_name 27 | cv_container_name = "CloudLeaf" 28 | } 29 | ``` 30 | 31 | ## Argument Reference 32 | 33 | * `name` - (Required) CLOS resource name. 34 | * `topology_name` - (Required) Topology name that this clos resource depends on. 35 | * `cv_container_name` - (Required) CVaaS Configlet Container Name to which the CloudLeaf Routers will be added to. 36 | * `fabric` - (Optional) full_mesh or hub_spoke, default value is `hub_spoke`. 37 | * `leaf_to_edge_peering` - (Optional) Leaf to edge VPC peering, default is `true`. 38 | * `leaf_to_edge_igw` - (Optional) Leaf to edge VPC connection through Internet Gateway, default is `false`. 39 | * `leaf_encryption` - (Optional) Support encryption using Ipsec between Leaf and Edge. Default is `false`. 40 | 41 | ## Attributes Reference 42 | 43 | In addition to the Arguments listed above - the following Attributes are exported 44 | 45 | * `ID` - The ID of the cloudeos_clos Resource. 46 | 47 | ## Timeouts 48 | 49 | * `delete` - (Defaults to 5 minutes) Used when deleting the cloudeos_clos Resource. -------------------------------------------------------------------------------- /docs/resources/cloudeos_wan.md: -------------------------------------------------------------------------------- 1 | # cloudeos_wan 2 | 3 | The `cloudeos_wan` resource is used to provide attributes for the underlay and overlay connectivity 4 | amongst Edges and Route Reflectors which are part of single WAN Network. In a traditional network topology, 5 | a WAN Network includes multiple site/branch/cloud Edges connected through Ipsec VPN and/or private connects. 6 | Similarly here, we have extended that concept which allows you to connect multiple clouds and regions 7 | in a single WAN fabric. 8 | 9 | The Edge/RR VPC and Edge CloudEOS router associate with a WAN using its `name`. 10 | It is also possible to create multiple isolated WAN fabrics by creating multiple `cloudeos_wan` resources 11 | and then associating the WAN name to the corresponding resource. 12 | 13 | 14 | ## Example Usage 15 | 16 | ```hcl 17 | resource "cloudeos_topology" "topology1" { 18 | topology_name = "topo-test" 19 | bgp_asn = "65000-65100" 20 | vtep_ip_cidr = "1.0.0.0/16" 21 | terminattr_ip_cidr = "4.0.0.0/16" 22 | dps_controlplane_cidr = "3.0.0.0/16" 23 | } 24 | 25 | resource "cloudeos_wan" "wan" { 26 | name = "wan-test" // wan name 27 | topology_name = cloudeos_topology.topology1.topology_name // topology name 28 | cv_container_name = "CloudEdge" // Container name on CVaaS 29 | 30 | } 31 | ``` 32 | 33 | ## Argument Reference 34 | 35 | * `name` - (Required) Name of the wan resource. 36 | * `topology_name` - (Required) Name of the topology this wan resource depends on. 37 | * `cv_container_name` - (Required) CVaaS Container Name to which the CloudEdge Routers 38 | will be added to. 39 | * `edge_to_edge_igw` - (Optional) Edge to Edge Connectivity through the Internet Gateway. 40 | * `edge_to_edge_peering` - (Optional) Peering across Edge VPC's, default is false. 41 | ( Not supported yet ) 42 | * `edge_to_edge_dedicated_connect` - (Optional) Dedicated connection between two Edge VPC, 43 | default is false. ( Not Supported yet ) 44 | 45 | ## Attributes Reference 46 | 47 | In addition to the Arguments listed above - the following Attributes are exported 48 | 49 | * `ID` - The ID of the cloudeos_wan resource. 50 | 51 | ## Timeouts 52 | 53 | * `delete` - (Defaults to 5 minutes) Used when deleting the Wan Resource. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider for Arista CloudEOS 2 | 3 | The Arista CloudEOS provider helps with automating the deployment of a multi-cloud network 4 | fabric using Arista CloudVision as a Service ( CVaaS ). The provider interacts with CVaaS to 5 | create a BGP/EVPN/VxLAN based overlay network between CloudEOS Routers running in various 6 | regions across Cloud Providers. Optionally, it can be used to automate the deployment of 7 | standalone CloudEOS routers across Cloud Providers and their integration with CVaaS, wherein 8 | the CloudEOS configuration to define the topology is created and applied by the user. 9 | 10 | ## Terminology 11 | 12 | * CVaaS : Arista [CloudVision](https://www.arista.com/en/products/eos/eos-cloudvision) as-a-Service. 13 | CloudVision as a Service is the root access point for customers to utilize the CloudEOS solution. 14 | CVaaS supports a single point of orchestration for multi-cloud, multi-tenant and multi-account management. 15 | * CloudEdge - CloudEdge is a instance of CloudEOS that provides interconnection services with other public clouds 16 | within the client’s autonomous system. The CloudEdge also interconnects VPCs and VNETs within a cloud provider region. 17 | * CloudLeaf - CloudLeaf is an instance of CloudEOS that is deployed in the VPC and VNETs that hosts the applications VMs. 18 | It is the gateway for all incoming and outgoing traffic for the VPC. 19 | * Cloud Network Private Segment (CNPS) - The VRF name used for segmentation across your cloud network. 20 | * CLOS topology - EPVN based spine-leaf topology to interconnect all leaf VPCs in a region 21 | to the CloudEdge routers deployed in the transit/Edge VPC. 22 | * WAN topology - EVPN based full mesh topology to interconnect all the CloudEdges over Internet. 23 | * DPS - [Dynamic Path Selection](https://www.arista.com/en/cg-veos-router/veos-router-dynamic-path-selection-overview) 24 | 25 | ## Requirements 26 | 27 | - [Terraform](https://www.terraform.io/downloads.html) 0.12+ 28 | - [Go](https://golang.org/doc/install) 1.13 (to build the provider plugin) 29 | 30 | ## Usage 31 | 32 | ### CloudEOS Provider 33 | ``` 34 | provider "cloudeos" { 35 | cvaas_domain = "apiserver.arista.io" 36 | cvaas_server = "arista.io" 37 | service_account_web_token = "..." 38 | } 39 | ``` 40 | 41 | ### Argument Reference 42 | * cvaas_domain - (Required) CVaaS Domain name 43 | * cvaas_server - (Required) CVaaS Server Name 44 | * service_account_web_token - (Required) The access token to authenticate the Terraform client to CVaaS. 45 | 46 | ## Resources 47 | Documentation for the resources supported by the CloudEOS Provider can be found in the [resources](https://github.com/aristanetworks/terraform-provider-cloudeos/tree/master/docs/resources) folder. 48 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_subnet.md: -------------------------------------------------------------------------------- 1 | # cloudeos_subnet 2 | 3 | The `cloudeos_subnet` resource sends the information about the subnet that has been deployed to CVaaS. 4 | ## Example Usage 5 | 6 | ```hcl 7 | resource "cloudeos_topology" "topology" { 8 | topology_name = "topo-test" 9 | bgp_asn = "65000-65100" 10 | vtep_ip_cidr = "1.0.0.0/16" 11 | terminattr_ip_cidr = "4.0.0.0/16" 12 | dps_controlplane_cidr = "3.0.0.0/16" 13 | } 14 | 15 | resource "cloudeos_clos" "clos" { 16 | name = "clos-test" 17 | topology_name = cloudeos_topology.topology.topology_name 18 | cv_container_name = "CloudLeaf" 19 | } 20 | 21 | resource "cloudeos_wan" "wan" { 22 | name = "wan-test" 23 | topology_name = cloudeos_topology.topology.topology_name 24 | cv_container_name = "CloudEdge" 25 | } 26 | 27 | resource "cloudeos_vpc_config" "vpc" { 28 | cloud_provider = "aws" 29 | topology_name = cloudeos_topology.topology.topology_name 30 | clos_name = cloudeos_clos.clos.name 31 | wan_name = cloudeos_wan.wan.name 32 | role = "CloudEdge" 33 | cnps = "Dev" 34 | tags = { 35 | Name = "edgeVpc" 36 | Cnps = "Dev" 37 | } 38 | region = "us-west-1" 39 | } 40 | 41 | resource "aws_vpc" "vpc" { 42 | cidr_block = "100.0.0.0/16" 43 | } 44 | 45 | resource "aws_security_group" "sg" { 46 | name = "example-sg" 47 | } 48 | 49 | resource "cloudeos_vpc_status" "vpc" { 50 | cloud_provider = cloudeos_vpc_config.vpc.cloud_provider 51 | vpc_id = aws_vpc.vpc.id 52 | security_group_id = aws_security_group.sg.id 53 | cidr_block = aws_vpc.vpc.cidr_block 54 | igw = aws_security_group.sg.name 55 | role = cloudeos_vpc_config.vpc.role 56 | topology_name = cloudeos_topology.topology.topology_name 57 | tags = cloudeos_vpc_config.vpc.tags 58 | clos_name = cloudeos_clos.clos.name 59 | wan_name = cloudeos_wan.wan.name 60 | cnps = cloudeos_vpc_config.vpc.cnps 61 | region = cloudeos_vpc_config.vpc.region 62 | account = "dummy_aws_account" 63 | tf_id = cloudeos_vpc_config.vpc.tf_id 64 | } 65 | 66 | resource "cloudeos_subnet" "subnet" { 67 | cloud_provider = cloudeos_vpc_status.vpc.cloud_provider 68 | vpc_id = cloudeos_vpc_status.vpc.vpc_id 69 | availability_zone = "us-west-1b" 70 | subnet_id = "subnet-id" 71 | cidr_block = "15.0.0.0/24" 72 | subnet_name = "edgeSubnet" 73 | } 74 | ``` 75 | 76 | ## Argument Reference 77 | 78 | * `cloud_provider` - (Required) Cloud Provider in which the subnet is being deployed. Supported: aws or azure. 79 | * `vpc_id` - (Required) VPC ID in which this subnet is created, equivalent to rg_name in Azure. 80 | * `subnet_id` - (Required) ID of subnet deployed in AWS/Azure. 81 | * `cidr_block` - (Required) CIDR of the subnet. 82 | * `subnet_name` - (Required) Name of the subnet. 83 | * `vnet_name` - (Optional) VNET name, only needed in Azure. 84 | * `availability_zone` - (Optional) Availability zone. 85 | 86 | ## Attributes Reference 87 | 88 | In addition to the Arguments listed above - the following Attributes are exported: 89 | 90 | * `ID` - The ID of the cloudeos_subnet resource. -------------------------------------------------------------------------------- /docs/resources/cloudeos_router_config.md: -------------------------------------------------------------------------------- 1 | # cloudeos_router_config 2 | 3 | The `cloudeos_router_config` resource should be created before the CloudEOS Router is deployed. It sends deployment 4 | information to CVaaS, which returns the bootstrap configuration with which the router 5 | will be deployed. After the CloudEOS Router boots up, it will start streaming to CVaaS and register itself. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "aws_vpc" "vpc" { 11 | cidr_block = "100.0.0.0/16" 12 | } 13 | 14 | resource "cloudeos_topology" "topology" { 15 | topology_name = "topo-test" 16 | bgp_asn = "65000-65100" 17 | vtep_ip_cidr = "192.168.0.0/24" 18 | terminattr_ip_cidr = "192.168.1.0/24" 19 | dps_controlplane_cidr = "192.168.2.0/24" 20 | } 21 | 22 | resource "cloudeos_router_config" "cloudeos" { 23 | cloud_provider = "aws" 24 | topology_name = cloudeos_topology.topology.topology_name 25 | role = "CloudEdge" 26 | cnps = "dev" 27 | vpc_id = aws_vpc.vpc.id 28 | region = aws_vpc.vpc.region 29 | is_rr = false 30 | intf_name = ["publicIntf", "internalIntf"] 31 | intf_private_ip = ["10.0.0.101", "10.0.1.101"] 32 | intf_type = ["public", "internal"] 33 | } 34 | ``` 35 | 36 | ## Argument Reference 37 | 38 | * `cloud_provider` - (Required) Cloud Provider for this deployment. Supports only aws or azure. 39 | * `vpc_id` - (Required) VPC/VNET ID in which this CloudEOS is deployed. 40 | * `region` - (Required) Region of deployment. 41 | * `topology_name` - (Required) Name of the topology in which this CloudEOS router is deployed in. 42 | * `intf_name` - (Required) List of interface names. 43 | * `intf_private_ip` - (Required) List of interface private IPs. Currently, only supports 1 IP address per interface. 44 | * `intf_type` - (Required) List of Interface type (public, private, internal). A `public` interface has a public IP 45 | associated with it. An `internal` interface is the interface which connects the Leaf and Edge routers. 46 | And a `private` interface is the default GW interface for all host traffic. 47 | * `cnps` - (Optional) Cloud Network Private Segments Name. ( VRF name ) 48 | * `tags` - (Optional) A mapping of tags to assign to the resource. 49 | * `role` - (Optional) CloudEdge or CloudLeaf (Same as VPC role). 50 | * `is_rr` - (Optional) true if this CloudEOS acts as a Route Reflector. 51 | * `ami` - (Optional) CloudEOS image. ( AWS only ) 52 | * `key_name` - (Optional) keypair name ( AWS only ) 53 | * `availability_zone` - (Optional) Availability Zone of VPC. 54 | 55 | ## Attributes Reference 56 | 57 | In addition to Arguments listed above - the following Attributes are exported 58 | 59 | * `ID` - The ID of cloudeos_router_config Resource. 60 | * `bootstrap_cfg` - Bootstrap configuration for the CloudEOS router. 61 | * `peer_routetable_id` - Router table ID of peer. 62 | 63 | ## Timeouts 64 | 65 | * `create` - (Default of 5 minute) Used when creating the cloudeos_config Resource. 66 | * `delete` - (Defaults to 10 minutes) Used when deleting the cloudeos_config Resource. -------------------------------------------------------------------------------- /cloudvision-apis/arista/redirector.v1/services.gen.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | // 6 | // Code generated by boomtown. DO NOT EDIT. 7 | // 8 | 9 | syntax = "proto3"; 10 | 11 | package arista.redirector.v1; 12 | option go_package = "arista/resources/arista/redirector.v1;redirector"; 13 | 14 | import "arista/redirector.v1/redirector.proto"; 15 | import "arista/time/time.proto"; 16 | import "arista/subscriptions/subscriptions.proto"; 17 | import "google/protobuf/timestamp.proto"; 18 | 19 | message AssignmentRequest { 20 | // Key uniquely identifies a Assignment instance to retrieve. 21 | // This value must be populated. 22 | AssignmentKey key = 1; 23 | 24 | // Time indicates the time for which you are interested in the data. 25 | // If no time is given, the server will use the time at which it makes the request. 26 | google.protobuf.Timestamp time = 2; 27 | }; 28 | 29 | message AssignmentResponse { 30 | // Value is the value requested. 31 | // This structure will be fully-populated as it exists in the datastore. If 32 | // optional fields were not given at creation, these fields will be empty or 33 | // set to default values. 34 | Assignment value = 1; 35 | 36 | // Time carries the (UTC) timestamp of the last-modification of the 37 | // Assignment instance in this response. 38 | google.protobuf.Timestamp time = 2; 39 | }; 40 | 41 | message AssignmentStreamRequest { 42 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 43 | // This requires all provided fields to be equal to the response. 44 | // 45 | // While transparent to users, this field also allows services to optimize internal 46 | // subscriptions if filter(s) are sufficiently specific. 47 | repeated Assignment partial_eq_filter = 1; 48 | 49 | // TimeRange allows limiting response data to within a specified time window. 50 | // If this field is populated, at least one of the two time fields are required. 51 | // 52 | // This field is not allowed in the Subscribe RPC. 53 | arista.time.TimeBounds time = 3; 54 | }; 55 | 56 | message AssignmentStreamResponse { 57 | // Value is a value deemed relevant to the initiating request. 58 | // This structure will always have its key-field populated. Which other fields are 59 | // populated, and why, depends on the value of Operation and what triggered this notification. 60 | Assignment value = 1; 61 | 62 | // Time holds the timestamp of this Assignment's last modification. 63 | google.protobuf.Timestamp time = 2; 64 | 65 | // Operation indicates how the Assignment value in this response should be considered. 66 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 67 | // once all initial data is streamed and the client begins to receive modification updates, 68 | // you should not see INITIAL again. 69 | arista.subscriptions.Operation type = 3; 70 | }; 71 | 72 | service AssignmentService { 73 | rpc GetOne (AssignmentRequest) returns (AssignmentResponse); 74 | rpc GetAll (AssignmentStreamRequest) returns (stream AssignmentStreamResponse); 75 | rpc Subscribe (AssignmentStreamRequest) returns (stream AssignmentStreamResponse); 76 | } 77 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_aws_vpn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | r "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 13 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 14 | ) 15 | 16 | func TestResourceAwsVpn(t *testing.T) { 17 | r.Test(t, r.TestCase{ 18 | Providers: testProviders, 19 | PreCheck: func() { testAccPreCheck(t) }, 20 | CheckDestroy: testResourceAwsVpnDestroy, 21 | Steps: []r.TestStep{ 22 | { 23 | Config: testResourceInitialAwsVpnConfig, 24 | Check: testResourceInitialAwsVpnCheck, 25 | }, 26 | }, 27 | }) 28 | } 29 | 30 | var testResourceInitialAwsVpnConfig = fmt.Sprintf(` 31 | provider "cloudeos" { 32 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 33 | cvaas_server = "www.cv-play.corp.arista.io" 34 | // clouddeploy token 35 | service_account_web_token = %q 36 | } 37 | 38 | resource "cloudeos_aws_vpn" "vpn_config" { 39 | cgw_id = "cgw-0e93e4eef31c42466" 40 | cnps = "dev" 41 | router_id = "rtr1" 42 | tgw_id = "tgw-0a5856fd8cb6fbee6" 43 | vpn_tgw_attachment_id = "tgw-attach-a1234576" 44 | tunnel1_aws_overlay_ip = "169.254.244.201" 45 | tunnel1_bgp_asn = "64512" 46 | tunnel1_bgp_holdtime = "30" 47 | tunnel1_aws_endpoint_ip = "3.230.55.101" 48 | tunnel1_router_overlay_ip = "169.254.244.202" 49 | tunnel1_preshared_key = "key1" 50 | tunnel2_bgp_asn = "64512" 51 | tunnel2_bgp_holdtime = "30" 52 | tunnel2_aws_endpoint_ip = "34.224.224.37" 53 | tunnel2_router_overlay_ip = "169.254.36.110" 54 | tunnel2_aws_overlay_ip = "169.254.36.11" 55 | tunnel2_preshared_key = "key1" 56 | vpc_id = "vpc-0d981c28a83c3fe55" 57 | vpn_connection_id = "vpn-091b0a507e134a329" 58 | vpn_gateway_id = "" 59 | }`, os.Getenv("token")) 60 | 61 | func testResourceInitialAwsVpnCheck(s *terraform.State) error { 62 | resourceState := s.Modules[0].Resources["cloudeos_aws_vpn.vpn_config"] 63 | if resourceState == nil { 64 | return fmt.Errorf("cloudeos_aws_vpn resource not found in state") 65 | } 66 | instanceState := resourceState.Primary 67 | if instanceState == nil { 68 | return fmt.Errorf("cloudeos_aws_vpn instance not found in state") 69 | } 70 | if instanceState.ID == "" { 71 | return fmt.Errorf("cloudeos_router_config ID not assigned %s", instanceState.ID) 72 | } 73 | 74 | if got, want := instanceState.Attributes["cnps"], "dev"; got != want { 75 | return fmt.Errorf("cloudeos_router_config cloud_provider contains %s; want %s", got, want) 76 | } 77 | 78 | if got, want := instanceState.Attributes["vpc_id"], "vpc-0d981c28a83c3fe55"; got != want { 79 | return fmt.Errorf("cloudeos_router_config vpc_id contains %s; want %s", got, want) 80 | } 81 | return nil 82 | } 83 | 84 | func testResourceAwsVpnDestroy(s *terraform.State) error { 85 | for _, rs := range s.RootModule().Resources { 86 | if rs.Type != "cloudeos_aws_vpn" { 87 | continue 88 | } 89 | // TODO 90 | return nil 91 | } 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_vpc_config.md: -------------------------------------------------------------------------------- 1 | # cloudeos_vpc_config 2 | 3 | The `cloudeos_vpc_config` resource sends the deployment information about the AWS VPC and Azure VNET to CVaaS. 4 | CVaaS returns the peering information required by the Leaf VPC/VNETs to create a VPC/VNET Peering connection with its 5 | corresponding Edge. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "cloudeos_topology" "topology" { 11 | topology_name = "topo-test" 12 | bgp_asn = "65000-65100" 13 | vtep_ip_cidr = "1.0.0.0/16" 14 | terminattr_ip_cidr = "4.0.0.0/16" 15 | dps_controlplane_cidr = "3.0.0.0/16" 16 | } 17 | 18 | resource "cloudeos_clos" "clos" { 19 | name = "clos-test" 20 | topology_name = cloudeos_topology.topology.topology_name 21 | cv_container_name = "CloudLeaf" 22 | } 23 | 24 | resource "cloudeos_wan" "wan" { 25 | name = "wan-test" 26 | topology_name = cloudeos_topology.topology.topology_name 27 | cv_container_name = "CloudEdge" 28 | } 29 | 30 | resource "cloudeos_vpc_config" "vpc" { 31 | cloud_provider = "aws" // Cloud Provider "aws/azure" 32 | topology_name = cloudeos_topology.topology.topology_name // Topology resource name 33 | clos_name = cloudeos_clos.clos.name // Clos resource name 34 | wan_name = cloudeos_wan.wan.name // Wan resource name (Only needed in "CloudEdge" role) 35 | role = "CloudEdge" // VPC role, CloudEdge/CloudLeaf 36 | cnps = "Dev" // Cloud Network Private Segments Name 37 | tags = { // A mapping of tags to assign to the resource 38 | Name = "edgeVpc" 39 | Cnps = "Dev" 40 | } 41 | region = "us-west-1" // region of deployment 42 | } 43 | ``` 44 | 45 | ## Argument Reference 46 | 47 | * `topology_name` - (Required) Name of topology resource. 48 | * `clos_name` - (Optional) CLOS Name this VPC refers to for attributes. 49 | * `wan_name` - (Optional) WAN Name this VPC refers to for attributes. 50 | * `rg_name` - (Optional) Resource group name, only valid for Azure. 51 | * `vnet_name` - (Optional) VNET name, only valid for Azure. 52 | * `role` - (Required) CloudEdge or CloudLeaf. 53 | * `tags` - (Optional) A mapping of tags to assign to the resource. 54 | 55 | ## Attributes Reference 56 | 57 | In addition to Arguments listed above - the following Attributes are exported 58 | 59 | * `ID` - The ID of cloudeos_vpc_config Resource. 60 | 61 | A CloudLeaf VPC peers with the CloudEdge VPC to enable communication between instances between them. 62 | The following Attributes are exported in CloudLeaf VPC that provides information about the peer CloudEdge VPC. 63 | 64 | * `peer_vpc_id` - ID of the CloudEdge peer VPC, only valid for AWS. 65 | * `peer_vpc_cidr` - CIDR of the CloudEdge peer VPC, only valid for AWS. 66 | * `peer_vnet_id` - ID of the CloudEdge peer VNET, only valid for Azure. 67 | * `peer_rg_name` - Resource Group name of the peer CloudEdge, only valid for Azure. 68 | * `peer_vnet_name` - VNET name of the peer CloudEdge, only valid for Azure. 69 | 70 | ## Timeouts 71 | 72 | * `create` - (Default of 3 minute) Used when creating the cloudeos_vpc_config Resource. 73 | * `delete` - (Defaults to 5 minutes) Used when deleting the cloudeos_vpc_config Resource. -------------------------------------------------------------------------------- /cloudvision-apis/arista/endpointlocation.v1/services.gen.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | // 6 | // Code generated by boomtown. DO NOT EDIT. 7 | // 8 | 9 | syntax = "proto3"; 10 | 11 | package arista.endpointlocation.v1; 12 | option go_package = "arista/resources/arista/endpointlocation.v1;endpointlocation"; 13 | 14 | import "arista/endpointlocation.v1/endpointlocation.proto"; 15 | import "arista/time/time.proto"; 16 | import "arista/subscriptions/subscriptions.proto"; 17 | import "google/protobuf/timestamp.proto"; 18 | 19 | message EndpointLocationRequest { 20 | // Key uniquely identifies a EndpointLocation instance to retrieve. 21 | // This value must be populated. 22 | EndpointLocationKey key = 1; 23 | 24 | // Time indicates the time for which you are interested in the data. 25 | // If no time is given, the server will use the time at which it makes the request. 26 | google.protobuf.Timestamp time = 2; 27 | }; 28 | 29 | message EndpointLocationResponse { 30 | // Value is the value requested. 31 | // This structure will be fully-populated as it exists in the datastore. If 32 | // optional fields were not given at creation, these fields will be empty or 33 | // set to default values. 34 | EndpointLocation value = 1; 35 | 36 | // Time carries the (UTC) timestamp of the last-modification of the 37 | // EndpointLocation instance in this response. 38 | google.protobuf.Timestamp time = 2; 39 | }; 40 | 41 | message EndpointLocationStreamRequest { 42 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 43 | // This requires all provided fields to be equal to the response. 44 | // 45 | // While transparent to users, this field also allows services to optimize internal 46 | // subscriptions if filter(s) are sufficiently specific. 47 | repeated EndpointLocation partial_eq_filter = 1; 48 | 49 | // TimeRange allows limiting response data to within a specified time window. 50 | // If this field is populated, at least one of the two time fields are required. 51 | // 52 | // This field is not allowed in the Subscribe RPC. 53 | arista.time.TimeBounds time = 3; 54 | }; 55 | 56 | message EndpointLocationStreamResponse { 57 | // Value is a value deemed relevant to the initiating request. 58 | // This structure will always have its key-field populated. Which other fields are 59 | // populated, and why, depends on the value of Operation and what triggered this notification. 60 | EndpointLocation value = 1; 61 | 62 | // Time holds the timestamp of this EndpointLocation's last modification. 63 | google.protobuf.Timestamp time = 2; 64 | 65 | // Operation indicates how the EndpointLocation value in this response should be considered. 66 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 67 | // once all initial data is streamed and the client begins to receive modification updates, 68 | // you should not see INITIAL again. 69 | arista.subscriptions.Operation type = 3; 70 | }; 71 | 72 | service EndpointLocationService { 73 | rpc GetOne (EndpointLocationRequest) returns (EndpointLocationResponse); 74 | rpc GetAll (EndpointLocationStreamRequest) returns (stream EndpointLocationStreamResponse); 75 | rpc Subscribe (EndpointLocationStreamRequest) returns (stream EndpointLocationStreamResponse); 76 | } 77 | -------------------------------------------------------------------------------- /publish_terraform: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script should be used to build or build and publish provider-cloudeos 4 | # binaries to dist. To build only, run this script as "./publish_terraform b". 5 | # To build and publish, run as "./publish_terraform" directly. 6 | # 7 | # Note, when build only provider-cloudeos version number will not change, 8 | # when build and publish, script will increment version number to build 9 | # and publish binaries with this new version number. New version number 10 | # must be merged so people will be on same page. 11 | set -e 12 | # Read current version from version file 13 | vfile="./cloudeos/version.go" 14 | if [ -f "$vfile" ]; then 15 | version=$(grep providerCloudEOSVersion $vfile | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') 16 | else 17 | version="0.0.0" 18 | fi 19 | echo provider-cloudeos current version $version 20 | 21 | # Increment the version if we are publishing 22 | if [ $# -eq 0 ] || [ $1 != 'b' ]; then 23 | oldversion=$version 24 | IFS='.' 25 | read -ra ADDR <<< "$version" 26 | d=${ADDR[2]} 27 | IFS=$OLDIFS 28 | version="${ADDR[0]}.${ADDR[1]}.$((d+1))" 29 | echo provider-cloudeos version $oldversion to $version 30 | 31 | # Write the new version back to version file 32 | sed -i "s/$oldversion/$version/g" "$vfile" 33 | fi 34 | 35 | # The directory structure matches what tf expects for providers setup locally(in our case 36 | # cloudeos). See https://www.terraform.io/docs/cloud/run/install-software.html#in-house-providers 37 | # When terraform init is invoked, the cloudeos provider in terraform.d will be picked up 38 | # and the remaining providers will be downloaded and setup in .terraform dir 39 | pushd ./ 40 | mkdir -p terraform.d/plugins/registry.terraform.io/aristanetworks/cloudeos/$version 41 | cd terraform.d/plugins/registry.terraform.io/aristanetworks/cloudeos/$version 42 | mkdir darwin_amd64 43 | mkdir linux_amd64 44 | popd 45 | 46 | # Build binaries and put into corresponding directories 47 | echo Building provider-cloudeos binary terraform-provider-cloudeos_v$version 48 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o terraform-provider-cloudeos_v$version 49 | mv terraform-provider-cloudeos_v$version terraform.d/plugins/registry.terraform.io/aristanetworks/cloudeos/$version/linux_amd64 50 | 51 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o terraform-provider-cloudeos_v$version 52 | mv terraform-provider-cloudeos_v$version terraform.d/plugins/registry.terraform.io/aristanetworks/cloudeos/$version/darwin_amd64 53 | 54 | if [ $# -eq 0 ] || [ $1 != 'b' ]; then 55 | echo Publishing provider-cloudeos binary tarball terraform-cloudeos-plugin_v$version.tar.gz and terraform-cloudeos-plugin_latest.tar.gz 56 | # Create tarball and publish 57 | DISTPATH="dist:/dist/release/CloudEOS-Terraform/SE-EFT1/" 58 | tar -czvf terraform-cloudeos-plugin_v$version.tar.gz terraform.d 59 | a4 scp terraform-cloudeos-plugin_v$version.tar.gz $DISTPATH 60 | tar -czvf terraform-cloudeos-plugin_dev_latest.tar.gz terraform.d 61 | a4 scp terraform-cloudeos-plugin_dev_latest.tar.gz $DISTPATH 62 | tar -czvf terraform-cloudeos-plugin_dev_latest_13.tar.gz terraform.d 63 | a4 scp terraform-cloudeos-plugin_dev_latest_13.tar.gz $DISTPATH 64 | 65 | echo Cleaning up 66 | # Cleaning up 67 | rm -r terraform.d 68 | rm terraform-cloudeos-plugin* 69 | 70 | echo Please git add/commit/push and merge your version file change!!! 71 | fi 72 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_subnet.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "strings" 11 | 12 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 13 | ) 14 | 15 | //cloudeosSubnet: Define the cloudeosSubnet schema ( input and output variables ) 16 | func cloudeosSubnet() *schema.Resource { 17 | return &schema.Resource{ 18 | Create: cloudeosSubnetCreate, 19 | Read: cloudeosSubnetRead, 20 | Update: cloudeosSubnetUpdate, 21 | Delete: cloudeosSubnetDelete, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "cloud_provider": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | Description: "aws/azure/gcp", 28 | ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { 29 | v := val.(string) 30 | if v != "aws" && v != "azure" && v != "gcp" { 31 | errs = append(errs, fmt.Errorf( 32 | "%q must be aws/azure/gcp got: %q", key, v)) 33 | } 34 | return 35 | }, 36 | }, 37 | // This is equivalent to rg_name in Azure 38 | "vpc_id": { 39 | Type: schema.TypeString, 40 | Required: true, 41 | }, 42 | // Only set in Azure 43 | "vnet_name": { 44 | Type: schema.TypeString, 45 | Optional: true, 46 | }, 47 | "availability_zone": { 48 | Type: schema.TypeString, 49 | Optional: true, 50 | Description: "availability zone", 51 | }, 52 | "subnet_id": { 53 | Type: schema.TypeString, 54 | Required: true, 55 | Description: "subnet id", 56 | }, 57 | "computed_subnet_id": { 58 | Type: schema.TypeString, 59 | Computed: true, 60 | Elem: &schema.Schema{Type: schema.TypeString}, 61 | }, 62 | "cidr_block": { 63 | Type: schema.TypeString, 64 | Required: true, 65 | Description: "CIDR block", 66 | }, 67 | "subnet_name": { 68 | Type: schema.TypeString, 69 | Required: true, 70 | Description: "Subnet names", 71 | }, 72 | "tf_id": { 73 | Computed: true, 74 | Type: schema.TypeString, 75 | }, 76 | }, 77 | } 78 | } 79 | 80 | func cloudeosSubnetCreate(d *schema.ResourceData, m interface{}) error { 81 | provider := m.(CloudeosProvider) 82 | err := provider.AddSubnet(d) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | err = d.Set("computed_subnet_id", d.Get("subnet_id").(string)) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | uuid := "cloudeos-subnet" + strings.TrimPrefix(d.Get("tf_id").(string), SubnetPrefix) 93 | log.Print("Successfully added " + uuid) 94 | d.SetId(uuid) 95 | return nil 96 | } 97 | 98 | func cloudeosSubnetRead(d *schema.ResourceData, m interface{}) error { 99 | return nil 100 | } 101 | 102 | func cloudeosSubnetUpdate(d *schema.ResourceData, m interface{}) error { 103 | provider := m.(CloudeosProvider) 104 | err := provider.AddSubnet(d) 105 | if err != nil { 106 | return err 107 | } 108 | log.Print("Successfully updated cloudeos-subnet" + 109 | strings.TrimPrefix(d.Get("tf_id").(string), SubnetPrefix)) 110 | return nil 111 | } 112 | 113 | func cloudeosSubnetDelete(d *schema.ResourceData, m interface{}) error { 114 | provider := m.(CloudeosProvider) 115 | err := provider.DeleteSubnet(d) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | log.Print("Successfully deleted cloudeos-subnet" + 121 | strings.TrimPrefix(d.Get("tf_id").(string), SubnetPrefix)) 122 | d.SetId("") 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_topology.md: -------------------------------------------------------------------------------- 1 | # `cloudeos_topology` 2 | 3 | The CloudEOS provider is used to create a network fabric ( topology ) which spans across multiple 4 | cloud providers and regions. The solution deploys the network fabric using BGP-EVPN, VxLAN, DPS and Ipsec. 5 | 6 | To get the desired parameters and requirements from the user about this fabric we have the following 7 | resources: `cloudeos_topology`, `cloudeos_clos` and `cloudeos_wan`. 8 | 9 | For example, a deployment which spans across two AWS regions ( us-east-1 and us-west-1 ) 10 | and one Azure region ( westus2 ) will need the user to create: one `cloudeos_topology` resource, 11 | one `cloudeos_wan` resource and three `cloudeos_clos` resource. 12 | 13 | The `cloudeos_topology` resource created above is then referenced by other `CloudEOS` resources to associate with 14 | a given topology. 15 | 16 | #### Note: Two `cloudeos_topology` with the same topology_name cannot be created. 17 | 18 | ## Example Usage 19 | 20 | ```hcl 21 | resource "cloudeos_topology" "topology" { 22 | topology_name = "topo-test" // topology name 23 | bgp_asn = "65000-65100" // BGP ASN range 24 | vtep_ip_cidr = "1.0.0.0/16" // VTEP CIDR 25 | terminattr_ip_cidr = "4.0.0.0/16" // Terminattr CIDR 26 | dps_controlplane_cidr = "3.0.0.0/16" // DPS control plane cidr 27 | } 28 | ``` 29 | 30 | ## Argument Reference 31 | 32 | * `topology_name` - (Required) Name of the topology. 33 | * `deploy_mode` - (Optional) Deployment mode for the topology. Valid values are "" (empty) - deploy and 34 | configure a fabric OR "provision" - Deploy the routers and onboard them to Cvaas, but do not create a 35 | fabric by configuring them. When not specified, deploy_mode defaults to "". See examples/ 36 | multicloud_tworegion_provisionmode for an example deployment using provision deploy mode. 37 | * `bgp_asn` - (Optional) A range of BGP ASN’s which would be used to configure CloudEOS instances, 38 | based on the role and region in which they are being deployed. For example, a CloudEdge and CloudLeaf 39 | instance in the same region and CLOS will use iBGP and will have the same ASN. Whereas 2 CloudEdge’s 40 | in different regions use eBGP and will have different ASNs. Required when deploy_mode is empty; Not needed 41 | when deploy_mode is provision. 42 | * `vtep_ip_cidr` - (Optional) CIDR block for VTEP IPs for CloudEOS Routers. Required when deploy_mode is empty; 43 | Not needed when deploy_mode is provision. 44 | * `terminattr_ip_cidr` - (Optional) TerminAttr is used by Arista devices to stream Telemetry to CVaaS. 45 | Every CloudEOS Router needs a unique TerminAttr local IP. Required when deploy_mode is empty; Not needed 46 | when deploy_mode is provision. 47 | * `dps_controlplane_cidr` - (Optional) Each CloudEOS router needs a unique IP for Dynamic Path Selection. 48 | Required when deploy_mode is empty; Not needed when deploy_mode is provision. 49 | * `eos_managed` - (Optional) List of CloudEOS devices already deployed. 50 | 51 | CVaaS reserves ip and asn from the ranges specified in the arguments above to deploy the fabric. The VNI range 52 | - 101 to 116 is reserved by CVaaS and any vni's needed to deploy the fabric are handed out from this range. 53 | Furthermore, a loopback10 interface is created and assigned an ip from the 198.18.0.0/16 range for each router. 54 | This allows configuration changes to be pushed out from CVaaS. 55 | ## Attributes Reference 56 | 57 | In addition to the Arguments listed above - the following Attributes are exported: 58 | 59 | * `ID` - The ID of the cloudeos_topology Resource. 60 | 61 | ## Timeouts 62 | 63 | * `delete` - (Defaults to 5 minutes) Used when deleting the Topology Resource. 64 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_wan.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | "log" 10 | "strings" 11 | "time" 12 | 13 | "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 15 | ) 16 | 17 | // cloudeosWan: Define the cloudeos wan topology schema ( input and output variables ) 18 | func cloudeosWan() *schema.Resource { 19 | return &schema.Resource{ 20 | Create: cloudeosWanCreate, 21 | Read: cloudeosWanRead, 22 | Update: cloudeosWanUpdate, 23 | Delete: cloudeosWanDelete, 24 | 25 | Timeouts: &schema.ResourceTimeout{ 26 | Delete: schema.DefaultTimeout(5 * time.Minute), 27 | }, 28 | 29 | Schema: map[string]*schema.Schema{ 30 | "name": { 31 | Type: schema.TypeString, 32 | Required: true, 33 | Description: "Wan fabric name", 34 | DiffSuppressFunc: suppressAttributeChange, 35 | }, 36 | "topology_name": { 37 | Type: schema.TypeString, 38 | Required: true, 39 | Description: "Base topology name", 40 | DiffSuppressFunc: suppressAttributeChange, 41 | }, 42 | "edge_to_edge_peering": { 43 | Type: schema.TypeBool, 44 | Optional: true, 45 | Default: false, 46 | }, 47 | "edge_to_edge_dedicated_connect": { 48 | Type: schema.TypeBool, 49 | Optional: true, 50 | Default: false, 51 | }, 52 | "edge_to_edge_igw": { 53 | Type: schema.TypeBool, 54 | Optional: true, 55 | Default: true, 56 | }, 57 | "cv_container_name": { 58 | Type: schema.TypeString, 59 | Optional: true, 60 | Description: "Container name for edge", 61 | Default: "CloudEdge", 62 | DiffSuppressFunc: suppressAttributeChange, 63 | }, 64 | "tf_id": { 65 | Computed: true, 66 | Type: schema.TypeString, 67 | }, 68 | }, 69 | } 70 | } 71 | 72 | func cloudeosWanCreate(d *schema.ResourceData, m interface{}) error { 73 | provider := m.(CloudeosProvider) 74 | allowed, err := provider.IsValidTopoAddition(d, "TOPO_INFO_WAN") 75 | if !allowed || err != nil { 76 | return err 77 | } 78 | 79 | err = provider.AddWanTopology(d) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | uuid := "cloudeos-wan" + strings.TrimPrefix(d.Get("tf_id").(string), WanPrefix) 85 | log.Print("Successfully added " + uuid) 86 | d.SetId(uuid) 87 | return nil 88 | } 89 | 90 | func cloudeosWanRead(d *schema.ResourceData, m interface{}) error { 91 | return nil 92 | } 93 | 94 | func cloudeosWanUpdate(d *schema.ResourceData, m interface{}) error { 95 | provider := m.(CloudeosProvider) 96 | err := provider.AddWanTopology(d) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | log.Print("Successfully updated cloudeos-wan" + 102 | strings.TrimPrefix(d.Get("tf_id").(string), WanPrefix)) 103 | return nil 104 | } 105 | 106 | func cloudeosWanDelete(d *schema.ResourceData, m interface{}) error { 107 | provider := m.(CloudeosProvider) 108 | err := provider.DeleteWanTopology(d) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | uuid := "cloudeos-wan" + strings.TrimPrefix(d.Get("tf_id").(string), WanPrefix) 114 | // wait for topology deletion 115 | err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { 116 | if err := provider.CheckTopologyDeletionStatus(d); err != nil { 117 | return resource.RetryableError(err) 118 | } 119 | return nil 120 | }) 121 | if err != nil { 122 | return errors.New("Failed to destroy " + uuid + " error: " + err.Error()) 123 | } 124 | 125 | log.Print("Successfully deleted " + uuid) 126 | d.SetId("") 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/tag.v1/tag.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | option deprecated = true; 8 | 9 | package arista.tag.v1; 10 | 11 | option go_package = "arista/resources/arista/tag.v1;tag"; 12 | 13 | import "google/protobuf/wrappers.proto"; 14 | 15 | import "fmp/extensions.proto"; 16 | 17 | // TagKey uniquely identifies a tag for a network element. 18 | message TagKey { 19 | option (fmp.model_key) = true; 20 | 21 | // Label is the label of the tag. 22 | google.protobuf.StringValue label = 1; 23 | // Value is the value of the tag. 24 | google.protobuf.StringValue value = 2; 25 | } 26 | 27 | // CreatorType specifies an entity that creates something. 28 | enum CreatorType { 29 | CREATOR_TYPE_UNSPECIFIED = 0; 30 | // CREATOR_TYPE_SYSTEM is the type for something created by the system. 31 | CREATOR_TYPE_SYSTEM = 1; 32 | // CREATOR_TYPE_USER is the type for something created by a user. 33 | CREATOR_TYPE_USER = 2; 34 | } 35 | 36 | // InterfaceTagConfig is a label-value pair that may or may 37 | // not be assigned to an interface. 38 | message InterfaceTagConfig { 39 | option (fmp.model) = "rw"; 40 | 41 | // Key uniquely identifies the interface tag. 42 | TagKey key = 1; 43 | } 44 | 45 | // InterfaceTag is a label-value pair that may or may 46 | // not be assigned to an interface. 47 | message InterfaceTag { 48 | option (fmp.model) = "ro"; 49 | 50 | // Key uniquely identifies the interface tag. 51 | TagKey key = 1; 52 | // CreatorType is the creator type of the tag. 53 | CreatorType creator_type = 2; 54 | } 55 | 56 | // InterfaceTagAssignmentKey uniquely identifies an interface 57 | // tag assignment. 58 | message InterfaceTagAssignmentKey { 59 | option (fmp.model_key) = true; 60 | 61 | // Label is the label of the tag. 62 | google.protobuf.StringValue label = 1; 63 | // Value is the value of the tag. 64 | google.protobuf.StringValue value = 2; 65 | // DeviceId is the ID of the interface's device. 66 | google.protobuf.StringValue device_id = 3; 67 | // InterfaceId is the ID of the interface. 68 | google.protobuf.StringValue interface_id = 4; 69 | } 70 | 71 | // InterfaceTagAssignmentConfig is the assignment of an interface tag 72 | // to a specific interface. 73 | message InterfaceTagAssignmentConfig { 74 | option (fmp.model) = "rw"; 75 | 76 | // Key uniquely identifies the interface tag assignment. 77 | InterfaceTagAssignmentKey key = 1; 78 | } 79 | 80 | // DeviceTagConfig is a label-value pair that may or may not 81 | // be assigned to a device. 82 | message DeviceTagConfig { 83 | option (fmp.model) = "rw"; 84 | 85 | // Key uniquely identifies the device tag. 86 | TagKey key = 1; 87 | } 88 | 89 | // DeviceTag is a label-value pair that may or may not 90 | // be assigned to a device. 91 | message DeviceTag { 92 | option (fmp.model) = "ro"; 93 | 94 | // Key uniquely identifies the device tag. 95 | TagKey key = 1; 96 | // CreatorType is the creator type of the tag. 97 | CreatorType creator_type = 2; 98 | } 99 | 100 | // DeviceTagAssignmentKey uniquely identifies a device tag 101 | // assignment. 102 | message DeviceTagAssignmentKey { 103 | option (fmp.model_key) = true; 104 | 105 | // Label is the label of the tag. 106 | google.protobuf.StringValue label = 1; 107 | // Value is the value of the tag. 108 | google.protobuf.StringValue value = 2; 109 | // DeviceId is the ID of the device. 110 | google.protobuf.StringValue device_id = 3; 111 | } 112 | 113 | // DeviceTagAssignmentConfig is the assignment of a device tag to a 114 | // specific device. 115 | message DeviceTagAssignmentConfig { 116 | option (fmp.model) = "rw"; 117 | 118 | // Key uniquely identifies the device tag assignment. 119 | DeviceTagAssignmentKey key = 1; 120 | } 121 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider for Arista CloudEOS 2 | 3 | The Arista CloudEOS provider helps with automating the deployment of a multi-cloud network 4 | fabric using Arista CloudVision as a Service ( CVaaS ). The provider interacts with CVaaS to 5 | create a BGP/EVPN/VxLAN based overlay network between CloudEOS Routers running in various 6 | regions across Cloud Providers. Optionally, it can be used to automate the deployment of 7 | standalone CloudEOS routers across Cloud Providers and their integration with CVaaS, wherein 8 | the CloudEOS configuration to define the topology is created and applied by the user. 9 | 10 | ## Terminology 11 | 12 | * CVaaS : Arista [CloudVision](https://www.arista.com/en/products/eos/eos-cloudvision) as-a-Service. 13 | CloudVision as a Service is the root access point for customers to utilize the CloudEOS solution. 14 | CVaaS supports a single point of orchestration for multi-cloud, multi-tenant and multi-account management. 15 | * CloudEdge - CloudEdge is a instance of CloudEOS that provides interconnection services with other public clouds 16 | within the client’s autonomous system. The CloudEdge also interconnects VPCs and VNETs within a cloud provider region. 17 | * CloudLeaf - CloudLeaf is an instance of CloudEOS that is deployed in the VPC and VNETs that hosts the applications VMs. 18 | It is the gateway for all incoming and outgoing traffic for the VPC. 19 | * Cloud Network Private Segment (CNPS) - The VRF name used for segmentation across your cloud network. 20 | * CLOS topology - EPVN based spine-leaf topology to interconnect all leaf VPCs in a region 21 | to the CloudEdge routers deployed in the transit/Edge VPC. 22 | * WAN topology - EVPN based full mesh topology to interconnect all the CloudEdges over Internet. 23 | * DPS - [Dynamic Path Selection](https://www.arista.com/en/cg-veos-router/veos-router-dynamic-path-selection-overview) 24 | 25 | ## Requirements 26 | 27 | - [Terraform](https://www.terraform.io/downloads.html) 0.13+ 28 | - [Go](https://golang.org/doc/install) 1.13 (to build the provider plugin) 29 | 30 | ## Usage 31 | 32 | ### CloudEOS Provider 33 | ``` 34 | provider "cloudeos" { 35 | cvaas_domain = "apiserver.arista.io" 36 | cvaas_server = "arista.io" 37 | service_account_web_token = "..." 38 | } 39 | ``` 40 | 41 | ### Argument Reference 42 | * cvaas_domain - (Required) CVaaS Domain name 43 | * cvaas_server - (Required) CVaaS Server Name 44 | * service_account_web_token - (Required) The access token to authenticate the Terraform client to CVaaS. 45 | 46 | ## Resources 47 | Documentation for the resources supported by the CloudEOS Provider can be found in the [resources](https://github.com/aristanetworks/terraform-provider-cloudeos/tree/master/docs/resources) folder. 48 | 49 | ## Limitations and Caveats 50 | 51 | ### v1.0.0 52 | * The `cloudeos_topology, cloudeos_clos and cloudeos_wan` resources do not support updates. These resources cannot be 53 | changed after the other cloudeos resources have been deployed. 54 | * A CloudLeaf VPC and Router should only be deployed after a CloudEdge VPC and Router have been deployed. 55 | Without a deployed CloudEdge router, CloudLeaf routers cannot stream to CVaaS. 56 | * A CloudEdge should only be destroyed after all the corresponding CloudLeafs have been destroyed. 57 | * The VPC `cidr_block` cannot be changed after the VPC is deployed. You will have to delete the VPC and redeploy 58 | again. 59 | * Before deploying the CloudEOS router, the Configlet Container must be manually created on CVaaS. 60 | * CloudEOS Route Reflector Routers ( with `is_rr = true` ) can only be deployed in a single VPC. 61 | * CloudEOS Route Reflector should be deployed in the same VPC as one of the CloudEOS Edge Routers. If you want the 62 | Route Reflectors to be in its own VPC, create a new `cloudeos_clos` resource and associate the `cloudeos_vpc_config` 63 | resource with the `cloudeos_clos` name. 64 | * The `cnps` attribute for the `cloudeos_vpc_config` doesn't support updates. 65 | To update `cnps` you will have to redeploy the resource. 66 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/tag.v2/tag.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.tag.v2; 8 | 9 | option go_package = "arista/resources/arista/tag.v2;tag"; 10 | 11 | import "google/protobuf/wrappers.proto"; 12 | import "fmp/extensions.proto"; 13 | 14 | // ElementType enumerates the types of network elements that can 15 | // be associated with tags. 16 | enum ElementType { 17 | ELEMENT_TYPE_UNSPECIFIED = 0; 18 | // ELEMENT_TYPE_DEVICE is used for device tags. 19 | ELEMENT_TYPE_DEVICE = 1; 20 | // ELEMENT_TYPE_INTERFACE is used for interface tags. 21 | ELEMENT_TYPE_INTERFACE = 2; 22 | } 23 | 24 | // TagKey uniquely identifies a tag. 25 | message TagKey { 26 | option (fmp.model_key) = true; 27 | // workspace_id is the ID of a workspace. The special ID "" 28 | // identifies the location where merged tags reside. 29 | google.protobuf.StringValue workspace_id = 1; 30 | // element_type is the category of network element to which 31 | // this tag can be assigned. 32 | ElementType element_type = 2; 33 | // label is an arbitrary label. 34 | google.protobuf.StringValue label = 3; 35 | // value is an arbitrary value. 36 | google.protobuf.StringValue value = 4; 37 | } 38 | 39 | // TagConfig holds a configuration for a user tag. 40 | message TagConfig { 41 | option (fmp.model) = "rw"; 42 | // key identifies a tag. The special workspace ID "" for 43 | // merged tags should not be set here. 44 | TagKey key = 1; 45 | // remove indicates whether to remove (true) or add (false, 46 | // unset) the tag identified by the key if the encompassing 47 | // workspace merges. 48 | google.protobuf.BoolValue remove = 2; 49 | } 50 | 51 | // CreatorType enumerates the types of entities that can create 52 | // a tag. 53 | enum CreatorType { 54 | CREATOR_TYPE_UNSPECIFIED = 0; 55 | // CREATOR_TYPE_SYSTEM is used for system tags. 56 | CREATOR_TYPE_SYSTEM = 1; 57 | // CREATOR_TYPE_USER is used for user tags. 58 | CREATOR_TYPE_USER = 2; 59 | } 60 | 61 | // Tag holds a merge-preview or the existing merged state (if the 62 | // workspace ID is "") of a tag. 63 | message Tag { 64 | option (fmp.model) = "ro"; 65 | // key identifies a tag. 66 | TagKey key = 1; 67 | // creator_type is the creator type of the tag. 68 | CreatorType creator_type = 2; 69 | } 70 | 71 | // TagAssignmentKey uniquely identifies an assignment between 72 | // a tag and a network element. 73 | message TagAssignmentKey { 74 | option (fmp.model_key) = true; 75 | // workspace_id is the ID of a workspace. The special ID "" 76 | // identifies the location where merged assignments reside. 77 | google.protobuf.StringValue workspace_id = 1; 78 | // element_type is the element type of a tag. What should 79 | // be set per element type: 80 | // 81 | // ELEMENT_TYPE_DEVICE: device_id 82 | // ELEMENT_TYPE_INTERFACE: device_id, interface_id 83 | ElementType element_type = 2; 84 | // label is the label of a tag. 85 | google.protobuf.StringValue label = 3; 86 | // value is the value of a tag. 87 | google.protobuf.StringValue value = 4; 88 | // device_id identifies a device. 89 | google.protobuf.StringValue device_id = 5; 90 | // interface_id identifies an interface on a device. 91 | google.protobuf.StringValue interface_id = 6; 92 | } 93 | 94 | // TagAssignmentConfig holds a configuration for an assignment 95 | // between a tag and a network element. 96 | message TagAssignmentConfig { 97 | option (fmp.model) = "rw"; 98 | // key identifies an assignment. The special workspace ID "" 99 | // for merged assignments should not be set here. 100 | TagAssignmentKey key = 1; 101 | // remove indicates whether to remove (true) or add (false, 102 | // unset) the assignment identified by the key if the 103 | // encompassing workspace merges. 104 | google.protobuf.BoolValue remove = 2; 105 | } 106 | 107 | // TagAssignment holds a merge-preview or the existing merged 108 | // state (if the workspace ID is "") of an assignment between 109 | // a tag and a network element. 110 | message TagAssignment { 111 | option (fmp.model) = "ro"; 112 | // key identifies an assignment. 113 | TagAssignmentKey key = 1; 114 | } 115 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_clos.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "log" 11 | "strings" 12 | "time" 13 | 14 | "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 15 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 16 | ) 17 | 18 | // cloudeosClos: Define the cloudeos clos schema ( input and output variables ) 19 | func cloudeosClos() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: cloudeosClosCreate, 22 | Read: cloudeosClosRead, 23 | Update: cloudeosClosUpdate, 24 | Delete: cloudeosClosDelete, 25 | 26 | Timeouts: &schema.ResourceTimeout{ 27 | Delete: schema.DefaultTimeout(5 * time.Minute), 28 | }, 29 | 30 | Schema: map[string]*schema.Schema{ 31 | "name": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | Description: "Clos topology name", 35 | DiffSuppressFunc: suppressAttributeChange, 36 | }, 37 | "topology_name": { 38 | Type: schema.TypeString, 39 | Required: true, 40 | Description: "Base topology name", 41 | DiffSuppressFunc: suppressAttributeChange, 42 | }, 43 | "fabric": { 44 | Type: schema.TypeString, 45 | Optional: true, 46 | Default: "hub_spoke", 47 | Description: "full_mesh or hub_spoke", 48 | ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { 49 | v := val.(string) 50 | if v != "full_mesh" && v != "hub_spoke" { 51 | errs = append(errs, fmt.Errorf( 52 | "%q must be full_mesh/hub_spoke got: %q", key, v)) 53 | } 54 | return 55 | }, 56 | }, 57 | "leaf_to_edge_peering": { 58 | Type: schema.TypeBool, 59 | Optional: true, 60 | Default: true, 61 | }, 62 | "leaf_to_edge_igw": { 63 | Type: schema.TypeBool, 64 | Optional: true, 65 | Default: false, 66 | }, 67 | "leaf_encryption": { 68 | Type: schema.TypeBool, 69 | Optional: true, 70 | Default: false, 71 | }, 72 | "cv_container_name": { 73 | Type: schema.TypeString, 74 | Optional: true, 75 | Default: "CloudLeaf", 76 | Description: "Container name for leaf", 77 | DiffSuppressFunc: suppressAttributeChange, 78 | }, 79 | "tf_id": { 80 | Computed: true, 81 | Type: schema.TypeString, 82 | }, 83 | }, 84 | } 85 | } 86 | 87 | func cloudeosClosCreate(d *schema.ResourceData, m interface{}) error { 88 | provider := m.(CloudeosProvider) 89 | allowed, err := provider.IsValidTopoAddition(d, "TOPO_INFO_CLOS") 90 | if !allowed || err != nil { 91 | return err 92 | } 93 | err = provider.AddClosTopology(d) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | uuid := "cloudeos-clos" + strings.TrimPrefix(d.Get("tf_id").(string), ClosPrefix) 99 | log.Print("Successfully added " + uuid) 100 | d.SetId(uuid) 101 | return nil 102 | } 103 | 104 | func cloudeosClosRead(d *schema.ResourceData, m interface{}) error { 105 | return nil 106 | } 107 | 108 | func cloudeosClosUpdate(d *schema.ResourceData, m interface{}) error { 109 | provider := m.(CloudeosProvider) 110 | err := provider.AddClosTopology(d) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | log.Print("Successfully updated cloudeos-clos" + 116 | strings.TrimPrefix(d.Get("tf_id").(string), ClosPrefix)) 117 | return nil 118 | } 119 | 120 | func cloudeosClosDelete(d *schema.ResourceData, m interface{}) error { 121 | provider := m.(CloudeosProvider) 122 | err := provider.DeleteClosTopology(d) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | uuid := "cloudeos-clos" + strings.TrimPrefix(d.Get("tf_id").(string), ClosPrefix) 128 | // wait for topology deletion 129 | err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { 130 | if err := provider.CheckTopologyDeletionStatus(d); err != nil { 131 | return resource.RetryableError(err) 132 | } 133 | return nil 134 | }) 135 | if err != nil { 136 | return errors.New("Failed to destroy " + uuid + " error: " + err.Error()) 137 | } 138 | 139 | log.Print("Successfully deleted " + uuid) 140 | d.SetId("") 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_aws_vpn.md: -------------------------------------------------------------------------------- 1 | # cloudeos_aws_vpn 2 | 3 | The `cloudeos_aws_vpn` resource sends the Ipsec Site-Site VPN connections and attachments information created in AWS to 4 | CVaaS to configure the CloudEOS router with the Ipsec VTI Tunnel in a given CNPS segment ( VRF ). This resource works 5 | for Site-Site VPN connections attached to AWS Transit Gateway Route tables and AWS VPN Gateways. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "aws_ec2_transit_gateway" "tgw" {} 11 | 12 | resource "aws_customer_gateway" "routerVpnGw" { 13 | bgp_asn = 65000 14 | ip_address = "10.0.0.1" 15 | type = "ipsec.1" 16 | } 17 | 18 | resource "aws_ec2_transit_gateway_route_table" "rtTable" { 19 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id 20 | } 21 | 22 | resource "aws_vpn_connection" "vpnConn" { 23 | customer_gateway_id = aws_customer_gateway.routerVpnGw.id 24 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id 25 | type = "ipsec.1" 26 | } 27 | 28 | resource "aws_ec2_transit_gateway_route_table_association" "tgw_rt_association" { 29 | transit_gateway_attachment_id = aws_vpn_connection.vpnConn.transit_gateway_attachment_id 30 | transit_gateway_route_table_id = 31 | } 32 | 33 | resource "cloudeos_aws_vpn" "vpn_config" { 34 | cgw_id = aws_customer_gateway.routerVpnGw.id 35 | cnps = "dev" 36 | router_id = "" 37 | vpn_connection_id = aws_vpn_connection.vpnConn.id 38 | tunnel1_aws_endpoint_ip = aws_vpn_connection.vpnConn.tunnel1_address 39 | tunnel1_aws_overlay_ip = aws_vpn_connection.vpnConn.tunnel1_vgw_inside_address 40 | tunnel1_router_overlay_ip = aws_vpn_connection.vpnConn.tunnel1_cgw_inside_address 41 | tunnel1_bgp_asn = aws_vpn_connection.vpnConn.tunnel1_bgp_asn 42 | tunnel1_bgp_holdtime = aws_vpn_connection.vpnConn.tunnel1_bgp_holdtime 43 | tunnel1_preshared_key = aws_vpn_connection.vpnConn.tunnel1_preshared_key 44 | tunnel2_aws_endpoint_ip = aws_vpn_connection.vpnConn.tunnel2_address 45 | tunnel2_aws_overlay_ip = aws_vpn_connection.vpnConn.tunnel2_vgw_inside_address 46 | tunnel2_router_overlay_ip = aws_vpn_connection.vpnConn.tunnel2_cgw_inside_address 47 | tunnel2_bgp_asn = aws_vpn_connection.vpnConn.tunnel1_bgp_asn 48 | tunnel2_bgp_holdtime = aws_vpn_connection.vpnConn.tunnel2_bgp_holdtime 49 | tunnel2_preshared_key = aws_vpn_connection.vpnConn.tunnel2_preshared_key 50 | tgw_id = aws_ec2_transit_gateway.tgw.id 51 | vpn_gateway_id = "" 52 | vpn_tgw_attachment_id = aws_vpn_connection.vpnConn.transit_gateway_attachment_id 53 | 54 | } 55 | ``` 56 | 57 | ## Argument Reference 58 | * `cgw_id` - (Required) AWS Customer Gateway ID 59 | * `cnps` - (Required) VRF Segment in which the Ipsec VPN is created. 60 | * `router_id` - (Required) CloudEOS Router to which the AWS Ipsec VPN terminates. 61 | * `vpn_connection_id` - (Required) AWS Site-to-Site VPN Connection ID 62 | * `tunnel1_aws_endpoint_ip` - (Required) AWS Tunnel1 Underlay IP Address 63 | * `tunnel1_aws_overlay_ip` - (Required) VPN Tunnel1 IP address 64 | * `tunnel1_router_overlay_ip` - (Required) CloudEOS Router Tunnel1 IP address 65 | * `tunnel1_bgp_asn` - (Required) AWS VPN Tunnel1 BGP ASN 66 | * `tunnel1_bgp_holdtime` - (Required) VPN Tunnel1 BGP Hold time 67 | * `tunnel1_preshared_key` - (Required) VPN Tunnel1 Ipsec Preshared key 68 | * `tunnel2_aws_endpoint_ip` - (Required) AWS VPN Tunnel2 Underlay IP Address 69 | * `tunnel2_aws_overlay_ip` - (Required) AWS VPN Tunnel2 IP address 70 | * `tunnel2_router_overlay_ip` - (Required) CloudEOS Router Tunnel2 IP address 71 | * `tunnel2_bgp_asn` - (Required) AWS VPN Tunnel2 BGP ASN 72 | * `tunnel2_bgp_holdtime` - (Required) VPN Tunnel2 BGP Hold time 73 | * `tunnel2_preshared_key` - (Required) VPN Tunnel2 Ipsec Preshared key 74 | * `tgw_id` - (Optional) AWS Transit Gateway ID, if the AWS Site-to-Site connection terminates on a TGW. 75 | * `vpn_gateway_id` - (Optional) AWS VPN Gateway ID, if the AWS Site-to-Site connection terminates on a VPN Gateway. 76 | * `vpn_tgw_attachment_id` - (Optional) AWS VPN Transit Gateway Attachment ID 77 | 78 | ## Attributes Reference 79 | 80 | In addition to Arguments listed above - the following Attributes are exported 81 | 82 | * `tf_id` - The ID of cloudeos_aws_vpn Resource. 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/event.v1/event.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.event.v1; 8 | 9 | option go_package = "arista/resources/arista/event.v1;event"; 10 | 11 | import "google/protobuf/timestamp.proto"; 12 | import "google/protobuf/wrappers.proto"; 13 | 14 | import "fmp/extensions.proto"; 15 | 16 | // EventSeverity is the severity level of the event 17 | enum EventSeverity { 18 | EVENT_SEVERITY_UNSPECIFIED = 0; 19 | EVENT_SEVERITY_INFO = 1; 20 | EVENT_SEVERITY_WARNING = 2; 21 | EVENT_SEVERITY_ERROR = 3; 22 | EVENT_SEVERITY_CRITICAL = 4; 23 | } 24 | 25 | // ComponentType describes the type of entity on which the event occured 26 | enum ComponentType { 27 | COMPONENT_TYPE_UNSPECIFIED = 0; 28 | COMPONENT_TYPE_DEVICE = 1; 29 | COMPONENT_TYPE_INTERFACE = 2; 30 | COMPONENT_TYPE_TURBINE = 3; 31 | } 32 | 33 | // EventComponent describes an entity on which the event occured 34 | message EventComponent { 35 | // type is the type of component 36 | ComponentType type = 1; 37 | // components identifies the entity on which the event occured 38 | map components = 2; 39 | } 40 | 41 | // EventComponents contains entities on which an event occured 42 | message EventComponents { 43 | // components describes the components on which an event occured 44 | repeated EventComponent components = 1; 45 | } 46 | 47 | // EventAck contains acknowledgement information of an event 48 | message EventAck { 49 | // ack is the acknowledgement state of an event 50 | google.protobuf.BoolValue ack = 1; 51 | // acker is the user that acknowledged the event 52 | google.protobuf.StringValue acker = 2; 53 | // ack_time is the time of acknowledgement 54 | google.protobuf.Timestamp ack_time = 3; 55 | } 56 | 57 | // EventNoteConfig configures a note 58 | message EventNoteConfig { 59 | // note is the text of the note 60 | google.protobuf.StringValue note = 1; 61 | } 62 | 63 | // Eventnote is the state of a note 64 | message EventNote { 65 | // note is the text of the note 66 | google.protobuf.StringValue note = 1; 67 | // note_creator is the creator of the note 68 | google.protobuf.StringValue note_creator = 2; 69 | } 70 | 71 | // EventKey uniquely identifies an event 72 | message EventKey { 73 | option (fmp.model_key) = true; 74 | // key is the event data identifier 75 | google.protobuf.StringValue key = 1; 76 | // timestamp is the time the event occured 77 | google.protobuf.Timestamp timestamp = 2; 78 | } 79 | 80 | // EventData is additional event data 81 | message EventData { 82 | // data is event data specific to the type of this event 83 | map data = 1; 84 | } 85 | 86 | // EventNotesConfig configures the notes of an event 87 | message EventNotesConfig { 88 | // notes is keyed by desired note time in Unix time, in milliseconds 89 | map notes = 1; 90 | } 91 | 92 | // EventAnnotationConfig configures an event annotation 93 | message EventAnnotationConfig{ 94 | option (fmp.model) = "rw"; 95 | // key is the event instance identifier 96 | EventKey key = 1; 97 | // ack is the acknowledgement state of an event 98 | google.protobuf.BoolValue ack = 2; 99 | // notes is the notes on an event 100 | EventNotesConfig notes = 3; 101 | } 102 | 103 | // EventNotes is the notes of an event state 104 | message EventNotes { 105 | // notes is keyed by the time desired 106 | map notes = 1; 107 | } 108 | 109 | // Event is a telemetry event 110 | message Event { 111 | option (fmp.model) = "ro"; 112 | // key is the event instance identifier 113 | EventKey key = 1; 114 | // severity is the severity of the event 115 | EventSeverity severity = 2; 116 | // title is the title of the event 117 | google.protobuf.StringValue title = 3; 118 | // description is the description of the event 119 | google.protobuf.StringValue description = 4; 120 | // event_type is the type of the event 121 | google.protobuf.StringValue event_type = 5; 122 | // data is the data of the event 123 | EventData data = 6; 124 | // components is the components on which the event occurred 125 | EventComponents components = 7; 126 | // ack is the acknowledgement status of the event 127 | EventAck ack = 8; 128 | // notes is the notes of the event 129 | EventNotes notes = 9; 130 | // last_updated_time is the time of the most recent update to the event 131 | google.protobuf.Timestamp last_updated_time = 10; 132 | } 133 | -------------------------------------------------------------------------------- /cloudeos/client_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "encoding/json" 9 | "log" 10 | 11 | "github.com/iancoleman/strcase" 12 | "google.golang.org/genproto/protobuf/field_mask" 13 | ) 14 | 15 | /******************************************************* 16 | * Use getOuterFieldMask() to get fieldMask of the provided protobuf struct. 17 | The returned fieldMask will have field names of struct set. 18 | * 19 | * For slices/arrays of embedded structs within a protobuf struct, the fieldMask is expected to be 20 | set within the fieldMask of the embedded struct. 21 | Use getOuterFieldMask() for this or invoke getFieldMask() with prefix="" 22 | For eg: 23 | type Bar struct { 24 | id string 25 | fieldMask *field_mask.FieldMask 26 | } 27 | 28 | type Foo struct { 29 | name string 30 | bar []*bar 31 | fieldMask *field_mask.FieldMask 32 | } 33 | 34 | An object of type Foo should look as below 35 | {name:"foo1", bar:{id:"xyz", fieldMask:{paths:id}}, 36 | bar:{id:"abc", fieldMask:{paths:id}}, fieldMask:{paths:name, paths:bar} 37 | 38 | * For embedded structs within a protobuf struct, the field names of the embedded struct should be 39 | prefixed with the field name representing the embedded struct in the outer protobuf struct. 40 | For eg: 41 | type struct Bar { 42 | id string 43 | fieldMask *field_mask.FieldMask 44 | } 45 | type struct Foo { 46 | name string 47 | bar *Bar 48 | fieldMask *field_mask.FieldMask 49 | } 50 | 51 | An object of type Foo should look as below 52 | {name:"foo1", bar:{id:"xyz", fieldMask:{}}, fieldMask:{paths:name, paths:bar.id} 53 | 54 | Use appendInnerFieldMask() to append prefixed inner field 55 | mask to the outer field mask. 56 | *********************************************************/ 57 | 58 | // Returns outer field mask if prefix is empty. 59 | // Returns inner field masks if prefix is provided. 60 | func getFieldMask(pbStruct interface{}, prefix string) (*field_mask.FieldMask, error) { 61 | // Convert the protobuf struct to json. Because of the omitempty tags in protobuf struct, 62 | // the marshalled json will have only fields that are set to non-default values. 63 | pbJSON, err := json.Marshal(pbStruct) 64 | if err != nil { 65 | log.Print("Failed to marshal protobuf struct") 66 | return nil, err 67 | } 68 | 69 | // Convert the json to map so as to retrieve the field names. 70 | pbMap := make(map[string]interface{}) 71 | err = json.Unmarshal(pbJSON, &pbMap) 72 | if err != nil { 73 | log.Print("Failed to unmarshal protobuf json to map") 74 | return nil, err 75 | } 76 | 77 | // The marshalled json has field names corresponding to the json tags of 78 | // the protobuf fields, while fieldMask in the grpc message is expected to be in 79 | // lower camel case. So extract the field names in the json and convert it 80 | // to lower camel case before adding it to the fieldMask. 81 | var fieldMask field_mask.FieldMask 82 | for key := range pbMap { 83 | path := strcase.ToLowerCamel(key) 84 | if prefix != "" { 85 | path = prefix + strcase.ToLowerCamel(key) 86 | } 87 | fieldMask.Paths = append(fieldMask.Paths, path) 88 | } 89 | 90 | return &fieldMask, nil 91 | } 92 | 93 | func getOuterFieldMask(pbStruct interface{}) (*field_mask.FieldMask, error) { 94 | fm, err := getFieldMask(pbStruct, "") 95 | return fm, err 96 | } 97 | 98 | func getPrefixedFieldMask(pbStruct interface{}, prefix string) (*field_mask.FieldMask, error) { 99 | fm, err := getFieldMask(pbStruct, prefix) 100 | return fm, err 101 | } 102 | 103 | // Appends prefixed inner fields in the outer field mask. 104 | func appendInnerFieldMask(innerPbStruct interface{}, outerFieldMask *field_mask.FieldMask, 105 | innerFieldMaskPrefix string) error { 106 | // We might have set the embedded struct field name in the outerFieldMask. 107 | // Remove that when we set the prefixed inner fields of the embedded 108 | // struct in the outer field mask. 109 | for i, path := range outerFieldMask.Paths { 110 | if path == innerFieldMaskPrefix[0:len(innerFieldMaskPrefix)-1] { 111 | outerFieldMask.Paths = append(outerFieldMask.Paths[:i], outerFieldMask.Paths[i+1:]...) 112 | } 113 | } 114 | 115 | // Get prefixed inner field mask and append it to outer field mask 116 | innerFieldMask, err := getPrefixedFieldMask(innerPbStruct, innerFieldMaskPrefix) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | outerFieldMask.Paths = append(outerFieldMask.Paths, 122 | innerFieldMask.Paths...) 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /cloudeos/awstgw.go: -------------------------------------------------------------------------------- 1 | package cloudeos 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | api "github.com/aristanetworks/terraform-provider-cloudeos/cloudeos/arista/clouddeploy.v1" 10 | 11 | fmp "github.com/aristanetworks/cloudvision-go/api/fmp" 12 | 13 | "github.com/golang/protobuf/ptypes/wrappers" 14 | 15 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 16 | ) 17 | 18 | func (p *CloudeosProvider) DeleteAwsVpnConfig(d *schema.ResourceData) error { 19 | client, err := p.grpcClient() 20 | if err != nil { 21 | log.Printf("DeleteAwsVpnConfig: Failed to create new CVaaS Grpc client, err: %v", err) 22 | return err 23 | } 24 | defer client.Close() 25 | 26 | awsVpnClient := api.NewAWSVpnConfigServiceClient(client) 27 | awsVpnKey := &api.AWSVpnKey{ 28 | TfId: &wrappers.StringValue{Value: d.Get("tf_id").(string)}, 29 | } 30 | awsVpnConfigDeleteRequest := api.AWSVpnConfigDeleteRequest{ 31 | Key: awsVpnKey, 32 | } 33 | 34 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(requestTimeout*time.Second)) 35 | defer cancel() 36 | resp, err := awsVpnClient.Delete(ctx, &awsVpnConfigDeleteRequest) 37 | if err != nil && resp != nil && resp.Key.GetTfId().GetValue() != d.Get("tf_id").(string) { 38 | return fmt.Errorf("Deleted key %v, tf_id %v", resp.GetKey().GetTfId().GetValue(), 39 | d.Get("tf_id").(string)) 40 | } 41 | return nil 42 | 43 | } 44 | 45 | func (p *CloudeosProvider) AddAwsVpnConfig(d *schema.ResourceData) error { 46 | client, err := p.grpcClient() 47 | if err != nil { 48 | log.Printf("AddAwsVpnConfig: Failed to create new CVaaS Grpc client, err: %v", err) 49 | return err 50 | } 51 | 52 | defer client.Close() 53 | awsVpnClient := api.NewAWSVpnConfigServiceClient(client) 54 | 55 | var tunnels []*api.TunnelInfo 56 | var tunnel1 api.TunnelInfo 57 | var tunnel2 api.TunnelInfo 58 | tunnel1Endpoint := d.Get("tunnel1_aws_endpoint_ip").(string) 59 | tunnel2Endpoint := d.Get("tunnel2_aws_endpoint_ip").(string) 60 | tunnel1RouterTunnelIp := d.Get("tunnel1_router_overlay_ip").(string) 61 | tunnel2RouterTunnelIp := d.Get("tunnel2_router_overlay_ip").(string) 62 | tunnel1BgpAsn := d.Get("tunnel1_bgp_asn").(string) 63 | tunnel2BgpAsn := d.Get("tunnel2_bgp_asn").(string) 64 | tunnel1AwsTunnelIp := d.Get("tunnel1_aws_overlay_ip").(string) 65 | tunnel2AwsTunnelIp := d.Get("tunnel2_aws_overlay_ip").(string) 66 | tunnel1BgpHoldTime := d.Get("tunnel1_bgp_holdtime").(string) 67 | tunnel2BgpHoldTime := d.Get("tunnel2_bgp_holdtime").(string) 68 | tunnel1PresharedKey := d.Get("tunnel1_preshared_key").(string) 69 | tunnel2PresharedKey := d.Get("tunnel2_preshared_key").(string) 70 | tunnel1.TunnelAwsEndpointIp = &fmp.IPAddress{Value: tunnel1Endpoint} 71 | tunnel2.TunnelAwsEndpointIp = &fmp.IPAddress{Value: tunnel2Endpoint} 72 | tunnel1.TunnelBgpAsn = &wrappers.StringValue{Value: tunnel1BgpAsn} 73 | tunnel2.TunnelBgpAsn = &wrappers.StringValue{Value: tunnel2BgpAsn} 74 | tunnel1.TunnelRouterOverlayIp = &fmp.IPAddress{Value: tunnel1RouterTunnelIp} 75 | tunnel2.TunnelRouterOverlayIp = &fmp.IPAddress{Value: tunnel2RouterTunnelIp} 76 | tunnel1.TunnelAwsOverlayIp = &fmp.IPAddress{Value: tunnel1AwsTunnelIp} 77 | tunnel2.TunnelAwsOverlayIp = &fmp.IPAddress{Value: tunnel2AwsTunnelIp} 78 | tunnel1.TunnelBgpHoldtime = &wrappers.StringValue{Value: tunnel1BgpHoldTime} 79 | tunnel2.TunnelBgpHoldtime = &wrappers.StringValue{Value: tunnel2BgpHoldTime} 80 | tunnel1.TunnelPresharedKey = &wrappers.StringValue{Value: tunnel1PresharedKey} 81 | tunnel2.TunnelPresharedKey = &wrappers.StringValue{Value: tunnel2PresharedKey} 82 | //Ipsec Info is default and we aren't passing that for now 83 | 84 | tunnels = append(tunnels, &tunnel1) 85 | tunnels = append(tunnels, &tunnel2) 86 | 87 | tunnelInfoList := &api.TunnelInfoList{ 88 | Values: tunnels, 89 | } 90 | awsVpnKey := &api.AWSVpnKey{ 91 | TfId: &wrappers.StringValue{Value: d.Get("tf_id").(string)}, 92 | } 93 | tgwId := d.Get("tgw_id").(string) 94 | vpnConnectionId := d.Get("vpn_connection_id").(string) 95 | awsVpnConfigInfo := &api.AWSVpnConfig{ 96 | Key: awsVpnKey, 97 | TgwId: &wrappers.StringValue{Value: tgwId}, 98 | VpnConnectionId: &wrappers.StringValue{Value: vpnConnectionId}, 99 | CgwId: &wrappers.StringValue{Value: d.Get("cgw_id").(string)}, 100 | CloudeosRouterId: &wrappers.StringValue{Value: d.Get("router_id").(string)}, 101 | CloudeosVpcId: &wrappers.StringValue{Value: d.Get("vpc_id").(string)}, 102 | VpnTgwAttachmentId: &wrappers.StringValue{Value: d.Get("vpn_tgw_attachment_id").(string)}, 103 | Cnps: &wrappers.StringValue{Value: d.Get("cnps").(string)}, 104 | VpnGatewayId: &wrappers.StringValue{Value: d.Get("vpn_gateway_id").(string)}, 105 | TunnelInfoList: tunnelInfoList, 106 | } 107 | 108 | awsVpnConfigSetRequest := api.AWSVpnConfigSetRequest{ 109 | Value: awsVpnConfigInfo, 110 | } 111 | 112 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(requestTimeout*time.Second)) 113 | defer cancel() 114 | resp, err := awsVpnClient.Set(ctx, &awsVpnConfigSetRequest) 115 | if err != nil && resp == nil { 116 | return err 117 | } 118 | 119 | value := resp.Value 120 | if value != nil && value.GetKey() != nil && value.GetKey().GetTfId() != nil { 121 | tf_id := value.GetKey().GetTfId().GetValue() 122 | d.Set("tf_id", tf_id) 123 | } 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_aws_vpn.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | 4 | package cloudeos 5 | 6 | import ( 7 | //"errors" 8 | "strings" 9 | //"time" 10 | 11 | //"github.com/hashicorp/terraform-plugin-sdk/helper/resource" 12 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 13 | ) 14 | 15 | //cloudeosAwsVpnStatus: Define the cloudeosAwsVpnStatus schema ( input and output variables ) 16 | func cloudeosAwsVpn() *schema.Resource { 17 | return &schema.Resource{ 18 | Create: cloudeosAwsVpnCreate, 19 | Read: cloudeosAwsVpnRead, 20 | Update: cloudeosAwsVpnUpdate, 21 | Delete: cloudeosAwsVpnDelete, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "cnps": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | Description: "Segment/VRF ID", 28 | }, 29 | "tgw_id": { 30 | Type: schema.TypeString, 31 | Optional: true, 32 | Description: "Transit Gateway ID", 33 | }, 34 | "router_id": { 35 | Required: true, 36 | Type: schema.TypeString, 37 | Description: "tf_id of the CloudEOS Router", 38 | }, 39 | "vpn_gateway_id": { 40 | Optional: true, 41 | Type: schema.TypeString, 42 | Description: "VPN Gateway ID", 43 | }, 44 | "vpn_connection_id": { 45 | Required: true, 46 | Type: schema.TypeString, 47 | Description: "Vpn connection ID", 48 | }, 49 | "vpn_tgw_attachment_id": { 50 | Optional: true, 51 | Type: schema.TypeString, 52 | Description: "TGW Attachment ID for the VPN connection", 53 | }, 54 | "cgw_id": { 55 | Required: true, 56 | Type: schema.TypeString, 57 | Description: "AWS Customer Gateway ID", 58 | }, 59 | "tunnel1_aws_endpoint_ip": { //tunnel1_address 60 | Required: true, 61 | Type: schema.TypeString, 62 | Description: "Public IP address of the AWS VPN Connection endpoint", 63 | }, 64 | "tunnel1_bgp_asn": { //tunnel1_bgp_asn 65 | Required: true, 66 | Type: schema.TypeString, 67 | Description: "BGP ASN", 68 | }, 69 | "tunnel1_router_overlay_ip": { //tunnel1_cgw_inside_address 70 | Required: true, 71 | Type: schema.TypeString, 72 | Description: "Tunnel Interface overlay IP address for the router", 73 | }, 74 | "tunnel1_aws_overlay_ip": { //tunnel1_vgw_inside_address 75 | Required: true, 76 | Type: schema.TypeString, 77 | Description: "Tunnel IP address of the AWS VPN Connection", 78 | }, 79 | "tunnel1_bgp_holdtime": { //tunnel1_bgp_hold_timer 80 | Required: true, 81 | Type: schema.TypeString, 82 | Description: "Hold timer value for BGP", 83 | }, 84 | "tunnel1_preshared_key": { //tunnel1_preshared_key 85 | Required: true, 86 | Type: schema.TypeString, 87 | Description: "Ipsec Preshared key for Tunnel1", 88 | Sensitive: true, 89 | }, 90 | "tunnel2_aws_endpoint_ip": { //tunnel2_address 91 | Required: true, 92 | Type: schema.TypeString, 93 | Description: "Public IP address of the AWS VPN Connection endpoint", 94 | }, 95 | "tunnel2_bgp_asn": { //tunnel2_bgp_asn 96 | Required: true, 97 | Type: schema.TypeString, 98 | Description: "BGP ASN", 99 | }, 100 | "tunnel2_router_overlay_ip": { //tunnel2_cgw_inside_address 101 | Required: true, 102 | Type: schema.TypeString, 103 | Description: "Tunnel interface IP address for the router", 104 | }, 105 | "tunnel2_aws_overlay_ip": { //tunnel2_vgw_inside_address 106 | Required: true, 107 | Type: schema.TypeString, 108 | Description: "Tunnel IP address of the AWS VPN Connection", 109 | }, 110 | "tunnel2_bgp_holdtime": { //tunnel2_bgp_hold_timer 111 | Required: true, 112 | Type: schema.TypeString, 113 | Description: "Hold timer value for BGP", 114 | }, 115 | "tunnel2_preshared_key": { //tunnel2_preshared_key 116 | Required: true, 117 | Type: schema.TypeString, 118 | Description: "Pre shared key for Tunnel1", 119 | Sensitive: true, 120 | }, 121 | "vpc_id": { 122 | Required: true, 123 | Type: schema.TypeString, 124 | Description: "VPC ID for the Router given in \"router_id\"", 125 | }, 126 | "tf_id": { 127 | Computed: true, 128 | Type: schema.TypeString, 129 | Description: "Unique resource ID", 130 | }, 131 | }, 132 | } 133 | } 134 | func cloudeosAwsVpnRead(d *schema.ResourceData, m interface{}) error { 135 | return nil 136 | } 137 | 138 | func cloudeosAwsVpnUpdate(d *schema.ResourceData, m interface{}) error { 139 | provider := m.(CloudeosProvider) 140 | err := provider.AddAwsVpnConfig(d) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func cloudeosAwsVpnDelete(d *schema.ResourceData, m interface{}) error { 149 | provider := m.(CloudeosProvider) 150 | 151 | err := provider.DeleteAwsVpnConfig(d) 152 | if err != nil { 153 | return err 154 | } 155 | d.SetId("") 156 | return nil 157 | } 158 | 159 | func cloudeosAwsVpnCreate(d *schema.ResourceData, m interface{}) error { 160 | provider := m.(CloudeosProvider) 161 | 162 | err := provider.AddAwsVpnConfig(d) 163 | if err != nil { 164 | return err 165 | } 166 | uuid := "cloudeos-aws-vpn" + strings.TrimPrefix(d.Get("tf_id").(string), AwsVpnPrefix) 167 | d.SetId(uuid) 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_clos_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "regexp" 11 | "testing" 12 | 13 | r "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 15 | ) 16 | 17 | func TestResourceClos(t *testing.T) { 18 | r.Test(t, r.TestCase{ 19 | Providers: testProviders, 20 | PreCheck: func() { testAccPreCheck(t) }, 21 | CheckDestroy: testResourceClosDestroy, 22 | Steps: []r.TestStep{ 23 | { 24 | Config: testResourceInitialClosConfig, 25 | Check: testResourceInitialClosCheck, 26 | }, 27 | { 28 | Config: testResourceClosDuplicateConfig, 29 | ExpectError: regexp.MustCompile("cloudeos_clos clos-test3 already exists"), 30 | }, 31 | { 32 | Config: testResourceUpdatedClosConfig, 33 | Check: testResourceUpdatedClosCheck, 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | var testResourceInitialClosConfig = fmt.Sprintf(` 40 | provider "cloudeos" { 41 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 42 | cvaas_server = "www.cv-play.corp.arista.io" 43 | // clouddeploy token 44 | service_account_web_token = %q 45 | } 46 | 47 | resource "cloudeos_topology" "topology" { 48 | topology_name = "topo-test3" 49 | bgp_asn = "65000-65100" 50 | vtep_ip_cidr = "1.0.0.0/16" 51 | terminattr_ip_cidr = "2.0.0.0/16" 52 | dps_controlplane_cidr = "3.0.0.0/16" 53 | } 54 | 55 | resource "cloudeos_clos" "clos" { 56 | name = "clos-test3" 57 | topology_name = cloudeos_topology.topology.topology_name 58 | cv_container_name = "CloudLeaf" 59 | } 60 | `, os.Getenv("token")) 61 | 62 | var testResourceClosDuplicateConfig = fmt.Sprintf(` 63 | provider "cloudeos" { 64 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 65 | cvaas_server = "www.cv-play.corp.arista.io" 66 | // clouddeploy token 67 | service_account_web_token = %q 68 | } 69 | 70 | resource "cloudeos_topology" "topology" { 71 | topology_name = "topo-test3" 72 | bgp_asn = "65000-65100" 73 | vtep_ip_cidr = "1.0.0.0/16" 74 | terminattr_ip_cidr = "2.0.0.0/16" 75 | dps_controlplane_cidr = "3.0.0.0/16" 76 | } 77 | resource "cloudeos_clos" "clos" { 78 | name = "clos-test3" 79 | topology_name = cloudeos_topology.topology.topology_name 80 | cv_container_name = "CloudLeaf" 81 | } 82 | 83 | resource "cloudeos_clos" "clos1" { 84 | name = "clos-test3" 85 | topology_name = cloudeos_topology.topology.topology_name 86 | cv_container_name = "CloudLeaf" 87 | depends_on = [cloudeos_clos.clos] 88 | } 89 | `, os.Getenv("token")) 90 | 91 | var closResourceID = "" 92 | 93 | func testResourceInitialClosCheck(s *terraform.State) error { 94 | resourceState := s.Modules[0].Resources["cloudeos_clos.clos"] 95 | if resourceState == nil { 96 | return fmt.Errorf("resource not found in state") 97 | } 98 | 99 | instanceState := resourceState.Primary 100 | if instanceState == nil { 101 | return fmt.Errorf("resource has no primary instance") 102 | } 103 | 104 | if instanceState.ID == "" { 105 | return fmt.Errorf("ID not assigned %s", instanceState.ID) 106 | } 107 | closResourceID = instanceState.ID 108 | 109 | if got, want := instanceState.Attributes["name"], "clos-test3"; got != want { 110 | return fmt.Errorf("clos contains %s; want %s", got, want) 111 | } 112 | 113 | if got, want := instanceState.Attributes["topology_name"], "topo-test3"; got != want { 114 | return fmt.Errorf("clos contains %s; want %s", got, want) 115 | } 116 | 117 | if got, want := instanceState.Attributes["cv_container_name"], "CloudLeaf"; got != want { 118 | return fmt.Errorf("clos contains %s; want %s", got, want) 119 | } 120 | return nil 121 | } 122 | 123 | var testResourceUpdatedClosConfig = fmt.Sprintf(` 124 | provider "cloudeos" { 125 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 126 | cvaas_server = "www.cv-play.corp.arista.io" 127 | // clouddeploy token 128 | service_account_web_token = %q 129 | } 130 | 131 | resource "cloudeos_topology" "topology" { 132 | topology_name = "topo-test3" 133 | bgp_asn = "65000-65100" 134 | vtep_ip_cidr = "1.0.0.0/16" 135 | terminattr_ip_cidr = "2.0.0.0/16" 136 | dps_controlplane_cidr = "3.0.0.0/16" 137 | } 138 | 139 | resource "cloudeos_clos" "clos" { 140 | name = "clos-test-update3" 141 | topology_name = cloudeos_topology.topology.topology_name 142 | cv_container_name = "CloudLeaf" 143 | } 144 | `, os.Getenv("token")) 145 | 146 | func testResourceUpdatedClosCheck(s *terraform.State) error { 147 | resourceState := s.Modules[0].Resources["cloudeos_clos.clos"] 148 | if resourceState == nil { 149 | return fmt.Errorf("cloudeos_clos.clos resource not found in state") 150 | } 151 | 152 | instanceState := resourceState.Primary 153 | if instanceState == nil { 154 | return fmt.Errorf("cloudeos_clos.clos resource has no primary instance") 155 | } 156 | 157 | if instanceState.ID != closResourceID { 158 | return fmt.Errorf("cloudeos_clos.clos ID has changed %s", instanceState.ID) 159 | } 160 | 161 | if got, want := instanceState.Attributes["name"], "clos-test-update3"; got != want { 162 | return fmt.Errorf("cloudeos_clos.clos name contains %s; want %s", got, want) 163 | } 164 | return nil 165 | } 166 | 167 | func testResourceClosDestroy(s *terraform.State) error { 168 | for _, rs := range s.RootModule().Resources { 169 | if rs.Type != "cloudeos_clos" { 170 | continue 171 | } 172 | // TODO 173 | return nil 174 | } 175 | return nil 176 | } 177 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/identityprovider.v1/identityprovider.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | syntax = "proto3"; 7 | 8 | package arista.identityprovider.v1; 9 | 10 | option go_package = "arista/resources/arista/identityprovider.v1;identityprovider"; 11 | 12 | import "google/protobuf/wrappers.proto"; 13 | 14 | import "fmp/extensions.proto"; 15 | 16 | import "fmp/wrappers.proto"; 17 | 18 | // OAuthKey contains OAuth provider ID. 19 | message OAuthKey { 20 | option (fmp.model_key) = true; 21 | // provider_id is the ID of the OAuth provider. 22 | google.protobuf.StringValue provider_id = 1; 23 | } 24 | 25 | // OAuthConfig holds the configuration for an OAuth provider. 26 | message OAuthConfig { 27 | option (fmp.model) = "rw"; 28 | // key is the ID of the OAuth provider. 29 | OAuthKey key = 1; 30 | // endpoint is the URL that identifies an OAuth authorization server. 31 | // This endpoint is used to interact with the provider. It must be a 32 | // URI [RFC3986] with a scheme component that must be https, a host component, 33 | // and optionally, port and path components, but no query or fragment components. 34 | google.protobuf.StringValue endpoint = 2; 35 | // client_id is the ID that the OAuth authorization server issues to the 36 | // registered client. 37 | google.protobuf.StringValue client_id = 3; 38 | // client_secret is the secret that the OAuth authorization server issues 39 | // to the registered client. 40 | google.protobuf.StringValue client_secret = 4; 41 | // algorithms is the set of signing algorithms. This is an optional field. 42 | // If specified, only this set of algorithms may be used to sign the JWT. 43 | // Otherwise, this defaults to the set of algorithms that the provider supports. 44 | fmp.RepeatedString algorithms = 5; 45 | // link_to_shared_provider indicates whether or not use the provider as a shared 46 | // provider. This is an optional field and set to false by default. 47 | google.protobuf.BoolValue link_to_shared_provider = 6; 48 | // jwks_uri is where signing keys are downloaded. This is an optional field. 49 | // Only needed if the default construction from endpoint would be incorrect. 50 | google.protobuf.StringValue jwks_uri = 7; 51 | // permitted_email_domains are domains of emails that users are allowed to use. 52 | // This is an optional field. If not set, all domains are accepted by default. 53 | fmp.RepeatedString permitted_email_domains = 8; 54 | // roles_scope_name is the name for a custom scope that includes a custom claim 55 | // that holds CloudVision roles in ID Token. 56 | // CloudVision appends this value to scope query parameter in the authorization 57 | // request URL. This is an optional field. If not set, CloudVision determines that 58 | // mapping roles from the provider is disabled. 59 | google.protobuf.StringValue roles_scope_name = 9; 60 | } 61 | 62 | // SAMLKey contains SAML Provider ID. 63 | message SAMLKey { 64 | option (fmp.model_key) = true; 65 | // provider_id is the ID of the SAML provider. 66 | google.protobuf.StringValue provider_id = 1; 67 | } 68 | 69 | // ProtocolBinding indicates SAML protocol binding to be used. 70 | enum ProtocolBinding { 71 | // PROTOCOL_BINDING_UNSPECIFIED indicates that a protocol binding is unspecified. 72 | PROTOCOL_BINDING_UNSPECIFIED = 0; 73 | // PROTOCOL_BINDING_HTTP_POST indicates HTTP-POST SAML protocol binding. 74 | PROTOCOL_BINDING_HTTP_POST = 1; 75 | // PROTOCOL_BINDING_HTTP_REDIRECT indicates HTTP-Redirect SAML protocol binding. 76 | PROTOCOL_BINDING_HTTP_REDIRECT = 2; 77 | } 78 | 79 | // SAMLConfig holds the configuration for a SAML provider. 80 | message SAMLConfig { 81 | option (fmp.model) = "rw"; 82 | // key is the ID of the SAML provider. 83 | SAMLKey key = 1; 84 | // idp_issuer identifies the SAML provider. There is no restriction on its format 85 | // other than a string to carry the issuer's name. 86 | google.protobuf.StringValue idp_issuer = 2; 87 | // idp_metadata_url is the URL that CloudVision uses to fetch the 88 | // SAML provider metadata. 89 | google.protobuf.StringValue idp_metadata_url = 3; 90 | // authreq_binding specifies the ProtocolBinding used to send SAML authentication 91 | // request to the SAML provider. 92 | ProtocolBinding authreq_binding = 4; 93 | // email_attrname specifies the Attribute name for email ID in Assertion of SAMLResponse 94 | // from the SAML provider. 95 | google.protobuf.StringValue email_attrname = 5; 96 | // link_to_shared_provider indicates whether or not use the provider as a shared 97 | // provider. This is an optional field and set to false by default. 98 | google.protobuf.BoolValue link_to_shared_provider = 6; 99 | // permitted_email_domains are domains of emails that users are allowed to use. 100 | // This is an optional field. If not set, all domains are accepted by default. 101 | fmp.RepeatedString permitted_email_domains = 7; 102 | // force_saml_authn indicates wether or not enable force authentication in SAML login. 103 | // This is an optional field. If not set, it defaults to false. 104 | google.protobuf.BoolValue force_saml_authn = 8; 105 | } 106 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_topology.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | "log" 10 | "strings" 11 | "time" 12 | 13 | "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 15 | ) 16 | 17 | // cloudeosTopology: Define the cloudeos topology schema ( input and output variables ) 18 | func cloudeosTopology() *schema.Resource { 19 | return &schema.Resource{ 20 | Create: cloudeosTopologyCreate, 21 | Read: cloudeosTopologyRead, 22 | Update: cloudeosTopologyUpdate, 23 | Delete: cloudeosTopologyDelete, 24 | 25 | Timeouts: &schema.ResourceTimeout{ 26 | Delete: schema.DefaultTimeout(5 * time.Minute), 27 | }, 28 | 29 | Schema: map[string]*schema.Schema{ 30 | "topology_name": { 31 | Type: schema.TypeString, 32 | Required: true, 33 | Description: "Name of the base topology", 34 | DiffSuppressFunc: suppressAttributeChange, 35 | }, 36 | "bgp_asn": { 37 | Type: schema.TypeString, 38 | Optional: true, 39 | Description: "Range, a-b, of BGP ASN’s used for topology", 40 | DiffSuppressFunc: suppressAttributeChange, 41 | }, 42 | "vtep_ip_cidr": { 43 | Type: schema.TypeString, 44 | Optional: true, 45 | Description: "CIDR block for VTEP IPs on cloudeos", 46 | ValidateFunc: validateCIDRBlock, 47 | DiffSuppressFunc: suppressAttributeChange, 48 | }, 49 | "terminattr_ip_cidr": { 50 | Type: schema.TypeString, 51 | Optional: true, 52 | Description: "Loopback IP range on cloudeos", 53 | ValidateFunc: validateCIDRBlock, 54 | DiffSuppressFunc: suppressAttributeChange, 55 | }, 56 | "dps_controlplane_cidr": { 57 | Type: schema.TypeString, 58 | Optional: true, 59 | Description: "CIDR block for TerminAttr IPs on cloudeos", 60 | ValidateFunc: validateCIDRBlock, 61 | DiffSuppressFunc: suppressAttributeChange, 62 | }, 63 | "eos_managed": { 64 | Type: schema.TypeSet, 65 | Optional: true, 66 | Elem: &schema.Schema{Type: schema.TypeString}, 67 | Description: "Existing cloudeos", 68 | Set: schema.HashString, 69 | }, 70 | "tf_id": { 71 | Computed: true, 72 | Type: schema.TypeString, 73 | }, 74 | "deploy_mode": { 75 | Type: schema.TypeString, 76 | Optional: true, 77 | DiffSuppressFunc: suppressAttributeChange, 78 | Default: "", 79 | Description: "Deployment type of the topology - provision or empty", 80 | }, 81 | }, 82 | } 83 | } 84 | 85 | func validateInputVarsAgainstDeployMode(d *schema.ResourceData, deployMode string) error { 86 | unexpectedVarsForProvisionMode := []string{"dps_controlplane_cidr", "terminattr_ip_cidr", 87 | "vtep_ip_cidr", "bgp_asn"} 88 | 89 | if strings.ToLower(deployMode) == "provision" { 90 | for _, inputVar := range unexpectedVarsForProvisionMode { 91 | if d.Get(inputVar).(string) != "" { 92 | return errors.New(inputVar + " should not be specified for a topology with " + 93 | "deploy mode provision") 94 | } 95 | } 96 | } else { 97 | // Ensure that the needed variables are present if deploy mode is not provision 98 | for _, inputVar := range unexpectedVarsForProvisionMode { 99 | if d.Get(inputVar).(string) == "" { 100 | return errors.New(inputVar + " is a required variable for cloudeos_topology") 101 | } 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | func cloudeosTopologyCreate(d *schema.ResourceData, m interface{}) error { 108 | provider := m.(CloudeosProvider) 109 | 110 | deployMode := d.Get("deploy_mode").(string) 111 | err := validateInputVarsAgainstDeployMode(d, deployMode) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | allowed, err := provider.IsValidTopoAddition(d, "TOPO_INFO_META") 117 | if !allowed || err != nil { 118 | return err 119 | } 120 | err = provider.AddTopology(d) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | uuid := "cloudeos-topology" + strings.TrimPrefix(d.Get("tf_id").(string), TopoPrefix) 126 | log.Print("Successfully added " + uuid) 127 | d.SetId(uuid) 128 | return nil 129 | } 130 | 131 | func cloudeosTopologyRead(d *schema.ResourceData, m interface{}) error { 132 | return nil 133 | } 134 | 135 | func cloudeosTopologyUpdate(d *schema.ResourceData, m interface{}) error { 136 | provider := m.(CloudeosProvider) 137 | err := provider.AddTopology(d) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | log.Print("Successfully updated cloudeos-topology" + 143 | strings.TrimPrefix(d.Get("tf_id").(string), TopoPrefix)) 144 | return nil 145 | } 146 | 147 | func cloudeosTopologyDelete(d *schema.ResourceData, m interface{}) error { 148 | provider := m.(CloudeosProvider) 149 | err := provider.DeleteTopology(d) 150 | if err != nil { 151 | return err 152 | } 153 | uuid := "cloudeos-topology" + strings.TrimPrefix(d.Get("tf_id").(string), TopoPrefix) 154 | // wait for topology deletion 155 | err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { 156 | if err := provider.CheckTopologyDeletionStatus(d); err != nil { 157 | return resource.RetryableError(err) 158 | } 159 | return nil 160 | }) 161 | if err != nil { 162 | return errors.New("Failed to destroy " + uuid + " error: " + err.Error()) 163 | } 164 | log.Print("Successfully deleted " + uuid) 165 | d.SetId("") 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_wan_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "regexp" 11 | "testing" 12 | 13 | r "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 15 | ) 16 | 17 | func TestResourceWan(t *testing.T) { 18 | r.Test(t, r.TestCase{ 19 | Providers: testProviders, 20 | PreCheck: func() { testAccPreCheck(t) }, 21 | CheckDestroy: testResourceWanDestroy, 22 | Steps: []r.TestStep{ 23 | { 24 | Config: testResourceInitialWanConfig, 25 | Check: testResourceInitialWanCheck, 26 | }, 27 | { 28 | Config: testResourceWanDuplicateConfig, 29 | ExpectError: regexp.MustCompile("cloudeos_wan wan-test3 already exists"), 30 | }, 31 | { 32 | Config: testResourceUpdatedWanConfig, 33 | Check: testResourceUpdatedWanCheck, 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | var testResourceInitialWanConfig = fmt.Sprintf(` 40 | provider "cloudeos" { 41 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 42 | cvaas_server = "www.cv-play.corp.arista.io" 43 | // clouddeploy token 44 | service_account_web_token = %q 45 | } 46 | 47 | resource "cloudeos_topology" "topology" { 48 | topology_name = "topo-test4" 49 | bgp_asn = "65000-65100" 50 | vtep_ip_cidr = "1.0.0.0/16" 51 | terminattr_ip_cidr = "2.0.0.0/16" 52 | dps_controlplane_cidr = "3.0.0.0/16" 53 | } 54 | 55 | resource "cloudeos_wan" "wan" { 56 | name = "wan-test2" 57 | topology_name = cloudeos_topology.topology.topology_name 58 | cv_container_name = "CloudEdge" 59 | } 60 | `, os.Getenv("token")) 61 | 62 | var testResourceWanDuplicateConfig = fmt.Sprintf(` 63 | provider "cloudeos" { 64 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 65 | cvaas_server = "www.cv-play.corp.arista.io" 66 | // clouddeploy token 67 | service_account_web_token = %q 68 | } 69 | 70 | resource "cloudeos_topology" "topology" { 71 | topology_name = "topo-test3" 72 | bgp_asn = "65000-65100" 73 | vtep_ip_cidr = "1.0.0.0/16" 74 | terminattr_ip_cidr = "2.0.0.0/16" 75 | dps_controlplane_cidr = "3.0.0.0/16" 76 | } 77 | 78 | resource "cloudeos_wan" "wan" { 79 | name = "wan-test3" 80 | topology_name = cloudeos_topology.topology.topology_name 81 | cv_container_name = "CloudEdge" 82 | } 83 | 84 | resource "cloudeos_wan" "wan1" { 85 | name = "wan-test3" 86 | topology_name = cloudeos_topology.topology.topology_name 87 | cv_container_name = "CloudEdge" 88 | depends_on = [cloudeos_wan.wan] 89 | } 90 | `, os.Getenv("token")) 91 | 92 | var wanResourceID = "" 93 | 94 | func testResourceInitialWanCheck(s *terraform.State) error { 95 | resourceState := s.Modules[0].Resources["cloudeos_wan.wan"] 96 | if resourceState == nil { 97 | return fmt.Errorf("cloudeos_wan.wan resource not found") 98 | } 99 | 100 | instanceState := resourceState.Primary 101 | if instanceState == nil { 102 | return fmt.Errorf("cloudeos_wan.wan has no primary instance") 103 | } 104 | 105 | if instanceState.ID == "" { 106 | return fmt.Errorf("cloudeos_wan.wan ID not assigned %s", instanceState.ID) 107 | } 108 | wanResourceID = instanceState.ID 109 | 110 | if got, want := instanceState.Attributes["name"], "wan-test2"; got != want { 111 | return fmt.Errorf("cloudeos_wan.wan name contains %s; want %s", got, want) 112 | } 113 | 114 | if got, want := instanceState.Attributes["topology_name"], "topo-test4"; got != want { 115 | return fmt.Errorf("cloudeos_wan.wan topology_name contains %s; want %s", got, want) 116 | } 117 | 118 | if got, want := instanceState.Attributes["cv_container_name"], "CloudEdge"; got != want { 119 | return fmt.Errorf("cloudeos_wan.wan cv_container_name contains %s; want %s", got, want) 120 | } 121 | return nil 122 | } 123 | 124 | var testResourceUpdatedWanConfig = fmt.Sprintf(` 125 | provider "cloudeos" { 126 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 127 | cvaas_server = "www.cv-play.corp.arista.io" 128 | // clouddeploy token 129 | service_account_web_token = %q 130 | } 131 | 132 | resource "cloudeos_topology" "topology" { 133 | topology_name = "topo-test2" 134 | bgp_asn = "65000-65100" 135 | vtep_ip_cidr = "1.0.0.0/16" 136 | terminattr_ip_cidr = "2.0.0.0/16" 137 | dps_controlplane_cidr = "3.0.0.0/16" 138 | } 139 | 140 | resource "cloudeos_wan" "wan" { 141 | name = "wan-test-update2" 142 | topology_name = cloudeos_topology.topology.topology_name 143 | cv_container_name = "CloudEdge" 144 | } 145 | `, os.Getenv("token")) 146 | 147 | func testResourceUpdatedWanCheck(s *terraform.State) error { 148 | resourceState := s.Modules[0].Resources["cloudeos_wan.wan"] 149 | if resourceState == nil { 150 | return fmt.Errorf("cloudeos_wan.wan resource not found") 151 | } 152 | 153 | instanceState := resourceState.Primary 154 | if instanceState == nil { 155 | return fmt.Errorf("cloudeos_wan.wan resource has no primary instance") 156 | } 157 | 158 | if instanceState.ID != wanResourceID { 159 | return fmt.Errorf("cloudeos_wan.wan ID has changed %s", instanceState.ID) 160 | } 161 | 162 | if got, want := instanceState.Attributes["name"], "wan-test-update2"; got != want { 163 | return fmt.Errorf("cloudeos_wan.wan name contains %s; want %s", got, want) 164 | } 165 | 166 | return nil 167 | } 168 | 169 | func testResourceWanDestroy(s *terraform.State) error { 170 | for _, rs := range s.RootModule().Resources { 171 | if rs.Type != "cloudeos_wan" { 172 | continue 173 | } 174 | // TODO 175 | return nil 176 | } 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_vpc_status.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "log" 11 | "strings" 12 | "time" 13 | 14 | "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 15 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 16 | ) 17 | 18 | //cloudeosVpcStatus: Define the cloudeos_vpc_status schema ( input and output variables ) 19 | func cloudeosVpcStatus() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: cloudeosVpcStatusCreate, 22 | Read: cloudeosVpcStatusRead, 23 | Update: cloudeosVpcStatusUpdate, 24 | Delete: cloudeosVpcStatusDelete, 25 | 26 | Timeouts: &schema.ResourceTimeout{ 27 | Delete: schema.DefaultTimeout(5 * time.Minute), 28 | }, 29 | 30 | Schema: map[string]*schema.Schema{ 31 | "cloud_provider": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | Description: "aws/azure/gcp", 35 | ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { 36 | v := val.(string) 37 | if v != "aws" && v != "azure" && v != "gcp" { 38 | errs = append(errs, fmt.Errorf( 39 | "%q must be aws/azure/gcp got: %q", key, v)) 40 | } 41 | return 42 | }, 43 | }, 44 | "cnps": { 45 | Required: true, 46 | Type: schema.TypeString, 47 | }, 48 | "region": { 49 | Required: true, 50 | Type: schema.TypeString, 51 | }, 52 | "rg_name": { 53 | Optional: true, 54 | Type: schema.TypeString, 55 | }, 56 | // This is equiv to vnet_id in Azure 57 | "vpc_id": { 58 | Type: schema.TypeString, 59 | Required: true, 60 | }, 61 | // Only set in Azure 62 | "vnet_name": { 63 | Type: schema.TypeString, 64 | Optional: true, 65 | }, 66 | "security_group_id": { 67 | Type: schema.TypeString, 68 | Optional: true, 69 | Description: "Security group id", 70 | }, 71 | "cidr_block": { 72 | Type: schema.TypeString, 73 | Optional: true, 74 | Description: "CIDR block", 75 | }, 76 | "igw": { 77 | Type: schema.TypeString, 78 | Optional: true, 79 | Description: "Internet gateway id ", 80 | }, 81 | "resource_group": { 82 | Type: schema.TypeString, 83 | Optional: true, 84 | Description: "Resource group needed by Azure", 85 | }, 86 | "role": { 87 | Required: true, 88 | Type: schema.TypeString, 89 | Description: "CloudEdge/CloudLeaf", 90 | ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { 91 | v := val.(string) 92 | if v != "CloudEdge" && v != "CloudLeaf" { 93 | errs = append(errs, fmt.Errorf( 94 | "%q must be CloudEdge/CloudLeaf got: %q", key, v)) 95 | } 96 | return 97 | }, 98 | }, 99 | "topology_name": { 100 | Optional: true, 101 | Type: schema.TypeString, 102 | Description: "Base topology name", 103 | }, 104 | "clos_name": { 105 | Optional: true, 106 | Type: schema.TypeString, 107 | Description: "ClosFabric name", 108 | }, 109 | "wan_name": { 110 | Optional: true, // leaf VPC won't have wan_name 111 | Type: schema.TypeString, 112 | Description: "WanFabric name", 113 | }, 114 | "tags": { 115 | Type: schema.TypeMap, 116 | Optional: true, 117 | Description: "A mapping of tags to assign to the resource", 118 | }, 119 | "tf_id": { 120 | Required: true, 121 | Type: schema.TypeString, 122 | }, 123 | "account": { 124 | Type: schema.TypeString, 125 | Required: true, 126 | Description: "The unique identifier of the account", 127 | }, 128 | "deploy_mode": { 129 | Type: schema.TypeString, 130 | Optional: true, 131 | DiffSuppressFunc: suppressAttributeChange, 132 | Description: "Deployment mode for the resources: provision or empty", 133 | }, 134 | }, 135 | } 136 | } 137 | 138 | func cloudeosVpcStatusCreate(d *schema.ResourceData, m interface{}) error { 139 | err := validateDeployModeWithRole(d) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | provider := m.(CloudeosProvider) 145 | err = provider.AddVpc(d) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | uuid := "cloudeos-vpc-status" + strings.TrimPrefix(d.Get("tf_id").(string), VpcPrefix) 151 | log.Print("Successfully added " + uuid) 152 | d.SetId(uuid) 153 | return nil 154 | } 155 | 156 | func cloudeosVpcStatusRead(d *schema.ResourceData, m interface{}) error { 157 | return nil 158 | } 159 | 160 | func cloudeosVpcStatusUpdate(d *schema.ResourceData, m interface{}) error { 161 | provider := m.(CloudeosProvider) 162 | err := provider.AddVpc(d) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | log.Print("Successfully Updated cloudeos-vpc-status" + 168 | strings.TrimPrefix(d.Get("tf_id").(string), VpcPrefix)) 169 | return nil 170 | } 171 | 172 | func cloudeosVpcStatusDelete(d *schema.ResourceData, m interface{}) error { 173 | provider := m.(CloudeosProvider) 174 | err := provider.DeleteVpc(d) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | uuid := "cloudeos-vpc-status" + strings.TrimPrefix(d.Get("tf_id").(string), VpcPrefix) 180 | // wait for vpc deletion 181 | err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { 182 | if err := provider.CheckVpcDeletionStatus(d); err != nil { 183 | return resource.RetryableError(err) 184 | } 185 | return nil 186 | }) 187 | if err != nil { 188 | return errors.New("Failed to destroy " + uuid + " error: " + err.Error()) 189 | } 190 | 191 | log.Print("Successfully deleted " + uuid) 192 | d.SetId("") 193 | return nil 194 | } 195 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/endpointlocation.v1/endpointlocation.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | syntax = "proto3"; 6 | 7 | package arista.endpointlocation.v1; 8 | 9 | option go_package = "arista/resources/arista/endpointlocation.v1;endpointlocation"; 10 | 11 | import "google/protobuf/timestamp.proto"; 12 | import "google/protobuf/wrappers.proto"; 13 | 14 | import "fmp/extensions.proto"; 15 | import "fmp/wrappers.proto"; 16 | 17 | enum MacType { 18 | MAC_TYPE_UNSPECIFIED = 0; 19 | MAC_TYPE_LEARNED_DYNAMIC = 1; 20 | MAC_TYPE_LEARNED_SECURE = 2; 21 | MAC_TYPE_CONFIGURED_DYNAMIC = 3; 22 | MAC_TYPE_CONFIGURED_SECURE = 4; 23 | MAC_TYPE_CONFIGURED_STATIC = 5; 24 | MAC_TYPE_PEER_DYNAMIC = 6; 25 | MAC_TYPE_PEER_STATIC = 7; 26 | MAC_TYPE_PEER_SECURE = 8; 27 | MAC_TYPE_LEARNED_REMOTE = 9; 28 | MAC_TYPE_CONFIGURED_REMOTE = 10; 29 | MAC_TYPE_RECEIVED_REMOTE = 11; 30 | MAC_TYPE_PEER_LEARNED_REMOTE = 12; 31 | MAC_TYPE_PEER_CONFIGURED_REMOTE = 13; 32 | MAC_TYPE_PEER_RECEIVED_REMOTE = 14; 33 | MAC_TYPE_EVPN_DYNAMIC_REMOTE = 15; 34 | MAC_TYPE_EVPN_CONFIGURED_REMOTE = 16; 35 | MAC_TYPE_PEER_EVPN_REMOTE = 17; 36 | MAC_TYPE_CONFIGURED_ROUTER = 18; 37 | MAC_TYPE_PEER_ROUTER = 19; 38 | MAC_TYPE_EVPN_INTF_DYNAMIC = 20; 39 | MAC_TYPE_EVPN_INTF_STATIC = 21; 40 | MAC_TYPE_AUTHENTICATED = 22; 41 | MAC_TYPE_PEER_AUTHENTICATED = 23; 42 | MAC_TYPE_PENDING_SECURE = 24; 43 | 44 | // This is used for capturing future MAC types 45 | MAC_TYPE_OTHER = 99999; 46 | } 47 | 48 | enum Likelihood { 49 | // These ports have unspecified likelihood 50 | LIKELIHOOD_UNSPECIFIED = 0; 51 | // These ports are very likely to be physically connected to the queried device 52 | LIKELIHOOD_VERY_LIKELY = 1; 53 | // These ports are likely to be physically connected to the queried device 54 | LIKELIHOOD_LIKELY = 2; 55 | // These ports are somewhat likely to be physically connected to the queried device 56 | LIKELIHOOD_SOMEWHAT_LIKELY = 3; 57 | // These ports are less likely to be physically connected to the queried device 58 | LIKELIHOOD_LESS_LIKELY = 4; 59 | } 60 | 61 | enum IdentifierType { 62 | IDENTIFIER_TYPE_UNSPECIFIED = 0; 63 | IDENTIFIER_TYPE_MAC_ADDR = 1; 64 | IDENTIFIER_TYPE_IPV4_ADDR = 2; 65 | IDENTIFIER_TYPE_IPV6_ADDR = 3; 66 | IDENTIFIER_TYPE_INVENTORY_DEVICE_ID = 4; 67 | IDENTIFIER_TYPE_PRIMARY_MANAGEMENT_IP = 5; 68 | IDENTIFIER_TYPE_HOSTNAME = 6; 69 | IDENTIFIER_TYPE_USERNAME = 7; 70 | 71 | // May be used for some unknown LLDP chassis id type 72 | IDENTIFIER_TYPE_OTHER = 99999; 73 | } 74 | 75 | enum IdentifierSource { 76 | IDENTIFIER_SOURCE_UNSPECIFIED = 0; 77 | IDENTIFIER_SOURCE_FDB = 1; 78 | IDENTIFIER_SOURCE_ARP = 2; 79 | IDENTIFIER_SOURCE_NEIGHBOR = 3; 80 | // Onboarded or inventory devices 81 | IDENTIFIER_SOURCE_DEVICE_INVENTORY = 4; 82 | IDENTIFIER_SOURCE_LLDP = 5; 83 | IDENTIFIER_SOURCE_DHCP = 6; 84 | IDENTIFIER_SOURCE_WIFI = 7; 85 | } 86 | 87 | message IdentifierSourceList { 88 | repeated IdentifierSource values = 1; 89 | } 90 | 91 | message Identifier { 92 | IdentifierType type = 1; 93 | google.protobuf.StringValue value = 2; 94 | IdentifierSourceList source_list = 3; 95 | } 96 | 97 | message IdentifierList { 98 | repeated Identifier values = 1; 99 | } 100 | 101 | enum Explanation { 102 | // Explanation for ports that have unspecified likelihood 103 | EXPLANATION_UNSPECIFIED = 0; 104 | // Explanation for ports directly connected to the queried endpoint device in LLDP 105 | EXPLANATION_DIRECT_CONNECTION = 1; 106 | // Explanation for ports directly connected to at least one non-inventory device in LLDP 107 | EXPLANATION_NON_INVENTORY_CONNECTION = 2; 108 | // Explanation for ports not directly connected to any device in LLDP 109 | EXPLANATION_NO_CONNECTION = 3; 110 | // Explanation for ports only connected to inventory devices in LLDP 111 | EXPLANATION_INVENTORY_CONNECTION = 4; 112 | // Explanation for ports that are on the inventory device itself 113 | EXPLANATION_OWN_PORT_INVENTORY_DEVICE = 5; 114 | // Explanation for ports directly connected to the queried device in LLDP (for inventory devices) 115 | EXPLANATION_DIRECT_CONNECTION_INVENTORY_DEVICE = 6; 116 | // Explanation for ports not directly connected to any device in LLDP (for inventory devices) 117 | EXPLANATION_NO_CONNECTION_INVENTORY_DEVICE = 7; 118 | // Explanation for ports connected to only other devices besides queried inventory device 119 | EXPLANATION_OTHER_CONNECTION_INVENTORY_DEVICE = 8; 120 | // Explanation for ports containing a virtual interface 121 | EXPLANATION_VIRTUAL = 9; 122 | // Explanation for wireless clients that are directly connected to a wireless access point 123 | EXPLANATION_WIRELESS_CONNECTION = 10; 124 | } 125 | 126 | message ExplanationList { 127 | repeated Explanation values = 1; 128 | } 129 | 130 | message Location { 131 | google.protobuf.StringValue device_id = 1; 132 | DeviceStatus device_status = 2; 133 | google.protobuf.StringValue interface = 3; 134 | google.protobuf.UInt32Value vlan_id = 4; 135 | google.protobuf.Timestamp learned_time = 5; 136 | MacType mac_type = 6; 137 | Likelihood likelihood = 7; 138 | ExplanationList explanation_list = 8; 139 | IdentifierList identifier_list = 9; 140 | } 141 | 142 | message LocationList { 143 | repeated Location values = 1; 144 | } 145 | 146 | enum DeviceType { 147 | DEVICE_TYPE_UNSPECIFIED = 0; 148 | DEVICE_TYPE_INVENTORY = 1; 149 | DEVICE_TYPE_ENDPOINT = 2; 150 | DEVICE_TYPE_WIFI_ENDPOINT = 3; 151 | } 152 | 153 | enum DeviceStatus { 154 | DEVICE_STATUS_UNSPECIFIED = 0; 155 | DEVICE_STATUS_ACTIVE = 1; 156 | DEVICE_STATUS_INACTIVE = 2; 157 | } 158 | 159 | message DeviceInfo { 160 | google.protobuf.StringValue device_name = 1; 161 | google.protobuf.BoolValue mobile = 2; 162 | google.protobuf.BoolValue tablet = 3; 163 | google.protobuf.UInt32Value score = 4; 164 | google.protobuf.StringValue version = 5; 165 | google.protobuf.StringValue mac_vendor = 6; 166 | google.protobuf.StringValue classification = 7; 167 | fmp.RepeatedString hierarchy = 8; 168 | } 169 | 170 | message Device { 171 | IdentifierList identifier_list = 1; 172 | DeviceType device_type = 2; 173 | // The list of possible locations, in order from most likely to least likely 174 | LocationList location_list = 3; 175 | DeviceStatus device_status = 4; 176 | DeviceInfo device_info = 5; 177 | } 178 | 179 | message DeviceMap { 180 | map values = 1; 181 | } 182 | 183 | message EndpointLocationKey { 184 | option (fmp.model_key) = true; 185 | 186 | google.protobuf.StringValue search_term = 1; 187 | } 188 | 189 | message EndpointLocation { 190 | option (fmp.model) = "ro"; 191 | 192 | EndpointLocationKey key = 1; 193 | DeviceMap device_map = 2; 194 | } 195 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/workspace.v1/workspace.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | syntax = "proto3"; 7 | 8 | package arista.workspace.v1; 9 | 10 | option go_package = "arista/resources/arista/workspace.v1;workspace"; 11 | 12 | import "fmp/extensions.proto"; 13 | import "fmp/wrappers.proto"; 14 | 15 | import "google/protobuf/timestamp.proto"; 16 | import "google/protobuf/wrappers.proto"; 17 | import "arista/configstatus.v1/configstatus.proto"; 18 | 19 | enum WorkspaceState { 20 | WORKSPACE_STATE_UNSPECIFIED = 0; 21 | WORKSPACE_STATE_PENDING = 1; 22 | WORKSPACE_STATE_SUBMITTED = 2; 23 | WORKSPACE_STATE_ABANDONED = 3; 24 | WORKSPACE_STATE_CONFLICTS = 4; 25 | WORKSPACE_STATE_ROLLED_BACK = 5; 26 | } 27 | 28 | // Operations on a workspace that can be requested by a client. 29 | // These are workspace-specific operations. The standard operations Add, Delete, etc. 30 | // are performed via the standard ("core") APIs. 31 | // This is an asynchronous request that returns immediately if the request is valid. 32 | // The result of the operation will be available in WorkspaceStatus when it is generated. 33 | enum Request { 34 | REQUEST_UNSPECIFIED = 0; 35 | REQUEST_START_BUILD = 1; // Start a new build 36 | REQUEST_CANCEL_BUILD = 2; // Cancel any pending build 37 | REQUEST_SUBMIT = 3; // Submit the workspace (merge into mainline) 38 | REQUEST_ABANDON = 4; // Abandon the workspace. Not delete 39 | REQUEST_ROLLBACK = 5; // Rollback an already submitted workspace 40 | } 41 | 42 | enum ResponseStatus { 43 | RESPONSE_STATUS_UNSPECIFIED = 0; 44 | RESPONSE_STATUS_SUCCESS = 1; 45 | RESPONSE_STATUS_FAIL = 2; 46 | } 47 | 48 | // RequestParams is the parameters associated with a WorkspaceRequest 49 | message RequestParams { 50 | google.protobuf.StringValue request_id = 1; 51 | } 52 | 53 | // Response is the response to the last Request, typically errors in processing 54 | message Response { 55 | ResponseStatus status = 1; 56 | google.protobuf.StringValue message = 2; 57 | } 58 | 59 | // Responses is the map of all request ID to response that are processed so far 60 | message Responses { 61 | map values = 1; 62 | } 63 | 64 | // WorkspaceKey is the key to get a workspace's status 65 | message WorkspaceKey { 66 | option (fmp.model_key) = true; 67 | google.protobuf.StringValue workspace_id = 1; 68 | } 69 | 70 | // WorkspaceConfig represents the configurable parameters of a workspace 71 | message WorkspaceConfig { 72 | option (fmp.model) = "rw"; 73 | WorkspaceKey key = 1; 74 | google.protobuf.StringValue display_name = 2; 75 | google.protobuf.StringValue description = 3; 76 | 77 | Request request = 4; 78 | RequestParams request_params = 5; 79 | } 80 | 81 | // Workspace is the status of a workspace 82 | message Workspace { 83 | option (fmp.model) = "ro"; 84 | WorkspaceKey key = 1; 85 | google.protobuf.Timestamp created_at = 2; 86 | google.protobuf.StringValue created_by = 3; 87 | google.protobuf.Timestamp last_modified_at = 4; 88 | google.protobuf.StringValue last_modified_by = 5; 89 | 90 | WorkspaceState state = 6; 91 | google.protobuf.StringValue last_build_id = 7; 92 | Responses responses = 8; 93 | fmp.RepeatedString cc_ids = 9; // Change Controls created by submitting this workspace 94 | } 95 | 96 | enum BuildState { 97 | BUILD_STATE_UNSPECIFIED = 0; 98 | BUILD_STATE_IN_PROGRESS = 1; 99 | BUILD_STATE_CANCELED = 2; 100 | BUILD_STATE_SUCCESS = 3; 101 | BUILD_STATE_FAIL = 4; 102 | } 103 | 104 | // BuildStage is the stage of a workspace build 105 | enum BuildStage { 106 | BUILD_STAGE_UNSPECIFIED = 0; 107 | BUILD_STAGE_INPUT_VALIDATION = 1; 108 | BUILD_STAGE_CONFIGLET_BUILD = 2; 109 | BUILD_STAGE_CONFIG_VALIDATION = 3; 110 | } 111 | 112 | // InputError represents an error in an input field, with either schema or value 113 | message InputError { 114 | google.protobuf.StringValue field_id = 1; // ID of the field 115 | fmp.RepeatedString path = 2; // Path leading up to the field 116 | fmp.RepeatedString members = 3; // Members of the field 117 | google.protobuf.StringValue message = 4; // The error message 118 | } 119 | 120 | // InputErrors is the nullable list of InputError 121 | message InputErrors { 122 | repeated InputError values = 1; 123 | } 124 | 125 | // InputValidationResult is the result of input validation of a studio 126 | message InputValidationResult { 127 | InputErrors input_schema_errors = 1; 128 | InputErrors input_value_errors = 2; 129 | fmp.RepeatedString other_errors = 3; 130 | } 131 | 132 | // InputValidationResults is the result of input validation, one per studio 133 | message InputValidationResults { 134 | map values = 1; 135 | } 136 | 137 | // TemplateError represents a single error generated by a template evaluation 138 | message TemplateError { 139 | google.protobuf.UInt32Value line_num = 1; // Line on which the error occurred 140 | google.protobuf.StringValue exception = 2; // The exception type 141 | google.protobuf.StringValue detail = 3; // Backtrace, etc. 142 | } 143 | 144 | // TemplateErrors is the nullable list of TemplateError 145 | message TemplateErrors { 146 | repeated TemplateError values = 1; 147 | } 148 | 149 | // ConfigletBuildResult is the output of configlet (template) build 150 | message ConfigletBuildResult { 151 | TemplateErrors template_errors = 1; 152 | google.protobuf.StringValue generated_config = 2; 153 | } 154 | 155 | // ConfigletBuildResults is the output of configlet build, one per studio 156 | message ConfigletBuildResults { 157 | map values = 1; 158 | } 159 | 160 | // ConfigValidationResult is the result of validating config with an EOS device 161 | message ConfigValidationResult { 162 | arista.configstatus.v1.ConfigSummary summary = 1; 163 | arista.configstatus.v1.ConfigErrors errors = 2; 164 | arista.configstatus.v1.ConfigErrors warnings = 3; 165 | } 166 | 167 | // BuildResult is the per-device build output 168 | message BuildResult { 169 | BuildState state = 1; 170 | BuildStage stage = 2; 171 | InputValidationResults input_validation_results = 3; 172 | ConfigletBuildResults configlet_build_results = 4; 173 | ConfigValidationResult config_validation_result = 5; 174 | } 175 | 176 | // BuildResults is the build output for all devices, indexed by device ID 177 | message BuildResults { 178 | map values = 1; 179 | } 180 | 181 | // WorkspaceBuildKey is the key to get the build result for a workspace 182 | message WorkspaceBuildKey { 183 | option (fmp.model_key) = true; 184 | // workspace_id is a required field which represents workspace ID 185 | google.protobuf.StringValue workspace_id = 1; 186 | // build_id is a required field which represents build ID 187 | google.protobuf.StringValue build_id = 2; 188 | } 189 | 190 | // WorkspaceBuild is the result, or output of a workspace build 191 | // This includes results for all devices across all studios in the workspace 192 | message WorkspaceBuild { 193 | option (fmp.model) = "ro"; 194 | WorkspaceBuildKey key = 1; 195 | BuildState state = 2; 196 | BuildResults build_results = 3; 197 | } -------------------------------------------------------------------------------- /cloudvision-apis/arista/dashboard.v1/dashboard.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | syntax = "proto3"; 7 | 8 | package arista.dashboard.v1; 9 | 10 | option go_package = "arista/resources/arista/dashboard.v1;dashboard"; 11 | 12 | import "google/protobuf/timestamp.proto"; 13 | import "google/protobuf/wrappers.proto"; 14 | 15 | import "fmp/extensions.proto"; 16 | import "fmp/wrappers.proto"; 17 | 18 | // Basic structures 19 | 20 | // Position represents a cell position in the UI. 21 | message Position { 22 | // x represents a position in the horizontal axis. 23 | google.protobuf.UInt32Value x = 1; 24 | 25 | // y represents a position in the vertical axis. 26 | google.protobuf.UInt32Value y = 2; 27 | } 28 | 29 | // Dimensions represents the dimensions in cells of the widgets in the UI. 30 | message Dimensions { 31 | // width of the widget in the UI, represented in number of cells. 32 | google.protobuf.UInt32Value width = 1; 33 | 34 | // height of the widget in the UI, represented in number of cells. 35 | google.protobuf.UInt32Value height = 2; 36 | } 37 | 38 | // WidgetStyles represents the widget's panel appearance. 39 | message WidgetStyles { 40 | // hide_title is used to hint the dashboard that the widget title must be hidden. 41 | google.protobuf.BoolValue hide_title = 1; 42 | 43 | // background_color is used to set the widget's background color. 44 | google.protobuf.StringValue background_color = 2; 45 | 46 | // hide_horizontal_bar is used to hint the dashboard that the title separator must be hidden. 47 | google.protobuf.BoolValue hide_horizontal_bar = 3; 48 | 49 | // titleSize is used to set widget's title size. 50 | google.protobuf.UInt32Value title_size = 4; 51 | } 52 | 53 | 54 | // Widgets are used to create a dashboard. 55 | // Each widget is responsible to display some type of data. 56 | // Widgets are individually configurable. 57 | message Widget { 58 | // id holds the unique identifier for the widget inside a dashboard 59 | google.protobuf.StringValue id = 1; 60 | 61 | // name of the widget is displayed at the top of the widget. 62 | google.protobuf.StringValue name = 2; 63 | 64 | // position of the widget, represented as a (x,y) coordinate in a grid. 65 | // Top left is at (0,0). 66 | Position position = 3; 67 | 68 | // dimension of the widget represents how many cell in the grid it takes. 69 | Dimensions dimensions = 4; 70 | 71 | // type is the widget type. Each type is handled differently in the UI, 72 | // and can use different `inputs`. 73 | google.protobuf.StringValue type = 5; 74 | 75 | // inputs contains metadata about the data the widget will display, encoded in a JSON string. 76 | // Internal data vary based on the widget type `type` and is managed by the client. 77 | google.protobuf.StringValue inputs = 6; 78 | 79 | // location is used as a position display hint, used and managed by the UI. 80 | google.protobuf.StringValue location = 7; 81 | 82 | // WidgetStyles represents the widget's panel appearance. 83 | WidgetStyles styles = 8; 84 | } 85 | 86 | // Dashboard State 87 | 88 | // Widgets holds a list of `Widget`s. 89 | message Widgets { 90 | repeated Widget values = 1; 91 | } 92 | 93 | // DashboardKey represents the dashboard unique identifier. 94 | message DashboardKey { 95 | option (fmp.model_key) = true; 96 | 97 | google.protobuf.StringValue dashboard_id = 1; 98 | } 99 | 100 | // DashboardConfig includes all user-editable dashboard fields. 101 | message DashboardConfig { 102 | option (fmp.model) = "rw"; 103 | 104 | // key is the unique identifier. It always must be defined. 105 | // If set, will create or update a dashboard. 106 | DashboardKey key = 1; 107 | 108 | // name is the dashboard name, displayed at the top of the dashboard. 109 | google.protobuf.StringValue name = 2; 110 | 111 | // description may include details about what is displayed in the dashboard. 112 | google.protobuf.StringValue description = 3; 113 | 114 | // widgets list of widgets in the dashboard. 115 | Widgets widgets = 4; 116 | } 117 | 118 | // DashboardMetadata includes versioning metadata. All the data here is managed internally, and 119 | // is read-only. 120 | message DashboardMetadata { 121 | // schema_version is managed internally. 122 | google.protobuf.StringValue schema_version = 1; 123 | 124 | // legacy_key holds the key of a previous version of the dashboard, in case it was migrated. 125 | google.protobuf.StringValue legacy_key = 2; 126 | 127 | // legacy_version tells from which version the dashboard was migrated from. 128 | google.protobuf.StringValue legacy_version = 3; 129 | 130 | // from_package records the contributing package key and version, if applicable. 131 | google.protobuf.StringValue from_package = 4; 132 | } 133 | 134 | 135 | // Filter is used to filter dashboards for non exact match cases. 136 | message Filter { 137 | // tags includes the values to be matched in the dashboard description. 138 | // Tags are matched by word. Generally, a tag is prefixed by a '#', 139 | // which must be omitted when provided here. 140 | // All provided tags must match inside a dashboard for it to be returned. 141 | // E.g., to match "#devices", the tag should be set to "devices". 142 | fmp.RepeatedString tags = 1; 143 | } 144 | 145 | // Dashboard state contains all dashboard data. 146 | message Dashboard { 147 | option (fmp.model) = "ro"; 148 | option (fmp.custom_filter) = "[]Filter"; 149 | 150 | // key is the unique identifier. It will always be defined. 151 | DashboardKey key = 1; 152 | 153 | // created_at represents the date the dashboard was first created. 154 | // Old dashboards may not have this field set. 155 | google.protobuf.Timestamp created_at = 2; 156 | 157 | // created_by keeps the name of the user who first created this dashboard. 158 | // Old dashboards may not have this field set. 159 | google.protobuf.StringValue created_by = 3; 160 | 161 | // last_modified_at holds the timestamp this dashboard was last updated by an user. 162 | // Old dashboards may not have this field set. 163 | google.protobuf.Timestamp last_modified_at = 4; 164 | 165 | // last_modified_by holds the username who last updated this dashboard. 166 | // Old dashboards may not have this field set. 167 | google.protobuf.StringValue last_modified_by = 5; 168 | 169 | // meta_data includes version metadata about the dashboard. 170 | DashboardMetadata meta_data = 6; 171 | 172 | // name is the dashboard name, displayed at the top of the dashboard. 173 | google.protobuf.StringValue name = 7; 174 | 175 | // description may include details about what is displayed in the dashboard. 176 | google.protobuf.StringValue description = 8; 177 | 178 | // widgets list of widgets in the dashboard. 179 | Widgets widgets = 9; 180 | } 181 | 182 | // GlobalDashboardConfig holds global configs related to Dashboards. 183 | message GlobalDashboardConfig { 184 | option (fmp.unkeyed_model) = "rw"; 185 | 186 | // default_dashboard is the default dashboard shown to a user. 187 | // To unset, use an empty key (`{dashboard_id: nil}`) in a `Set()` call. 188 | DashboardKey default_dashboard = 1; 189 | } 190 | -------------------------------------------------------------------------------- /docs/resources/cloudeos_vpc_status.md: -------------------------------------------------------------------------------- 1 | # cloudeos_vpc_status Resource 2 | 3 | The `cloudeos_vpc_status` resource provides AWS VPC or Azure Resource Group/VNET deployment information to CVaaS. 4 | 5 | ## Example Usage 6 | 7 | ### AWS example 8 | 9 | ```hcl 10 | resource "cloudeos_topology" "topology" { 11 | topology_name = "topo-test" 12 | bgp_asn = "65000-65100" 13 | vtep_ip_cidr = "1.0.0.0/16" 14 | terminattr_ip_cidr = "4.0.0.0/16" 15 | dps_controlplane_cidr = "3.0.0.0/16" 16 | } 17 | 18 | resource "cloudeos_clos" "clos" { 19 | name = "clos-test" 20 | topology_name = cloudeos_topology.topology.topology_name 21 | cv_container_name = "CloudLeaf" 22 | } 23 | 24 | resource "cloudeos_wan" "wan" { 25 | name = "wan-test" 26 | topology_name = cloudeos_topology.topology.topology_name 27 | cv_container_name = "CloudEdge" 28 | } 29 | 30 | resource "cloudeos_vpc_config" "vpc" { 31 | cloud_provider = "aws" 32 | topology_name = cloudeos_topology.topology.topology_name 33 | clos_name = cloudeos_clos.clos.name 34 | wan_name = cloudeos_wan.wan.name 35 | role = "CloudEdge" 36 | cnps = "Dev" 37 | tags = { 38 | Name = "edgeVpc" 39 | Cnps = "Dev" 40 | } 41 | region = "us-west-1" 42 | } 43 | 44 | resource "aws_vpc" "vpc" { 45 | cidr_block = "100.0.0.0/16" 46 | } 47 | 48 | resource "aws_security_group" "sg" { 49 | name = "example_sg" 50 | } 51 | 52 | resource "cloudeos_vpc_status" "vpc" { 53 | cloud_provider = cloudeos_vpc_config.vpc.cloud_provider // Provider name 54 | vpc_id = aws_vpc.vpc.id // ID of the aws vpc 55 | security_group_id = aws_security_group.sg.id // security group associated with VPC 56 | cidr_block = aws_vpc.vpc.cidr_block // VPC CIDR block 57 | igw = aws_security_group.sg.name // IGW name 58 | role = cloudeos_vpc_config.vpc.role // VPC role (CloudEdge/CloudLeaf) 59 | topology_name = cloudeos_topology.topology.topology_name // Topology Name 60 | tags = cloudeos_vpc_config.vpc.tags // A mapping of tags to assign to the resource 61 | clos_name = cloudeos_clos.clos.name // Clos Name 62 | wan_name = cloudeos_wan.wan.name // Wan Name 63 | cnps = cloudeos_vpc_config.vpc.cnps // Cloud Network Private Segments Name 64 | region = cloudeos_vpc_config.vpc.region // Region of deployment 65 | account = "dummy_aws_account" // The unique identifier of the account 66 | tf_id = cloudeos_vpc_config.vpc.tf_id 67 | } 68 | ``` 69 | 70 | ### Azure example 71 | 72 | ```hcl 73 | resource "cloudeos_topology" "topology" { 74 | topology_name = "topo-test" 75 | bgp_asn = "65000-65100" 76 | vtep_ip_cidr = "1.0.0.0/16" 77 | terminattr_ip_cidr = "4.0.0.0/16" 78 | dps_controlplane_cidr = "3.0.0.0/16" 79 | } 80 | 81 | resource "cloudeos_clos" "clos" { 82 | name = "clos-test" 83 | topology_name = cloudeos_topology.topology.topology_name 84 | cv_container_name = "CloudLeaf" 85 | } 86 | 87 | resource "cloudeos_wan" "wan" { 88 | name = "wan-test" 89 | topology_name = cloudeos_topology.topology.topology_name 90 | cv_container_name = "CloudEdge" 91 | } 92 | 93 | resource "cloudeos_vpc_config" "vpc" { 94 | cloud_provider = "azure" 95 | topology_name = cloudeos_topology.topology.topology_name 96 | clos_name = cloudeos_clos.clos.name 97 | wan_name = cloudeos_wan.wan.name 98 | role = "CloudEdge" 99 | cnps = "Dev" 100 | tags = { 101 | Name = "azureEdgeVpc" 102 | Cnps = "Dev" 103 | } 104 | vnet_name = "edge1Vnet" 105 | region = "westus2" 106 | } 107 | 108 | resource "azurerm_resource_group" "rg" { 109 | name = "edge1RG" 110 | location = cloudeos_vpc_config.vpc.region 111 | } 112 | 113 | resource "azurerm_virtual_network" "vnet" { 114 | name = cloudeos_vpc_config.vpc.vnet_name 115 | address_space = ["100.0.0.0/16"] 116 | resource_group_name = azurerm_resource_group.rg.name 117 | location = azurerm_resource_group.rg.location 118 | tags = cloudeos_vpc_config.vpc.tags 119 | } 120 | 121 | resource "azurerm_network_security_group" "sg" { 122 | depends_on = [azurerm_resource_group.rg] 123 | name = "example_sg" 124 | location = azurerm_resource_group.rg.location 125 | resource_group_name = azurerm_resource_group.rg.name 126 | } 127 | 128 | data "azurerm_client_config" "current" {} 129 | 130 | resource "cloudeos_vpc_status" "vpc" { 131 | cloud_provider = cloudeos_vpc_config.vpc.cloud_provider // Provider name 132 | rg_name = azurerm_resource_group.rg.name // Azure resource group name 133 | vpc_id = azurerm_virtual_network.vnet.id // ID of the azure virtual network 134 | security_group_id = azurerm_network_security_group.sg[0].id // security group associated with virtual network 135 | cidr_block = azurerm_virtual_network.vnet.address_space[0] // VPC CIDR block 136 | role = cloudeos_vpc_config.vpc.role // VPC role (CloudEdge/CloudLeaf) 137 | topology_name = cloudeos_topology.topology.topology_name // Topology Name 138 | tags = cloudeos_vpc_config.vpc.tags // A mapping of tags to assign to the resource 139 | clos_name = cloudeos_clos.clos.name // Clos Name 140 | wan_name = cloudeos_wan.wan.name // Wan Name 141 | cnps = cloudeos_vpc_config.vpc.cnps // Cloud Network Private Segments Name 142 | region = cloudeos_vpc_config.vpc.region // Region of deployment 143 | account = data.azurerm_client_config.current.subscription_id // The unique identifier of the account 144 | tf_id = cloudeos_vpc_config.vpc.tf_id 145 | } 146 | ``` 147 | 148 | ## Argument Reference 149 | 150 | * `cloud_provider` - (Required) The Cloud Provider in which the VPC/VNET is deployed. 151 | * `cnps` - (Required) Cloud Network Private Segments Name. ( VRF Name ) 152 | * `topology_name` - (Required) Name of topology resource. 153 | * `region` - (Required) Region of deployment. 154 | * `vpc_id` - (Required) VPC ID, this is equiv to vnet_id in Azure. 155 | * `role` - (Required) CloudEdge or CloudLeaf. 156 | * `role` - (Required) VPC role, CloudEdge/CloudLeaf. 157 | * `account` - (Required) The unique identifier of the account. 158 | * `rg_name` - (Optional) Resource group name, only valid for Azure. 159 | * `vnet_name` - (Optional) VNET name, only valid for Azure. 160 | * `clos_name` - (Optional) Clos Name this VPC refers to for attributes. 161 | * `wan_name` - (Optional) Wan Name this VPC refers to for attributes. 162 | * `tags` - (Optional) A mapping of tags to assign to the resource. 163 | * `cidr_block` - (Optional) CIDR Block for VPC. 164 | * `igw`- (Optional) Internet gateway id, only valid for AWS. 165 | 166 | ## Attributes Reference 167 | 168 | In addition to Arguments listed above - the following Attributes are exported 169 | 170 | * `ID` - The ID of cloudeos_vpc_status Resource. 171 | 172 | ## Timeouts 173 | 174 | * `delete` - (Defaults to 5 minutes) Used when deleting the cloudeos_vpc_status Resource. -------------------------------------------------------------------------------- /docs/resources/cloudeos_router_status.md: -------------------------------------------------------------------------------- 1 | # cloudeos_router_status 2 | 3 | The `cloudeos_router_status` resource should be created after a CloudEOS router has been deployed. It sends all the information 4 | about the deployed CloudEOS router to CVaaS. Unlike `cloudeos_router_config` which takes minimal input about how the 5 | CloudEOS router should be deployed, `cloudeos_router_status` provides detailed deployment information after the router 6 | is deployed. 7 | 8 | ## Example Usage 9 | 10 | ```hcl 11 | resource "azurerm_resource_group" "rg" { 12 | name = "example-rg" 13 | location = "westus2" 14 | } 15 | 16 | resource "azurerm_virtual_network" "vnet" { 17 | name = "example-vnet" 18 | address_space = ["10.0.0.0/16"] 19 | resource_group_name = azurerm_resource_group.rg.name 20 | location = azurerm_resource_group.rg.location 21 | } 22 | 23 | resource "azurerm_subnet" "internal" { 24 | name = "internal" 25 | resource_group_name = azurerm_resource_group.rg.name 26 | virtual_network_name = azurerm_virtual_network.rg.name 27 | address_prefix = "10.0.2.0/24" 28 | } 29 | 30 | resource "azurerm_public_ip" "publicip" { 31 | name = "cloudeos-pip" 32 | location = azurerm_resource_group.rg.location 33 | resource_group_name = azurerm_resource_group.rg.name 34 | allocation_method = "Static" 35 | zones = [2] 36 | } 37 | 38 | resource "azurerm_subnet" "public" { 39 | name = "internal" 40 | resource_group_name = azurerm_resource_group.rg.name 41 | virtual_network_name = azurerm_virtual_network.rg.name 42 | address_prefix = "10.0.1.0/24" 43 | } 44 | 45 | resource "azurerm_network_interface" "internalIntf" { 46 | name = "internalIntf" 47 | location = azurerm_resource_group.rg.location 48 | resource_group_name = azurerm_resource_group.rg.name 49 | 50 | ip_configuration { 51 | name = "internalIp" 52 | subnet_id = azurerm_subnet.internal.id 53 | private_ip_address = "10.0.2.101" 54 | } 55 | } 56 | 57 | resource "azurerm_network_interface" "publicIntf" { 58 | name = "publicIntf" 59 | location = azurerm_resource_group.rg.location 60 | resource_group_name = azurerm_resource_group.rg.name 61 | 62 | ip_configuration { 63 | name = "publicIp" 64 | subnet_id = azurerm_subnet.public.id 65 | private_ip_address = "10.0.1.101" 66 | public_ip_address_id = azurerm_public_ip.publicip.id 67 | } 68 | } 69 | 70 | resource "cloudeos_router_config" "cloudeos" { 71 | cloud_provider = "azure" 72 | topology_name = cloudeos_topology.topology.topology_name 73 | role = "CloudEdge" 74 | cnps = "dev" 75 | vpc_id = azurerm_virtual_network.vnet.id 76 | region = azurerm_resource_group.rg.location 77 | is_rr = false 78 | intf_name = ["publicIntf", "internalIntf"] 79 | intf_private_ip = [azurerm_network_interface.publicIntf.private_ip_address, 80 | azurerm_network_interface.internalIntf.private_ip_address] 81 | intf_type = ["public", "internal"] 82 | } 83 | 84 | data "template_file" "user_data_specific" { 85 | template = file("file.txt") 86 | vars = { 87 | bootstrap_cfg = cloudeos_router_config.router[0].bootstrap_cfg 88 | } 89 | } 90 | 91 | resource "azurerm_virtual_machine" "cloudeosVm" { 92 | name = "example-cloudeos" 93 | location = azurerm_resource_group.rg.location 94 | resource_group_name = azurerm_resource_group.rg.name 95 | vm_size = "Standard_D4_v2" 96 | primary_network_interface_id = azurerm_network_interface.publicIntf.id 97 | network_interface_ids = [azurerm_network_interface.publicIntf.id, azurerm_network_interface.internalIntf.id] 98 | 99 | storage_image_reference { 100 | publisher = "arista-networks" 101 | offer = "cloudeos-router-payg" 102 | sku = "cloudeos-4_24_0-payg" 103 | version = "4.24.01" 104 | } 105 | 106 | storage_os_disk { 107 | name = "cloudeos-disk1" 108 | caching = "ReadWrite" 109 | create_option = "FromImage" 110 | managed_disk_type = "Standard_LRS" 111 | } 112 | 113 | os_profile { 114 | computer_name = "example-cloudeos" 115 | admin_username = "testadmin" 116 | admin_password = "Password1234!" 117 | custom_data = data.template_file.user_data_specific[0].rendered 118 | } 119 | os_profile_linux_config { 120 | disable_password_authentication = false 121 | } 122 | } 123 | 124 | resource "cloudeos_router_status" "cloudeos" { 125 | cloud_provider = "azure" 126 | cv_container = "CloudEdge" 127 | cnps = "dev" 128 | vpc_id = azurerm_virtual_network.vnet.id 129 | instance_id = azurerm_virtual_machine.cloueosVm.id 130 | instance_type = azurerm_virtual_machine.cloueosVm.instance_type 131 | region = "westus2" 132 | primary_network_interface_id = azurerm_network_interface.publicIntf.id 133 | public_ip = azurerm_public_ip.publicip.ip_address 134 | intf_name = cloudeos_router_config.cloudeos.intf_name 135 | intf_id = [azurerm_network_interface.publicIntf.id, azurerm_network_interface.internalIntf.id] 136 | intf_private_ip = cloudeos_router_config.cloudeos.intf_private_ip 137 | intf_subnet_id = [azurerm_subnet.public.id, azurerm_subnet.internal.id] 138 | intf_type = cloudeos_router_config.cloudeos.intf_type 139 | tf_id = cloudeos_router_config.cloudeos.tf_id 140 | is_rr = "false" 141 | } 142 | ``` 143 | 144 | ## Argument Reference 145 | 146 | * `cloud_provider` - (Required) CloudProvider type. Supports only aws or azure. 147 | * `instance_type` - (Required) Instance ID of deployed CloudEOS. 148 | * `intf_name` - (Required) List of interface names for the routers. 149 | * `intf_id` - (Required) List of interface IDs attached to the routers. 150 | * `intf_private_ip` - (Required) List of private IPs attached to the interfaces. 151 | * `intf_subnet_id` - (Required) List of subnet IDs of interfaces. 152 | * `intf_type` - (Required) List of interface types. Values supported : public, internal, private. 153 | * `region` - (Required) Region of deployment. 154 | * `cv_container` - (Optional) Container in CVaaS to which the router will be added to. 155 | * `vpc_id` - (Optional) VPC/VNET ID of the VPC in which the CloudEOS is deployed in. 156 | * `rg_name` - (Optional) Resource group name, only for Azure. 157 | * `rg_location` - (Optional) Resource group location, only for Azure. 158 | * `tags` - (Optional) A mapping of tags to assign to the resource. 159 | * `availability_zone` - (Optional) Availability Zone in which the router is deployed in. 160 | * `primary_network_interface_id` - (Optional) 161 | * `availability_set_id` - (Optional) Availability Set. 162 | * `public_ip` - (Optional) Public IP of interface. 163 | * `private_rt_table_ids` - (Optional) List of private interface route table IDs. 164 | * `internal_rt_table_ids` - (Optional) List of internal interface route table IDs. 165 | * `public_rt_table_ids` - (Optional) List of public route table IDs. 166 | * `ha_name` - (Optional) Cloud HA pair name. 167 | * `cnps` - (Optional) Cloud Network Private Segments ( VRF name ) 168 | * `is_rr` - (Optional) true if this CloudEOS acts as a Route Reflector. 169 | 170 | ## Attributes Reference 171 | 172 | In addition to Arguments listed above - the following Attributes are exported 173 | 174 | * `ID` - The ID of cloudeos_router_status Resource. 175 | 176 | ## Timeouts 177 | 178 | * `delete` - (Defaults to 10 minutes) Used when deleting the cloudeos_status Resource. -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_vpc_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "regexp" 11 | "testing" 12 | 13 | r "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 14 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 15 | ) 16 | 17 | func TestResourceVpcConfig(t *testing.T) { 18 | r.Test(t, r.TestCase{ 19 | Providers: testProviders, 20 | PreCheck: func() { testAccPreCheck(t) }, 21 | CheckDestroy: testResourceVpcConfigDestroy, 22 | Steps: []r.TestStep{ 23 | { 24 | Config: testLeafVpcConfigInProvisionMode, 25 | ExpectError: regexp.MustCompile("only applicable to resources with role CloudEdge"), 26 | }, 27 | { 28 | Config: testResourceInitialVpcConfig, 29 | Check: testResourceInitialVpcConfigCheck, 30 | }, 31 | { 32 | Config: testResourceUpdatedVpcConfig, 33 | Check: testResourceUpdatedVpcConfigCheck, 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | // Note that in an actual deployment, deploy_mode in 40 | // cloudeos_vpc_config will be copied from cloudeos_topology 41 | // in the modules 42 | var testLeafVpcConfigInProvisionMode = fmt.Sprintf(` 43 | provider "cloudeos" { 44 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 45 | cvaas_server = "www.cv-play.corp.arista.io" 46 | // clouddeploy token 47 | service_account_web_token = %q 48 | } 49 | 50 | resource "cloudeos_topology" "topology" { 51 | topology_name = "topo-test32" 52 | deploy_mode = "provision" 53 | } 54 | 55 | resource "cloudeos_wan" "wan" { 56 | name = "wan-test32" 57 | topology_name = cloudeos_topology.topology.topology_name 58 | cv_container_name = "CloudEdge" 59 | } 60 | 61 | resource "cloudeos_vpc_config" "vpc" { 62 | cloud_provider = "aws" 63 | topology_name = cloudeos_topology.topology.topology_name 64 | wan_name = cloudeos_wan.wan.name 65 | role = "CloudLeaf" 66 | cnps = "" 67 | tags = { 68 | Name = "edgeVpc" 69 | } 70 | region = "us-west-1" 71 | deploy_mode = "provision" 72 | } 73 | `, os.Getenv("token")) 74 | 75 | var testResourceInitialVpcConfig = fmt.Sprintf(` 76 | provider "cloudeos" { 77 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 78 | cvaas_server = "www.cv-play.corp.arista.io" 79 | // clouddeploy token 80 | service_account_web_token = %q 81 | } 82 | 83 | resource "cloudeos_topology" "topology" { 84 | topology_name = "topo-test4" 85 | bgp_asn = "65000-65100" 86 | vtep_ip_cidr = "1.0.0.0/16" 87 | terminattr_ip_cidr = "2.0.0.0/16" 88 | dps_controlplane_cidr = "3.0.0.0/16" 89 | } 90 | 91 | resource "cloudeos_clos" "clos" { 92 | name = "clos-test4" 93 | topology_name = cloudeos_topology.topology.topology_name 94 | cv_container_name = "CloudLeaf" 95 | } 96 | 97 | resource "cloudeos_wan" "wan" { 98 | name = "wan-test4" 99 | topology_name = cloudeos_topology.topology.topology_name 100 | cv_container_name = "CloudEdge" 101 | } 102 | 103 | resource "cloudeos_vpc_config" "vpc" { 104 | cloud_provider = "aws" 105 | topology_name = cloudeos_topology.topology.topology_name 106 | clos_name = cloudeos_clos.clos.name 107 | wan_name = cloudeos_wan.wan.name 108 | role = "CloudEdge" 109 | cnps = "Dev" 110 | tags = { 111 | Name = "edgeVpc" 112 | Cnps = "Dev" 113 | } 114 | region = "us-west-1" 115 | } 116 | `, os.Getenv("token")) 117 | 118 | func testResourceInitialVpcConfigCheck(s *terraform.State) error { 119 | resourceState := s.Modules[0].Resources["cloudeos_vpc_config.vpc"] 120 | if resourceState == nil { 121 | return fmt.Errorf("cloudeos_vpc_config.vpc resource not found") 122 | } 123 | 124 | instanceState := resourceState.Primary 125 | if instanceState == nil { 126 | return fmt.Errorf("cloudeos_vpc_config.vpc resource has no primary instance") 127 | } 128 | 129 | if instanceState.ID == "" { 130 | return fmt.Errorf("cloudeos_vpc_config ID not assigned %s", instanceState.ID) 131 | } 132 | 133 | if got, want := instanceState.Attributes["cloud_provider"], "aws"; got != want { 134 | return fmt.Errorf("cloudeos_vpc_config cloud_provider contains %s; want %s", got, want) 135 | } 136 | 137 | if got, want := instanceState.Attributes["topology_name"], "topo-test4"; got != want { 138 | return fmt.Errorf("cloudeos_vpc_config topology_name contains %s; want %s", got, want) 139 | } 140 | 141 | if got, want := instanceState.Attributes["clos_name"], "clos-test4"; got != want { 142 | return fmt.Errorf("cloudeos_vpc_config clos_name contains %s; want %s", got, want) 143 | } 144 | 145 | if got, want := instanceState.Attributes["wan_name"], "wan-test4"; got != want { 146 | return fmt.Errorf("cloudeos_vpc_config wan_name contains %s; want %s", got, want) 147 | } 148 | 149 | if got, want := instanceState.Attributes["role"], "CloudEdge"; got != want { 150 | return fmt.Errorf("cloudeos_vpc_config role contains %s; want %s", got, want) 151 | } 152 | 153 | if got, want := instanceState.Attributes["cnps"], "Dev"; got != want { 154 | return fmt.Errorf("cloudeos_vpc_config cnps contains %s; want %s", got, want) 155 | } 156 | 157 | if got, want := instanceState.Attributes["tags.Name"], "edgeVpc"; got != want { 158 | return fmt.Errorf("cloudeos_vpc_config tags contains %s; want %s", got, want) 159 | } 160 | return nil 161 | } 162 | 163 | var testResourceUpdatedVpcConfig = fmt.Sprintf(` 164 | provider "cloudeos" { 165 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 166 | cvaas_server = "www.cv-play.corp.arista.io" 167 | // clouddeploy token 168 | service_account_web_token = %q 169 | } 170 | 171 | resource "cloudeos_topology" "topology" { 172 | topology_name = "topo-test4" 173 | bgp_asn = "65000-65100" 174 | vtep_ip_cidr = "1.0.0.0/16" 175 | terminattr_ip_cidr = "2.0.0.0/16" 176 | dps_controlplane_cidr = "3.0.0.0/16" 177 | } 178 | 179 | resource "cloudeos_clos" "clos" { 180 | name = "clos-test4" 181 | topology_name = cloudeos_topology.topology.topology_name 182 | cv_container_name = "CloudLeaf" 183 | } 184 | 185 | resource "cloudeos_wan" "wan" { 186 | name = "wan-test4" 187 | topology_name = cloudeos_topology.topology.topology_name 188 | cv_container_name = "CloudEdge" 189 | } 190 | 191 | resource "cloudeos_vpc_config" "vpc" { 192 | cloud_provider = "aws" 193 | topology_name = cloudeos_topology.topology.topology_name 194 | clos_name = cloudeos_clos.clos.name 195 | wan_name = cloudeos_wan.wan.name 196 | role = "CloudEdge" 197 | cnps = "Dev" 198 | tags = { 199 | Name = "updatedVpcName" 200 | Cnps = "Dev" 201 | } 202 | region = "us-west-1" 203 | } 204 | `, os.Getenv("token")) 205 | 206 | func testResourceUpdatedVpcConfigCheck(s *terraform.State) error { 207 | resourceState := s.Modules[0].Resources["cloudeos_vpc_config.vpc"] 208 | if resourceState == nil { 209 | return fmt.Errorf("cloudeos_vpc_config.vpc resource not found") 210 | } 211 | 212 | instanceState := resourceState.Primary 213 | if instanceState == nil { 214 | return fmt.Errorf("cloudeos_vpc_config.vpc resource has no primary instance") 215 | } 216 | 217 | if instanceState.ID == "" { 218 | return fmt.Errorf("cloudeos_vpc_config ID not assigned %s", instanceState.ID) 219 | } 220 | 221 | if got, want := instanceState.Attributes["tags.Name"], "updatedVpcName"; got != want { 222 | return fmt.Errorf("cloudeos_vpc_config tags contains %s; want %s", got, want) 223 | } 224 | return nil 225 | } 226 | 227 | func testResourceVpcConfigDestroy(s *terraform.State) error { 228 | for _, rs := range s.RootModule().Resources { 229 | if rs.Type != "cloudeos_vpc_config" { 230 | continue 231 | } 232 | // TODO 233 | return nil 234 | } 235 | return nil 236 | } 237 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_subnet_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | r "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 13 | "github.com/hashicorp/terraform-plugin-sdk/terraform" 14 | ) 15 | 16 | func TestResourceSubnet(t *testing.T) { 17 | r.Test(t, r.TestCase{ 18 | Providers: testProviders, 19 | PreCheck: func() { testAccPreCheck(t) }, 20 | CheckDestroy: testResourceSubnetDestroy, 21 | Steps: []r.TestStep{ 22 | { 23 | Config: testResourceInitialSubnetConfig, 24 | Check: testResourceInitialSubnetConfigCheck, 25 | }, 26 | { 27 | Config: testResourceUpdatedSubnetConfig, 28 | Check: testResourceUpdatedSubnetConfigCheck, 29 | }, 30 | }, 31 | }) 32 | } 33 | 34 | var testResourceInitialSubnetConfig = fmt.Sprintf(` 35 | provider "cloudeos" { 36 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 37 | cvaas_server = "www.cv-play.corp.arista.io" 38 | // clouddeploy token 39 | service_account_web_token = %q 40 | } 41 | 42 | resource "cloudeos_topology" "topology" { 43 | topology_name = "topo-test13" 44 | bgp_asn = "65000-65100" 45 | vtep_ip_cidr = "1.0.0.0/16" 46 | terminattr_ip_cidr = "2.0.0.0/16" 47 | dps_controlplane_cidr = "3.0.0.0/16" 48 | } 49 | 50 | resource "cloudeos_clos" "clos" { 51 | name = "clos-test6" 52 | topology_name = cloudeos_topology.topology.topology_name 53 | cv_container_name = "CloudLeaf" 54 | } 55 | 56 | resource "cloudeos_wan" "wan" { 57 | name = "wan-test6" 58 | topology_name = cloudeos_topology.topology.topology_name 59 | cv_container_name = "CloudEdge" 60 | } 61 | 62 | resource "cloudeos_vpc_config" "vpc" { 63 | cloud_provider = "aws" 64 | topology_name = cloudeos_topology.topology.topology_name 65 | clos_name = cloudeos_clos.clos.name 66 | wan_name = cloudeos_wan.wan.name 67 | role = "CloudEdge" 68 | cnps = "Dev" 69 | tags = { 70 | Name = "edgeVpc" 71 | Cnps = "Dev" 72 | } 73 | region = "us-west-1" 74 | } 75 | 76 | resource "cloudeos_vpc_status" "vpc" { 77 | cloud_provider = cloudeos_vpc_config.vpc.cloud_provider 78 | vpc_id = "vpc-dummy" 79 | security_group_id = "sg-dummy" 80 | cidr_block = "11.0.0.0/16" 81 | igw = "egdeVpcigw" 82 | role = cloudeos_vpc_config.vpc.role 83 | topology_name = cloudeos_topology.topology.topology_name 84 | tags = cloudeos_vpc_config.vpc.tags 85 | clos_name = cloudeos_clos.clos.name 86 | wan_name = cloudeos_wan.wan.name 87 | cnps = "Dev" 88 | region = cloudeos_vpc_config.vpc.region 89 | account = "dummy_aws_account" 90 | tf_id = cloudeos_vpc_config.vpc.tf_id 91 | } 92 | 93 | resource "cloudeos_subnet" "subnet" { 94 | cloud_provider = cloudeos_vpc_status.vpc.cloud_provider 95 | vpc_id = cloudeos_vpc_status.vpc.vpc_id 96 | availability_zone = "us-west-1b" 97 | subnet_id = "subnet-id" 98 | cidr_block = "11.0.0.0/24" 99 | subnet_name = "edgeSubnet0" 100 | } 101 | `, os.Getenv("token")) 102 | 103 | var resourceSubnetID = "" 104 | 105 | func testResourceInitialSubnetConfigCheck(s *terraform.State) error { 106 | resourceState := s.Modules[0].Resources["cloudeos_subnet.subnet"] 107 | if resourceState == nil { 108 | return fmt.Errorf("cloudeos_subnet.subnet resource not found") 109 | } 110 | 111 | instanceState := resourceState.Primary 112 | if instanceState == nil { 113 | return fmt.Errorf("cloudeos_subnet.subnet resource has no primary instance") 114 | } 115 | 116 | if instanceState.ID == "" { 117 | return fmt.Errorf("cloudeos_subnet.subnet ID not assigned %s", instanceState.ID) 118 | } 119 | resourceSubnetID = instanceState.ID 120 | 121 | if got, want := instanceState.Attributes["cloud_provider"], "aws"; got != want { 122 | return fmt.Errorf("cloudeos_subnet.subnet cloud_provider contains %s; want %s", got, want) 123 | } 124 | 125 | if got, want := instanceState.Attributes["vpc_id"], "vpc-dummy"; got != want { 126 | return fmt.Errorf("cloudeos_subnet.subnet vpc_id contains %s; want %s", got, want) 127 | } 128 | 129 | if got, want := instanceState.Attributes["availability_zone"], "us-west-1b"; got != want { 130 | return fmt.Errorf("cloudeos_subnet availability_zone contains %s; want %s", got, want) 131 | } 132 | 133 | if got, want := instanceState.Attributes["subnet_id"], "subnet-id"; got != want { 134 | return fmt.Errorf("cloudeos_subnet.subnet subnet_id contains %s; want %s", got, want) 135 | } 136 | 137 | if got, want := instanceState.Attributes["cidr_block"], "11.0.0.0/24"; got != want { 138 | return fmt.Errorf("cloudeos_subnet.subnet cidr_block contains %s; want %s", got, want) 139 | } 140 | 141 | if got, want := instanceState.Attributes["subnet_name"], "edgeSubnet0"; got != want { 142 | return fmt.Errorf("cloudeos_subnet.subnet subnet_name contains %s; want %s", got, want) 143 | } 144 | return nil 145 | } 146 | 147 | var testResourceUpdatedSubnetConfig = fmt.Sprintf(` 148 | provider "cloudeos" { 149 | cvaas_domain = "apiserver.cv-play.corp.arista.io" 150 | cvaas_server = "www.cv-play.corp.arista.io" 151 | // clouddeploy token 152 | service_account_web_token = %q 153 | } 154 | 155 | resource "cloudeos_topology" "topology" { 156 | topology_name = "topo-test13" 157 | bgp_asn = "65000-65100" 158 | vtep_ip_cidr = "1.0.0.0/16" 159 | terminattr_ip_cidr = "2.0.0.0/16" 160 | dps_controlplane_cidr = "3.0.0.0/16" 161 | } 162 | 163 | resource "cloudeos_clos" "clos" { 164 | name = "clos-test6" 165 | topology_name = cloudeos_topology.topology.topology_name 166 | cv_container_name = "CloudLeaf" 167 | } 168 | 169 | resource "cloudeos_wan" "wan" { 170 | name = "wan-test6" 171 | topology_name = cloudeos_topology.topology.topology_name 172 | cv_container_name = "CloudEdge" 173 | } 174 | 175 | resource "cloudeos_vpc_config" "vpc" { 176 | cloud_provider = "aws" 177 | topology_name = cloudeos_topology.topology.topology_name 178 | clos_name = cloudeos_clos.clos.name 179 | wan_name = cloudeos_wan.wan.name 180 | role = "CloudEdge" 181 | cnps = "Dev" 182 | tags = { 183 | Name = "edgeVpc" 184 | Cnps = "Dev" 185 | } 186 | region = "us-west-1" 187 | } 188 | 189 | resource "cloudeos_vpc_status" "vpc" { 190 | cloud_provider = cloudeos_vpc_config.vpc.cloud_provider 191 | vpc_id = "vpc-dummy" 192 | security_group_id = "sg-dummy" 193 | cidr_block = "11.0.0.0/16" 194 | igw = "egdeVpcigw" 195 | role = cloudeos_vpc_config.vpc.role 196 | topology_name = cloudeos_topology.topology.topology_name 197 | tags = cloudeos_vpc_config.vpc.tags 198 | clos_name = cloudeos_clos.clos.name 199 | wan_name = cloudeos_wan.wan.name 200 | cnps = "Dev" 201 | region = cloudeos_vpc_config.vpc.region 202 | account = "dummy_aws_account" 203 | tf_id = cloudeos_vpc_config.vpc.tf_id 204 | } 205 | 206 | resource "cloudeos_subnet" "subnet" { 207 | cloud_provider = cloudeos_vpc_status.vpc.cloud_provider 208 | vpc_id = cloudeos_vpc_status.vpc.vpc_id 209 | availability_zone = "us-west-1b" 210 | subnet_id = "subnet-id" 211 | cidr_block = "11.0.0.0/24" 212 | subnet_name = "updatedEdgeSubnet0" 213 | } 214 | `, os.Getenv("token")) 215 | 216 | func testResourceUpdatedSubnetConfigCheck(s *terraform.State) error { 217 | resourceState := s.Modules[0].Resources["cloudeos_subnet.subnet"] 218 | if resourceState == nil { 219 | return fmt.Errorf("cloudeos_subnet.subnet resource not found") 220 | } 221 | 222 | instanceState := resourceState.Primary 223 | if instanceState == nil { 224 | return fmt.Errorf("cloudeos_subnet.subnet resource has no primary instance") 225 | } 226 | 227 | if instanceState.ID != resourceSubnetID { 228 | return fmt.Errorf("cloudeos_subnet.subnet ID has changed %s", instanceState.ID) 229 | } 230 | 231 | if got, want := instanceState.Attributes["subnet_name"], "updatedEdgeSubnet0"; got != want { 232 | return fmt.Errorf("cloudeos_subnet.subnet subnet_name contains %s; want %s", got, want) 233 | } 234 | return nil 235 | } 236 | 237 | func testResourceSubnetDestroy(s *terraform.State) error { 238 | for _, rs := range s.RootModule().Resources { 239 | if rs.Type != "cloudeos_subnet" { 240 | continue 241 | } 242 | // TODO 243 | return nil 244 | } 245 | return nil 246 | } 247 | -------------------------------------------------------------------------------- /cloudeos/resource_cloudeos_router_status.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Mozilla Public License Version 2.0 3 | // that can be found in the LICENSE file. 4 | 5 | package cloudeos 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "log" 11 | "strings" 12 | "time" 13 | 14 | "github.com/hashicorp/terraform-plugin-sdk/helper/resource" 15 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 16 | ) 17 | 18 | //cloudeosRouterStatus: Define the cloudeosRouterStatus schema ( input and output variables ) 19 | func cloudeosRouterStatus() *schema.Resource { 20 | return &schema.Resource{ 21 | Create: cloudeosRouterStatusCreate, 22 | Read: cloudeosRouterStatusRead, 23 | Update: cloudeosRouterStatusUpdate, 24 | Delete: cloudeosRouterStatusDelete, 25 | 26 | Timeouts: &schema.ResourceTimeout{ 27 | Delete: schema.DefaultTimeout(10 * time.Minute), 28 | }, 29 | 30 | Schema: map[string]*schema.Schema{ 31 | "cloud_provider": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | Description: "aws / azure / gcp", 35 | }, 36 | "cv_container": { 37 | Optional: true, 38 | Type: schema.TypeString, 39 | Description: "Container to which cvp should add this device", 40 | }, 41 | // Set by AWS resource 42 | "vpc_id": { 43 | Optional: true, 44 | Type: schema.TypeString, 45 | Description: "Vpc id of cloudeos", 46 | }, 47 | // Set in Azure 48 | "rg_name": { 49 | Optional: true, 50 | Type: schema.TypeString, 51 | }, 52 | "rg_location": { 53 | Optional: true, 54 | Type: schema.TypeString, 55 | }, 56 | "instance_type": { 57 | Required: true, 58 | Type: schema.TypeString, 59 | }, 60 | "instance_id": { 61 | Optional: true, 62 | Type: schema.TypeString, 63 | Description: "VM instance ID", 64 | ForceNew: true, 65 | }, 66 | "tags": { 67 | Type: schema.TypeMap, 68 | Optional: true, 69 | Description: "A mapping of tags to assign to the resource", 70 | }, 71 | "availability_zone": { 72 | Optional: true, 73 | Type: schema.TypeString, 74 | }, 75 | "primary_network_interface_id": { 76 | Optional: true, 77 | Type: schema.TypeString, 78 | }, 79 | "availability_set_id": { 80 | Optional: true, 81 | Type: schema.TypeString, 82 | Description: "Availability set if for Azure", 83 | }, 84 | "public_ip": { 85 | Optional: true, 86 | Type: schema.TypeString, 87 | Description: "Public ip address", 88 | }, 89 | "intf_name": { 90 | Type: schema.TypeList, 91 | Required: true, 92 | Description: "Interface name", 93 | Elem: &schema.Schema{Type: schema.TypeString}, 94 | }, 95 | "intf_id": { 96 | Type: schema.TypeList, 97 | Required: true, 98 | Description: "Interface id", 99 | Elem: &schema.Schema{Type: schema.TypeString}, 100 | }, 101 | "intf_private_ip": { 102 | Type: schema.TypeList, 103 | Required: true, 104 | Description: "Private IP address", 105 | Elem: &schema.Schema{Type: schema.TypeString}, 106 | }, 107 | "intf_subnet_id": { 108 | Type: schema.TypeList, 109 | Required: true, 110 | Description: "Subnet id attached to intf", 111 | Elem: &schema.Schema{Type: schema.TypeString}, 112 | }, 113 | "intf_type": { 114 | Type: schema.TypeList, 115 | Required: true, 116 | Description: "Interface type", 117 | Elem: &schema.Schema{Type: schema.TypeString}, 118 | }, 119 | "private_rt_table_ids": { 120 | Type: schema.TypeList, 121 | Optional: true, 122 | Elem: &schema.Schema{Type: schema.TypeString}, 123 | }, 124 | "internal_rt_table_ids": { 125 | Type: schema.TypeList, 126 | Optional: true, 127 | Elem: &schema.Schema{Type: schema.TypeString}, 128 | }, 129 | "public_rt_table_ids": { 130 | Type: schema.TypeList, 131 | Optional: true, 132 | Elem: &schema.Schema{Type: schema.TypeString}, 133 | }, 134 | "ha_name": { 135 | Optional: true, 136 | Type: schema.TypeString, 137 | }, 138 | "cnps": { 139 | Optional: true, 140 | Type: schema.TypeString, 141 | }, 142 | "region": { 143 | Required: true, 144 | Type: schema.TypeString, 145 | }, 146 | "is_rr": { 147 | Optional: true, 148 | Type: schema.TypeBool, 149 | }, 150 | "deployment_status": { 151 | Type: schema.TypeString, 152 | Optional: true, 153 | Description: "Deployment Status of the CloudEOS Router", 154 | Computed: true, 155 | }, 156 | "tf_id": { 157 | Required: true, 158 | Type: schema.TypeString, 159 | }, 160 | "routing_resource_info": { 161 | Type: schema.TypeList, 162 | Optional: true, 163 | Description: "List of all route table and association resources.", 164 | Elem: &schema.Schema{Type: schema.TypeString}, 165 | }, 166 | "router_bgp_asn": { 167 | Type: schema.TypeString, 168 | Computed: true, 169 | Optional: true, 170 | Description: "BGP ASN computed on the CloudEOS Router", 171 | }, 172 | "deploy_mode": { 173 | Type: schema.TypeString, 174 | Optional: true, 175 | DiffSuppressFunc: suppressAttributeChange, 176 | Description: "Deployment mode for the resources: provision or empty", 177 | }, 178 | }, 179 | } 180 | } 181 | 182 | func cloudeosRouterStatusCreate(d *schema.ResourceData, m interface{}) error { 183 | provider := m.(CloudeosProvider) 184 | err := provider.AddRouter(d) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | // In the standard deploy mode, we retrieve the bgp_asn allocated for the router 190 | // and set it in the resource, which is then passed to aws_customer_gateway 191 | // resource, if the router is set to serve as a remote vpn gateway. For deploy mode 192 | // provision, this isn't possible since we don't really allocate asn for the routers 193 | // and we do not support this mode for the tgw stuff, as of now. So skip this. 194 | deployMode := strings.ToLower(d.Get("deploy_mode").(string)) 195 | 196 | if deployMode != "provision" { 197 | err := provider.GetRouterStatusAndSetBgpAsn(d) 198 | if err != nil { 199 | return fmt.Errorf("GetRouter failed: %s", err) 200 | } 201 | bgpAsn := d.Get("router_bgp_asn").(string) 202 | // If we can't find an asn in the standard deploy mode in the router status entry, 203 | // something is wrong (since this is allocated when router_config resource is created. 204 | // Error out since this state means something is broken). Do we want to cleanup? 205 | if bgpAsn == "" { 206 | return fmt.Errorf("BGP ASN for the Router not returned") 207 | } 208 | } 209 | 210 | uuid := "cloudeos-router-status" + strings.TrimPrefix(d.Get("tf_id").(string), RtrPrefix) 211 | log.Print("Successfully added " + uuid) 212 | d.SetId(uuid) 213 | return nil 214 | } 215 | 216 | func cloudeosRouterStatusRead(d *schema.ResourceData, m interface{}) error { 217 | return nil 218 | } 219 | 220 | func cloudeosRouterStatusUpdate(d *schema.ResourceData, m interface{}) error { 221 | provider := m.(CloudeosProvider) 222 | err := provider.AddRouter(d) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | log.Print("Successfully updated cloudeos-router-status" + 228 | strings.TrimPrefix(d.Get("tf_id").(string), RtrPrefix)) 229 | return nil 230 | } 231 | 232 | func cloudeosRouterStatusDelete(d *schema.ResourceData, m interface{}) error { 233 | provider := m.(CloudeosProvider) 234 | err := provider.DeleteRouter(d) 235 | if err != nil { 236 | return err 237 | } 238 | 239 | uuid := "cloudeos-router-status" + strings.TrimPrefix(d.Get("tf_id").(string), RtrPrefix) 240 | // wait for router deletion 241 | err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { 242 | if err := provider.CheckRouterDeletionStatus(d); err != nil { 243 | return resource.RetryableError(err) 244 | } 245 | return nil 246 | }) 247 | if err != nil { 248 | return errors.New("Failed to destroy " + uuid + " Error: " + err.Error()) 249 | } 250 | 251 | log.Print("Successfully deleted " + uuid) 252 | d.SetId("") 253 | return nil 254 | } 255 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/configstatus.v1/configstatus.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | syntax = "proto3"; 7 | 8 | package arista.configstatus.v1; 9 | 10 | option go_package = "arista/resources/arista/configstatus.v1;configstatus"; 11 | 12 | option java_package = "com.arista.configstatus.v1"; 13 | option java_outer_classname = "Configstatus"; 14 | option java_multiple_files = true; 15 | 16 | import "google/protobuf/timestamp.proto"; 17 | import "google/protobuf/wrappers.proto"; 18 | 19 | import "fmp/extensions.proto"; 20 | 21 | // ErrorCode indicates warnings and errors produced during computing config 22 | enum ErrorCode { 23 | ERROR_CODE_UNSPECIFIED = 0; 24 | // DEVICE_WARNING indicates device warning 25 | ERROR_CODE_DEVICE_WARNING = 1; 26 | // DEVICE_ERROR indicates device error 27 | ERROR_CODE_DEVICE_ERROR = 2; 28 | // UNREACHABLE_DEVICE indicates the device cannot be reached 29 | ERROR_CODE_UNREACHABLE_DEVICE = 3; 30 | // CONFIG_FILTER_ERROR indicates error from partial config management filters 31 | ERROR_CODE_CONFIG_FILTER_ERROR = 4; 32 | // INTERNAL indicates internal errors 33 | ERROR_CODE_INTERNAL = 5; 34 | } 35 | 36 | // ConfigError represents errors reported by CVP when handling device configuration 37 | message ConfigError { 38 | ErrorCode error_code = 1; 39 | google.protobuf.StringValue error_msg = 2; 40 | // Line_num represents line number, if any 41 | google.protobuf.Int32Value line_num = 3; 42 | // Configlet_name represents the originating configlet name. Configlet_name 43 | // and line_num point to the line where config warning or config error originate. 44 | google.protobuf.StringValue configlet_name = 4; 45 | } 46 | 47 | message ConfigErrors { 48 | repeated ConfigError values = 1; 49 | } 50 | 51 | // DiffOp is the operation to a line from one side of diff to get to another 52 | enum DiffOp { 53 | DIFF_OP_UNSPECIFIED = 0; 54 | // NOP indicates no change. A and B are identical at this line 55 | DIFF_OP_NOP = 1; 56 | // IGNORE indicates a line that's ignored in either A or B. 57 | // One of a_line_num or b_line_num will be -1 58 | DIFF_OP_IGNORE = 2; 59 | // ADD is an addition of a line from A 60 | DIFF_OP_ADD = 3; 61 | // DELETE is deletion of a line from B 62 | DIFF_OP_DELETE = 4; 63 | // CHANGE is a modification to a line in A 64 | DIFF_OP_CHANGE = 5; 65 | } 66 | // ConfigFilterCode indicates if a config line matches PCM filter(s) 67 | enum ConfigFilterCode { 68 | // UNSPECIFIED indicates config line did not match any partial config management (PCM) filter 69 | CONFIG_FILTER_CODE_UNSPECIFIED = 0; 70 | // MANAGED_LINE indicates config line matches managed PCM filter hence is managed 71 | CONFIG_FILTER_CODE_MANAGED_LINE = 1; 72 | // UNMANAGED_LINE indicates config line matches unmanaged PCM filter hence is not managed 73 | CONFIG_FILTER_CODE_UNMANAGED_LINE = 2; 74 | } 75 | // DiffEntry represents one entry in a Diff 76 | message DiffEntry { 77 | DiffOp op = 1; 78 | // line number in A this diff applies to 79 | google.protobuf.Int32Value a_line_num = 2; 80 | // line number in B this diff applies to 81 | google.protobuf.Int32Value b_line_num = 3; 82 | // line number in B of the leading command of the containing block 83 | google.protobuf.Int32Value b_parent_line_num = 4; 84 | // content of config line in A 85 | google.protobuf.StringValue a_line = 5; 86 | // content of config line in B 87 | google.protobuf.StringValue b_line = 6; 88 | // Config filter code of the line in A 89 | ConfigFilterCode a_filter_code = 7; 90 | // Config filter code of the line in B 91 | ConfigFilterCode b_filter_code = 8; 92 | } 93 | // DiffEntries indicates potential multiple lines of config diff 94 | message DiffEntries { 95 | repeated DiffEntry values = 1; 96 | } 97 | 98 | // ConfigSyncCode indicates config synchronization status 99 | enum ConfigSyncCode { 100 | CONFIG_SYNC_CODE_UNSPECIFIED = 0; 101 | // IN_SYNC indicates designed config and running config are identical 102 | CONFIG_SYNC_CODE_IN_SYNC = 1; 103 | // OUT_OF_SYNC indicates designed config and running config are not identical 104 | CONFIG_SYNC_CODE_OUT_OF_SYNC = 2; 105 | } 106 | 107 | // ConfigSummary represents device configuration summary. 108 | message ConfigSummary { 109 | ConfigSyncCode sync = 1; 110 | // Number of lines with code no-operation 111 | google.protobuf.Int32Value nop_lines = 2; 112 | // Number of lines with code IGNORE 113 | google.protobuf.Int32Value ignored_lines = 3; 114 | // Number of lines with code ADD 115 | google.protobuf.Int32Value added_lines = 4; 116 | // Number of lines with code DELETE 117 | google.protobuf.Int32Value deleted_lines = 5; 118 | // Number of lines with code CHANGE 119 | google.protobuf.Int32Value changed_lines = 6; 120 | // Number of designed config errors 121 | google.protobuf.Int32Value designed_config_errors = 7; 122 | // Number of designed config warnings 123 | google.protobuf.Int32Value designed_config_warnings = 8; 124 | // Timestamp at which running config is updated 125 | google.protobuf.Timestamp running_config_update_time = 9; 126 | // Timestamp at which designed config is updated 127 | google.protobuf.Timestamp designed_config_update_time = 10; 128 | // The HTTP URI client can use to GET running config and associated errors 129 | google.protobuf.StringValue running_config_uri = 11; 130 | // The HTTP URI client can use to GET designed config and associated errors 131 | google.protobuf.StringValue designed_config_uri = 12; 132 | // The HTTP URI client can use to GET config diff and associated errors 133 | google.protobuf.StringValue diff_uri = 13; 134 | // Digest of the config diff. For example, it can be SHA-256 hash of the config diff 135 | google.protobuf.StringValue digest = 14; 136 | } 137 | 138 | enum ConfigType { 139 | CONFIG_TYPE_UNSPECIFIED = 0; 140 | CONFIG_TYPE_RUNNING_CONFIG = 1; 141 | CONFIG_TYPE_DESIGNED_CONFIG = 2; 142 | } 143 | 144 | // ConfigKey uniquely identifies a config request. 145 | message ConfigKey { 146 | option (fmp.model_key) = true; 147 | 148 | // Device_id is the serial number of the device 149 | google.protobuf.StringValue device_id = 1; 150 | // Type describes the config type 151 | ConfigType type = 2; 152 | } 153 | 154 | // Configuration represents device's CLI configuration 155 | message Configuration { 156 | option (fmp.model) = "ro"; 157 | 158 | ConfigKey key = 1; 159 | // Uri represents the HTTP URI client can use to GET config body and associated errors 160 | google.protobuf.StringValue uri = 2; 161 | ConfigError error = 3; 162 | } 163 | 164 | // ConfigDiffKey uniquely identifies a configuration diff request 165 | message ConfigDiffKey { 166 | option (fmp.model_key) = true; 167 | 168 | // A_device_id is the serial number of the device on A side (left hand side) 169 | google.protobuf.StringValue a_device_id = 1; 170 | // A_type is the config type on A side (left hand side) 171 | ConfigType a_type = 2; 172 | // A_time is the time at which to fetch config on A side (left hand side) 173 | google.protobuf.Timestamp a_time = 3; 174 | 175 | // B_device_id is the serial number of the device on B side (right hand side) 176 | google.protobuf.StringValue b_device_id = 4; 177 | // B_type is the config type on B side (right hand side) 178 | ConfigType b_type = 5; 179 | // B_time is the time at which to fetch config on B side (right hand side) 180 | google.protobuf.Timestamp b_time = 6; 181 | } 182 | 183 | message ConfigDiff { 184 | option (fmp.model) = "ro"; 185 | 186 | // Key represents config diff key 187 | ConfigDiffKey key = 1; 188 | 189 | // Uri represents the HTTP URI client can use to GET config diff and associated errors 190 | google.protobuf.StringValue uri = 2; 191 | ConfigError error = 3; 192 | } 193 | 194 | // SummaryKey uniquely identifies a device summary request 195 | message SummaryKey { 196 | option (fmp.model_key) = true; 197 | 198 | // Device_id is the serial number of the device 199 | google.protobuf.StringValue device_id = 1; 200 | } 201 | 202 | message Summary { 203 | option (fmp.model) = "ro"; 204 | 205 | SummaryKey key = 1; 206 | 207 | ConfigSummary summary = 2; 208 | ConfigError error = 3; 209 | } 210 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/configstatus.v1/services.gen.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Arista Networks, Inc. All rights reserved. 2 | // Arista Networks, Inc. Confidential and Proprietary. 3 | // Subject to Arista Networks, Inc.'s EULA. 4 | // FOR INTERNAL USE ONLY. NOT FOR DISTRIBUTION. 5 | 6 | // 7 | // Code generated by boomtown. DO NOT EDIT. 8 | // 9 | 10 | syntax = "proto3"; 11 | 12 | package arista.configstatus.v1; 13 | option go_package = "arista/resources/arista/configstatus.v1;configstatus"; 14 | 15 | option java_package = "com.arista.configstatus.v1"; 16 | option java_outer_classname = "ConfigstatusServices"; 17 | option java_multiple_files = true; 18 | 19 | import "arista/configstatus.v1/configstatus.proto"; 20 | import "arista/time/time.proto"; 21 | import "arista/subscriptions/subscriptions.proto"; 22 | import "google/protobuf/timestamp.proto"; 23 | 24 | message ConfigDiffRequest { 25 | // Key uniquely identifies a ConfigDiff instance to retrieve. 26 | // This value must be populated. 27 | ConfigDiffKey key = 1; 28 | 29 | // Time indicates the time for which you are interested in the data. 30 | // If no time is given, the server will use the time at which it makes the request. 31 | google.protobuf.Timestamp time = 2; 32 | }; 33 | 34 | message ConfigDiffResponse { 35 | // Value is the value requested. 36 | // This structure will be fully-populated as it exists in the datastore. If 37 | // optional fields were not given at creation, these fields will be empty or 38 | // set to default values. 39 | ConfigDiff value = 1; 40 | 41 | // Time carries the (UTC) timestamp of the last-modification of the 42 | // ConfigDiff instance in this response. 43 | google.protobuf.Timestamp time = 2; 44 | }; 45 | 46 | message ConfigDiffStreamRequest { 47 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 48 | // This requires all provided fields to be equal to the response. 49 | // 50 | // While transparent to users, this field also allows services to optimize internal 51 | // subscriptions if filter(s) are sufficiently specific. 52 | repeated ConfigDiff partial_eq_filter = 1; 53 | 54 | // TimeRange allows limiting response data to within a specified time window. 55 | // If this field is populated, at least one of the two time fields are required. 56 | // 57 | // This field is not allowed in the Subscribe RPC. 58 | arista.time.TimeBounds time = 3; 59 | }; 60 | 61 | message ConfigDiffStreamResponse { 62 | // Value is a value deemed relevant to the initiating request. 63 | // This structure will always have its key-field populated. Which other fields are 64 | // populated, and why, depends on the value of Operation and what triggered this notification. 65 | ConfigDiff value = 1; 66 | 67 | // Time holds the timestamp of this ConfigDiff's last modification. 68 | google.protobuf.Timestamp time = 2; 69 | 70 | // Operation indicates how the ConfigDiff value in this response should be considered. 71 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 72 | // once all initial data is streamed and the client begins to receive modification updates, 73 | // you should not see INITIAL again. 74 | arista.subscriptions.Operation type = 3; 75 | }; 76 | 77 | service ConfigDiffService { 78 | rpc GetOne (ConfigDiffRequest) returns (ConfigDiffResponse); 79 | rpc GetAll (ConfigDiffStreamRequest) returns (stream ConfigDiffStreamResponse); 80 | rpc Subscribe (ConfigDiffStreamRequest) returns (stream ConfigDiffStreamResponse); 81 | } 82 | 83 | message ConfigurationRequest { 84 | // Key uniquely identifies a Configuration instance to retrieve. 85 | // This value must be populated. 86 | ConfigKey key = 1; 87 | 88 | // Time indicates the time for which you are interested in the data. 89 | // If no time is given, the server will use the time at which it makes the request. 90 | google.protobuf.Timestamp time = 2; 91 | }; 92 | 93 | message ConfigurationResponse { 94 | // Value is the value requested. 95 | // This structure will be fully-populated as it exists in the datastore. If 96 | // optional fields were not given at creation, these fields will be empty or 97 | // set to default values. 98 | Configuration value = 1; 99 | 100 | // Time carries the (UTC) timestamp of the last-modification of the 101 | // Configuration instance in this response. 102 | google.protobuf.Timestamp time = 2; 103 | }; 104 | 105 | message ConfigurationStreamRequest { 106 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 107 | // This requires all provided fields to be equal to the response. 108 | // 109 | // While transparent to users, this field also allows services to optimize internal 110 | // subscriptions if filter(s) are sufficiently specific. 111 | repeated Configuration partial_eq_filter = 1; 112 | 113 | // TimeRange allows limiting response data to within a specified time window. 114 | // If this field is populated, at least one of the two time fields are required. 115 | // 116 | // This field is not allowed in the Subscribe RPC. 117 | arista.time.TimeBounds time = 3; 118 | }; 119 | 120 | message ConfigurationStreamResponse { 121 | // Value is a value deemed relevant to the initiating request. 122 | // This structure will always have its key-field populated. Which other fields are 123 | // populated, and why, depends on the value of Operation and what triggered this notification. 124 | Configuration value = 1; 125 | 126 | // Time holds the timestamp of this Configuration's last modification. 127 | google.protobuf.Timestamp time = 2; 128 | 129 | // Operation indicates how the Configuration value in this response should be considered. 130 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 131 | // once all initial data is streamed and the client begins to receive modification updates, 132 | // you should not see INITIAL again. 133 | arista.subscriptions.Operation type = 3; 134 | }; 135 | 136 | service ConfigurationService { 137 | rpc GetOne (ConfigurationRequest) returns (ConfigurationResponse); 138 | rpc GetAll (ConfigurationStreamRequest) returns (stream ConfigurationStreamResponse); 139 | rpc Subscribe (ConfigurationStreamRequest) returns (stream ConfigurationStreamResponse); 140 | } 141 | 142 | message SummaryRequest { 143 | // Key uniquely identifies a Summary instance to retrieve. 144 | // This value must be populated. 145 | SummaryKey key = 1; 146 | 147 | // Time indicates the time for which you are interested in the data. 148 | // If no time is given, the server will use the time at which it makes the request. 149 | google.protobuf.Timestamp time = 2; 150 | }; 151 | 152 | message SummaryResponse { 153 | // Value is the value requested. 154 | // This structure will be fully-populated as it exists in the datastore. If 155 | // optional fields were not given at creation, these fields will be empty or 156 | // set to default values. 157 | Summary value = 1; 158 | 159 | // Time carries the (UTC) timestamp of the last-modification of the 160 | // Summary instance in this response. 161 | google.protobuf.Timestamp time = 2; 162 | }; 163 | 164 | message SummaryStreamRequest { 165 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 166 | // This requires all provided fields to be equal to the response. 167 | // 168 | // While transparent to users, this field also allows services to optimize internal 169 | // subscriptions if filter(s) are sufficiently specific. 170 | repeated Summary partial_eq_filter = 1; 171 | 172 | // TimeRange allows limiting response data to within a specified time window. 173 | // If this field is populated, at least one of the two time fields are required. 174 | // 175 | // This field is not allowed in the Subscribe RPC. 176 | arista.time.TimeBounds time = 3; 177 | }; 178 | 179 | message SummaryStreamResponse { 180 | // Value is a value deemed relevant to the initiating request. 181 | // This structure will always have its key-field populated. Which other fields are 182 | // populated, and why, depends on the value of Operation and what triggered this notification. 183 | Summary value = 1; 184 | 185 | // Time holds the timestamp of this Summary's last modification. 186 | google.protobuf.Timestamp time = 2; 187 | 188 | // Operation indicates how the Summary value in this response should be considered. 189 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 190 | // once all initial data is streamed and the client begins to receive modification updates, 191 | // you should not see INITIAL again. 192 | arista.subscriptions.Operation type = 3; 193 | }; 194 | 195 | service SummaryService { 196 | rpc GetOne (SummaryRequest) returns (SummaryResponse); 197 | rpc GetAll (SummaryStreamRequest) returns (stream SummaryStreamResponse); 198 | rpc Subscribe (SummaryStreamRequest) returns (stream SummaryStreamResponse); 199 | } 200 | -------------------------------------------------------------------------------- /cloudvision-apis/arista/event.v1/services.gen.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Arista Networks, Inc. 2 | // Use of this source code is governed by the Apache License 2.0 3 | // that can be found in the COPYING file. 4 | 5 | // 6 | // Code generated by boomtown. DO NOT EDIT. 7 | // 8 | 9 | syntax = "proto3"; 10 | 11 | package arista.event.v1; 12 | option go_package = "arista/resources/arista/event.v1;event"; 13 | 14 | import "arista/event.v1/event.proto"; 15 | import "arista/time/time.proto"; 16 | import "arista/subscriptions/subscriptions.proto"; 17 | import "google/protobuf/timestamp.proto"; 18 | 19 | message EventRequest { 20 | // Key uniquely identifies a Event instance to retrieve. 21 | // This value (and all fields, unless otherwise specified) must be populated. 22 | EventKey key = 1; 23 | 24 | // Time indicates the time for which you are interested in the data. 25 | // If no time is given, the server will use the time at twhich it makes the request. 26 | google.protobuf.Timestamp time = 2; 27 | }; 28 | 29 | message EventResponse { 30 | // Value is the value requested. 31 | // This structure will be fully-populated as it exists in the datastore. If 32 | // optional fields were not given at creation, these fields will be empty or 33 | // set to default values. 34 | Event value = 1; 35 | 36 | // Time carries the (UTC) timestamp of the last-modification of the 37 | // Event instance in this response. 38 | google.protobuf.Timestamp time = 2; 39 | }; 40 | 41 | message EventStreamRequest { 42 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 43 | // This requires all provided fields to be equal to the response. 44 | // 45 | // While transparent to users, this field also allows services to optimize internal 46 | // subscriptions if filter(s) are sufficiently specific. 47 | repeated Event partial_eq_filter = 1; 48 | 49 | // TimeRange allows limiting response data to within a specified time window. 50 | // If this field is populated, at least one of the two time fields are required. 51 | // 52 | // This field is not allowed in the Subscribe RPC. 53 | arista.time.TimeBounds time = 3; 54 | }; 55 | 56 | message EventStreamResponse { 57 | // Value is a value deemed relevant to the initiating request. 58 | // This structure will always have its key-field populated. Which other fields are 59 | // populated, and why, depends on the value of Operation and what triggered this notification. 60 | Event value = 1; 61 | 62 | // Time holds the timestamp of this Event's last modification. 63 | google.protobuf.Timestamp time = 2; 64 | 65 | // Operation indicates how the Event value in this response should be considered. 66 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 67 | // once all initial data is streamed and the client begins to receive modification updates, 68 | // you should not see INITIAL again. 69 | arista.subscriptions.Operation type = 3; 70 | }; 71 | 72 | service EventService { 73 | // GetOne returns a unary model as specified by the key in the request. 74 | // The key must be provided and all fields populated (unless otherwise specified). 75 | rpc GetOne (EventRequest) returns (EventResponse); 76 | // GetAll returns all entities for this model, with optional filtering. 77 | rpc GetAll (EventStreamRequest) returns (stream EventStreamResponse); 78 | // Subscribe first returns all initial state known to the system, 79 | // then will send deltas as entities are changed. 80 | rpc Subscribe (EventStreamRequest) returns (stream EventStreamResponse); 81 | } 82 | 83 | message EventAnnotationConfigRequest { 84 | // Key uniquely identifies a EventAnnotationConfig instance to retrieve. 85 | // This value (and all fields, unless otherwise specified) must be populated. 86 | EventKey key = 1; 87 | 88 | // Time indicates the time for which you are interested in the data. 89 | // If no time is given, the server will use the time at twhich it makes the request. 90 | google.protobuf.Timestamp time = 2; 91 | }; 92 | 93 | message EventAnnotationConfigResponse { 94 | // Value is the value requested. 95 | // This structure will be fully-populated as it exists in the datastore. If 96 | // optional fields were not given at creation, these fields will be empty or 97 | // set to default values. 98 | EventAnnotationConfig value = 1; 99 | 100 | // Time carries the (UTC) timestamp of the last-modification of the 101 | // EventAnnotationConfig instance in this response. 102 | google.protobuf.Timestamp time = 2; 103 | }; 104 | 105 | message EventAnnotationConfigStreamRequest { 106 | // PartialEqFilter provides a way to server-side filter a GetAll/Subscribe. 107 | // This requires all provided fields to be equal to the response. 108 | // 109 | // While transparent to users, this field also allows services to optimize internal 110 | // subscriptions if filter(s) are sufficiently specific. 111 | repeated EventAnnotationConfig partial_eq_filter = 1; 112 | 113 | // TimeRange allows limiting response data to within a specified time window. 114 | // If this field is populated, at least one of the two time fields are required. 115 | // 116 | // This field is not allowed in the Subscribe RPC. 117 | arista.time.TimeBounds time = 3; 118 | }; 119 | 120 | message EventAnnotationConfigStreamResponse { 121 | // Value is a value deemed relevant to the initiating request. 122 | // This structure will always have its key-field populated. Which other fields are 123 | // populated, and why, depends on the value of Operation and what triggered this notification. 124 | EventAnnotationConfig value = 1; 125 | 126 | // Time holds the timestamp of this EventAnnotationConfig's last modification. 127 | google.protobuf.Timestamp time = 2; 128 | 129 | // Operation indicates how the EventAnnotationConfig value in this response should be considered. 130 | // Under non-subscribe requests, this value should always be INITIAL. In a subscription, 131 | // once all initial data is streamed and the client begins to receive modification updates, 132 | // you should not see INITIAL again. 133 | arista.subscriptions.Operation type = 3; 134 | }; 135 | 136 | message EventAnnotationConfigSetRequest { 137 | // EventAnnotationConfig carries the value to set into the datastore. 138 | // See the documentation on the EventAnnotationConfig struct for which fields are required. 139 | EventAnnotationConfig value = 1; 140 | }; 141 | 142 | message EventAnnotationConfigSetResponse { 143 | // Value carries all the values given in the EventAnnotationConfigSetRequest as well 144 | // as any server-generated values. 145 | EventAnnotationConfig value = 1; 146 | 147 | // Time indicates the (UTC) timestamp at which the system recognizes the 148 | // creation. The only guarantees made about this timestamp are: 149 | // 150 | // - it is after the time the request was received 151 | // - a time-ranged query with StartTime==CreatedAt will include this instance. 152 | // 153 | google.protobuf.Timestamp time = 2; 154 | }; 155 | 156 | message EventAnnotationConfigDeleteRequest { 157 | // Key indicates which EventAnnotationConfig instance to remove. 158 | // This field (and all keys, unless otherwise specified) must always be set. 159 | EventKey key = 1; 160 | }; 161 | 162 | message EventAnnotationConfigDeleteResponse { 163 | // Key echoes back the key of the deleted EventAnnotationConfig instance. 164 | EventKey key = 1; 165 | 166 | // Time indicates the (UTC) timestamp at which the system recognizes the 167 | // deletion. The only guarantees made about this timestamp are: 168 | // 169 | // - it is after the time the request was received 170 | // - a time-ranged query with StartTime==DeletedAt will not include this instance. 171 | // 172 | google.protobuf.Timestamp time = 2; 173 | }; 174 | 175 | service EventAnnotationConfigService { 176 | // GetOne returns a unary model as specified by the key in the request. 177 | // The key must be provided and all fields populated (unless otherwise specified). 178 | rpc GetOne (EventAnnotationConfigRequest) returns (EventAnnotationConfigResponse); 179 | // GetAll returns all entities for this model, with optional filtering. 180 | rpc GetAll (EventAnnotationConfigStreamRequest) returns (stream EventAnnotationConfigStreamResponse); 181 | // Subscribe first returns all initial state known to the system, 182 | // then will send deltas as entities are changed. 183 | rpc Subscribe (EventAnnotationConfigStreamRequest) returns (stream EventAnnotationConfigStreamResponse); 184 | // Set allows setting values for the entity specified by the key in the request. 185 | // The key must be provided and all fields set (unless otherwise specified). 186 | rpc Set (EventAnnotationConfigSetRequest) returns (EventAnnotationConfigSetResponse); 187 | // Delete will remove the entity specified by the key within the request. 188 | // The key must be provided and all fields populated (unless otherwise specified). 189 | rpc Delete (EventAnnotationConfigDeleteRequest) returns (EventAnnotationConfigDeleteResponse); 190 | } 191 | -------------------------------------------------------------------------------- /cloudeos/utils.go: -------------------------------------------------------------------------------- 1 | package cloudeos 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "strconv" 9 | "strings" 10 | 11 | cdv1_api "github.com/aristanetworks/terraform-provider-cloudeos/cloudeos/arista/clouddeploy.v1" 12 | 13 | fmp "github.com/aristanetworks/cloudvision-go/api/fmp" 14 | 15 | "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 16 | ) 17 | 18 | // Given a ResourceData with 'role' and 'deploy_mode' attributes, 19 | // this shall specify whether the deploy_mode value is valid 20 | // for the given role. Currently, when deploy_mode ='provision' 21 | // we only accept resources with role = 'CloudEdge' 22 | func validateDeployModeWithRole(d *schema.ResourceData) error { 23 | 24 | deployMode := strings.ToLower(d.Get("deploy_mode").(string)) 25 | role := d.Get("role").(string) 26 | if deployMode == "provision" && strings.EqualFold("CloudLeaf", role) { 27 | return errors.New("Deploy mode provision is only applicable to " + 28 | "resources with role CloudEdge") 29 | } 30 | return nil 31 | } 32 | 33 | func getCloudProviderType(d *schema.ResourceData) cdv1_api.CloudProviderType { 34 | cloudProvider := d.Get("cloud_provider").(string) 35 | cpType := cdv1_api.CloudProviderType_CLOUD_PROVIDER_TYPE_UNSPECIFIED 36 | switch { 37 | case strings.EqualFold("aws", cloudProvider): 38 | cpType = cdv1_api.CloudProviderType_CLOUD_PROVIDER_TYPE_AWS 39 | case strings.EqualFold("azure", cloudProvider): 40 | cpType = cdv1_api.CloudProviderType_CLOUD_PROVIDER_TYPE_AZURE 41 | } 42 | return cpType 43 | } 44 | 45 | func getAwsVpcName(d *schema.ResourceData) (string, error) { 46 | var vpcName string 47 | if value, ok := d.GetOk("tags"); ok { 48 | tags := value.(map[string]interface{}) 49 | for k, v := range tags { 50 | if strings.EqualFold("Name", k) { 51 | vpcName = v.(string) 52 | } 53 | } 54 | } else { 55 | return "", fmt.Errorf("Router name not configured in tags") 56 | } 57 | 58 | return vpcName, nil 59 | } 60 | 61 | func getCpTypeAndVpcName(d *schema.ResourceData) (string, cdv1_api.CloudProviderType) { 62 | var vpcName string 63 | var cpType cdv1_api.CloudProviderType 64 | cloudProvider := d.Get("cloud_provider").(string) 65 | switch { 66 | case strings.EqualFold("aws", cloudProvider): 67 | cpType = cdv1_api.CloudProviderType_CLOUD_PROVIDER_TYPE_AWS 68 | vpcName, _ = getAwsVpcName(d) 69 | case strings.EqualFold("azure", cloudProvider): 70 | cpType = cdv1_api.CloudProviderType_CLOUD_PROVIDER_TYPE_AZURE 71 | vpcName = d.Get("vnet_name").(string) 72 | } 73 | return vpcName, cpType 74 | } 75 | 76 | func getRoleType(role string) cdv1_api.RoleType { 77 | var roleType cdv1_api.RoleType 78 | switch { 79 | case strings.EqualFold("CloudEdge", role): 80 | roleType = cdv1_api.RoleType_ROLE_TYPE_EDGE 81 | case strings.EqualFold("CloudSpine", role): 82 | roleType = cdv1_api.RoleType_ROLE_TYPE_SPINE 83 | case strings.EqualFold("CloudLeaf", role): 84 | roleType = cdv1_api.RoleType_ROLE_TYPE_LEAF 85 | default: 86 | roleType = cdv1_api.RoleType_ROLE_TYPE_UNSPECIFIED 87 | } 88 | return roleType 89 | } 90 | 91 | func getAndCreateRouteTableIDs(d *schema.ResourceData) *cdv1_api.RouteTableIds { 92 | privateRtTblList := d.Get("private_rt_table_ids").([]interface{}) 93 | internalRtTblList := d.Get("internal_rt_table_ids").([]interface{}) 94 | publicRtTblList := d.Get("public_rt_table_ids").([]interface{}) 95 | 96 | priv := make([]string, len(privateRtTblList)) 97 | for i, v := range privateRtTblList { 98 | priv[i] = fmt.Sprint(v) 99 | } 100 | pub := make([]string, len(publicRtTblList)) 101 | for i, v := range publicRtTblList { 102 | pub[i] = fmt.Sprint(v) 103 | } 104 | internal := make([]string, len(internalRtTblList)) 105 | for i, v := range internalRtTblList { 106 | internal[i] = fmt.Sprint(v) 107 | } 108 | routeTableList := cdv1_api.RouteTableIds{ 109 | Public: &fmp.RepeatedString{Values: pub}, 110 | Private: &fmp.RepeatedString{Values: priv}, 111 | Internal: &fmp.RepeatedString{Values: internal}, 112 | } 113 | 114 | return &routeTableList 115 | } 116 | 117 | func getBgpAsn(bgpAsnRange string) (uint32, uint32, error) { 118 | if bgpAsnRange == "" { 119 | log.Printf("[CVaaS-ERROR] bgp_asn cannot be empty") 120 | return uint32(0), uint32(0), errors.New("bgp_asn is empty") 121 | } 122 | asnRange := strings.Split(bgpAsnRange, "-") 123 | if len(asnRange) != 2 { 124 | log.Printf("[CVaaS-ERROR] Can't parse bgp_asn") 125 | return uint32(0), uint32(0), errors.New("Can't parse bgp_asn") 126 | } 127 | asnLow, err := strconv.ParseUint(asnRange[0], 10, 32) 128 | if err != nil { 129 | log.Printf("[CVaaS-ERROR]Can't parse bgp asn") 130 | return uint32(0), uint32(0), err 131 | } 132 | asnHigh, err := strconv.ParseUint(asnRange[1], 10, 32) 133 | if err != nil { 134 | log.Printf("[CVaaS-ERROR]Can't parse bgp asn") 135 | return uint32(0), uint32(0), err 136 | } 137 | log.Printf("[CVaaS-INFO]Bgp Asn Range %v - %v", asnLow, asnHigh) 138 | return uint32(asnLow), uint32(asnHigh), err 139 | } 140 | 141 | func getRouterNameFromSchema(d *schema.ResourceData) (string, error) { 142 | var routerName string 143 | if value, ok := d.GetOk("tags"); ok { 144 | tags := value.(map[string]interface{}) 145 | for k, v := range tags { 146 | if strings.EqualFold("Name", k) { 147 | routerName = v.(string) 148 | } 149 | } 150 | } else { 151 | return "", fmt.Errorf("Router name not configured in tags") 152 | } 153 | 154 | return routerName, nil 155 | } 156 | 157 | func setBootStrapCfg(d *schema.ResourceData, cfg string) error { 158 | if strings.EqualFold(cfg, "") { 159 | log.Printf("[WARN]The CloudEOS Router is deployed but without bootstrap configuration") 160 | } 161 | bootstrapCfg := "%EOS-STARTUP-CONFIG-START%\n" + 162 | cfg + 163 | "\n" + 164 | "%EOS-STARTUP-CONFIG-END%\n" 165 | 166 | imageOffer := d.Get("cloudeos_image_offer") 167 | value, licensesExist := d.GetOk("licenses") 168 | if licensesExist && imageOffer == "cloudeos-router-byol" { 169 | licenses := value.(*schema.Set).List() 170 | for _, v := range licenses { 171 | license := v.(map[string]interface{}) 172 | marker := "" 173 | switch license["type"] { 174 | case "ipsec": 175 | marker = "IPSEC" 176 | case "bandwidth": 177 | marker = "BANDWIDTH" 178 | default: 179 | return fmt.Errorf("Unrecognised license type : %s", license["type"]) 180 | } 181 | if marker != "" { 182 | filePath := license["path"].(string) 183 | content, err := ioutil.ReadFile(filePath) 184 | if err != nil { 185 | return fmt.Errorf("Problem reading %s license file: %s , %v", license["type"], filePath, err) 186 | } else { 187 | bootstrapCfg = bootstrapCfg + 188 | "%LICENSE-" + marker + "-START%\n" + 189 | string(content) + 190 | "%LICENSE-" + marker + "-END%\n" 191 | } 192 | } 193 | } 194 | } 195 | if err := d.Set("bootstrap_cfg", bootstrapCfg); err != nil { 196 | return fmt.Errorf("Error bootstrap_cfg: %v", err) 197 | } 198 | return nil 199 | } 200 | 201 | func parseRtrResponse(ent *cdv1_api.RouterConfig, d *schema.ResourceData) error { 202 | // Parse the bootstrap_cfg, haRtrId, peerRtTable from response and set 203 | // in schema 204 | var bootstrapCfg string 205 | var haRtrID string 206 | var peerRtTblID []string // Internal peer route table ID 207 | var publicRtTblID []string 208 | var privateRtTblID []string 209 | var internalRtTblID []string 210 | 211 | bootstrapCfg = ent.GetCvInfo().GetBootstrapCfg().GetValue() 212 | haRtrID = ent.GetCvInfo().GetHaRtrId().GetValue() 213 | for _, id := range ent.GetCvInfo().GetPeerVpcRtTableId().GetValues() { 214 | peerRtTblID = append(peerRtTblID, id) 215 | } 216 | 217 | for _, id := range ent.GetCvInfo().GetHaRtTableIds().GetInternal().GetValues() { 218 | internalRtTblID = append(internalRtTblID, id) 219 | } 220 | 221 | for _, id := range ent.GetCvInfo().GetHaRtTableIds().GetPublic().GetValues() { 222 | publicRtTblID = append(publicRtTblID, id) 223 | } 224 | for _, id := range ent.GetCvInfo().GetHaRtTableIds().GetPrivate().GetValues() { 225 | privateRtTblID = append(privateRtTblID, id) 226 | } 227 | 228 | // set bootstrap_cfg 229 | if err := setBootStrapCfg(d, bootstrapCfg); err != nil { 230 | return err 231 | } 232 | if err := d.Set("ha_rtr_id", haRtrID); err != nil { 233 | return fmt.Errorf("Not able to set ha_rtr_id: %v", err) 234 | } 235 | if err := d.Set("peerroutetableid1", peerRtTblID); err != nil { 236 | return fmt.Errorf("Not able to set peer route table ID: %v ", err) 237 | } 238 | if err := d.Set("peer_routetable_id", peerRtTblID); err != nil { 239 | return fmt.Errorf("Not able to set peer route table ID: %v ", err) 240 | } 241 | if err := d.Set("public_rt_table_id", publicRtTblID); err != nil { 242 | return fmt.Errorf("Not able to set public route table id: %v", err) 243 | } 244 | if err := d.Set("private_rt_table_id", privateRtTblID); err != nil { 245 | return fmt.Errorf("Not able to set private route table ID: %v", err) 246 | } 247 | if err := d.Set("internal_rt_table_id", internalRtTblID); err != nil { 248 | return fmt.Errorf("Not able to set internal route table ID: %v", err) 249 | } 250 | return nil 251 | } 252 | --------------------------------------------------------------------------------