github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachprod/vm/aws/terraformgen/terraformgen.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  // Command terraformgen generate the terraform file used to configure AWS for
    12  // multiregion support.
    13  package main
    14  
    15  import (
    16  	"fmt"
    17  	"io"
    18  	"os"
    19  	"text/template"
    20  
    21  	"github.com/spf13/cobra"
    22  )
    23  
    24  var templates = []struct {
    25  	name, template string
    26  }{
    27  	{"header",
    28  		`# ---------------------------------------------------------------------------------------------------------------------
    29  # TERRAFORM SETTINGS
    30  # ---------------------------------------------------------------------------------------------------------------------
    31  terraform {
    32    required_version = ">= 0.11.8"
    33    backend "s3" {
    34      key            = "terraform/{{ .ResourcePrefix }}"
    35      bucket         = "{{ .ResourcePrefix }}-cloud-state"
    36      region         = "us-east-2"
    37    }
    38  }
    39  
    40  # ---------------------------------------------------------------------------------------------------------------------
    41  # Variable names that should not change beyond initial config.
    42  # ---------------------------------------------------------------------------------------------------------------------
    43  locals {
    44    account_number = {{ printf "%q" .AccountNumber }}
    45    label          = {{ printf "%q" .ResourcePrefix }}
    46  }
    47  
    48  # ---------------------------------------------------------------------------------------------------------------------
    49  # AWS
    50  # ---------------------------------------------------------------------------------------------------------------------`},
    51  
    52  	{"regions",
    53  		`{{ range .Regions }}
    54  provider "aws" {
    55    alias   = "{{ $.Resource . }}"
    56    region  = "{{ . }}"
    57  
    58    # Fixed fields, DO NOT MODIFY.
    59    version = "~> 1.41"
    60  }
    61  
    62  module "aws_{{ $.Resource . }}" {
    63    providers {
    64      aws  = "aws.{{ $.Resource . }}"
    65    }
    66    region = {{ . | printf "%q" }}
    67    source = "aws-region"
    68    label  = {{ $.ResourcePrefix | printf "%q" }}
    69  }
    70  {{ end }}`},
    71  
    72  	{"peerings",
    73  		`{{ range  .Peerings }}
    74  module "vpc_peer_{{index . 0 }}-{{ index . 1 }}" {
    75    providers {
    76      aws.owner    = "aws.{{ index . 0 }}"
    77      aws.peer     = "aws.{{ index . 1 }}"
    78    }
    79    owner_vpc_info = "${module.aws_{{index . 0}}.vpc_info}"
    80    peer_vpc_info  = "${module.aws_{{index . 1}}.vpc_info}"
    81  
    82    label          = {{ $.ResourcePrefix | printf "%q" }}
    83    source         = "aws-vpc-peer"
    84  }
    85  {{ end }}
    86  `},
    87  
    88  	{"output",
    89  		`output "regions" {
    90    value = "${list({{- range $index, $el := .Regions }}{{ if $index }},{{end}}
    91      "${module.aws_{{ $.Resource . }}.region_info}"
    92      {{- end }}
    93    )}"
    94  }
    95  `},
    96  	{"terraform",
    97  		`{{ template "header" . }}
    98  {{ template "regions" . }}
    99  {{ template "peerings" . }}
   100  {{ template "output" . }}
   101  `},
   102  }
   103  
   104  var tmpl = func() *template.Template {
   105  	cur := template.New("base")
   106  	for _, t := range templates {
   107  		cur = template.Must(cur.New(t.name).Parse(t.template))
   108  	}
   109  	return cur
   110  }()
   111  
   112  type data struct {
   113  	AccountNumber  string
   114  	ResourcePrefix string
   115  	Regions        []string
   116  }
   117  
   118  func (d *data) Resource(s string) string {
   119  	return d.ResourcePrefix + "-" + s
   120  }
   121  
   122  func (d *data) Peerings() (peerings [][2]string) {
   123  	for i := 0; i < len(d.Regions); i++ {
   124  		for j := i + 1; j < len(d.Regions); j++ {
   125  			peerings = append(peerings, [2]string{
   126  				d.Resource(d.Regions[i]),
   127  				d.Resource(d.Regions[j]),
   128  			})
   129  		}
   130  	}
   131  	return peerings
   132  }
   133  
   134  // Defeat the unused linter because it's not smart enough to know about template
   135  // calls.
   136  var _ = (*data)(nil).Peerings
   137  var _ = (*data)(nil).Resource
   138  
   139  var defaultData = data{
   140  	Regions: []string{
   141  		"ap-northeast-1",
   142  		"ap-northeast-2",
   143  		"ap-south-1",
   144  		"ap-southeast-1",
   145  		"ap-southeast-2",
   146  		"ca-central-1",
   147  		"eu-central-1",
   148  		"eu-west-1",
   149  		"eu-west-2",
   150  		"eu-west-3",
   151  		"sa-east-1",
   152  		"us-east-1",
   153  		"us-east-2",
   154  		"us-west-1",
   155  		"us-west-2",
   156  	},
   157  	AccountNumber:  "541263489771",
   158  	ResourcePrefix: "roachprod",
   159  }
   160  
   161  func main() {
   162  	data := defaultData
   163  	output := "-"
   164  	rootCmd := &cobra.Command{
   165  		Use:   "terraformgen",
   166  		Short: "terraformgen generates a terraform file for use with roachprod on aws.",
   167  		Long: `
   168  terraformgen generates a terraform main file which, when combined with the 
   169  modules defined in the sibling terraform directory to this programs source code
   170  will set up VPCs and security groups for each of specified regions for
   171  the provided account number. The json artifact created as a result of running
   172  terraform apply is consumed by roachprod.
   173  `,
   174  		Run: func(_ *cobra.Command, _ []string) {
   175  			out := io.Writer(os.Stderr)
   176  			if output != "-" {
   177  				f, err := os.Create(output)
   178  				exitIfError(err)
   179  				defer f.Close()
   180  				out = f
   181  			}
   182  			exitIfError(tmpl.Execute(out, &data))
   183  		},
   184  	}
   185  	rootCmd.Flags().StringSliceVar(&data.Regions, "regions", data.Regions,
   186  		"list of regions to operate in")
   187  	rootCmd.Flags().StringVar(&data.AccountNumber, "account-number", data.AccountNumber,
   188  		"AWS account number to use")
   189  	rootCmd.Flags().StringVarP(&output, "output", "o", output,
   190  		"path to output the generated file, \"-\" for stderr")
   191  
   192  	exitIfError(rootCmd.Execute())
   193  }
   194  
   195  func exitIfError(err error) {
   196  	if err == nil {
   197  		return
   198  	}
   199  	fmt.Fprintf(os.Stderr, "%v\n", err)
   200  	os.Exit(1)
   201  }