From 0fb6806f3b83b91174d6a9f3ac4748cd79bcfea8 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Mon, 29 Apr 2024 17:50:24 -0700 Subject: [PATCH] sg/msp: warn user if sg-msp lockfile differs from current version (#62134) In https://github.com/sourcegraph/managed-services/pull/1288 I'm introducing granular, per-category version locks for https://github.com/sourcegraph/managed-services/issues/599. This change adds warnings that are shown to the user on version mismatches. This may cause some friction with users, but for now, let's just deal with this on a case-by-case basis - our primary goal right now is to build a process for https://github.com/sourcegraph/managed-services/issues/599 by introducing more granular version locks. Requires https://github.com/sourcegraph/sourcegraph/pull/62176 so that we can access the same version format as the one used in our lockfiles. Details on our locking strategy is here: https://sourcegraph.notion.site/Deploying-new-versions-of-MSP-1808e7e45bd54f419dd93af542d99238#58dabe4992754ca18ed39bc212ccbbba ## Test plan ``` sg msp generate -all ``` ![image](https://github.com/sourcegraph/sourcegraph/assets/23356519/d5c19821-7a26-4a91-a55e-1de03de2912b) --- dev/sg/msp/helpers.go | 45 ++++++++++++++++++++++++++++++++++++++++- dev/sg/msp/repo/repo.go | 43 +++++++++++++++++++++++++++++++++++++++ dev/sg/msp/sg_msp.go | 9 +++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/dev/sg/msp/helpers.go b/dev/sg/msp/helpers.go index 29c0ed8f92b..64beefc61a7 100644 --- a/dev/sg/msp/helpers.go +++ b/dev/sg/msp/helpers.go @@ -7,6 +7,7 @@ import ( "path/filepath" "slices" "strings" + "sync" "github.com/urfave/cli/v2" "golang.org/x/exp/maps" @@ -152,7 +153,44 @@ func syncEnvironmentWorkspaces(c *cli.Context, tfc *terraformcloud.Client, servi return std.Out.WriteMarkdown(summary.String()) } +type toolingLockfileChecker struct { + version string + categories map[spec.EnvironmentCategory]*sync.Once +} + +// checkCategoryVersion performs warning checks for the given environment category's +// tooling version. +// +// Requires UseManagedServicesRepo. +func (c *toolingLockfileChecker) checkCategoryVersion(out *std.Output, category spec.EnvironmentCategory) { + var categoryOnce *sync.Once + if o, ok := c.categories[category]; ok { + categoryOnce = o + } else { + categoryOnce = &sync.Once{} + c.categories[category] = categoryOnce + } + + categoryOnce.Do(func() { + lockedSgVersion, err := msprepo.ToolingLockfileVersion(category) + if err != nil { + out.WriteWarningf("Unable to determine locked 'sg' version for category %q: %s", + category, err.Error()) + } else if lockedSgVersion != c.version { + out.WriteWarningf(`Lockfile for category %q declares 'sg' version %q, you are using %q - generated outputs may differ from what is expected. +If there is a diff in the generated output, try running the following:`, + category, lockedSgVersion, c.version) + _ = out.WriteCode("bash", fmt.Sprintf( + "sg update -release %q &&\n SG_SKIP_AUTO_UPDATE=true sg msp generate -all -category %q", + lockedSgVersion, string(category))) + } + }) +} + type generateTerraformOptions struct { + // tooling is used to validate the current tooling version matches what + // is expected, and warn the user if there is a mismatch. + tooling *toolingLockfileChecker // targetEnv generates the specified env only, otherwise generates all targetEnv string // targetCategory generates the specified category only @@ -188,6 +226,10 @@ func generateTerraform(service *spec.Spec, opts generateTerraformOptions) error continue } + // Check tooling version and emit warnings + opts.tooling.checkCategoryVersion(std.Out, env.Category) + + // Then, start our actual work pending := std.Out.Pending(output.StylePending.Linef( "[%s] Preparing Terraform for %q environment %q", serviceID, env.Category, env.ID)) renderer := managedservicesplatform.Renderer{ @@ -262,7 +304,8 @@ func generateTerraform(service *spec.Spec, opts generateTerraformOptions) error } pending.Complete(output.Styledf(output.StyleSuccess, - "[%s] Infrastructure assets generated in %q!", serviceID, renderer.OutputDir)) + "[%s] Category %q environment %q infrastructure assets generated!", + serviceID, env.Category, env.ID)) } return nil diff --git a/dev/sg/msp/repo/repo.go b/dev/sg/msp/repo/repo.go index 30e59d96281..08954e97a25 100644 --- a/dev/sg/msp/repo/repo.go +++ b/dev/sg/msp/repo/repo.go @@ -2,9 +2,11 @@ package repo import ( "context" + "fmt" "io/fs" "os" "path/filepath" + "strings" "github.com/urfave/cli/v2" @@ -13,6 +15,7 @@ import ( "github.com/sourcegraph/sourcegraph/dev/managedservicesplatform/spec" "github.com/sourcegraph/sourcegraph/dev/sg/internal/std" "github.com/sourcegraph/sourcegraph/lib/cliutil/completions" + "github.com/sourcegraph/sourcegraph/lib/errors" ) // UseManagedServicesRepo is a cli.BeforeFunc that enforces that we are in the @@ -94,19 +97,59 @@ func ServicesAndEnvironmentsCompletion(additionalArgs ...func(args cli.Args) (op return completions.CompletePositionalArgs(append(args, additionalArgs...)...) } +// ServiceYAMLPath returns the relative path to the service.yaml file for the +// given service. +// +// Requires UseManagedServicesRepo to be relevant. func ServiceYAMLPath(serviceID string) string { return filepath.Join("services", serviceID, "service.yaml") } +// ServiceEnvironmentYAMLPath returns the relative path to the Terraform Stacks +// directory for the given service environment's stack. +// +// Requires UseManagedServicesRepo to be relevant. func ServiceStackPath(serviceID, envID, stackID string) string { return filepath.Join("services", serviceID, "terraform", envID, "stacks", stackID) } +// ServiceStackTerraformPath returns the relative path to the Terraform CDKTF +// configuration file for the given service environment's stack. +// +// Requires UseManagedServicesRepo to be relevant. func ServiceStackCDKTFPath(serviceID, envID, stackID string) string { return filepath.Join(ServiceStackPath(serviceID, envID, stackID), "cdk.tf.json") } +// ToolingLockfileVersion retrieves the contents of the sg-msp lockfile for the +// given category (./sg-msp-$CATEGORY.lock). +// +// Requires UseManagedServicesRepo. +func ToolingLockfileVersion(category spec.EnvironmentCategory) (string, error) { + lockfile := fmt.Sprintf("sg-msp-%s.lock", category) + if category == "" { + lockfile = "sg-msp.lock" // fallback to the old format (no category) + } + + contents, err := os.ReadFile(lockfile) + if err != nil { + // Try to fall back to category-less-lockfile + if v, fallbackErr := ToolingLockfileVersion(""); fallbackErr == nil { + return v, nil + } + // Otherwise, return the error we got. + return "", errors.Wrapf(err, "read %q", lockfile) + } + + version := strings.TrimSpace(string(contents)) + if len(version) == 0 { + return "", errors.Newf("empty %q", lockfile) + } + return version, nil +} + // GitRevision gets the revision of the managed-services repository. +// // Requires UseManagedServicesRepo. func GitRevision(ctx context.Context) (string, error) { return run.Cmd(ctx, "git rev-parse HEAD"). diff --git a/dev/sg/msp/sg_msp.go b/dev/sg/msp/sg_msp.go index 294c438f675..ae020dadc11 100644 --- a/dev/sg/msp/sg_msp.go +++ b/dev/sg/msp/sg_msp.go @@ -8,6 +8,7 @@ import ( "slices" "sort" "strings" + "sync" "github.com/urfave/cli/v2" "golang.org/x/exp/maps" @@ -320,6 +321,11 @@ sg msp generate -all -category=test } } + toolingChecker := &toolingLockfileChecker{ + version: c.App.Version, + categories: make(map[spec.EnvironmentCategory]*sync.Once), + } + // Generate a specific service environment if '-all' is not provided if !generateAll { std.Out.WriteNoticef("Generating a specific service environment...") @@ -328,6 +334,7 @@ sg msp generate -all -category=test return err } return generateTerraform(svc, generateTerraformOptions{ + tooling: toolingChecker, targetEnv: env.ID, stableGenerate: stableGenerate, }) @@ -341,6 +348,7 @@ sg msp generate -all -category=test return err } return generateTerraform(svc, generateTerraformOptions{ + tooling: toolingChecker, stableGenerate: stableGenerate, targetCategory: generateCategory, }) @@ -360,6 +368,7 @@ sg msp generate -all -category=test return err } if err := generateTerraform(s, generateTerraformOptions{ + tooling: toolingChecker, stableGenerate: stableGenerate, targetCategory: generateCategory, }); err != nil {