Skip to content

Commit bf2f5b8

Browse files
authored
feat: adds template command (#83)
1 parent 6ab15e2 commit bf2f5b8

File tree

12 files changed

+317
-96
lines changed

12 files changed

+317
-96
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- name: Deploy
6868
uses: input-output-hk/catalyst-forge/actions/run@master
6969
with:
70-
command: deploy
70+
command: deploy push
7171
args: ${{ matrix.deployment }}
7272
local: ${{ inputs.local }}
7373
verbosity: ${{ inputs.verbosity }}

cli/cmd/cmds/deploy/cmd.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package deploy
2+
3+
type DeployCmd struct {
4+
Push PushCmd `cmd:"" help:"Pushes a project deployment to the GitOps repo."`
5+
Template TemplateCmd `cmd:"" help:"Generates a project's deployment YAML."`
6+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package cmds
1+
package deploy
22

33
import (
44
"fmt"
@@ -7,11 +7,11 @@ import (
77
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
88
)
99

10-
type DeployCmd struct {
10+
type PushCmd struct {
1111
Project string `arg:"" help:"The path to the project to deploy." kong:"arg,predictor=path"`
1212
}
1313

14-
func (c *DeployCmd) Run(ctx run.RunContext) error {
14+
func (c *PushCmd) Run(ctx run.RunContext) error {
1515
project, err := ctx.ProjectLoader.Load(c.Project)
1616
if err != nil {
1717
return fmt.Errorf("could not load project: %w", err)

cli/cmd/cmds/deploy/template.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package deploy
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/input-output-hk/catalyst-forge/cli/pkg/deployment"
7+
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
8+
)
9+
10+
type TemplateCmd struct {
11+
Project string `arg:"" help:"The path to the project." kong:"arg,predictor=path"`
12+
}
13+
14+
func (c *TemplateCmd) Run(ctx run.RunContext) error {
15+
project, err := ctx.ProjectLoader.Load(c.Project)
16+
if err != nil {
17+
return fmt.Errorf("could not load project: %w", err)
18+
}
19+
20+
bundle, err := deployment.GenerateBundle(&project)
21+
if err != nil {
22+
return fmt.Errorf("could not generate bundle: %w", err)
23+
}
24+
25+
templater, err := deployment.NewDefaultBundleTemplater(ctx.Logger)
26+
if err != nil {
27+
return fmt.Errorf("could not create bundle templater: %w", err)
28+
}
29+
30+
out, err := templater.Render(bundle)
31+
if err != nil {
32+
return fmt.Errorf("could not render bundle: %w", err)
33+
}
34+
35+
fmt.Println(out)
36+
37+
return nil
38+
}

cli/cmd/cmds/run.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmds
22

33
import (
4+
"github.com/input-output-hk/catalyst-forge/cli/pkg/earthly"
45
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
56
"github.com/input-output-hk/catalyst-forge/lib/tools/earthfile"
67
)
@@ -35,3 +36,30 @@ func (c *RunCmd) Run(ctx run.RunContext) error {
3536

3637
return nil
3738
}
39+
40+
// generateOpts generates the options for the Earthly executor based on command
41+
// flags.
42+
func generateOpts(flags *RunCmd, ctx run.RunContext) []earthly.EarthlyExecutorOption {
43+
var opts []earthly.EarthlyExecutorOption
44+
45+
if flags != nil {
46+
if flags.Artifact != "" {
47+
opts = append(opts, earthly.WithArtifact(flags.Artifact))
48+
}
49+
50+
if ctx.CI {
51+
opts = append(opts, earthly.WithCI())
52+
}
53+
54+
// Users can explicitly set the platforms to use without being in CI mode.
55+
if flags.Platform != nil {
56+
opts = append(opts, earthly.WithPlatforms(flags.Platform...))
57+
}
58+
59+
if len(flags.TargetArgs) > 0 && flags.TargetArgs[0] != "" {
60+
opts = append(opts, earthly.WithTargetArgs(flags.TargetArgs...))
61+
}
62+
}
63+
64+
return opts
65+
}

cli/cmd/cmds/scan.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"cuelang.org/go/cue"
1010
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
1111
"github.com/input-output-hk/catalyst-forge/cli/pkg/scan"
12+
"github.com/input-output-hk/catalyst-forge/cli/pkg/utils"
1213
"golang.org/x/exp/maps"
1314
)
1415

@@ -55,14 +56,14 @@ func (c *ScanCmd) Run(ctx run.RunContext) error {
5556
}
5657
}
5758

58-
printJson(result, c.Pretty)
59+
utils.PrintJson(result, c.Pretty)
5960
case c.Blueprint:
6061
result := make(map[string]cue.Value)
6162
for path, project := range projects {
6263
result[path] = project.Raw().Value()
6364
}
6465

65-
printJson(result, c.Pretty)
66+
utils.PrintJson(result, c.Pretty)
6667
case c.Earthfile && len(c.Filter) > 0:
6768
result := make(map[string]map[string][]string)
6869
for _, filter := range c.Filter {
@@ -97,9 +98,9 @@ func (c *ScanCmd) Run(ctx run.RunContext) error {
9798
sort.Strings(enumerated[filter])
9899
}
99100

100-
printJson(enumerated, c.Pretty)
101+
utils.PrintJson(enumerated, c.Pretty)
101102
} else {
102-
printJson(result, c.Pretty)
103+
utils.PrintJson(result, c.Pretty)
103104
}
104105
case c.Earthfile:
105106
result := make(map[string][]string)
@@ -112,15 +113,27 @@ func (c *ScanCmd) Run(ctx run.RunContext) error {
112113
if ctx.CI {
113114
enumerated := enumerate(result)
114115
sort.Strings(enumerated)
115-
printJson(enumerated, c.Pretty)
116+
utils.PrintJson(enumerated, c.Pretty)
116117
} else {
117-
printJson(result, c.Pretty)
118+
utils.PrintJson(result, c.Pretty)
118119
}
119120
default:
120121
keys := maps.Keys(projects)
121122
sort.Strings(keys)
122-
printJson(keys, c.Pretty)
123+
utils.PrintJson(keys, c.Pretty)
123124
}
124125

125126
return nil
126127
}
128+
129+
// enumerate enumerates the Earthfile+Target pairs from the target map.
130+
func enumerate(data map[string][]string) []string {
131+
var result []string
132+
for path, targets := range data {
133+
for _, target := range targets {
134+
result = append(result, fmt.Sprintf("%s+%s", path, target))
135+
}
136+
}
137+
138+
return result
139+
}

cli/cmd/cmds/secret.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
9+
"github.com/input-output-hk/catalyst-forge/cli/pkg/utils"
910
"github.com/input-output-hk/catalyst-forge/lib/project/schema"
1011
"github.com/input-output-hk/catalyst-forge/lib/project/secrets"
1112
)
@@ -99,7 +100,7 @@ func (c *Get) Run(ctx run.RunContext) error {
99100
fmt.Println(mappedSecret[c.Key])
100101
return nil
101102
} else {
102-
printJson(mappedSecret, false)
103+
utils.PrintJson(mappedSecret, false)
103104
return nil
104105
}
105106
}

cli/cmd/cmds/util.go

Lines changed: 0 additions & 73 deletions
This file was deleted.

cli/cmd/main.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/alecthomas/kong"
1111
"github.com/charmbracelet/log"
1212
"github.com/input-output-hk/catalyst-forge/cli/cmd/cmds"
13+
"github.com/input-output-hk/catalyst-forge/cli/cmd/cmds/deploy"
1314
"github.com/input-output-hk/catalyst-forge/cli/pkg/run"
1415
"github.com/input-output-hk/catalyst-forge/lib/project/project"
1516
"github.com/input-output-hk/catalyst-forge/lib/project/schema"
@@ -21,18 +22,24 @@ import (
2122

2223
var version = "dev"
2324

25+
type GlobalArgs struct {
26+
CI bool `help:"Run in CI mode."`
27+
Local bool `short:"l" help:"Forces all runs to happen locally (ignores any remote satellites)."`
28+
Verbose int `short:"v" type:"counter" help:"Enable verbose logging."`
29+
}
30+
2431
var cli struct {
25-
cmds.GlobalArgs
26-
27-
Deploy cmds.DeployCmd `kong:"cmd" help:"Deploy a project."`
28-
Dump cmds.DumpCmd `kong:"cmd" help:"Dumps a project's blueprint to JSON."`
29-
CI cmds.CICmd `kong:"cmd" help:"Simulate a CI run."`
30-
Release cmds.ReleaseCmd `kong:"cmd" help:"Release a project."`
31-
Run cmds.RunCmd `kong:"cmd" help:"Run an Earthly target."`
32-
Scan cmds.ScanCmd `kong:"cmd" help:"Scan for Earthfiles."`
33-
Secret cmds.SecretCmd `kong:"cmd" help:"Manage secrets."`
34-
Validate cmds.ValidateCmd `kong:"cmd" help:"Validates a project."`
35-
Version VersionCmd `kong:"cmd" help:"Print the version."`
32+
GlobalArgs
33+
34+
Deploy deploy.DeployCmd `kong:"cmd" help:"Deploy a project."`
35+
Dump cmds.DumpCmd `cmd:"" help:"Dumps a project's blueprint to JSON."`
36+
CI cmds.CICmd `cmd:"" help:"Simulate a CI run."`
37+
Release cmds.ReleaseCmd `cmd:"" help:"Release a project."`
38+
Run cmds.RunCmd `cmd:"" help:"Run an Earthly target."`
39+
Scan cmds.ScanCmd `cmd:"" help:"Scan for Earthfiles."`
40+
Secret cmds.SecretCmd `cmd:"" help:"Manage secrets."`
41+
Validate cmds.ValidateCmd `cmd:"" help:"Validates a project."`
42+
Version VersionCmd `cmd:"" help:"Print the version."`
3643

3744
InstallCompletions kongplete.InstallCompletions `cmd:"" help:"install shell completions"`
3845
}

cli/pkg/deployment/template.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package deployment
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log/slog"
7+
"path/filepath"
8+
9+
"github.com/input-output-hk/catalyst-forge/cli/pkg/executor"
10+
"github.com/spf13/afero"
11+
)
12+
13+
// BundleTemplater is an interface for rendering a bundle template to YAML.
14+
type BundleTemplater interface {
15+
Render(Bundle) (string, error)
16+
}
17+
18+
type DefaultBundleTemplater struct {
19+
fs afero.Fs
20+
logger *slog.Logger
21+
timoni executor.WrappedExecuter
22+
stdout *bytes.Buffer
23+
stderr *bytes.Buffer
24+
workdir string
25+
}
26+
27+
func (t DefaultBundleTemplater) Render(bundle Bundle) (string, error) {
28+
t.logger.Info("Encoding bundle")
29+
src, err := bundle.Encode()
30+
if err != nil {
31+
return "", err
32+
}
33+
34+
bundlePath := filepath.Join(t.workdir, "bundle.cue")
35+
t.logger.Info("Writing bundle", "path", bundlePath)
36+
if err := afero.WriteFile(t.fs, bundlePath, src, 0644); err != nil {
37+
return "", fmt.Errorf("could not write bundle: %w", err)
38+
}
39+
40+
_, err = t.timoni.Execute("bundle", "build", "--log-pretty=false", "--log-color=false", "-f", bundlePath)
41+
if err != nil {
42+
t.logger.Error("Failed to build bundle", "error", err)
43+
t.logger.Error("Timoni output", "output", t.stderr.String())
44+
return "", fmt.Errorf("could not build bundle: %w", err)
45+
}
46+
47+
return t.stdout.String(), nil
48+
}
49+
50+
func NewDefaultBundleTemplater(logger *slog.Logger) (*DefaultBundleTemplater, error) {
51+
fs := afero.NewOsFs()
52+
workdir, err := afero.TempDir(fs, "", "catalyst-forge-")
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to create temporary directory: %w", err)
55+
}
56+
57+
var stdout, stderr bytes.Buffer
58+
59+
return &DefaultBundleTemplater{
60+
fs: afero.NewOsFs(),
61+
logger: logger,
62+
timoni: executor.NewLocalWrappedExecutor(
63+
executor.NewLocalExecutor(logger, executor.WithRedirectTo(&stdout, &stderr)),
64+
"timoni",
65+
),
66+
stdout: &stdout,
67+
stderr: &stderr,
68+
workdir: workdir,
69+
}, nil
70+
}

0 commit comments

Comments
 (0)