github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachprod/vm/aws/config.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package aws 12 13 import ( 14 "encoding/json" 15 "io/ioutil" 16 "sort" 17 "strings" 18 ) 19 20 // The below directives create two files, the first is a terraform main file 21 // that lives in the terraform subdirectory. The second file it produces is the 22 // bindata embedded.go file with the asset produced by running 23 // `terraform output`. 24 25 //go:generate terraformgen -o terraform/main.tf 26 //go:generate go-bindata -mode 0600 -modtime 1400000000 -pkg aws -o embedded.go config.json old.json 27 //go:generate gofmt -s -w embedded.go 28 //go:generate goimports -w embedded.go 29 30 // awsConfig holds all of the required information to create and manage aws 31 // cloud resources. 32 // 33 // The struct is constructed by deserializing json that follows the form of 34 // the below example. 35 // 36 // { 37 // "regions": { 38 // "sensitive": false, 39 // "type": "list", 40 // "value": [ 41 // { 42 // "ami_id": "ami-48630c2e", 43 // "region": "ap-northeast-1", 44 // "security_group": "sg-0006e480d77a10104", 45 // "subnets": { 46 // "ap-northeast-1a": "subnet-0d144db3c9e47edf5", 47 // "ap-northeast-1c": "subnet-02fcaaa6212fc3c1a", 48 // "ap-northeast-1d": "subnet-0e9006ef8b3bef61f" 49 // } 50 // } 51 // ] 52 // } 53 // 54 // It has this awkward structure to deal with the terraform serialization 55 // of lists. Ideally terraform would output an artifact whose structure mirrors 56 // the serialization of the desired data structure elegantly but instead we use 57 // but it was deemed more straightforward to utilize the json.Unmarshaler 58 // interface to construct a well formed value from the terraform output. 59 type awsConfig struct { 60 // regions is a slice of region structs sorted by name. 61 regions []awsRegion 62 // azByName maps from availability zone name to a struct which itself refers 63 // back to a region. 64 azByName map[string]*availabilityZone 65 } 66 67 type awsRegion struct { 68 Name string `json:"region"` 69 SecurityGroup string `json:"security_group"` 70 AMI string `json:"ami_id"` 71 AvailabilityZones availabilityZones `json:"subnets"` 72 } 73 74 type availabilityZone struct { 75 name string 76 subnetID string 77 region *awsRegion // set up in awsConfig.UnmarshalJSON 78 } 79 80 // UnmarshalJSON implement json.Unmarshaler. 81 // 82 // The extra logic is used to sort the data by region and to hook up the 83 // azByName map to point from AZ name to availabilityZone and to set up the 84 // backpointers from availabilityZone to region. 85 func (c *awsConfig) UnmarshalJSON(data []byte) error { 86 type raw struct { 87 Regions struct { 88 Value []awsRegion `json:"value"` 89 } `json:"regions"` 90 } 91 var v raw 92 if err := json.Unmarshal(data, &v); err != nil { 93 return err 94 } 95 *c = awsConfig{ 96 regions: v.Regions.Value, 97 azByName: make(map[string]*availabilityZone), 98 } 99 sort.Slice(c.regions, func(i, j int) bool { 100 return c.regions[i].Name < c.regions[j].Name 101 }) 102 for i := range c.regions { 103 r := &c.regions[i] 104 for i := range r.AvailabilityZones { 105 az := &r.AvailabilityZones[i] 106 az.region = r 107 c.azByName[az.name] = az 108 } 109 } 110 return nil 111 } 112 113 func (c *awsConfig) getRegion(name string) *awsRegion { 114 i := sort.Search(len(c.regions), func(i int) bool { 115 return c.regions[i].Name >= name 116 }) 117 if i < len(c.regions) && c.regions[i].Name == name { 118 return &c.regions[i] 119 } 120 return nil 121 } 122 123 func (c *awsConfig) regionNames() (names []string) { 124 for _, r := range c.regions { 125 names = append(names, r.Name) 126 } 127 return names 128 } 129 130 func (c *awsConfig) getAvailabilityZone(azName string) *availabilityZone { 131 return c.azByName[azName] 132 } 133 134 func (c *awsConfig) availabilityZoneNames() (zoneNames []string) { 135 for _, r := range c.regions { 136 for _, az := range r.AvailabilityZones { 137 zoneNames = append(zoneNames, az.name) 138 } 139 } 140 sort.Strings(zoneNames) 141 return zoneNames 142 } 143 144 // availabilityZones is a slice of availabilityZone which implements 145 // json.Marshaler and json.Unmarshaler. 146 type availabilityZones []availabilityZone 147 148 func (s *availabilityZones) UnmarshalJSON(data []byte) error { 149 var m map[string]string 150 if err := json.Unmarshal(data, &m); err != nil { 151 return err 152 } 153 *s = make(availabilityZones, 0, len(m)) 154 for az, sn := range m { 155 *s = append(*s, availabilityZone{ 156 name: az, 157 subnetID: sn, 158 }) 159 } 160 sort.Slice(*s, func(i, j int) bool { 161 return (*s)[i].name < (*s)[j].name 162 }) 163 return nil 164 } 165 166 // awsConfigValue implements pflag.Value and is used to accept a path flag and 167 // use it to open a file and parse an awsConfig. 168 type awsConfigValue struct { 169 path string 170 awsConfig 171 } 172 173 // Set is part of the pflag.Value interface. 174 func (c *awsConfigValue) Set(path string) (err error) { 175 if path == "" { 176 return nil 177 } 178 c.path = path 179 var data []byte 180 if strings.HasPrefix(path, "embedded:") { 181 data, err = Asset(path[strings.Index(path, ":")+1:]) 182 } else { 183 data, err = ioutil.ReadFile(path) 184 } 185 if err != nil { 186 return err 187 } 188 return json.Unmarshal(data, &c.awsConfig) 189 } 190 191 // Type is part of the pflag.Value interface. 192 func (c *awsConfigValue) Type() string { 193 return "aws config path" 194 } 195 196 // String is part of the pflag.Value interface. 197 func (c *awsConfigValue) String() string { 198 if c.path == "" { 199 return "see config.json" 200 } 201 return c.path 202 }