diff --git a/cmd/cliflags/flags.go b/cmd/cliflags/flags.go index 66fa9487..9d56cbe7 100644 --- a/cmd/cliflags/flags.go +++ b/cmd/cliflags/flags.go @@ -17,6 +17,7 @@ const ( PortFlag = "port" ProjectFlag = "project" RoleFlag = "role" + SyncOnceFlag = "sync-once" AccessTokenFlagDescription = "LaunchDarkly access token with write-level access" AnalyticsOptOutDescription = "Opt out of analytics tracking" @@ -27,6 +28,7 @@ const ( OutputFlagDescription = "Command response output format in either JSON or plain text" PortFlagDescription = "Port for the dev server to run on" ProjectFlagDescription = "Default project key" + SyncOnceFlagDescription = "Only sync new projects. Existing projects will not be resynced on startup" ) func AllFlagsHelp() map[string]string { @@ -40,5 +42,6 @@ func AllFlagsHelp() map[string]string { OutputFlag: OutputFlagDescription, PortFlag: PortFlagDescription, ProjectFlag: ProjectFlagDescription, + SyncOnceFlag: SyncOnceFlagDescription, } } diff --git a/cmd/config/testdata/help.golden b/cmd/config/testdata/help.golden index 6745e12a..ab035b99 100644 --- a/cmd/config/testdata/help.golden +++ b/cmd/config/testdata/help.golden @@ -10,6 +10,7 @@ Supported settings: - `output`: Command response output format in either JSON or plain text - `port`: Port for the dev server to run on - `project`: Default project key +- `sync-once`: Only sync new projects. Existing projects will not be resynced on startup Usage: ldcli config [flags] diff --git a/cmd/dev_server/start_server.go b/cmd/dev_server/start_server.go index f990cba6..d14ceb38 100644 --- a/cmd/dev_server/start_server.go +++ b/cmd/dev_server/start_server.go @@ -43,6 +43,9 @@ func NewStartServerCmd(client dev_server.Client) *cobra.Command { cmd.Flags().String(OverrideFlag, "", `Stringified JSON representation of flag overrides ex. {"flagName": true, "stringFlagName": "test" }`) _ = viper.BindPFlag(OverrideFlag, cmd.Flags().Lookup(OverrideFlag)) + cmd.Flags().Bool(cliflags.SyncOnceFlag, false, cliflags.SyncOnceFlagDescription) + _ = viper.BindPFlag(cliflags.SyncOnceFlag, cmd.Flags().Lookup(cliflags.SyncOnceFlag)) + return cmd } @@ -58,6 +61,7 @@ func startServer(client dev_server.Client) func(*cobra.Command, []string) error Enabled: true, ProjectKey: viper.GetString(cliflags.ProjectFlag), EnvKey: viper.GetString(SourceEnvironmentFlag), + SyncOnce: viper.GetBool(cliflags.SyncOnceFlag), } if viper.IsSet(ContextFlag) { var c ldcontext.Context diff --git a/internal/dev_server/model/sync.go b/internal/dev_server/model/sync.go index dd0f323a..946ffefb 100644 --- a/internal/dev_server/model/sync.go +++ b/internal/dev_server/model/sync.go @@ -18,6 +18,7 @@ type InitialProjectSettings struct { EnvKey string Context *ldcontext.Context `json:"context,omitempty"` Overrides map[string]FlagValue `json:"overrides,omitempty"` + SyncOnce bool } func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) error { @@ -29,17 +30,23 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e var project Project project, createError := CreateProject(ctx, settings.ProjectKey, settings.EnvKey, settings.Context) if createError != nil { - if errors.Is(createError, ErrAlreadyExists) { - log.Printf("Project [%s] exists, refreshing data", settings.ProjectKey) - var updateErr error - project, updateErr = UpdateProject(ctx, settings.ProjectKey, settings.Context, &settings.EnvKey) - if updateErr != nil { - return updateErr - } - - } else { + if !errors.Is(createError, ErrAlreadyExists) { return createError } + + // If set, don't resync and don't apply overrides because whatever you have locally + // is already set up with what you want. + if settings.SyncOnce { + log.Printf("Project [%s] exists, but --sync-once flag is set, skipping refresh", settings.ProjectKey) + return nil + } + + log.Printf("Project [%s] exists, refreshing data", settings.ProjectKey) + var updateErr error + project, updateErr = UpdateProject(ctx, settings.ProjectKey, settings.Context, &settings.EnvKey) + if updateErr != nil { + return updateErr + } } for flagKey, val := range settings.Overrides { _, err := UpsertOverride(ctx, settings.ProjectKey, flagKey, val) diff --git a/internal/dev_server/model/sync_test.go b/internal/dev_server/model/sync_test.go index 68c49f44..dfc53895 100644 --- a/internal/dev_server/model/sync_test.go +++ b/internal/dev_server/model/sync_test.go @@ -170,4 +170,22 @@ func TestInitialSync(t *testing.T) { assert.NoError(t, err) }) + t.Run("If SyncOnce is set and the project already exists, return early", func(t *testing.T) { + api.EXPECT().GetSdkKey(gomock.Any(), projKey, sourceEnvKey).Return(sdkKey, nil) + sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), sdkKey).Return(allFlagsState, nil) + api.EXPECT().GetAllFlags(gomock.Any(), projKey).Return(allFlags, nil) + store.EXPECT().InsertProject(gomock.Any(), gomock.Any()).Return(model.ErrAlreadyExists) + + input := model.InitialProjectSettings{ + Enabled: true, + ProjectKey: projKey, + EnvKey: sourceEnvKey, + Context: nil, + SyncOnce: true, + } + err := model.CreateOrSyncProject(ctx, input) + + assert.NoError(t, err) + }) + }