Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/manifest/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func NewSyncCommand(clients *shared.ClientFactory) *cobra.Command {
Hidden: true,
Example: style.ExampleCommandsf([]style.ExampleCommand{
{Command: "manifest sync", Meaning: "Sync project manifest with app settings"},
{Command: "manifest sync --force", Meaning: "Push project manifest to app settings without prompting"},
{Command: "manifest sync --force-remote", Meaning: "Pull app settings to project manifest without prompting"},
}),
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -47,6 +49,10 @@ func NewSyncCommand(clients *shared.ClientFactory) *cobra.Command {
style.CommandText("--experiment manifest-sync"),
)
}
if clients.Config.ForceFlag && clients.Config.ForceRemoteFlag {
return slackerror.New(slackerror.ErrMismatchedFlags).
WithMessage("Cannot use both %s and %s flags", style.CommandText("--force"), style.CommandText("--force-remote"))
}
return cmdutil.IsValidProjectDirectory(clients)
},
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -65,5 +71,6 @@ func NewSyncCommand(clients *shared.ClientFactory) *cobra.Command {
return err
},
}
cmd.Flags().BoolVar(&clients.Config.ForceRemoteFlag, "force-remote", false, "use all app settings values without prompting")
return cmd
}
10 changes: 10 additions & 0 deletions cmd/manifest/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ func TestSyncCommand(t *testing.T) {
// the gate itself should pass.
ExpectedErrorStrings: []string{},
},
"errors when both --force and --force-remote are set": {
CmdArgs: []string{"--force-remote"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
cm.AddDefaultMocks()
cf.Config.ExperimentsFlag = []string{string(experiment.ManifestSync)}
cf.Config.LoadExperiments(ctx, cf.IO.PrintDebug)
cf.Config.ForceFlag = true
},
ExpectedErrorStrings: []string{"Cannot use both", "--force", "--force-remote"},
},
}, func(clients *shared.ClientFactory) *cobra.Command {
return NewSyncCommand(clients)
})
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Config struct {
DeprecatedWorkspaceFlag string
DisableTelemetryFlag bool
ForceFlag bool
ForceRemoteFlag bool
LogstashHostResolved string
NoColor bool
RuntimeFlag string
Expand Down
8 changes: 7 additions & 1 deletion internal/manifest/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@ func Sync(ctx context.Context, clients *shared.ClientFactory, app types.App, aut
if err != nil {
return nil, err
}
case clients.Config.ForceRemoteFlag:
merged, err = MergeAllFrom(localManifest.AppManifest, remoteManifest.AppManifest, diffs, MergeAllRemote)
if err != nil {
return nil, err
}
case !clients.IO.IsTTY():
return nil, slackerror.New(slackerror.ErrAppManifestUpdate).
WithRemediation("Run %s interactively to resolve manifest differences, or pass %s to push the project manifest to app settings",
WithRemediation("Run %s interactively to resolve manifest differences, or pass %s to push the project manifest to app settings or %s to pull app settings to project",
style.CommandText("slack manifest sync"),
style.CommandText("--force"),
style.CommandText("--force-remote"),
)
default:
merged, err = resolveInteractively(ctx, clients, localManifest.AppManifest, remoteManifest.AppManifest, diffs)
Expand Down
24 changes: 24 additions & 0 deletions internal/manifest/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,30 @@ func Test_Sync(t *testing.T) {
f.clientsMock.API.AssertCalled(t, "UpdateApp", mock.Anything, "xoxb-test", "A123", mock.Anything, true, true)
})

t.Run("force-remote flag merges all remote and pushes to API", func(t *testing.T) {
f := newSyncTestFixture(t)
f.projectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
f.manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).
Return(localManifest, nil)
f.manifestMock.On("GetManifestRemote", mock.Anything, mock.Anything, mock.Anything).
Return(remoteManifest, nil)
f.clients.Config.ForceRemoteFlag = true
f.clientsMock.API.On("UpdateApp", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(api.UpdateAppResult{}, nil)
f.cacheMock.On("NewManifestHash", mock.Anything, mock.Anything).Return(cache.Hash("newhash"), nil)
f.cacheMock.On("SetManifestHash", mock.Anything, mock.Anything, mock.Anything).Return(nil)
_ = afero.WriteFile(f.fs, "/project/manifest.json", []byte(`{"display_information":{"name":"App"}}`), 0644)

result, err := Sync(f.ctx, f.clients, testApp, testAuth)

require.NoError(t, err)
require.NotNil(t, result)
assert.True(t, result.HasDifferences)
assert.True(t, result.WriteBack.Written)
// Verify remote value was used — the merged manifest should have "Remote" description
assert.Equal(t, "Remote", result.Merged.DisplayInformation.Description)
})

t.Run("API UpdateApp failure is propagated", func(t *testing.T) {
f := newSyncTestFixture(t)
f.projectConfig.On("GetManifestSource", mock.Anything).Return(config.ManifestSourceLocal, nil)
Expand Down
Loading