Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): performs dry-runs on deployments #120

Merged
merged 2 commits into from
Jan 17, 2025
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ jobs:
earthly_token: ${{ secrets.earthly_token }}

final:
needs: [check, build, package, test, release]
needs: [check, build, package, test, release, deploy]
if: ${{ always() && (contains(needs.*.result, 'failure') || !failure() && !cancelled()) }}
runs-on: ubuntu-latest
steps:
Expand Down
7 changes: 4 additions & 3 deletions cli/cmd/cmds/deploy/deploy.go → cli/cmd/cmds/deploy/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ func (c *PushCmd) Run(ctx run.RunContext) error {
return fmt.Errorf("could not load project: %w", err)
}

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

deployer := deployment.NewGitopsDeployer(&project, &ctx.SecretStore, ctx.Logger)
deployer := deployment.NewGitopsDeployer(&project, &ctx.SecretStore, ctx.Logger, dryrun)
if err := deployer.Load(); err != nil {
return fmt.Errorf("could not load deployer: %w", err)
}
Expand Down
37 changes: 24 additions & 13 deletions cli/pkg/deployment/gitops.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (g gitRemote) Push(repo *git.Repository, o *git.PushOptions) error {

// GitopsDeployer is a deployer that deploys projects to a GitOps repository.
type GitopsDeployer struct {
dryrun bool
fs billy.Filesystem
repo *git.Repository
kcl KCLRunner
Expand Down Expand Up @@ -108,21 +109,29 @@ func (g *GitopsDeployer) Deploy() error {
}
}

changes, err := g.hasChanges()
if err != nil {
return fmt.Errorf("could not check if worktree has changes: %w", err)
} else if !changes {
return ErrNoChanges
}
if !g.dryrun {
changes, err := g.hasChanges()
if err != nil {
return fmt.Errorf("could not check if worktree has changes: %w", err)
} else if !changes {
return ErrNoChanges
}

g.logger.Info("Committing changes", "path", bundlePath)
if err := g.commit(); err != nil {
return fmt.Errorf("could not commit changes: %w", err)
}
g.logger.Info("Committing changes", "path", bundlePath)
if err := g.commit(); err != nil {
return fmt.Errorf("could not commit changes: %w", err)
}

g.logger.Info("Pushing changes")
if err := g.push(); err != nil {
return fmt.Errorf("could not push changes: %w", err)
g.logger.Info("Pushing changes")
if err := g.push(); err != nil {
return fmt.Errorf("could not push changes: %w", err)
}
} else {
g.logger.Info("Dry-run: not committing or pushing changes")
g.logger.Info("Dumping manifests")
for _, r := range result {
fmt.Print(r.Manifests)
}
}

return nil
Expand Down Expand Up @@ -245,12 +254,14 @@ func NewGitopsDeployer(
project *project.Project,
store *secrets.SecretStore,
logger *slog.Logger,
dryrun bool,
) GitopsDeployer {
if logger == nil {
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
}

return GitopsDeployer{
dryrun: dryrun,
fs: memfs.New(),
kcl: NewKCLRunner(logger),
logger: logger,
Expand Down
26 changes: 25 additions & 1 deletion cli/pkg/deployment/gitops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestDeploy(t *testing.T) {
project projectParams
yaml string
execFail bool
dryrun bool
setup func(*testing.T, *GitopsDeployer, *testutils.InMemRepo)
validate func(*testing.T, *GitopsDeployer, mockGitRemote, *testutils.InMemRepo)
expectErr bool
Expand All @@ -87,6 +88,7 @@ func TestDeploy(t *testing.T) {
project: defaultParams,
yaml: "yaml",
execFail: false,
dryrun: false,
setup: func(t *testing.T, deployer *GitopsDeployer, repo *testutils.InMemRepo) {
deployer.token = "test"
repo.MkdirAll(t, "deploy/dev/apps")
Expand All @@ -106,6 +108,27 @@ func TestDeploy(t *testing.T) {
expectErr: false,
expectedErr: "",
},
{
name: "dry-run",
mock: mockGitRemote{},
project: defaultParams,
yaml: "yaml",
execFail: false,
dryrun: true,
setup: func(t *testing.T, deployer *GitopsDeployer, repo *testutils.InMemRepo) {
deployer.token = "test"
repo.MkdirAll(t, "deploy/dev/apps")
},
validate: func(t *testing.T, deployer *GitopsDeployer, mock mockGitRemote, repo *testutils.InMemRepo) {
assert.True(t, repo.Exists(t, "deploy/dev/apps/test/main.yaml"), "main.yaml does not exist")
assert.Equal(t, repo.ReadFile(t, "deploy/dev/apps/test/main.yaml"), []byte("yaml"), "main.yaml content is incorrect")

_, err := repo.Repo.Head()
require.Error(t, err) // No commit should be made
},
expectErr: false,
expectedErr: "",
},
{
name: "no changes",
mock: mockGitRemote{},
Expand Down Expand Up @@ -138,7 +161,8 @@ func TestDeploy(t *testing.T) {
repo := testutils.NewInMemRepo(t)
var calls []string
deployer := GitopsDeployer{
fs: repo.Fs,
dryrun: tt.dryrun,
fs: repo.Fs,
kcl: KCLRunner{
logger: testutils.NewNoopLogger(),
kcl: newWrappedExecuterMock(tt.yaml, &calls, tt.execFail),
Expand Down
2 changes: 1 addition & 1 deletion cli/pkg/deployment/kcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func encodeValues(ctx *cue.Context, module schema.Module) ([]byte, error) {

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

Expand Down
4 changes: 2 additions & 2 deletions cli/pkg/deployment/kcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ func TestKCLRunnerRunDeployment(t *testing.T) {
assert.Equal(t, "key: value\n", r.result["main"].Values)
assert.Equal(t, "output", r.result["support"].Manifests)
assert.Equal(t, "key1: value1\n", r.result["support"].Values)
assert.Contains(t, r.calls, "run -q -D name= -D namespace=default -D values={\"key\":\"value\"} -D 1.0.0 oci://test.com/module")
assert.Contains(t, r.calls, "run -q -D name= -D namespace=default -D values={\"key1\":\"value1\"} -D 1.0.0 oci://test.com/module1")
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")
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")
},
},
{
Expand Down
10 changes: 4 additions & 6 deletions foundry/api/blueprint.cue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ project: {
}
deployment: {
on: {
//merge: {}
//tag: {}
always: {}
merge: {}
tag: {}
}
environment: "dev"
modules: {
Expand Down Expand Up @@ -47,9 +46,8 @@ project: {
release: {
docker: {
on: {
//merge: {}
//tag: {}
always: {}
merge: {}
tag: {}
}
config: {
tag: _ @forge(name="GIT_HASH_OR_TAG")
Expand Down
Loading