diff --git a/mmv1/products/workstations/WorkstationCluster.yaml b/mmv1/products/workstations/WorkstationCluster.yaml index e7eb8669697a..336e3ab04f10 100644 --- a/mmv1/products/workstations/WorkstationCluster.yaml +++ b/mmv1/products/workstations/WorkstationCluster.yaml @@ -65,6 +65,14 @@ examples: test_vars_overrides: 'key_short_name': '"tf-test-key-" + acctest.RandString(t, 10)' 'value_short_name': '"tf-test-value-" + acctest.RandString(t, 10)' +samples: + - name: "workstation_cluster_urls" + primary_resource_id: "default" + steps: + - name: "workstation_cluster_custom_urls" + vars: + cluster_id: "custom-urls-cluster" + cluster_network_name: "workstations-network" parameters: - name: 'workstationClusterId' type: String @@ -120,6 +128,17 @@ properties: type: String description: | Human-readable name for this resource. + - name: 'workstationAuthorizationUrl' + type: String + default_from_api: true + description: | + Specifies the redirect URL for unauthorized requests received by workstation VMs in this cluster. + Redirects to this endpoint will send a base64 encoded `state` query param containing the target workstation name and original request hostname. The endpoint is responsible for retrieving a token using `GenerateAccessToken` and redirecting back to the original hostname with the token. + - name: 'workstationLaunchUrl' + type: String + description: | + Specifies the launch URL for workstations in this cluster. Requests sent to unstarted workstations will be redirected to this URL. + Requests redirected to the launch endpoint will be sent with a `workstation` query parameter containing the full workstation resource name and project ID, respectively. The launch endpoint is responsible for starting the workstation, polling it until it reaches `STATE_RUNNING`, and then issuing a redirect to the workstation's host URL. - name: 'degraded' type: Boolean description: | @@ -221,3 +240,4 @@ properties: "123/costCenter": "marketing" immutable: true ignore_read: true + # dummy change so the presubmit status won't show at the original PR diff --git a/mmv1/templates/terraform/samples/services/workstations/workstation_cluster_custom_urls.tf.tmpl b/mmv1/templates/terraform/samples/services/workstations/workstation_cluster_custom_urls.tf.tmpl new file mode 100644 index 000000000000..8911fb3181ba --- /dev/null +++ b/mmv1/templates/terraform/samples/services/workstations/workstation_cluster_custom_urls.tf.tmpl @@ -0,0 +1,24 @@ +resource "google_workstations_workstation_cluster" "{{$.PrimaryResourceId}}" { + workstation_cluster_id = "{{index $.Vars "cluster_id"}}" + network = google_compute_network.default.id + subnetwork = google_compute_subnetwork.default.id + location = "us-central1" + + workstation_authorization_url = "https://workstations.cloud.google.com/ui/auth" + workstation_launch_url = "https://console.cloud.google.com/workstations/launch" +} + +data "google_project" "project" { +} + +resource "google_compute_network" "default" { + name = "{{index $.Vars "cluster_network_name"}}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "default" { + name = "{{index $.Vars "cluster_network_name"}}" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = google_compute_network.default.name +} diff --git a/tools/template-check/cmd/testdata/resource1.yaml b/tools/template-check/cmd/testdata/resource1.yaml index 23e0b7095803..d214cee8f592 100644 --- a/tools/template-check/cmd/testdata/resource1.yaml +++ b/tools/template-check/cmd/testdata/resource1.yaml @@ -58,3 +58,10 @@ properties: type: String description: Service id of the App Engine application required: true + +samples: + - name: "sample1" + steps: + - name: "step1" + - name: "step2" + config_path: "custom/path/to/step2.tf.tmpl" diff --git a/tools/template-check/cmd/unusedtmpl.go b/tools/template-check/cmd/unusedtmpl.go index b826e906aaa6..ef052a01ead3 100644 --- a/tools/template-check/cmd/unusedtmpl.go +++ b/tools/template-check/cmd/unusedtmpl.go @@ -16,6 +16,7 @@ import ( const unusedTmplDesc = "Check whether any template files are not used in product yamls" var exampleFilePathReg = regexp.MustCompile(".*mmv1/templates/terraform/examples/([a-zA-Z0-9_-]+).tf.tmpl") +var sampleFilePathReg = regexp.MustCompile(".*mmv1/templates/terraform/samples/services/.*\\.tf\\.tmpl") type unusedTmplOptions struct { rootOptions *rootOptions @@ -29,8 +30,15 @@ type tree struct { type resourceYaml struct { Examples []struct { - Name string - } + Name string `yaml:"name"` + } `yaml:"examples,omitempty"` + Samples []struct { + Name string `yaml:"name"` + Steps []struct { + Name string `yaml:"name"` + ConfigPath string `yaml:"config_path,omitempty"` + } `yaml:"steps"` + } `yaml:"samples,omitempty"` } func newUnusedTmplCmd(rootOptions *rootOptions) *cobra.Command { @@ -54,7 +62,7 @@ func (o *unusedTmplOptions) run() error { if len(o.fileList) == 0 { return nil } - newCustomTmpls, newExamples := processInputFiles(o.fileList) + newCustomTmpls, newExamples, newSamples := processInputFiles(o.fileList) found := false // get repo dir from tmpl files @@ -93,16 +101,31 @@ func (o *unusedTmplOptions) run() error { } } + if len(newSamples) > 0 { + samples, err := findSamples(productFiles) + if err != nil { + return err + } + for _, file := range newSamples { + templatePath := strings.ReplaceAll(file, repoPath+"/mmv1/", "") + if _, ok := samples[templatePath]; !ok { + found = true + fmt.Fprintf(os.Stderr, "File %s not used in any product yaml.\n", file) + } + } + } if found { return fmt.Errorf("found templates not used") } return nil } -func processInputFiles(fileList []string) (customTmpls []string, examples []string) { +func processInputFiles(fileList []string) (customTmpls []string, examples []string, samples []string) { for _, v := range fileList { if exampleFilePathReg.MatchString(v) { examples = append(examples, v) + } else if sampleFilePathReg.MatchString(v) { + samples = append(samples, v) } else if strings.Contains(v, "mmv1/templates/terraform") && strings.HasSuffix(v, ".tmpl") { customTmpls = append(customTmpls, v) } else { @@ -254,3 +277,33 @@ func findExamples(yamlFiles []string) (map[string]bool, error) { } return allExamples, nil } + +// findSamples parsed yaml files to get samples. +// It returns a map of samples where the key is the inferred sample path. +func findSamples(yamlFiles []string) (map[string]bool, error) { + allSamples := map[string]bool{} + for _, yamlFile := range yamlFiles { + b, err := os.ReadFile(yamlFile) + if err != nil { + return nil, err + } + + var r resourceYaml + if err := yaml.Unmarshal(b, &r); err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml file for samples %s: %s", yamlFile, err) + } + packageName := filepath.Base(filepath.Dir(yamlFile)) + for _, sample := range r.Samples { + for _, step := range sample.Steps { + var tmplPath string + if step.ConfigPath != "" { + tmplPath = step.ConfigPath + } else { + tmplPath = fmt.Sprintf("templates/terraform/samples/services/%s/%s.tf.tmpl", packageName, step.Name) + } + allSamples[tmplPath] = true + } + } + } + return allSamples, nil +} diff --git a/tools/template-check/cmd/unusedtmpl_test.go b/tools/template-check/cmd/unusedtmpl_test.go index a9c1961b808f..b01a49a392a4 100644 --- a/tools/template-check/cmd/unusedtmpl_test.go +++ b/tools/template-check/cmd/unusedtmpl_test.go @@ -12,14 +12,17 @@ func TestProcessInput(t *testing.T) { "mmv1/templates/terraform/examples/abc.go.tmpl", "mmv1/templates/terraform/examples/subfolder/abc.tf.tmpl", "mmv1/templates/terraform/custom_flatten/abc.go.tmpl", + "mmv1/templates/terraform/samples/services/workstations/workstation_cluster_custom_urls.tf.tmpl", } - tmpl, examples := processInputFiles(fileList) - wantTmpl, wantExamples := []string{ + tmpl, examples, samples := processInputFiles(fileList) + wantTmpl, wantExamples, wantSamples := []string{ "mmv1/templates/terraform/examples/abc.go.tmpl", "mmv1/templates/terraform/examples/subfolder/abc.tf.tmpl", "mmv1/templates/terraform/custom_flatten/abc.go.tmpl", }, []string{ "mmv1/templates/terraform/examples/abc.tf.tmpl", + }, []string{ + "mmv1/templates/terraform/samples/services/workstations/workstation_cluster_custom_urls.tf.tmpl", } if diff := cmp.Diff(wantTmpl, tmpl); diff != "" { @@ -28,6 +31,9 @@ func TestProcessInput(t *testing.T) { if diff := cmp.Diff(wantExamples, examples); diff != "" { t.Errorf("processInputFiles() got diff(-want, got) for example files = %s", diff) } + if diff := cmp.Diff(wantSamples, samples); diff != "" { + t.Errorf("processInputFiles() got diff(-want, got) for sample files = %s", diff) + } } func TestFindTmpls(t *testing.T) { @@ -52,6 +58,7 @@ func TestFindTmpls(t *testing.T) { "templates/terraform/custom_flatten/bigquery_dataset_ref.go.tmpl": true, "templates/terraform/iam/example_config_body/app_engine_service.tf.tmpl": true, "templates/terraform/state_migrations/big_query_job.go.tmpl": true, + "custom/path/to/step2.tf.tmpl": true, } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("findTmpls() got unexpected diff(-want, got) = %s", diff) @@ -84,3 +91,19 @@ func TestFindExamples(t *testing.T) { t.Errorf("findExamples() got unexpected diff(-want, got) = %s", diff) } } + +func TestFindSamples(t *testing.T) { + yamlFiles := []string{"testdata/resource1.yaml"} + got, err := findSamples(yamlFiles) + if err != nil { + t.Fatal(err) + } + + want := map[string]bool{ + "templates/terraform/samples/services/testdata/step1.tf.tmpl": true, + "custom/path/to/step2.tf.tmpl": true, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("findSamples() got unexpected diff(-want, got) = %s", diff) + } +}