From fb06df80fcc83dc64ab274aae7abdc9d12142cdd Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Tue, 23 Apr 2024 11:41:03 -0400 Subject: [PATCH] Refactor generalError to use appName In order to inject the value of TFP-AppName, we will need a backend.Cloud ref available to use. This change modifies generalError as a receiver method for backend.Cloud types. We also duplicate this method as a receiver for TestSuiteRunner, since TestSuiteRunner does not have a backend.Cloud ref. --- internal/cloud/backend.go | 14 ++++----- internal/cloud/backend_apply.go | 6 ++-- internal/cloud/backend_common.go | 34 ++++++++++---------- internal/cloud/backend_plan.go | 26 ++++++++-------- internal/cloud/backend_taskStages.go | 8 ++--- internal/cloud/test.go | 46 +++++++++++++++++++++++----- 6 files changed, 83 insertions(+), 51 deletions(-) diff --git a/internal/cloud/backend.go b/internal/cloud/backend.go index 4f760e2c8a..b1540a93ee 100644 --- a/internal/cloud/backend.go +++ b/internal/cloud/backend.go @@ -904,7 +904,7 @@ func (b *Cloud) Operation(ctx context.Context, op *backendrun.Operation) (*backe r, err := b.client.Runs.Read(cancelCtx, r.ID) if err != nil { var diags tfdiags.Diagnostics - diags = diags.Append(generalError("Failed to retrieve run", err)) + diags = diags.Append(b.generalError("Failed to retrieve run", err)) op.ReportResult(runningOp, diags) return } @@ -915,7 +915,7 @@ func (b *Cloud) Operation(ctx context.Context, op *backendrun.Operation) (*backe if opErr == context.Canceled { if err := b.cancel(cancelCtx, op, r); err != nil { var diags tfdiags.Diagnostics - diags = diags.Append(generalError("Failed to retrieve run", err)) + diags = diags.Append(b.generalError("Failed to retrieve run", err)) op.ReportResult(runningOp, diags) return } @@ -942,7 +942,7 @@ func (b *Cloud) cancel(cancelCtx context.Context, op *backendrun.Operation, r *t Description: "Only 'yes' will be accepted to cancel.", }) if err != nil { - return generalError("Failed asking to cancel", err) + return b.generalError("Failed asking to cancel", err) } if v != "yes" { if b.CLI != nil { @@ -960,7 +960,7 @@ func (b *Cloud) cancel(cancelCtx context.Context, op *backendrun.Operation, r *t // Try to cancel the remote operation. err := b.client.Runs.Cancel(cancelCtx, r.ID, tfe.RunCancelOptions{}) if err != nil { - return generalError("Failed to cancel run", err) + return b.generalError("Failed to cancel run", err) } if b.CLI != nil { b.CLI.Output(b.Colorize().Color(strings.TrimSpace(operationCanceled))) @@ -1280,7 +1280,7 @@ func (wm WorkspaceMapping) tfeTags() []*tfe.Tag { return tags } -func generalError(msg string, err error) error { +func (b *Cloud) generalError(msg string, err error) error { var diags tfdiags.Diagnostics if urlErr, ok := err.(*url.Error); ok { @@ -1294,7 +1294,7 @@ func generalError(msg string, err error) error { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, fmt.Sprintf("%s: %v", msg, err), - "For security, HCP Terraform and Terraform Enterprise return '404 Not Found' responses for resources\n"+ + fmt.Sprintf("For security, %s returns '404 Not Found' responses for resources\n", b.appName)+ "for resources that a user doesn't have access to, in addition to resources that\n"+ "do not exist. If the resource does exist, please check the permissions of the provided token.", )) @@ -1303,7 +1303,7 @@ func generalError(msg string, err error) error { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, fmt.Sprintf("%s: %v", msg, err), - `HCP Terraform or Terraform Enterprise returned an unexpected error. Sometimes `+ + fmt.Sprintf(`%s returned an unexpected error. Sometimes `, b.appName)+ `this is caused by network connection problems, in which case you could retry `+ `the command. If the issue persists please open a support ticket to get help `+ `resolving the problem.`, diff --git a/internal/cloud/backend_apply.go b/internal/cloud/backend_apply.go index 39e8953d64..26afd2b134 100644 --- a/internal/cloud/backend_apply.go +++ b/internal/cloud/backend_apply.go @@ -143,7 +143,7 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera // Retrieve the run to get its current status. r, err = b.client.Runs.Read(stopCtx, r.ID) if err != nil { - return r, generalError("Failed to retrieve run", err) + return r, b.generalError("Failed to retrieve run", err) } // Return if the run cannot be confirmed. @@ -184,7 +184,7 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera // Do the apply! if !op.AutoApprove && err != errRunApproved { if err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{}); err != nil { - return r, generalError("Failed to approve the apply command", err) + return r, b.generalError("Failed to approve the apply command", err) } } @@ -232,7 +232,7 @@ func (b *Cloud) renderApplyLogs(ctx context.Context, run *tfe.Run) error { l, isPrefix, err = reader.ReadLine() if err != nil { if err != io.EOF { - return generalError("Failed to read logs", err) + return b.generalError("Failed to read logs", err) } next = false } diff --git a/internal/cloud/backend_common.go b/internal/cloud/backend_common.go index f72d315981..7389d36449 100644 --- a/internal/cloud/backend_common.go +++ b/internal/cloud/backend_common.go @@ -61,7 +61,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op // Retrieve the run to get its current status. r, err := b.client.Runs.Read(stopCtx, r.ID) if err != nil { - return r, generalError("Failed to retrieve run", err) + return r, b.generalError("Failed to retrieve run", err) } // Return if the run is no longer pending. @@ -92,7 +92,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op // Retrieve the workspace used to run this operation in. w, err = b.client.Workspaces.Read(stopCtx, b.Organization, w.Name) if err != nil { - return nil, generalError("Failed to retrieve workspace", err) + return nil, b.generalError("Failed to retrieve workspace", err) } // If the workspace is locked the run will not be queued and we can @@ -100,7 +100,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op if w.Locked && w.CurrentRun != nil { cr, err := b.client.Runs.Read(stopCtx, w.CurrentRun.ID) if err != nil { - return r, generalError("Failed to retrieve current run", err) + return r, b.generalError("Failed to retrieve current run", err) } if cr.Status == tfe.RunPending { b.CLI.Output(b.Colorize().Color( @@ -117,7 +117,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op for { rl, err := b.client.Runs.List(stopCtx, w.ID, options) if err != nil { - return r, generalError("Failed to retrieve run list", err) + return r, b.generalError("Failed to retrieve run list", err) } // Loop through all runs to calculate the workspace queue position. @@ -172,7 +172,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op for { rq, err := b.client.Organizations.ReadRunQueue(stopCtx, b.Organization, options) if err != nil { - return r, generalError("Failed to retrieve queue", err) + return r, b.generalError("Failed to retrieve queue", err) } // Search through all queued items to find our run. @@ -195,7 +195,7 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backendrun.Op if position > 0 { c, err := b.client.Organizations.ReadCapacity(stopCtx, b.Organization) if err != nil { - return r, generalError("Failed to retrieve capacity", err) + return r, b.generalError("Failed to retrieve capacity", err) } b.CLI.Output(b.Colorize().Color(fmt.Sprintf( "Waiting for %d queued run(s) to finish before starting...%s", @@ -242,7 +242,7 @@ func (b *Cloud) costEstimate(stopCtx, cancelCtx context.Context, op *backendrun. // Retrieve the cost estimate to get its current status. ce, err := b.client.CostEstimates.Read(stopCtx, r.CostEstimate.ID) if err != nil { - return generalError("Failed to retrieve cost estimate", err) + return b.generalError("Failed to retrieve cost estimate", err) } // If the run is canceled or errored, but the cost-estimate still has @@ -263,7 +263,7 @@ func (b *Cloud) costEstimate(stopCtx, cancelCtx context.Context, op *backendrun. case tfe.CostEstimateFinished: delta, err := strconv.ParseFloat(ce.DeltaMonthlyCost, 64) if err != nil { - return generalError("Unexpected error", err) + return b.generalError("Unexpected error", err) } sign := "+" @@ -326,14 +326,14 @@ func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backendrun.O // return once the policy check is complete. logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID) if err != nil { - return generalError("Failed to retrieve policy check logs", err) + return b.generalError("Failed to retrieve policy check logs", err) } reader := bufio.NewReaderSize(logs, 64*1024) // Retrieve the policy check to get its current status. pc, err := b.client.PolicyChecks.Read(stopCtx, pc.ID) if err != nil { - return generalError("Failed to retrieve policy check", err) + return b.generalError("Failed to retrieve policy check", err) } // If the run is canceled or errored, but the policy check still has @@ -367,7 +367,7 @@ func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backendrun.O l, isPrefix, err = reader.ReadLine() if err != nil { if err != io.EOF { - return generalError("Failed to read logs", err) + return b.generalError("Failed to read logs", err) } next = false } @@ -400,7 +400,7 @@ func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backendrun.O if op.AutoApprove { if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil { - return generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) + return b.generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) } } else if !b.input { return errPolicyOverrideNeedsUIConfirmation @@ -419,7 +419,7 @@ func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backendrun.O if err != errRunOverridden { if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil { - return generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) + return b.generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) } } else { runURL := fmt.Sprintf(runHeader, b.Hostname, b.Organization, op.Workspace, r.ID) @@ -457,7 +457,7 @@ func (b *Cloud) confirm(stopCtx context.Context, op *backendrun.Operation, opts // Retrieve the run again to get its current status. r, err := b.client.Runs.Read(stopCtx, r.ID) if err != nil { - result <- generalError("Failed to retrieve run", err) + result <- b.generalError("Failed to retrieve run", err) return } @@ -524,7 +524,7 @@ func (b *Cloud) confirm(stopCtx context.Context, op *backendrun.Operation, opts // Retrieve the run again to get its current status. r, err = b.client.Runs.Read(stopCtx, r.ID) if err != nil { - return generalError("Failed to retrieve run", err) + return b.generalError("Failed to retrieve run", err) } // Make sure we discard the run if possible. @@ -532,9 +532,9 @@ func (b *Cloud) confirm(stopCtx context.Context, op *backendrun.Operation, opts err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{}) if err != nil { if op.PlanMode == plans.DestroyMode { - return generalError("Failed to discard destroy", err) + return b.generalError("Failed to discard destroy", err) } - return generalError("Failed to discard apply", err) + return b.generalError("Failed to discard apply", err) } } diff --git a/internal/cloud/backend_plan.go b/internal/cloud/backend_plan.go index 6a1934710c..25c21f5893 100644 --- a/internal/cloud/backend_plan.go +++ b/internal/cloud/backend_plan.go @@ -139,7 +139,7 @@ func (b *Cloud) plan(stopCtx, cancelCtx context.Context, op *backendrun.Operatio cv, err := b.client.ConfigurationVersions.Create(stopCtx, w.ID, configOptions) if err != nil { - return nil, generalError("Failed to create configuration version", err) + return nil, b.generalError("Failed to create configuration version", err) } var configDir string @@ -147,7 +147,7 @@ func (b *Cloud) plan(stopCtx, cancelCtx context.Context, op *backendrun.Operatio // De-normalize the configuration directory path. configDir, err = filepath.Abs(op.ConfigDir) if err != nil { - return nil, generalError( + return nil, b.generalError( "Failed to get absolute path of the configuration directory: %v", err) } @@ -185,21 +185,21 @@ in order to capture the filesystem context the remote workspace expects: // be executed when we are destroying and doesn't need the config. configDir, err = ioutil.TempDir("", "tf") if err != nil { - return nil, generalError("Failed to create temporary directory", err) + return nil, b.generalError("Failed to create temporary directory", err) } defer os.RemoveAll(configDir) // Make sure the configured working directory exists. err = os.MkdirAll(filepath.Join(configDir, w.WorkingDirectory), 0700) if err != nil { - return nil, generalError( + return nil, b.generalError( "Failed to create temporary working directory", err) } } err = b.client.ConfigurationVersions.Upload(stopCtx, cv.UploadURL, configDir) if err != nil { - return nil, generalError("Failed to upload configuration files", err) + return nil, b.generalError("Failed to upload configuration files", err) } uploaded := false @@ -212,7 +212,7 @@ in order to capture the filesystem context the remote workspace expects: case <-time.After(planConfigurationVersionsPollInterval): cv, err = b.client.ConfigurationVersions.Read(stopCtx, cv.ID) if err != nil { - return nil, generalError("Failed to retrieve configuration version", err) + return nil, b.generalError("Failed to retrieve configuration version", err) } if cv.Status == tfe.ConfigurationUploaded { @@ -222,7 +222,7 @@ in order to capture the filesystem context the remote workspace expects: } if !uploaded { - return nil, generalError( + return nil, b.generalError( "Failed to upload configuration files", errors.New("operation timed out")) } @@ -245,7 +245,7 @@ in order to capture the filesystem context the remote workspace expects: // Shouldn't get here because we should update this for each new // plan mode we add, mapping it to the corresponding RunCreateOptions // field. - return nil, generalError( + return nil, b.generalError( "Invalid plan mode", fmt.Errorf("%s doesn't support %s", b.appName, op.PlanMode), ) @@ -291,7 +291,7 @@ in order to capture the filesystem context the remote workspace expects: r, err := b.client.Runs.Create(stopCtx, runOptions) if err != nil { - return r, generalError("Failed to create run", err) + return r, b.generalError("Failed to create run", err) } // When the lock timeout is set, if the run is still pending and @@ -367,7 +367,7 @@ in order to capture the filesystem context the remote workspace expects: // Retrieve the run to get its current status. r, err = b.client.Runs.Read(stopCtx, r.ID) if err != nil { - return r, generalError("Failed to retrieve run", err) + return r, b.generalError("Failed to retrieve run", err) } // If the run is canceled or errored, we still continue to the @@ -453,7 +453,7 @@ func (b *Cloud) renderPlanLogs(ctx context.Context, op *backendrun.Operation, ru l, isPrefix, err = reader.ReadLine() if err != nil { if err != io.EOF { - return generalError("Failed to read logs", err) + return b.generalError("Failed to read logs", err) } next = false } @@ -523,11 +523,11 @@ func (b *Cloud) renderPlanLogs(ctx context.Context, op *backendrun.Operation, ru if renderSRO || shouldGenerateConfig { jsonBytes, err := readRedactedPlan(ctx, b.client.BaseURL(), b.Token, run.Plan.ID) if err != nil { - return generalError("Failed to read JSON plan", err) + return b.generalError("Failed to read JSON plan", err) } redactedPlan, err = decodeRedactedPlan(jsonBytes) if err != nil { - return generalError("Failed to decode JSON plan", err) + return b.generalError("Failed to decode JSON plan", err) } } diff --git a/internal/cloud/backend_taskStages.go b/internal/cloud/backend_taskStages.go index a16875864d..6a38492ea9 100644 --- a/internal/cloud/backend_taskStages.go +++ b/internal/cloud/backend_taskStages.go @@ -51,7 +51,7 @@ func (b *Cloud) runTaskStages(ctx context.Context, client *tfe.Client, runId str // This error would be expected for older versions of TFE that do not allow // fetching task_stages. if !strings.HasSuffix(err.Error(), "Invalid include parameter") { - return taskStages, generalError("Failed to retrieve run", err) + return taskStages, b.generalError("Failed to retrieve run", err) } } @@ -64,7 +64,7 @@ func (b *Cloud) getTaskStageWithAllOptions(ctx *IntegrationContext, stageID stri } stage, err := b.client.TaskStages.Read(ctx.StopContext, stageID, &options) if err != nil { - return nil, generalError("Failed to retrieve task stage", err) + return nil, b.generalError("Failed to retrieve task stage", err) } else { return stage, nil } @@ -94,7 +94,7 @@ func (b *Cloud) runTaskStage(ctx *IntegrationContext, output IntegrationOutputWr } stage, err := b.client.TaskStages.Read(ctx.StopContext, stageID, &options) if err != nil { - return false, generalError("Failed to retrieve task stage", err) + return false, b.generalError("Failed to retrieve task stage", err) } switch stage.Status { @@ -198,7 +198,7 @@ func (b *Cloud) processStageOverrides(context *IntegrationContext, output Integr if err != errRunOverridden { if _, err = b.client.TaskStages.Override(context.StopContext, taskStageID, tfe.TaskStageOverrideOptions{}); err != nil { - return false, generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) + return false, b.generalError(fmt.Sprintf("Failed to override policy check.\n%s", runURL), err) } else { return true, nil } diff --git a/internal/cloud/test.go b/internal/cloud/test.go index 718f91c66a..5259361f30 100644 --- a/internal/cloud/test.go +++ b/internal/cloud/test.go @@ -170,7 +170,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { configurationVersion, err := client.ConfigurationVersions.CreateForRegistryModule(runner.StoppedCtx, id) if err != nil { - diags = diags.Append(generalError("Failed to create configuration version", err)) + diags = diags.Append(runner.generalError("Failed to create configuration version", err)) return moduletest.Error, diags } @@ -179,7 +179,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { } if err := client.ConfigurationVersions.Upload(runner.StoppedCtx, configurationVersion.UploadURL, configDirectory); err != nil { - diags = diags.Append(generalError("Failed to upload configuration version", err)) + diags = diags.Append(runner.generalError("Failed to upload configuration version", err)) return moduletest.Error, diags } @@ -216,7 +216,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { run, err := client.TestRuns.Create(context.Background(), opts) if err != nil { - diags = diags.Append(generalError("Failed to create test run", err)) + diags = diags.Append(runner.generalError("Failed to create test run", err)) return moduletest.Error, diags } @@ -235,7 +235,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { for i := 0; !completed; i++ { run, err := client.TestRuns.Read(context.Background(), id, run.ID) if err != nil { - diags = diags.Append(generalError("Failed to retrieve test run", err)) + diags = diags.Append(runner.generalError("Failed to retrieve test run", err)) return // exit early } @@ -277,7 +277,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { // Refresh the run now we know it is finished. run, err = client.TestRuns.Read(context.Background(), id, run.ID) if err != nil { - diags = diags.Append(generalError("Failed to retrieve completed test run", err)) + diags = diags.Append(runner.generalError("Failed to retrieve completed test run", err)) return moduletest.Error, diags } @@ -483,7 +483,7 @@ func (runner *TestSuiteRunner) renderLogs(client *tfe.Client, run *tfe.TestRun, logs, err := client.TestRuns.Logs(context.Background(), moduleId, run.ID) if err != nil { - diags = diags.Append(generalError("Failed to retrieve logs", err)) + diags = diags.Append(runner.generalError("Failed to retrieve logs", err)) return diags } @@ -497,7 +497,7 @@ func (runner *TestSuiteRunner) renderLogs(client *tfe.Client, run *tfe.TestRun, l, isPrefix, err = reader.ReadLine() if err != nil { if err != io.EOF { - diags = diags.Append(generalError("Failed to read logs", err)) + diags = diags.Append(runner.generalError("Failed to read logs", err)) return diags } next = false @@ -601,3 +601,35 @@ func (runner *TestSuiteRunner) renderLogs(client *tfe.Client, run *tfe.TestRun, return diags } + +func (runner *TestSuiteRunner) generalError(msg string, err error) error { + var diags tfdiags.Diagnostics + + if urlErr, ok := err.(*url.Error); ok { + err = urlErr.Err + } + + switch err { + case context.Canceled: + return err + case tfe.ErrResourceNotFound: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + fmt.Sprintf("%s: %v", msg, err), + fmt.Sprintf("For security, %s return '404 Not Found' responses for resources\n", runner.appName)+ + "for resources that a user doesn't have access to, in addition to resources that\n"+ + "do not exist. If the resource does exist, please check the permissions of the provided token.", + )) + return diags.Err() + default: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + fmt.Sprintf("%s: %v", msg, err), + fmt.Sprintf(`%s returned an unexpected error. Sometimes `, runner.appName)+ + `this is caused by network connection problems, in which case you could retry `+ + `the command. If the issue persists please open a support ticket to get help `+ + `resolving the problem.`, + )) + return diags.Err() + } +}