github.com/GoogleCloudPlatform/deploystack@v1.12.8/deploystack_test.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package deploystack
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/GoogleCloudPlatform/deploystack/config"
    29  	"github.com/GoogleCloudPlatform/deploystack/gcloud"
    30  	"github.com/GoogleCloudPlatform/deploystack/github"
    31  	"github.com/GoogleCloudPlatform/deploystack/terraform"
    32  	"github.com/go-test/deep"
    33  	"github.com/kylelemons/godebug/diff"
    34  	cp "github.com/otiai10/copy"
    35  )
    36  
    37  var testFilesDir = filepath.Join(os.Getenv("DEPLOYSTACK_PATH"), "testdata")
    38  
    39  var nosqltestdata = filepath.Join(testFilesDir, "reposformeta")
    40  
    41  // Getting this right was such a huge pain in the ass, I'm using it a few times
    42  // AND NEVER TOUCHING IT AGAIN
    43  
    44  var nosqlMeta = Meta{
    45  	DeployStack: config.Config{
    46  		Title:         "NoSQL Client Server",
    47  		Name:          "nosql-client-server",
    48  		Duration:      5,
    49  		Project:       true,
    50  		Region:        true,
    51  		RegionType:    "compute",
    52  		RegionDefault: "us-central1",
    53  		Zone:          true,
    54  		PathTerraform: "terraform",
    55  		PathMessages:  ".deploystack/messages",
    56  		PathScripts:   ".deploystack/scripts",
    57  
    58  		DocumentationLink: "https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/nosql-client-server",
    59  		AuthorSettings: config.Settings{
    60  			{
    61  				Name:  "basename",
    62  				Value: "nosql-client-server",
    63  				Type:  "string",
    64  			},
    65  		},
    66  		Description: "",
    67  		Products: []config.Product{
    68  			{Product: "Compute Engine", Info: "Server - which will run mongodb"},
    69  			{Product: "Compute Engine", Info: "Client - which will run a custom go application"},
    70  		},
    71  	},
    72  	Terraform: terraform.Blocks{
    73  		{
    74  			Name: "project",
    75  			Kind: "data",
    76  			Type: "google_project",
    77  			File: filepath.Join(
    78  				nosqltestdata,
    79  				"deploystack-nosql-client-server",
    80  				"terraform",
    81  				"main.tf",
    82  			),
    83  			Start: 21,
    84  			Text: `
    85  data "google_project" "project" {
    86    project_id = var.project_id
    87  }`,
    88  		},
    89  		{
    90  			Name: "all",
    91  			Kind: "managed",
    92  			Type: "google_project_service",
    93  			File: filepath.Join(
    94  				nosqltestdata,
    95  				"deploystack-nosql-client-server",
    96  				"terraform",
    97  				"main.tf",
    98  			),
    99  			Start: 25,
   100  			Text: `
   101  resource "google_project_service" "all" {
   102    for_each                   = toset(var.gcp_service_list)
   103    project                    = data.google_project.project.number
   104    service                    = each.key
   105    disable_dependent_services = false
   106    disable_on_destroy         = false
   107  }`,
   108  		},
   109  		{
   110  			Name: "default",
   111  			Kind: "data",
   112  			Type: "google_compute_network",
   113  			File: filepath.Join(
   114  				nosqltestdata,
   115  				"deploystack-nosql-client-server",
   116  				"terraform",
   117  				"main.tf",
   118  			),
   119  			Start: 34,
   120  			Text: `
   121  data "google_compute_network" "default" {
   122    project    = var.project_id
   123    name       = "default"
   124    depends_on = [google_project_service.all]
   125  }`,
   126  		},
   127  
   128  		{
   129  			Name: "main",
   130  			Kind: "managed",
   131  			Type: "google_compute_network",
   132  			File: filepath.Join(
   133  				nosqltestdata,
   134  				"deploystack-nosql-client-server",
   135  				"terraform",
   136  				"main.tf",
   137  			),
   138  			Start: 40,
   139  			Text: `
   140  resource "google_compute_network" "main" {
   141    provider                = google-beta
   142    name                    = "${var.basename}-network"
   143    auto_create_subnetworks = true
   144    project                 = var.project_id
   145    depends_on              = [google_project_service.all]
   146  }`,
   147  		},
   148  		{
   149  			Name: "default-allow-http",
   150  			Kind: "managed",
   151  			Type: "google_compute_firewall",
   152  			File: filepath.Join(
   153  				nosqltestdata,
   154  				"deploystack-nosql-client-server",
   155  				"terraform",
   156  				"main.tf",
   157  			),
   158  			Start: 48,
   159  			Text: `
   160  resource "google_compute_firewall" "default-allow-http" {
   161    name    = "deploystack-allow-http"
   162    project = data.google_project.project.number
   163    network = google_compute_network.main.name
   164  
   165    allow {
   166      protocol = "tcp"
   167      ports    = ["80"]
   168    }
   169  
   170    source_ranges = ["0.0.0.0/0"]
   171  
   172    target_tags = ["http-server"]
   173    depends_on  = [google_project_service.all]
   174  }`,
   175  		},
   176  
   177  		{
   178  			Name: "default-allow-internal",
   179  			Kind: "managed",
   180  			Type: "google_compute_firewall",
   181  			File: filepath.Join(
   182  				nosqltestdata,
   183  				"deploystack-nosql-client-server",
   184  				"terraform",
   185  				"main.tf",
   186  			),
   187  			Start: 64,
   188  			Text: `
   189  resource "google_compute_firewall" "default-allow-internal" {
   190    name    = "deploystack-allow-internal"
   191    project = data.google_project.project.number
   192    network = google_compute_network.main.name
   193  
   194    allow {
   195      protocol = "tcp"
   196      ports    = ["0-65535"]
   197    }
   198  
   199    allow {
   200      protocol = "udp"
   201      ports    = ["0-65535"]
   202    }
   203  
   204    allow {
   205      protocol = "icmp"
   206    }
   207  
   208    source_ranges = ["10.128.0.0/20"]
   209    depends_on    = [google_project_service.all]
   210  
   211  }`,
   212  		},
   213  
   214  		{
   215  			Name: "default-allow-ssh",
   216  			Kind: "managed",
   217  			Type: "google_compute_firewall",
   218  			File: filepath.Join(
   219  				nosqltestdata,
   220  				"deploystack-nosql-client-server",
   221  				"terraform",
   222  				"main.tf",
   223  			),
   224  			Start: 88,
   225  			Text: `
   226  resource "google_compute_firewall" "default-allow-ssh" {
   227    name    = "deploystack-allow-ssh"
   228    project = data.google_project.project.number
   229    network = google_compute_network.main.name
   230  
   231    allow {
   232      protocol = "tcp"
   233      ports    = ["22"]
   234    }
   235  
   236    source_ranges = ["0.0.0.0/0"]
   237  
   238    target_tags = ["ssh-server"]
   239    depends_on  = [google_project_service.all]
   240  }`,
   241  		},
   242  
   243  		{
   244  			Name: "server",
   245  			Kind: "managed",
   246  			Type: "google_compute_instance",
   247  			File: filepath.Join(
   248  				nosqltestdata,
   249  				"deploystack-nosql-client-server",
   250  				"terraform",
   251  				"main.tf",
   252  			),
   253  			Start: 105,
   254  			Text: `# Create Instances
   255  resource "google_compute_instance" "server" {
   256    name                      = "server"
   257    zone                      = var.zone
   258    project                   = var.project_id
   259    machine_type              = "e2-standard-2"
   260    tags                      = ["ssh-server", "http-server"]
   261    allow_stopping_for_update = true
   262  
   263  
   264    boot_disk {
   265      auto_delete = true
   266      device_name = "server"
   267      initialize_params {
   268        image = "family/ubuntu-1804-lts"
   269        size  = 10
   270        type  = "pd-standard"
   271      }
   272    }
   273  
   274    network_interface {
   275      network = google_compute_network.main.name
   276      access_config {
   277        // Ephemeral public IP
   278      }
   279    }
   280  
   281    service_account {
   282      scopes = ["https://www.googleapis.com/auth/logging.write"]
   283    }
   284  
   285    metadata_startup_script = <<SCRIPT
   286      apt-get update
   287      apt-get install -y mongodb
   288      service mongodb stop
   289      sed -i 's/bind_ip = 127.0.0.1/bind_ip = 0.0.0.0/' /etc/mongodb.conf
   290      iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 27017
   291      service mongodb start
   292    SCRIPT
   293    depends_on              = [google_project_service.all]
   294  }`,
   295  		},
   296  
   297  		{
   298  			Name: "client",
   299  			Kind: "managed",
   300  			Type: "google_compute_instance",
   301  			File: filepath.Join(
   302  				nosqltestdata,
   303  				"deploystack-nosql-client-server",
   304  				"terraform",
   305  				"main.tf",
   306  			),
   307  			Start: 146,
   308  			Text: `
   309  resource "google_compute_instance" "client" {
   310    name                      = "client"
   311    zone                      = var.zone
   312    project                   = var.project_id
   313    machine_type              = "e2-standard-2"
   314    tags                      = ["http-server", "https-server", "ssh-server"]
   315    allow_stopping_for_update = true
   316  
   317    boot_disk {
   318      auto_delete = true
   319      device_name = "client"
   320      initialize_params {
   321        image = "family/ubuntu-1804-lts"
   322        size  = 10
   323        type  = "pd-standard"
   324      }
   325    }
   326    service_account {
   327      scopes = ["https://www.googleapis.com/auth/logging.write"]
   328    }
   329  
   330    network_interface {
   331      network = google_compute_network.main.name
   332  
   333      access_config {
   334        // Ephemeral public IP
   335      }
   336    }
   337  
   338    metadata_startup_script = <<SCRIPT
   339      add-apt-repository ppa:longsleep/golang-backports -y && \
   340      apt update -y && \
   341      apt install golang-go git -y
   342      mkdir /modcache
   343      mkdir /go
   344      mkdir /app && cd /app
   345      git clone https://github.com/GoogleCloudPlatform/deploystack-nosql-client-server.git
   346      cd /app/deploystack-nosql-client-server/code/client
   347      GOPATH=/go GOMODCACHE=/modcache GOCACHE=/modcache go build -o trainers main.go model.go
   348      echo "GOPATH=/go GOMODCACHE=/modcache GOCACHE=/modcache DBHOST=${google_compute_instance.server.network_interface.0.network_ip} ./trainers"
   349      GOPATH=/go GOMODCACHE=/modcache GOCACHE=/modcache DBHOST=${google_compute_instance.server.network_interface.0.network_ip} ./trainers & 
   350    SCRIPT
   351  
   352  
   353    depends_on = [google_project_service.all]
   354  }`,
   355  		},
   356  
   357  		{
   358  			Name:  "project_id",
   359  			Kind:  "variable",
   360  			Type:  "string",
   361  			File:  filepath.Join(nosqltestdata, "deploystack-nosql-client-server", "terraform", "variables.tf"),
   362  			Start: 17,
   363  			Text: `
   364  variable "project_id" {
   365    type = string
   366  }`,
   367  		},
   368  
   369  		{
   370  			Name:  "zone",
   371  			Kind:  "variable",
   372  			Type:  "string",
   373  			File:  filepath.Join(nosqltestdata, "deploystack-nosql-client-server", "terraform", "variables.tf"),
   374  			Start: 21,
   375  			Text: `
   376  variable "zone" {
   377    type = string
   378  }`,
   379  		},
   380  
   381  		{
   382  			Name:  "region",
   383  			Kind:  "variable",
   384  			Type:  "string",
   385  			File:  filepath.Join(nosqltestdata, "deploystack-nosql-client-server", "terraform", "variables.tf"),
   386  			Start: 25,
   387  			Text: `
   388  variable "region" {
   389    type = string
   390  }`,
   391  		},
   392  
   393  		{
   394  			Name:  "basename",
   395  			Kind:  "variable",
   396  			Type:  "string",
   397  			File:  filepath.Join(nosqltestdata, "deploystack-nosql-client-server", "terraform", "variables.tf"),
   398  			Start: 29,
   399  			Text: `
   400  variable "basename" {
   401    type = string
   402  }`,
   403  		},
   404  
   405  		{
   406  			Name:  "gcp_service_list",
   407  			Kind:  "variable",
   408  			Type:  "list(string)",
   409  			File:  filepath.Join(nosqltestdata, "deploystack-nosql-client-server", "terraform", "variables.tf"),
   410  			Start: 33,
   411  			Text: `
   412  variable "gcp_service_list" {
   413    description = "The list of apis necessary for the project"
   414    type        = list(string)
   415    default = [
   416      "compute.googleapis.com",
   417    ]
   418  }`,
   419  		},
   420  	},
   421  }
   422  
   423  func compareValues(label string, want interface{}, got interface{}, t *testing.T) {
   424  	if !reflect.DeepEqual(want, got) {
   425  		t.Fatalf("%s: expected: \n|%v|\ngot: \n|%v|", label, want, got)
   426  	}
   427  }
   428  
   429  func TestPrecheck(t *testing.T) {
   430  	wd, err := filepath.Abs(".")
   431  	if err != nil {
   432  		t.Fatalf("error setting up environment for testing %v", err)
   433  	}
   434  
   435  	testdata := fmt.Sprintf("%s/testdata/configs", wd)
   436  	tests := map[string]struct {
   437  		wd   string
   438  		want string
   439  	}{
   440  		"single": {
   441  			wd:   fmt.Sprintf("%s/preferred", testdata),
   442  			want: "",
   443  		},
   444  	}
   445  
   446  	for name, tc := range tests {
   447  		t.Run(name, func(t *testing.T) {
   448  
   449  			oldWD, _ := os.Getwd()
   450  			err := os.Chdir(tc.wd)
   451  			if err != nil {
   452  				t.Fatalf("error changing wd: %s", err)
   453  			}
   454  
   455  			out := captureOutput(func() {
   456  				Precheck()
   457  			})
   458  
   459  			if !strings.Contains(tc.want, string(out)) {
   460  				t.Fatalf("expected to contain: %+v, got: %+v", tc.want, string(out))
   461  			}
   462  
   463  			os.Chdir(oldWD)
   464  		})
   465  	}
   466  }
   467  func TestPrecheckMulti(t *testing.T) {
   468  	// Precheck exits if it is called in testing with multiple stacks
   469  	// so make throwing the exit the test
   470  
   471  	if os.Getenv("BE_CRASHER") == "1" {
   472  		wd, err := filepath.Abs(".")
   473  		if err != nil {
   474  			t.Fatalf("error setting up environment for testing %v", err)
   475  		}
   476  
   477  		testdata := fmt.Sprintf("%s/testdata/configs", wd)
   478  		path := fmt.Sprintf("%s/multi", testdata)
   479  		oldWD, _ := os.Getwd()
   480  		if err := os.Chdir(path); err != nil {
   481  			t.Fatalf("error changing wd: %s", err)
   482  		}
   483  
   484  		Precheck()
   485  		os.Chdir(oldWD)
   486  		return
   487  	}
   488  	// So this is what I got advice to do, but it caused panics
   489  	// Setting it to explicitly "go test" fixed it
   490  	// cmd := exec.Command(os.Args[0], "-test.run=TestPrecheckMulti")
   491  	cmd := exec.Command("go", "test", "-timeout", "2s", "-test.run=TestPrecheckMulti", "-coverage")
   492  	cmd.Env = append(os.Environ(), "BE_CRASHER=1")
   493  	err := cmd.Run()
   494  	if e, ok := err.(*exec.ExitError); ok && !e.Success() {
   495  		return
   496  	}
   497  	t.Fatalf("process ran with err %v, want exit status 1", err)
   498  
   499  }
   500  
   501  func captureOutput(f func()) string {
   502  	rescueStdout := os.Stdout
   503  	r, w, _ := os.Pipe()
   504  	os.Stdout = w
   505  
   506  	f()
   507  
   508  	w.Close()
   509  	out, _ := ioutil.ReadAll(r)
   510  	os.Stdout = rescueStdout
   511  	return string(out)
   512  }
   513  
   514  func TestCacheContact(t *testing.T) {
   515  	tests := map[string]struct {
   516  		in  gcloud.ContactData
   517  		err error
   518  	}{
   519  		"basic": {
   520  			in: gcloud.ContactData{
   521  				AllContacts: gcloud.DomainRegistrarContact{
   522  					Email: "test@example.com",
   523  					Phone: "+155555551212",
   524  					PostalAddress: gcloud.PostalAddress{
   525  						RegionCode:         "US",
   526  						PostalCode:         "94502",
   527  						AdministrativeArea: "CA",
   528  						Locality:           "San Francisco",
   529  						AddressLines:       []string{"345 Spear Street"},
   530  						Recipients:         []string{"Googler"},
   531  					},
   532  				},
   533  			},
   534  			err: nil,
   535  		},
   536  		"err": {
   537  			in:  gcloud.ContactData{},
   538  			err: fmt.Errorf("stat contact.yaml: no such file or directory"),
   539  		},
   540  	}
   541  
   542  	for name, tc := range tests {
   543  		t.Run(name, func(t *testing.T) {
   544  			ContactSave(tc.in)
   545  
   546  			if tc.err == nil {
   547  				if _, err := os.Stat(contactfile); errors.Is(err, os.ErrNotExist) {
   548  					t.Fatalf("expected no error,  got: %+v", err)
   549  				}
   550  			} else {
   551  				if _, err := os.Stat(contactfile); err.Error() != tc.err.Error() {
   552  					t.Fatalf("expected %+v, got: %+v", tc.err, err)
   553  				}
   554  
   555  			}
   556  
   557  			os.Remove(contactfile)
   558  
   559  		})
   560  	}
   561  }
   562  
   563  func TestCheckForContact(t *testing.T) {
   564  	tests := map[string]struct {
   565  		in   string
   566  		want gcloud.ContactData
   567  	}{
   568  		"basic": {
   569  			in: "testdata/contact/contact.yaml",
   570  			want: gcloud.ContactData{
   571  				AllContacts: gcloud.DomainRegistrarContact{
   572  					Email: "test@example.com",
   573  					Phone: "+155555551212",
   574  					PostalAddress: gcloud.PostalAddress{
   575  						RegionCode:         "US",
   576  						PostalCode:         "94502",
   577  						AdministrativeArea: "CA",
   578  						Locality:           "San Francisco",
   579  						AddressLines:       []string{"345 Spear Street"},
   580  						Recipients:         []string{"Googler"},
   581  					},
   582  				},
   583  			},
   584  		},
   585  
   586  		"empty": {
   587  			in:   contactfile,
   588  			want: gcloud.ContactData{},
   589  		},
   590  	}
   591  
   592  	for name, tc := range tests {
   593  		t.Run(name, func(t *testing.T) {
   594  
   595  			oldContactFile := contactfile
   596  			contactfile = tc.in
   597  
   598  			got := ContactCheck()
   599  			if !reflect.DeepEqual(tc.want, got) {
   600  				t.Fatalf("expected: %+v, got: %+v", tc.want, got)
   601  			}
   602  
   603  			contactfile = oldContactFile
   604  		})
   605  	}
   606  }
   607  
   608  func TestInit(t *testing.T) {
   609  	errUnableToRead := errors.New("unable to read config file: ")
   610  	tests := map[string]struct {
   611  		path string
   612  		want config.Stack
   613  		err  error
   614  	}{
   615  		"error": {
   616  			path: "sadasd",
   617  			want: config.Stack{},
   618  			err:  errUnableToRead,
   619  		},
   620  		"no_custom": {
   621  			path: "testdata/dsfolders/no_customs",
   622  			want: config.Stack{
   623  				Config: config.Config{
   624  					Title:         "TESTCONFIG",
   625  					Name:          "test",
   626  					Description:   "A test string for usage with this stuff.",
   627  					Duration:      5,
   628  					Project:       true,
   629  					Region:        true,
   630  					RegionType:    "functions",
   631  					RegionDefault: "us-central1",
   632  				},
   633  			},
   634  			err: nil,
   635  		},
   636  		"no_name": {
   637  			path: "testdata/dsfolders/no_name",
   638  			want: config.Stack{
   639  				Config: config.Config{
   640  					Title:         "NONAME",
   641  					Name:          "",
   642  					Description:   "A test string for usage with this stuff.",
   643  					Duration:      5,
   644  					Project:       true,
   645  					Region:        true,
   646  					RegionType:    "functions",
   647  					RegionDefault: "us-central1",
   648  				},
   649  			},
   650  			err: fmt.Errorf("could retrieve name of stack: could not open local git directory: repository does not exist \nDeployStack author: fix this by adding a 'name' key and value to the deploystack config"),
   651  		},
   652  		"custom": {
   653  			path: "testdata/dsfolders/customs",
   654  			want: config.Stack{
   655  				Config: config.Config{
   656  					Title:         "TESTCONFIG",
   657  					Name:          "test",
   658  					Description:   "A test string for usage with this stuff.",
   659  					Duration:      5,
   660  					Project:       false,
   661  					Region:        false,
   662  					RegionType:    "",
   663  					RegionDefault: "",
   664  					CustomSettings: []config.Custom{
   665  						{Name: "nodes", Description: "Nodes", Default: "3"},
   666  					},
   667  				},
   668  			},
   669  			err: nil,
   670  		},
   671  		"custom_options": {
   672  			path: "testdata/dsfolders/customs_options",
   673  			want: config.Stack{
   674  				Config: config.Config{
   675  					Title:         "TESTCONFIG",
   676  					Name:          "test",
   677  					Description:   "A test string for usage with this stuff.",
   678  					Duration:      5,
   679  					Project:       false,
   680  					Region:        false,
   681  					RegionType:    "",
   682  					RegionDefault: "",
   683  
   684  					CustomSettings: []config.Custom{
   685  						{
   686  							Name:        "nodes",
   687  							Description: "Nodes",
   688  							Default:     "3",
   689  							Options:     []string{"1", "2", "3"},
   690  						},
   691  					},
   692  				},
   693  			},
   694  			err: nil,
   695  		},
   696  	}
   697  
   698  	for name, tc := range tests {
   699  		t.Run(name, func(t *testing.T) {
   700  
   701  			s, err := Init(tc.path)
   702  
   703  			if tc.err == nil {
   704  				if err != nil {
   705  					t.Fatalf("expected: no error got: %+v", err)
   706  				}
   707  			}
   708  
   709  			if errors.Is(err, tc.err) {
   710  				if err != nil && tc.err != nil && err.Error() != tc.err.Error() {
   711  					t.Fatalf("expected: error(%s) got: error(%s)", tc.err, err)
   712  				}
   713  			}
   714  
   715  			compareValues("Name", tc.want.Config.Name, s.Config.Name, t)
   716  			compareValues("Title", tc.want.Config.Title, s.Config.Title, t)
   717  			compareValues("Description", tc.want.Config.Description, s.Config.Description, t)
   718  			compareValues("Duration", tc.want.Config.Duration, s.Config.Duration, t)
   719  			compareValues("Project", tc.want.Config.Project, s.Config.Project, t)
   720  			compareValues("Region", tc.want.Config.Region, s.Config.Region, t)
   721  			compareValues("RegionType", tc.want.Config.RegionType, s.Config.RegionType, t)
   722  			compareValues("RegionDefault", tc.want.Config.RegionDefault, s.Config.RegionDefault, t)
   723  			for i, v := range s.Config.CustomSettings {
   724  				compareValues(v.Name, tc.want.Config.CustomSettings[i], v, t)
   725  			}
   726  		})
   727  	}
   728  }
   729  
   730  func TestShortName(t *testing.T) {
   731  	tests := map[string]struct {
   732  		in   string
   733  		want string
   734  	}{
   735  		"deploystack-repo":     {in: "https://github.com/GoogleCloudPlatform/deploystack-cost-sentry", want: "cost-sentry"},
   736  		"non-deploystack-repo": {in: "https://github.com/tpryan/microservices-demo", want: "microservices-demo"},
   737  	}
   738  
   739  	for name, tc := range tests {
   740  		t.Run(name, func(t *testing.T) {
   741  			m := Meta{}
   742  			m.Github.Name = tc.in
   743  
   744  			got := m.ShortName()
   745  			if !reflect.DeepEqual(tc.want, got) {
   746  				t.Fatalf("expected: %v, got: %v", tc.want, got)
   747  			}
   748  		})
   749  	}
   750  }
   751  
   752  func TestShortNameUnderscore(t *testing.T) {
   753  	tests := map[string]struct {
   754  		in   string
   755  		want string
   756  	}{
   757  		"deploystack-repo":     {in: "https://github.com/GoogleCloudPlatform/deploystack-cost-sentry", want: "cost_sentry"},
   758  		"non-deploystack-repo": {in: "https://github.com/tpryan/microservices-demo", want: "microservices_demo"},
   759  	}
   760  
   761  	for name, tc := range tests {
   762  		t.Run(name, func(t *testing.T) {
   763  			m := Meta{}
   764  			m.Github.Name = tc.in
   765  
   766  			got := m.ShortNameUnderscore()
   767  			if !reflect.DeepEqual(tc.want, got) {
   768  				t.Fatalf("expected: %v, got: %v", tc.want, got)
   769  			}
   770  		})
   771  	}
   772  }
   773  
   774  func TestGetRepo(t *testing.T) {
   775  	wd, err := filepath.Abs(".")
   776  	if err != nil {
   777  		t.Fatalf("error setting up environment for testing %v", err)
   778  	}
   779  
   780  	testdata := fmt.Sprintf("%s/testdata/repoforgithub", wd)
   781  	tests := map[string]struct {
   782  		repo github.Repo
   783  		path string
   784  		want string
   785  		err  error
   786  	}{
   787  		"deploystack-nosql-client-server": {
   788  			repo: github.Repo{
   789  				Name:   "deploystack-nosql-client-server",
   790  				Owner:  "GoogleCloudPlatform",
   791  				Branch: "main",
   792  			},
   793  			path: testdata,
   794  			want: fmt.Sprintf("%s/deploystack-nosql-client-server", testdata),
   795  		},
   796  
   797  		"deploystack-cost-sentry": {
   798  			repo: github.Repo{
   799  				Name:   "deploystack-cost-sentry",
   800  				Owner:  "GoogleCloudPlatform",
   801  				Branch: "main",
   802  			},
   803  			path: testdata,
   804  			want: fmt.Sprintf("%s/deploystack-cost-sentry_1", testdata),
   805  		},
   806  	}
   807  
   808  	for name, tc := range tests {
   809  		t.Run(name, func(t *testing.T) {
   810  
   811  			// Defered to make sure it runs even if things fatal
   812  			defer func() {
   813  				err = os.RemoveAll(tc.want)
   814  				if err != nil {
   815  					t.Logf(err.Error())
   816  				}
   817  			}()
   818  
   819  			got, err := DownloadRepo(tc.repo, tc.path)
   820  
   821  			if tc.err == nil && err != nil {
   822  				t.Fatalf("expected: no error got: %+v", err)
   823  			}
   824  
   825  			if !reflect.DeepEqual(tc.want, got) {
   826  				t.Fatalf("expected: %+v, got: %+v", tc.want, got)
   827  			}
   828  
   829  			if _, err := os.Stat(tc.want); os.IsNotExist(err) {
   830  				t.Fatalf("expected: %s to exist it does not", err)
   831  			}
   832  
   833  		})
   834  	}
   835  }
   836  
   837  func TestGetAcceptableDir(t *testing.T) {
   838  	wd, err := filepath.Abs(".")
   839  	if err != nil {
   840  		t.Fatalf("error setting up environment for testing %v", err)
   841  	}
   842  	testdata := fmt.Sprintf("%s/testdata/repoforgithub", wd)
   843  
   844  	tests := map[string]struct {
   845  		in   string
   846  		want string
   847  	}{
   848  		"doesnotexist": {
   849  			in:   fmt.Sprintf("%s/testfolder", testdata),
   850  			want: fmt.Sprintf("%s/testfolder", testdata),
   851  		},
   852  		"exists": {
   853  			in:   fmt.Sprintf("%s/alreadyexists", testdata),
   854  			want: fmt.Sprintf("%s/alreadyexists_2", testdata),
   855  		},
   856  	}
   857  
   858  	for name, tc := range tests {
   859  		t.Run(name, func(t *testing.T) {
   860  			got := UniquePath(tc.in)
   861  			if !reflect.DeepEqual(tc.want, got) {
   862  				t.Fatalf("expected: %+v, got: %+v", tc.want, got)
   863  			}
   864  		})
   865  	}
   866  }
   867  
   868  func TestNewMeta(t *testing.T) {
   869  	testdata := filepath.Join(testFilesDir, "reposformeta")
   870  
   871  	tests := map[string]struct {
   872  		path string
   873  		want Meta
   874  		err  error
   875  	}{
   876  		"deploystack-nosql-client-server": {
   877  			path: filepath.Join(testdata, "deploystack-nosql-client-server"),
   878  			want: nosqlMeta,
   879  		},
   880  	}
   881  
   882  	for name, tc := range tests {
   883  		t.Run(name, func(t *testing.T) {
   884  			got, err := NewMeta(tc.path)
   885  
   886  			got.Terraform.Sort()
   887  			tc.want.Terraform.Sort()
   888  
   889  			if tc.err == nil && err != nil {
   890  				t.Fatalf("expected no error, got %s", err)
   891  			}
   892  
   893  			if !reflect.DeepEqual(tc.want, got) {
   894  
   895  				for i := range tc.want.Terraform {
   896  					fmt.Println(diff.Diff(
   897  						tc.want.Terraform[i].Text,
   898  						got.Terraform[i].Text,
   899  					))
   900  				}
   901  
   902  				fmt.Println(diff.Diff(
   903  					tc.want.DeployStack.Description,
   904  					got.DeployStack.Description,
   905  				))
   906  
   907  				diff := deep.Equal(tc.want, got)
   908  				t.Log("a common reason for this error is that one of the outside repos changed")
   909  				t.Errorf("compare failed: %v", diff)
   910  			}
   911  		})
   912  	}
   913  }
   914  
   915  func TestSuggest(t *testing.T) {
   916  	tests := map[string]struct {
   917  		in   Meta
   918  		want config.Config
   919  		err  error
   920  	}{
   921  		"nosql-client-server": {
   922  			in: nosqlMeta,
   923  			want: config.Config{
   924  				Title:         "NoSQL Client Server",
   925  				Name:          "nosql-client-server",
   926  				Duration:      5,
   927  				Project:       true,
   928  				Region:        true,
   929  				RegionType:    "compute",
   930  				RegionDefault: "us-central1",
   931  				Zone:          true,
   932  				PathTerraform: "terraform",
   933  				PathMessages:  ".deploystack/messages",
   934  				PathScripts:   ".deploystack/scripts",
   935  
   936  				DocumentationLink: "https://cloud.google.com/shell/docs/cloud-shell-tutorials/deploystack/nosql-client-server",
   937  				AuthorSettings: config.Settings{
   938  					{
   939  						Name:  "basename",
   940  						Value: "nosql-client-server",
   941  						Type:  "string",
   942  					},
   943  				},
   944  				Description: "",
   945  				Products: []config.Product{
   946  					{Product: "Compute Engine", Info: "Server - which will run mongodb"},
   947  					{Product: "Compute Engine", Info: "Client - which will run a custom go application"},
   948  				},
   949  			},
   950  		},
   951  	}
   952  
   953  	for name, tc := range tests {
   954  		t.Run(name, func(t *testing.T) {
   955  			got, err := tc.in.Suggest()
   956  
   957  			if tc.err == nil && err != nil {
   958  				t.Fatalf("expected no error, got %s", err)
   959  			}
   960  
   961  			if !reflect.DeepEqual(tc.want, got) {
   962  
   963  				fmt.Println(diff.Diff(
   964  					tc.want.Description,
   965  					got.Description,
   966  				))
   967  
   968  				diff := deep.Equal(tc.want, got)
   969  				t.Errorf("compare failed: %v", diff)
   970  			}
   971  		})
   972  	}
   973  }
   974  
   975  func TestAttemptRepo(t *testing.T) {
   976  	tempName, err := os.MkdirTemp("", "testrepos")
   977  	defer os.RemoveAll(tempName)
   978  	if err != nil {
   979  		t.Fatalf("could not get a temp directory for test: %s", err)
   980  	}
   981  
   982  	err = os.Mkdir(filepath.Join(tempName, "deploystack-single-vm"), 0666)
   983  	if err != nil {
   984  		t.Fatalf("could not get a make a directory for test: %s", err)
   985  	}
   986  
   987  	tests := map[string]struct {
   988  		name     string
   989  		wd       string
   990  		wantdir  string
   991  		wantrepo github.Repo
   992  		err      error
   993  	}{
   994  		"deploystack-nosql-client-server": {
   995  			name:    "deploystack-nosql-client-server",
   996  			wd:      tempName,
   997  			wantdir: filepath.Join(tempName, "deploystack-nosql-client-server"),
   998  			wantrepo: github.Repo{
   999  				Name:   "deploystack-nosql-client-server",
  1000  				Owner:  "GoogleCloudPlatform",
  1001  				Branch: "main",
  1002  			},
  1003  		},
  1004  		"deploystack-single-vm": {
  1005  			name:    "deploystack-single-vm",
  1006  			wd:      tempName,
  1007  			wantdir: filepath.Join(tempName, "deploystack-single-vm_1"),
  1008  			wantrepo: github.Repo{
  1009  				Name:   "deploystack-single-vm",
  1010  				Owner:  "GoogleCloudPlatform",
  1011  				Branch: "main",
  1012  			},
  1013  		},
  1014  		"cost-sentry": {
  1015  			name:    "cost-sentry",
  1016  			wd:      tempName,
  1017  			wantdir: filepath.Join(tempName, "deploystack-cost-sentry"),
  1018  			wantrepo: github.Repo{
  1019  				Name:   "deploystack-cost-sentry",
  1020  				Owner:  "GoogleCloudPlatform",
  1021  				Branch: "main",
  1022  			},
  1023  		},
  1024  
  1025  		"notvalid": {
  1026  			name:    "badreponame",
  1027  			wd:      tempName,
  1028  			wantdir: filepath.Join(tempName, "deploystack-badreponame"),
  1029  			wantrepo: github.Repo{
  1030  				Name:   "deploystack-badreponame",
  1031  				Owner:  "GoogleCloudPlatform",
  1032  				Branch: "main",
  1033  			},
  1034  			err: fmt.Errorf("cannot clone repo"),
  1035  		},
  1036  	}
  1037  
  1038  	for name, tc := range tests {
  1039  		t.Run(name, func(t *testing.T) {
  1040  			gotdir, gotrepo, err := AttemptRepo(tc.name, tc.wd)
  1041  
  1042  			if tc.err == nil && err != nil {
  1043  				t.Fatalf("expected no error, got: %s", err)
  1044  			}
  1045  
  1046  			if tc.err != nil && err != nil {
  1047  				if !strings.Contains(err.Error(), tc.err.Error()) {
  1048  					t.Fatalf("expected error %s, got: %s", tc.err, err)
  1049  				}
  1050  			}
  1051  
  1052  			if !reflect.DeepEqual(tc.wantdir, gotdir) {
  1053  				diff := deep.Equal(tc.wantdir, gotdir)
  1054  				t.Errorf("compare failed: %v", diff)
  1055  			}
  1056  
  1057  			if !reflect.DeepEqual(tc.wantrepo, gotrepo) {
  1058  				diff := deep.Equal(tc.wantrepo, gotrepo)
  1059  				t.Errorf("compare failed: %v", diff)
  1060  			}
  1061  		})
  1062  	}
  1063  }
  1064  
  1065  func TestWriteConfig(t *testing.T) {
  1066  	tempName, err := os.MkdirTemp("", "testreposwc")
  1067  	defer os.RemoveAll(tempName)
  1068  	if err != nil {
  1069  		t.Fatalf("could not get a temp directory for test: %s", err)
  1070  	}
  1071  
  1072  	src := filepath.Join(testFilesDir, "reposformeta", "terraform-google-load-balanced-vms")
  1073  	dest := filepath.Join(tempName, "terraform-google-load-balanced-vms")
  1074  
  1075  	if err := cp.Copy(src, dest); err != nil {
  1076  		t.Fatalf("could create a test directory for test: %s", err)
  1077  	}
  1078  
  1079  	gh := github.Repo{
  1080  		Name:   "terraform-google-load-balanced-vms",
  1081  		Owner:  "GoogleCloudPlatform",
  1082  		Branch: "main",
  1083  	}
  1084  
  1085  	if err := WriteConfig(dest, gh); err != nil {
  1086  		t.Fatalf("writeconfig: failed %s", err)
  1087  	}
  1088  
  1089  	target := filepath.Join(dest, ".deploystack", "deploystack.yaml")
  1090  	if _, err := os.Stat(target); err != nil {
  1091  		t.Fatalf("writeconfig: failed to write file %s", err)
  1092  	}
  1093  }