Skip to content

Commit 710ecda

Browse files
authored
feat(cli): performs dry-runs on deployments (#120)
1 parent 924e6bb commit 710ecda

File tree

7 files changed

+61
-27
lines changed

7 files changed

+61
-27
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ jobs:
162162
earthly_token: ${{ secrets.earthly_token }}
163163

164164
final:
165-
needs: [check, build, package, test, release]
165+
needs: [check, build, package, test, release, deploy]
166166
if: ${{ always() && (contains(needs.*.result, 'failure') || !failure() && !cancelled()) }}
167167
runs-on: ubuntu-latest
168168
steps:

cli/cmd/cmds/deploy/deploy.go renamed to cli/cmd/cmds/deploy/push.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ func (c *PushCmd) Run(ctx run.RunContext) error {
1919
return fmt.Errorf("could not load project: %w", err)
2020
}
2121

22+
var dryrun bool
2223
eh := events.NewDefaultEventHandler(ctx.Logger)
2324
if !eh.Firing(&project, project.GetDeploymentEvents()) && !c.Force {
24-
ctx.Logger.Info("No deployment event is firing, skipping deployment")
25-
return nil
25+
ctx.Logger.Info("No deployment event is firing, performing dry-run")
26+
dryrun = true
2627
}
2728

28-
deployer := deployment.NewGitopsDeployer(&project, &ctx.SecretStore, ctx.Logger)
29+
deployer := deployment.NewGitopsDeployer(&project, &ctx.SecretStore, ctx.Logger, dryrun)
2930
if err := deployer.Load(); err != nil {
3031
return fmt.Errorf("could not load deployer: %w", err)
3132
}

cli/pkg/deployment/gitops.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (g gitRemote) Push(repo *git.Repository, o *git.PushOptions) error {
4545

4646
// GitopsDeployer is a deployer that deploys projects to a GitOps repository.
4747
type GitopsDeployer struct {
48+
dryrun bool
4849
fs billy.Filesystem
4950
repo *git.Repository
5051
kcl KCLRunner
@@ -108,21 +109,29 @@ func (g *GitopsDeployer) Deploy() error {
108109
}
109110
}
110111

111-
changes, err := g.hasChanges()
112-
if err != nil {
113-
return fmt.Errorf("could not check if worktree has changes: %w", err)
114-
} else if !changes {
115-
return ErrNoChanges
116-
}
112+
if !g.dryrun {
113+
changes, err := g.hasChanges()
114+
if err != nil {
115+
return fmt.Errorf("could not check if worktree has changes: %w", err)
116+
} else if !changes {
117+
return ErrNoChanges
118+
}
117119

118-
g.logger.Info("Committing changes", "path", bundlePath)
119-
if err := g.commit(); err != nil {
120-
return fmt.Errorf("could not commit changes: %w", err)
121-
}
120+
g.logger.Info("Committing changes", "path", bundlePath)
121+
if err := g.commit(); err != nil {
122+
return fmt.Errorf("could not commit changes: %w", err)
123+
}
122124

123-
g.logger.Info("Pushing changes")
124-
if err := g.push(); err != nil {
125-
return fmt.Errorf("could not push changes: %w", err)
125+
g.logger.Info("Pushing changes")
126+
if err := g.push(); err != nil {
127+
return fmt.Errorf("could not push changes: %w", err)
128+
}
129+
} else {
130+
g.logger.Info("Dry-run: not committing or pushing changes")
131+
g.logger.Info("Dumping manifests")
132+
for _, r := range result {
133+
fmt.Print(r.Manifests)
134+
}
126135
}
127136

128137
return nil
@@ -245,12 +254,14 @@ func NewGitopsDeployer(
245254
project *project.Project,
246255
store *secrets.SecretStore,
247256
logger *slog.Logger,
257+
dryrun bool,
248258
) GitopsDeployer {
249259
if logger == nil {
250260
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
251261
}
252262

253263
return GitopsDeployer{
264+
dryrun: dryrun,
254265
fs: memfs.New(),
255266
kcl: NewKCLRunner(logger),
256267
logger: logger,

cli/pkg/deployment/gitops_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func TestDeploy(t *testing.T) {
7676
project projectParams
7777
yaml string
7878
execFail bool
79+
dryrun bool
7980
setup func(*testing.T, *GitopsDeployer, *testutils.InMemRepo)
8081
validate func(*testing.T, *GitopsDeployer, mockGitRemote, *testutils.InMemRepo)
8182
expectErr bool
@@ -87,6 +88,7 @@ func TestDeploy(t *testing.T) {
8788
project: defaultParams,
8889
yaml: "yaml",
8990
execFail: false,
91+
dryrun: false,
9092
setup: func(t *testing.T, deployer *GitopsDeployer, repo *testutils.InMemRepo) {
9193
deployer.token = "test"
9294
repo.MkdirAll(t, "deploy/dev/apps")
@@ -106,6 +108,27 @@ func TestDeploy(t *testing.T) {
106108
expectErr: false,
107109
expectedErr: "",
108110
},
111+
{
112+
name: "dry-run",
113+
mock: mockGitRemote{},
114+
project: defaultParams,
115+
yaml: "yaml",
116+
execFail: false,
117+
dryrun: true,
118+
setup: func(t *testing.T, deployer *GitopsDeployer, repo *testutils.InMemRepo) {
119+
deployer.token = "test"
120+
repo.MkdirAll(t, "deploy/dev/apps")
121+
},
122+
validate: func(t *testing.T, deployer *GitopsDeployer, mock mockGitRemote, repo *testutils.InMemRepo) {
123+
assert.True(t, repo.Exists(t, "deploy/dev/apps/test/main.yaml"), "main.yaml does not exist")
124+
assert.Equal(t, repo.ReadFile(t, "deploy/dev/apps/test/main.yaml"), []byte("yaml"), "main.yaml content is incorrect")
125+
126+
_, err := repo.Repo.Head()
127+
require.Error(t, err) // No commit should be made
128+
},
129+
expectErr: false,
130+
expectedErr: "",
131+
},
109132
{
110133
name: "no changes",
111134
mock: mockGitRemote{},
@@ -138,7 +161,8 @@ func TestDeploy(t *testing.T) {
138161
repo := testutils.NewInMemRepo(t)
139162
var calls []string
140163
deployer := GitopsDeployer{
141-
fs: repo.Fs,
164+
dryrun: tt.dryrun,
165+
fs: repo.Fs,
142166
kcl: KCLRunner{
143167
logger: testutils.NewNoopLogger(),
144168
kcl: newWrappedExecuterMock(tt.yaml, &calls, tt.execFail),

cli/pkg/deployment/kcl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func encodeValues(ctx *cue.Context, module schema.Module) ([]byte, error) {
147147

148148
// run runs a KCL module with the given module container and arguments.
149149
func (k *KCLRunner) run(container string, moduleArgs KCLModuleArgs) ([]byte, error) {
150-
args := []string{"run", "-q"}
150+
args := []string{"run", "-q", "--no_style"}
151151
args = append(args, moduleArgs.Serialize()...)
152152
args = append(args, fmt.Sprintf("oci://%s", container))
153153

cli/pkg/deployment/kcl_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ func TestKCLRunnerRunDeployment(t *testing.T) {
140140
assert.Equal(t, "key: value\n", r.result["main"].Values)
141141
assert.Equal(t, "output", r.result["support"].Manifests)
142142
assert.Equal(t, "key1: value1\n", r.result["support"].Values)
143-
assert.Contains(t, r.calls, "run -q -D name= -D namespace=default -D values={\"key\":\"value\"} -D 1.0.0 oci://test.com/module")
144-
assert.Contains(t, r.calls, "run -q -D name= -D namespace=default -D values={\"key1\":\"value1\"} -D 1.0.0 oci://test.com/module1")
143+
assert.Contains(t, r.calls, "run -q --no_style -D name= -D namespace=default -D values={\"key\":\"value\"} -D 1.0.0 oci://test.com/module")
144+
assert.Contains(t, r.calls, "run -q --no_style -D name= -D namespace=default -D values={\"key1\":\"value1\"} -D 1.0.0 oci://test.com/module1")
145145
},
146146
},
147147
{

foundry/api/blueprint.cue

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ project: {
1515
}
1616
deployment: {
1717
on: {
18-
//merge: {}
19-
//tag: {}
20-
always: {}
18+
merge: {}
19+
tag: {}
2120
}
2221
environment: "dev"
2322
modules: {
@@ -47,9 +46,8 @@ project: {
4746
release: {
4847
docker: {
4948
on: {
50-
//merge: {}
51-
//tag: {}
52-
always: {}
49+
merge: {}
50+
tag: {}
5351
}
5452
config: {
5553
tag: _ @forge(name="GIT_HASH_OR_TAG")

0 commit comments

Comments
 (0)