diff --git a/cloud/parameterstore/fakeparameter/db.go b/cloud/parameterstore/fakeparameter/db.go index 10972ace955..da921342f36 100644 --- a/cloud/parameterstore/fakeparameter/db.go +++ b/cloud/parameterstore/fakeparameter/db.go @@ -22,6 +22,8 @@ var ( // FindOneID finds a single fake parameter by its name. func FindOneID(ctx context.Context, id string) (*FakeParameter, error) { + checkTestingEnvironment() + var p FakeParameter err := evergreen.GetEnvironment().DB().Collection(Collection).FindOne(ctx, bson.M{NameKey: id}).Decode(&p) if adb.ResultsNotFound(err) { @@ -35,6 +37,8 @@ func FindOneID(ctx context.Context, id string) (*FakeParameter, error) { // FindByIDs finds one or more fake parameters by their names. func FindByIDs(ctx context.Context, ids ...string) ([]FakeParameter, error) { + checkTestingEnvironment() + if len(ids) == 0 { return nil, nil } @@ -52,6 +56,8 @@ func FindByIDs(ctx context.Context, ids ...string) ([]FakeParameter, error) { // DeleteOneID deletes a fake parameter with the given ID. Returns whether any // matching parameter was deleted. func DeleteOneID(ctx context.Context, id string) (bool, error) { + checkTestingEnvironment() + res, err := evergreen.GetEnvironment().DB().Collection(Collection).DeleteOne(ctx, bson.M{NameKey: id}) if err != nil { return false, errors.Wrap(err, "deleting fake parameter by ID") diff --git a/cloud/parameterstore/fakeparameter/fake_parameter.go b/cloud/parameterstore/fakeparameter/fake_parameter.go index 3738078bca8..211dedab216 100644 --- a/cloud/parameterstore/fakeparameter/fake_parameter.go +++ b/cloud/parameterstore/fakeparameter/fake_parameter.go @@ -18,7 +18,10 @@ import ( // to "test". var ExecutionEnvironmentType = "production" -func init() { +// checkTestingEnvironment performs a safety check to verify that logic in this +// package is only called in a testing environment. If this is called in a +// non-testing environment, the process will exit with a fatal error. +func checkTestingEnvironment() { if ExecutionEnvironmentType != "test" { grip.EmergencyFatal(message.Fields{ "message": "fake Parameter Store testing code called in a non-testing environment", @@ -41,6 +44,8 @@ type FakeParameter struct { // Insert inserts a single parameter into the fake parameter store. func (p *FakeParameter) Insert(ctx context.Context) error { + checkTestingEnvironment() + _, err := evergreen.GetEnvironment().DB().Collection(Collection).InsertOne(ctx, p) return err } @@ -48,6 +53,8 @@ func (p *FakeParameter) Insert(ctx context.Context) error { // Upsert inserts a single parameter into the fake parameter store or replaces // an one if one with the same ID already exists. func (p *FakeParameter) Upsert(ctx context.Context) error { + checkTestingEnvironment() + _, err := evergreen.GetEnvironment().DB().Collection(Collection).ReplaceOne(ctx, bson.M{NameKey: p.Name}, p, options.Replace().SetUpsert(true)) return err } diff --git a/cloud/parameterstore/fakeparameter/ssm_client.go b/cloud/parameterstore/fakeparameter/ssm_client.go index 73afb54bba0..28fb0563e4f 100644 --- a/cloud/parameterstore/fakeparameter/ssm_client.go +++ b/cloud/parameterstore/fakeparameter/ssm_client.go @@ -20,11 +20,14 @@ type FakeSSMClient struct{} // NewFakeSSMClient returns a fake SSM client implementation backed by the DB. // This should only be used in testing. func NewFakeSSMClient() *FakeSSMClient { + checkTestingEnvironment() return &FakeSSMClient{} } // PutParameter inserts a parameter into the fake parameter store. func (c *FakeSSMClient) PutParameter(ctx context.Context, input *ssm.PutParameterInput) (*ssm.PutParameterOutput, error) { + checkTestingEnvironment() + name := utility.FromStringPtr(input.Name) value := utility.FromStringPtr(input.Value) catcher := grip.NewBasicCatcher() @@ -55,6 +58,8 @@ func (c *FakeSSMClient) PutParameter(ctx context.Context, input *ssm.PutParamete // DeleteParametersSimple is the same as DeleteParameters but only returns the // deleted parameter names. func (c *FakeSSMClient) DeleteParametersSimple(ctx context.Context, input *ssm.DeleteParametersInput) ([]string, error) { + checkTestingEnvironment() + output, err := c.DeleteParameters(ctx, input) if err != nil { return nil, err @@ -70,6 +75,8 @@ func (c *FakeSSMClient) DeleteParametersSimple(ctx context.Context, input *ssm.D // DeleteParameters deletes the specified parameters from the fake parameter // store. func (c *FakeSSMClient) DeleteParameters(ctx context.Context, input *ssm.DeleteParametersInput) ([]*ssm.DeleteParametersOutput, error) { + checkTestingEnvironment() + names := input.Names if len(names) == 0 { return nil, nil @@ -98,6 +105,8 @@ func (c *FakeSSMClient) DeleteParameters(ctx context.Context, input *ssm.DeleteP // GetParametersSimple is the same as GetParameters but only returns the // parameters. func (c *FakeSSMClient) GetParametersSimple(ctx context.Context, input *ssm.GetParametersInput) ([]ssmTypes.Parameter, error) { + checkTestingEnvironment() + outputs, err := c.GetParameters(ctx, input) if err != nil { return nil, err @@ -113,6 +122,8 @@ func (c *FakeSSMClient) GetParametersSimple(ctx context.Context, input *ssm.GetP // GetParameters retrieves the specified parameters from the fake parameter // store. func (c *FakeSSMClient) GetParameters(ctx context.Context, input *ssm.GetParametersInput) ([]*ssm.GetParametersOutput, error) { + checkTestingEnvironment() + names := input.Names if len(names) == 0 { return nil, nil diff --git a/config.go b/config.go index bf8b78b6e88..18df33f7135 100644 --- a/config.go +++ b/config.go @@ -33,7 +33,7 @@ var ( // ClientVersion is the commandline version string used to control updating // the CLI. The format is the calendar date (YYYY-MM-DD). - ClientVersion = "2025-01-09a" + ClientVersion = "2025-01-09b" // Agent version to control agent rollover. The format is the calendar date // (YYYY-MM-DD). diff --git a/model/github_app_auth.go b/model/github_app_auth.go index 25a51ef90dd..16486705e37 100644 --- a/model/github_app_auth.go +++ b/model/github_app_auth.go @@ -26,7 +26,7 @@ func githubAppCheckAndRunParameterStoreOp(ctx context.Context, appAuth *githubap if err != nil { return errors.Wrapf(err, "checking project ref '%s' to verify if GitHub app should use Parameter Store", appAuth.Id) } - isPSEnabled, err := isParameterStoreEnabledForProject(ctx, ref) + isPSEnabled, err := isParameterStoreEnabledForProject(ctx, ref, true) if err != nil { return errors.Wrapf(err, "checking if Parameter Store is enabled for project '%s'", appAuth.Id) } diff --git a/model/project_vars.go b/model/project_vars.go index 21dcdb3b0dc..3a180383ddd 100644 --- a/model/project_vars.go +++ b/model/project_vars.go @@ -610,10 +610,11 @@ func getUpdatedParamMappings(original ParameterMappings, upserted, deleted map[s } // isParameterStoreEnabledForProject checks if Parameter Store is enabled for a -// project. +// project. If ignoreProjectFeatureFlag is enabled, the project will use +// Parameter Store regardless of the project-level feature flag. // TODO (DEVPROD-11882): remove feature flag checks once all project vars are // using Parameter Store and the rollout is stable. -func isParameterStoreEnabledForProject(ctx context.Context, ref *ProjectRef) (bool, error) { +func isParameterStoreEnabledForProject(ctx context.Context, ref *ProjectRef, ignoreProjectFeatureFlag bool) (bool, error) { flags, err := evergreen.GetServiceFlags(ctx) if err != nil { return false, errors.Wrap(err, "getting service flags") @@ -622,6 +623,10 @@ func isParameterStoreEnabledForProject(ctx context.Context, ref *ProjectRef) (bo return false, nil } + if ignoreProjectFeatureFlag { + return true, nil + } + if ref == nil { return false, errors.Errorf("ref is nil") } @@ -901,7 +906,7 @@ func (projectVars *ProjectVars) checkAndRunParameterStoreOp(ctx context.Context, "project_id": projectVars.Id, "epic": "DEVPROD-5552", })) - isPSEnabled, err := isParameterStoreEnabledForProject(ctx, ref) + isPSEnabled, err := isParameterStoreEnabledForProject(ctx, ref, false) grip.Error(message.WrapError(err, message.Fields{ "message": "could not check if Parameter Store is enabled for project; assuming it's disabled and will not use Parameter Store", "op": opName, diff --git a/operations/flags.go b/operations/flags.go index 60f342c766d..c3fb03d64f9 100644 --- a/operations/flags.go +++ b/operations/flags.go @@ -44,6 +44,7 @@ const ( syncTasksFlagName = "sync_tasks" syncTimeoutFlagName = "sync_timeout" tasksFlagName = "tasks" + testingEnvFlagName = "testing-env" traceEndpointFlagName = "trace_endpoint" uncommittedChangesFlag = "uncommitted" variantsFlagName = "variants" @@ -89,7 +90,12 @@ func serviceConfigFlags(flags ...cli.Flag) []cli.Flag { cli.BoolFlag{ Name: overwriteConfFlagName, Usage: "overwrite the configuration in the DB with the file", - }) + }, + cli.BoolFlag{ + Name: testingEnvFlagName, + Usage: "if true, run Evergreen service as a testing environment", + }, + ) } func addProjectFlag(flags ...cli.Flag) []cli.Flag { diff --git a/operations/service_deploy_smoke.go b/operations/service_deploy_smoke.go index c0235f6087e..f0d3baa166c 100644 --- a/operations/service_deploy_smoke.go +++ b/operations/service_deploy_smoke.go @@ -41,7 +41,7 @@ func startLocalEvergreen() cli.Command { return errors.Wrap(err, "getting working directory") } binary := filepath.Join(wd, "clients", runtime.GOOS+"_"+runtime.GOARCH, "evergreen") - if err := smokeRunBinary(exit, "web.service", wd, binary, "service", "web", "--db", "evergreen_local"); err != nil { + if err := smokeRunBinary(exit, "web.service", wd, binary, "service", "web", "--db", "evergreen_local", "--testing-env"); err != nil { return errors.Wrap(err, "running web service") } <-exit diff --git a/operations/service_web.go b/operations/service_web.go index 1b2910cda00..c6c67ae2c9e 100644 --- a/operations/service_web.go +++ b/operations/service_web.go @@ -11,6 +11,8 @@ import ( "github.com/evergreen-ci/evergreen" "github.com/evergreen-ci/evergreen/auth" + "github.com/evergreen-ci/evergreen/cloud/parameterstore" + "github.com/evergreen-ci/evergreen/cloud/parameterstore/fakeparameter" "github.com/evergreen-ci/evergreen/service" "github.com/evergreen-ci/gimlet" "github.com/evergreen-ci/utility" @@ -74,6 +76,27 @@ func startWebService() cli.Command { db := parseDB(c) env, err := evergreen.NewEnvironment(ctx, confPath, versionID, clientS3Bucket, db, tp) grip.EmergencyFatal(errors.Wrap(err, "configuring application environment")) + + if c.Bool(testingEnvFlagName) { + // If running in a testing environment (e.g. local Evergreen), + // use a fake implementation of Parameter Store since testing + // environments won't have access to a real Parameter Store + // instance. + fakeparameter.ExecutionEnvironmentType = "test" + + opts := parameterstore.ParameterManagerOptions{ + PathPrefix: env.Settings().ParameterStore.Prefix, + CachingEnabled: true, + SSMClient: fakeparameter.NewFakeSSMClient(), + DB: env.DB(), + } + pm, err := parameterstore.NewParameterManager(ctx, opts) + if err != nil { + return errors.Wrap(err, "creating parameter manager") + } + env.SetParameterManager(pm) + } + evergreen.SetEnvironment(env) if c.Bool(overwriteConfFlagName) { grip.EmergencyFatal(errors.Wrap(env.SaveConfig(ctx), "saving config"))