From 6d55e29531c4daab45446557d5c42766c035815f Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Mon, 3 Feb 2025 12:01:49 -0500 Subject: [PATCH 1/6] Skip syncing on start if flag set --- cmd/cliflags/flags.go | 2 ++ cmd/dev_server/start_server.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/cliflags/flags.go b/cmd/cliflags/flags.go index 66fa9487..5cbdc7bd 100644 --- a/cmd/cliflags/flags.go +++ b/cmd/cliflags/flags.go @@ -17,6 +17,7 @@ const ( PortFlag = "port" ProjectFlag = "project" RoleFlag = "role" + SyncOnStartFlag = "should-sync" 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" + SyncOnStartFlagDescription = "Sync local flags with Launchdarkly on server startup." ) func AllFlagsHelp() map[string]string { diff --git a/cmd/dev_server/start_server.go b/cmd/dev_server/start_server.go index f990cba6..350d706b 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.SyncOnStartFlag, true, cliflags.SyncOnStartFlagDescription) + _ = viper.BindPFlag(cliflags.SyncOnStartFlag, cmd.Flags().Lookup(cliflags.SyncOnStartFlag)) + return cmd } @@ -55,7 +58,7 @@ func startServer(client dev_server.Client) func(*cobra.Command, []string) error if viper.IsSet(cliflags.ProjectFlag) && viper.IsSet(SourceEnvironmentFlag) { initialSetting = model.InitialProjectSettings{ - Enabled: true, + Enabled: viper.GetBool(cliflags.SyncOnStartFlag), ProjectKey: viper.GetString(cliflags.ProjectFlag), EnvKey: viper.GetString(SourceEnvironmentFlag), } From f2b610752f4afeb04e0ffcc92012aa1881f5ae93 Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Mon, 3 Feb 2025 15:46:21 -0500 Subject: [PATCH 2/6] Skip sync specifically --- cmd/cliflags/flags.go | 5 +++-- cmd/dev_server/start_server.go | 7 ++++--- internal/dev_server/model/sync.go | 12 ++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cmd/cliflags/flags.go b/cmd/cliflags/flags.go index 5cbdc7bd..9d56cbe7 100644 --- a/cmd/cliflags/flags.go +++ b/cmd/cliflags/flags.go @@ -17,7 +17,7 @@ const ( PortFlag = "port" ProjectFlag = "project" RoleFlag = "role" - SyncOnStartFlag = "should-sync" + SyncOnceFlag = "sync-once" AccessTokenFlagDescription = "LaunchDarkly access token with write-level access" AnalyticsOptOutDescription = "Opt out of analytics tracking" @@ -28,7 +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" - SyncOnStartFlagDescription = "Sync local flags with Launchdarkly on server startup." + SyncOnceFlagDescription = "Only sync new projects. Existing projects will not be resynced on startup" ) func AllFlagsHelp() map[string]string { @@ -42,5 +42,6 @@ func AllFlagsHelp() map[string]string { OutputFlag: OutputFlagDescription, PortFlag: PortFlagDescription, ProjectFlag: ProjectFlagDescription, + SyncOnceFlag: SyncOnceFlagDescription, } } diff --git a/cmd/dev_server/start_server.go b/cmd/dev_server/start_server.go index 350d706b..d14ceb38 100644 --- a/cmd/dev_server/start_server.go +++ b/cmd/dev_server/start_server.go @@ -43,8 +43,8 @@ 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.SyncOnStartFlag, true, cliflags.SyncOnStartFlagDescription) - _ = viper.BindPFlag(cliflags.SyncOnStartFlag, cmd.Flags().Lookup(cliflags.SyncOnStartFlag)) + cmd.Flags().Bool(cliflags.SyncOnceFlag, false, cliflags.SyncOnceFlagDescription) + _ = viper.BindPFlag(cliflags.SyncOnceFlag, cmd.Flags().Lookup(cliflags.SyncOnceFlag)) return cmd } @@ -58,9 +58,10 @@ func startServer(client dev_server.Client) func(*cobra.Command, []string) error if viper.IsSet(cliflags.ProjectFlag) && viper.IsSet(SourceEnvironmentFlag) { initialSetting = model.InitialProjectSettings{ - Enabled: viper.GetBool(cliflags.SyncOnStartFlag), + 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..0eb84b22 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,16 +30,19 @@ 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) { + if !errors.Is(createError, ErrAlreadyExists) { + return createError + } + + if settings.SyncOnce { + log.Printf("Project [%s] exists, but --sync-once flag is set, skipping refresh", settings.ProjectKey) + } else { 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 { - return createError } } for flagKey, val := range settings.Overrides { From d8bf3be9eb5033fe345a6027039617f9bca5899b Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Mon, 3 Feb 2025 16:02:52 -0500 Subject: [PATCH 3/6] remove need to use project --- internal/dev_server/model/sync.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/dev_server/model/sync.go b/internal/dev_server/model/sync.go index 0eb84b22..519272fd 100644 --- a/internal/dev_server/model/sync.go +++ b/internal/dev_server/model/sync.go @@ -27,8 +27,7 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e } log.Printf("Initial project [%s] with env [%s]", settings.ProjectKey, settings.EnvKey) - var project Project - project, createError := CreateProject(ctx, settings.ProjectKey, settings.EnvKey, settings.Context) + _, createError := CreateProject(ctx, settings.ProjectKey, settings.EnvKey, settings.Context) if createError != nil { if !errors.Is(createError, ErrAlreadyExists) { return createError @@ -39,7 +38,7 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e } else { log.Printf("Project [%s] exists, refreshing data", settings.ProjectKey) var updateErr error - project, updateErr = UpdateProject(ctx, settings.ProjectKey, settings.Context, &settings.EnvKey) + _, updateErr = UpdateProject(ctx, settings.ProjectKey, settings.Context, &settings.EnvKey) if updateErr != nil { return updateErr } @@ -52,6 +51,6 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e } } - log.Printf("Successfully synced Initial project [%s]", project.Key) + log.Printf("Successfully synced Initial project [%s]", settings.ProjectKey) return nil } From 19a46f0ba51ab794a38ee3e5419c883a6855ca0f Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Tue, 4 Feb 2025 08:33:21 -0500 Subject: [PATCH 4/6] Return early --- internal/dev_server/model/sync.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/dev_server/model/sync.go b/internal/dev_server/model/sync.go index 519272fd..122dfa80 100644 --- a/internal/dev_server/model/sync.go +++ b/internal/dev_server/model/sync.go @@ -27,7 +27,8 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e } log.Printf("Initial project [%s] with env [%s]", settings.ProjectKey, settings.EnvKey) - _, createError := CreateProject(ctx, settings.ProjectKey, settings.EnvKey, settings.Context) + var project Project + project, createError := CreateProject(ctx, settings.ProjectKey, settings.EnvKey, settings.Context) if createError != nil { if !errors.Is(createError, ErrAlreadyExists) { return createError @@ -35,13 +36,14 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e if settings.SyncOnce { log.Printf("Project [%s] exists, but --sync-once flag is set, skipping refresh", settings.ProjectKey) - } else { - log.Printf("Project [%s] exists, refreshing data", settings.ProjectKey) - var updateErr error - _, updateErr = UpdateProject(ctx, settings.ProjectKey, settings.Context, &settings.EnvKey) - if updateErr != nil { - return updateErr - } + 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 { @@ -51,6 +53,6 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e } } - log.Printf("Successfully synced Initial project [%s]", settings.ProjectKey) + log.Printf("Successfully synced Initial project [%s]", project.Key) return nil } From d290187ae3df3dfc3e66369084f34c1b8a32691b Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Tue, 4 Feb 2025 08:35:28 -0500 Subject: [PATCH 5/6] add comment --- internal/dev_server/model/sync.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/dev_server/model/sync.go b/internal/dev_server/model/sync.go index 122dfa80..946ffefb 100644 --- a/internal/dev_server/model/sync.go +++ b/internal/dev_server/model/sync.go @@ -34,6 +34,8 @@ func CreateOrSyncProject(ctx context.Context, settings InitialProjectSettings) e 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 From 01d3580422b809d7b7d0c0559f76b8e9d19b1034 Mon Sep 17 00:00:00 2001 From: Thomas Varney Date: Tue, 4 Feb 2025 08:48:21 -0500 Subject: [PATCH 6/6] Add test and fix existing one --- cmd/config/testdata/help.golden | 1 + internal/dev_server/model/sync_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) 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/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) + }) + }