e2etestrunner/setuptf/setuptf.go (174 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package setuptf import ( "context" "encoding/json" "fmt" "log" "os" "os/exec" ) const ( tfPersistentDir = "tf/persistent" Push SubscriptionMode = "push" Pull SubscriptionMode = "pull" ) type SubscriptionMode string type tfOutput struct { PubsubInfoWrapper struct { Value PubsubInfo `json:"value"` } `json:"pubsub_info"` } type TopicInfo struct { TopicName string `json:"topic_name"` SubscriptionName string `json:"subscription_name"` } type PubsubInfo struct { RequestTopic TopicInfo `json:"request_topic"` ResponseTopic TopicInfo `json:"response_topic"` } func runWithOutput(cmd *exec.Cmd, logger *log.Logger) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr logger.Printf("Running command: %v\n", cmd) if err := cmd.Run(); err != nil { logger.Println(err) return err } return nil } func initCommand(ctx context.Context, projectID string) *exec.Cmd { return exec.CommandContext( ctx, "terraform", "init", "-input=false", fmt.Sprintf("-backend-config=bucket=%v-e2e-tfstate", projectID), ) } // Runs the sequence of terraform commands most environemnts need, returns the // output bytes of `terraform output -json` and a cleanup function to teardown // the created resources. // // 1. Run terraform init // 2. Create a new terraform workspace for the test run ID // 3. Run terraform apply // 4. Get output results from terraform output // // Cleanup method runs terraform destroy and then deletes the workspace. func SetupTf( ctx context.Context, projectID string, testRunID string, tfDir string, // the Dir to set when running terraform commands in e.g. tf/gke tfVars map[string]string, // key-values for terraform input vars to send to terraform logger *log.Logger, ) (*PubsubInfo, func(), error) { tfVarArgs := tfVarMapToArgs(projectID, tfVars) cmd := initCommand(ctx, projectID) cmd.Args = append(cmd.Args, tfVarArgs...) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { return nil, func() {}, err } cleanup := func() { defer deleteWorkspace(ctx, testRunID, tfDir, logger) // Run terraform destroy cmd = exec.CommandContext( ctx, "terraform", "destroy", "-input=false", "-auto-approve", ) cmd.Args = append(cmd.Args, tfVarArgs...) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { logger.Panic(err) } } // Create new terraform workspace cmd = exec.CommandContext(ctx, "terraform", "workspace", "new", testRunID) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { // try to switch to workspace if it already exists cmd = exec.CommandContext(ctx, "terraform", "workspace", "select", testRunID) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { return nil, cleanup, err } } // Run terraform apply cmd = exec.CommandContext( ctx, "terraform", "apply", "-input=false", "-auto-approve", ) cmd.Args = append(cmd.Args, tfVarArgs...) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { return nil, cleanup, err } // Run terraform output cmd = exec.CommandContext(ctx, "terraform", "output", "-json") cmd.Dir = tfDir out, err := cmd.Output() if err != nil { logger.Println(err) return nil, cleanup, err } tfOutput := &tfOutput{} if err := json.Unmarshal(out, tfOutput); err != nil { return nil, cleanup, err } return &tfOutput.PubsubInfoWrapper.Value, cleanup, nil } // Create persistent resources (in tf/persistent) that are used across tests. No // cleanup is required func ApplyPersistent( ctx context.Context, projectID string, autoApprove bool, logger *log.Logger, ) error { logger.Println("Applying any changes to persistent resources") // Run terraform init cmd := initCommand(ctx, projectID) cmd.Dir = tfPersistentDir if err := runWithOutput(cmd, logger); err != nil { return err } // Select default terraform workspace cmd = exec.CommandContext(ctx, "terraform", "workspace", "select", "default") cmd.Dir = tfPersistentDir if err := runWithOutput(cmd, logger); err != nil { return err } // Run terraform apply cmd = exec.CommandContext( ctx, "terraform", "apply", "-input=false", // lock may not be acquired immediately in CI if there are multiple // jobs, but should only be a short wait "-lock-timeout=10m", fmt.Sprintf("-var=project_id=%v", projectID), ) if autoApprove { cmd.Args = append(cmd.Args, "-auto-approve") } else { cmd.Stdin = os.Stdin } cmd.Dir = tfPersistentDir if err := runWithOutput(cmd, logger); err != nil { return err } return nil } func deleteWorkspace( ctx context.Context, testRunID string, tfDir string, // the Dir to set when running terraform commands in e.g. tf/gke logger *log.Logger, ) { // first, switch to default terraform workspace cmd := exec.CommandContext(ctx, "terraform", "workspace", "select", "default") cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { logger.Panic(err) } // issue delete cmd = exec.CommandContext(ctx, "terraform", "workspace", "delete", testRunID) cmd.Dir = tfDir if err := runWithOutput(cmd, logger); err != nil { logger.Panic(err) } } func tfVarMapToArgs( projectID string, tfVars map[string]string, ) []string { tfVarArgs := []string{fmt.Sprintf("-var=project_id=%v", projectID)} for k, v := range tfVars { tfVarArgs = append(tfVarArgs, fmt.Sprintf("-var=%v=%v", k, v)) } return tfVarArgs }