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  }