Skip to content

Commit d99d5c3

Browse files
committed
feat(policy-devel): Adapt policy develop init to WASM
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
1 parent d1dea77 commit d99d5c3

File tree

14 files changed

+576
-50
lines changed

14 files changed

+576
-50
lines changed

app/cli/cmd/policy_develop_init.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,41 @@ func newPolicyDevelopInitCmd() *cobra.Command {
2929
name string
3030
description string
3131
directory string
32+
policyType string
3233
)
3334

3435
cmd := &cobra.Command{
3536
Use: "init",
3637
Short: "Initialize a new policy",
3738
Long: `Initialize a new policy by creating template policy files in the specified directory.
38-
By default, it creates chainloop-policy.yaml and chainloop-policy.rego files.`,
39-
Example: `
40-
# Initialize in current directory with separate files
39+
40+
Policy Types:
41+
rego - Create a Rego-based policy using Open Policy Agent (default)
42+
wasm-go - Create a WebAssembly policy using Go and TinyGo
43+
wasm-js - Create a WebAssembly policy using JavaScript and Extism`,
44+
Example: ` # Initialize a Rego policy in current directory (default)
4145
chainloop policy develop init
4246
43-
# Initialize in specific directory with embedded format and policy name
44-
chainloop policy develop init --directory ./policies --embedded --name mypolicy`,
47+
# Initialize a Rego policy with custom name
48+
chainloop policy develop init --type rego --name my-policy
49+
50+
# Initialize a WASM Go policy
51+
chainloop policy develop init --type wasm-go --name my-policy --directory ./policies
52+
53+
# Initialize a WASM JS policy
54+
chainloop policy develop init --type wasm-js --name validation --description "My validation policy"`,
4555
RunE: func(_ *cobra.Command, _ []string) error {
4656
if directory == "" {
4757
directory = "."
4858
}
59+
4960
opts := &action.PolicyInitOpts{
5061
Force: force,
5162
Embedded: embedded,
5263
Name: name,
5364
Description: description,
5465
Directory: directory,
66+
PolicyType: policyType,
5567
}
5668

5769
policyInit, err := action.NewPolicyInit(opts, ActionOpts)
@@ -70,9 +82,11 @@ By default, it creates chainloop-policy.yaml and chainloop-policy.rego files.`,
7082
}
7183

7284
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite existing files")
73-
cmd.Flags().BoolVar(&embedded, "embedded", false, "initialize an embedded policy (single YAML file)")
85+
cmd.Flags().BoolVar(&embedded, "embedded", false, "initialize an embedded policy (single YAML file, Rego only)")
7486
cmd.Flags().StringVar(&name, "name", "", "name of the policy")
7587
cmd.Flags().StringVar(&description, "description", "", "description of the policy")
76-
cmd.Flags().StringVar(&directory, "directory", "", "directory for policy")
88+
cmd.Flags().StringVar(&directory, "directory", "", "directory for policy files")
89+
cmd.Flags().StringVar(&policyType, "type", "", "policy type: rego (default), wasm-go, or wasm-js")
90+
7791
return cmd
7892
}

app/cli/internal/policydevel/init.go

Lines changed: 201 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,37 @@ import (
2828
//go:embed templates/*
2929
var templateFS embed.FS
3030

31+
// PolicyType represents the type of policy to initialize
32+
type PolicyType string
33+
34+
const (
35+
PolicyTypeRego PolicyType = "rego"
36+
PolicyTypeWasmGo PolicyType = "wasm-go"
37+
PolicyTypeWasmJS PolicyType = "wasm-js"
38+
)
39+
3140
const (
32-
policyTemplateRegoPath = "templates/example-policy.rego"
33-
policyTemplatePath = "templates/example-policy.yaml"
41+
// Rego templates
42+
regoTemplateDir = "templates/rego"
43+
regoPolicyFile = "example-policy.rego"
44+
regoYAMLFile = "example-policy.yaml"
45+
46+
// WASM Go templates
47+
wasmGoTemplateDir = "templates/wasm-go"
48+
wasmGoPolicyFile = "policy.go.tmpl"
49+
wasmGoModFile = "go.mod.tmpl"
50+
wasmGoYAMLFile = "policy.yaml"
51+
wasmGoMakefileFile = "Makefile"
52+
53+
// WASM JS templates
54+
wasmJSTemplateDir = "templates/wasm-js"
55+
wasmJSPolicyFile = "policy.js"
56+
wasmJSPackageFile = "package.json"
57+
wasmJSEsbuildFile = "esbuild.js"
58+
wasmJSDTSFile = "policy.d.ts"
59+
wasmJSYAMLFile = "policy.yaml"
60+
61+
// Defaults
3462
defaultPolicyName = "policy"
3563
defaultPolicyDescription = "Chainloop validation policy"
3664
defaultMaterialKind = "SBOM_CYCLONEDX_JSON"
@@ -45,90 +73,220 @@ type TemplateData struct {
4573
MaterialKind string
4674
}
4775

48-
type Content struct {
49-
YAML string
50-
Rego string
51-
}
52-
5376
type InitOptions struct {
5477
Directory string
78+
PolicyType PolicyType
5579
Embedded bool
5680
Force bool
5781
Name string
5882
Description string
5983
}
6084

6185
func Initialize(opts *InitOptions) error {
62-
content, err := loadAndProcessTemplates(opts)
86+
// Default to Rego if no type specified
87+
if opts.PolicyType == "" {
88+
opts.PolicyType = PolicyTypeRego
89+
}
90+
91+
// Route to appropriate initializer based on policy type
92+
switch opts.PolicyType {
93+
case PolicyTypeRego:
94+
return initializeRegoPolicy(opts)
95+
case PolicyTypeWasmGo:
96+
return initializeWasmGoPolicy(opts)
97+
case PolicyTypeWasmJS:
98+
return initializeWasmJSPolicy(opts)
99+
default:
100+
return fmt.Errorf("unsupported policy type: %s", opts.PolicyType)
101+
}
102+
}
103+
104+
// initializeRegoPolicy creates a Rego-based policy
105+
func initializeRegoPolicy(opts *InitOptions) error {
106+
// Load templates
107+
regoContent, err := templateFS.ReadFile(filepath.Join(regoTemplateDir, regoPolicyFile))
63108
if err != nil {
64-
return fmt.Errorf("failed to process templates: %w", err)
109+
return fmt.Errorf("failed to read Rego template: %w", err)
65110
}
66111

112+
yamlContent, err := templateFS.ReadFile(filepath.Join(regoTemplateDir, regoYAMLFile))
113+
if err != nil {
114+
return fmt.Errorf("failed to read YAML template: %w", err)
115+
}
116+
117+
// Prepare template data
118+
data := &TemplateData{
119+
Name: getPolicyName(opts.Name),
120+
Description: getPolicyDescription(opts.Description),
121+
RegoPath: sanitizeName(getPolicyName(opts.Name)) + ".rego",
122+
RegoContent: string(regoContent),
123+
Embedded: opts.Embedded,
124+
MaterialKind: defaultMaterialKind,
125+
}
126+
127+
// Process YAML template
128+
yamlProcessed, err := executeTemplate(string(yamlContent), data)
129+
if err != nil {
130+
return fmt.Errorf("failed to process YAML template: %w", err)
131+
}
132+
133+
// Prepare files to write
67134
files := make(map[string]string)
68-
fileNameBase := sanitizeName(getPolicyName(opts.Name))
135+
fileNameBase := sanitizeName(data.Name)
69136

70-
if opts.Embedded {
71-
files[fileNameBase+".yaml"] = content.YAML
72-
} else {
73-
files[fileNameBase+".yaml"] = content.YAML
74-
files[fileNameBase+".rego"] = content.Rego
137+
files[fileNameBase+".yaml"] = yamlProcessed
138+
if !opts.Embedded {
139+
files[fileNameBase+".rego"] = data.RegoContent
75140
}
76141

77142
return writeFiles(opts.Directory, files, opts.Force)
78143
}
79144

80-
func getPolicyName(name string) string {
81-
if name == "" {
82-
return defaultPolicyName
145+
// initializeWasmGoPolicy creates a WASM Go-based policy
146+
func initializeWasmGoPolicy(opts *InitOptions) error {
147+
// Load templates
148+
policyContent, err := templateFS.ReadFile(filepath.Join(wasmGoTemplateDir, wasmGoPolicyFile))
149+
if err != nil {
150+
return fmt.Errorf("failed to read Go policy template: %w", err)
83151
}
84-
return name
85-
}
86152

87-
func getPolicyDescription(description string) string {
88-
if description == "" {
89-
return defaultPolicyDescription
153+
goModContent, err := templateFS.ReadFile(filepath.Join(wasmGoTemplateDir, wasmGoModFile))
154+
if err != nil {
155+
return fmt.Errorf("failed to read go.mod template: %w", err)
90156
}
91-
return description
157+
158+
yamlContent, err := templateFS.ReadFile(filepath.Join(wasmGoTemplateDir, wasmGoYAMLFile))
159+
if err != nil {
160+
return fmt.Errorf("failed to read YAML template: %w", err)
161+
}
162+
163+
makefileContent, err := templateFS.ReadFile(filepath.Join(wasmGoTemplateDir, wasmGoMakefileFile))
164+
if err != nil {
165+
return fmt.Errorf("failed to read Makefile template: %w", err)
166+
}
167+
168+
// Prepare template data
169+
data := &TemplateData{
170+
Name: getPolicyName(opts.Name),
171+
Description: getPolicyDescription(opts.Description),
172+
MaterialKind: defaultMaterialKind,
173+
}
174+
175+
// Process templates
176+
policyProcessed, err := executeTemplate(string(policyContent), data)
177+
if err != nil {
178+
return fmt.Errorf("failed to process policy template: %w", err)
179+
}
180+
181+
goModProcessed, err := executeTemplate(string(goModContent), data)
182+
if err != nil {
183+
return fmt.Errorf("failed to process go.mod template: %w", err)
184+
}
185+
186+
yamlProcessed, err := executeTemplate(string(yamlContent), data)
187+
if err != nil {
188+
return fmt.Errorf("failed to process YAML template: %w", err)
189+
}
190+
191+
makefileProcessed, err := executeTemplate(string(makefileContent), data)
192+
if err != nil {
193+
return fmt.Errorf("failed to process Makefile template: %w", err)
194+
}
195+
196+
// Prepare files to write
197+
files := map[string]string{
198+
"policy.go": policyProcessed,
199+
"go.mod": goModProcessed,
200+
"policy.yaml": yamlProcessed,
201+
"Makefile": makefileProcessed,
202+
}
203+
204+
return writeFiles(opts.Directory, files, opts.Force)
92205
}
93206

94-
func loadAndProcessTemplates(opts *InitOptions) (*Content, error) {
95-
regoContent, err := templateFS.ReadFile(policyTemplateRegoPath)
207+
// initializeWasmJSPolicy creates a WASM JavaScript-based policy
208+
func initializeWasmJSPolicy(opts *InitOptions) error {
209+
// Load templates
210+
policyContent, err := templateFS.ReadFile(filepath.Join(wasmJSTemplateDir, wasmJSPolicyFile))
96211
if err != nil {
97-
return nil, fmt.Errorf("failed to read Rego template: %w", err)
212+
return fmt.Errorf("failed to read JS policy template: %w", err)
98213
}
99214

215+
packageContent, err := templateFS.ReadFile(filepath.Join(wasmJSTemplateDir, wasmJSPackageFile))
216+
if err != nil {
217+
return fmt.Errorf("failed to read package.json template: %w", err)
218+
}
219+
220+
esbuildContent, err := templateFS.ReadFile(filepath.Join(wasmJSTemplateDir, wasmJSEsbuildFile))
221+
if err != nil {
222+
return fmt.Errorf("failed to read esbuild.js template: %w", err)
223+
}
224+
225+
dtsContent, err := templateFS.ReadFile(filepath.Join(wasmJSTemplateDir, wasmJSDTSFile))
226+
if err != nil {
227+
return fmt.Errorf("failed to read policy.d.ts template: %w", err)
228+
}
229+
230+
yamlContent, err := templateFS.ReadFile(filepath.Join(wasmJSTemplateDir, wasmJSYAMLFile))
231+
if err != nil {
232+
return fmt.Errorf("failed to read YAML template: %w", err)
233+
}
234+
235+
// Prepare template data
100236
data := &TemplateData{
101237
Name: getPolicyName(opts.Name),
102238
Description: getPolicyDescription(opts.Description),
103-
RegoPath: sanitizeName(getPolicyName(opts.Name)) + ".rego",
104-
RegoContent: string(regoContent),
105-
Embedded: opts.Embedded,
106239
MaterialKind: defaultMaterialKind,
107240
}
108241

109-
// Process main template
110-
content, err := templateFS.ReadFile(policyTemplatePath)
242+
// Process templates
243+
policyProcessed, err := executeTemplate(string(policyContent), data)
111244
if err != nil {
112-
return nil, fmt.Errorf("failed to read policy template: %w", err)
245+
return fmt.Errorf("failed to process policy template: %w", err)
113246
}
114247

115-
yamlContent, err := executeTemplate(string(content), data)
248+
packageProcessed, err := executeTemplate(string(packageContent), data)
116249
if err != nil {
117-
return nil, fmt.Errorf("failed to execute template: %w", err)
250+
return fmt.Errorf("failed to process package.json template: %w", err)
118251
}
119252

120-
// For non-embedded case, we still need the Rego content to write to file
121-
if !opts.Embedded {
122-
return &Content{
123-
YAML: yamlContent,
124-
Rego: data.RegoContent,
125-
}, nil
253+
dtsProcessed, err := executeTemplate(string(dtsContent), data)
254+
if err != nil {
255+
return fmt.Errorf("failed to process policy.d.ts template: %w", err)
256+
}
257+
258+
yamlProcessed, err := executeTemplate(string(yamlContent), data)
259+
if err != nil {
260+
return fmt.Errorf("failed to process YAML template: %w", err)
261+
}
262+
263+
// Prepare files to write (esbuild.js doesn't need template processing)
264+
files := map[string]string{
265+
"policy.js": policyProcessed,
266+
"package.json": packageProcessed,
267+
"esbuild.js": string(esbuildContent),
268+
"policy.d.ts": dtsProcessed,
269+
"policy.yaml": yamlProcessed,
270+
}
271+
272+
return writeFiles(opts.Directory, files, opts.Force)
273+
}
274+
275+
func getPolicyName(name string) string {
276+
if name == "" {
277+
return defaultPolicyName
126278
}
279+
return name
280+
}
127281

128-
return &Content{YAML: yamlContent}, nil
282+
func getPolicyDescription(description string) string {
283+
if description == "" {
284+
return defaultPolicyDescription
285+
}
286+
return description
129287
}
130288

131-
// Add custom template functions
289+
// executeTemplate processes a template with the given data
132290
func executeTemplate(content string, data *TemplateData) (string, error) {
133291
tmpl := template.New("policy").Funcs(template.FuncMap{
134292
"sanitize": sanitizeName,

0 commit comments

Comments
 (0)