diff --git a/alpha/template/composite/builder.go b/alpha/template/composite/builder.go index 615cd0925..3d1f7af95 100644 --- a/alpha/template/composite/builder.go +++ b/alpha/template/composite/builder.go @@ -2,16 +2,22 @@ package composite import ( "bytes" + "context" "fmt" "io" "os" "os/exec" "path" + "path/filepath" "strings" "sigs.k8s.io/yaml" "github.com/operator-framework/operator-registry/alpha/declcfg" + basictemplate "github.com/operator-framework/operator-registry/alpha/template/basic" + semvertemplate "github.com/operator-framework/operator-registry/alpha/template/semver" + "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/lib/config" ) const ( @@ -28,13 +34,13 @@ type ContainerConfig struct { } type BuilderConfig struct { - ContainerCfg ContainerConfig - OutputType string - CurrentDirectory string + ContainerCfg ContainerConfig + OutputType string + InputDirectory string } type Builder interface { - Build(dir string, td TemplateDefinition) error + Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error Validate(dir string) error } @@ -50,7 +56,7 @@ func NewBasicBuilder(builderCfg BuilderConfig) *BasicBuilder { } } -func (bb *BasicBuilder) Build(dir string, td TemplateDefinition) error { +func (bb *BasicBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { if td.Schema != BasicBuilderSchema { return fmt.Errorf("schema %q does not match the basic template builder schema %q", td.Schema, BasicBuilderSchema) } @@ -78,23 +84,25 @@ func (bb *BasicBuilder) Build(dir string, td TemplateDefinition) error { return fmt.Errorf("basic template configuration is invalid: %s", strings.Join(validationErrs, ",")) } - // build the container command - containerCmd := exec.Command(bb.builderCfg.ContainerCfg.ContainerTool, - "run", - "--rm", - "-v", - fmt.Sprintf("%s:%s:Z", bb.builderCfg.CurrentDirectory, bb.builderCfg.ContainerCfg.WorkingDir), - bb.builderCfg.ContainerCfg.BaseImage, - "alpha", - "render-template", - "basic", - path.Join(bb.builderCfg.ContainerCfg.WorkingDir, basicConfig.Input)) + b := basictemplate.Template{Registry: reg} + reader, err := os.Open(basicConfig.Input) + if err != nil { + return fmt.Errorf("error reading basic template: %v", err) + } + defer reader.Close() + + dcfg, err := b.Render(ctx, reader) + if err != nil { + return fmt.Errorf("error rendering basic template: %v", err) + } - return build(containerCmd, path.Join(dir, basicConfig.Output), bb.builderCfg.OutputType) + destPath := path.Join(bb.builderCfg.ContainerCfg.WorkingDir, dir, basicConfig.Output) + + return build(dcfg, destPath, bb.builderCfg.OutputType) } func (bb *BasicBuilder) Validate(dir string) error { - return validate(bb.builderCfg.ContainerCfg, path.Join(bb.builderCfg.CurrentDirectory, dir)) + return validate(bb.builderCfg.ContainerCfg, dir) } type SemverBuilder struct { @@ -109,7 +117,7 @@ func NewSemverBuilder(builderCfg BuilderConfig) *SemverBuilder { } } -func (sb *SemverBuilder) Build(dir string, td TemplateDefinition) error { +func (sb *SemverBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { if td.Schema != SemverBuilderSchema { return fmt.Errorf("schema %q does not match the semver template builder schema %q", td.Schema, SemverBuilderSchema) } @@ -137,23 +145,26 @@ func (sb *SemverBuilder) Build(dir string, td TemplateDefinition) error { return fmt.Errorf("semver template configuration is invalid: %s", strings.Join(validationErrs, ",")) } - // build the container command - containerCmd := exec.Command(sb.builderCfg.ContainerCfg.ContainerTool, - "run", - "--rm", - "-v", - fmt.Sprintf("%s:%s:Z", sb.builderCfg.CurrentDirectory, sb.builderCfg.ContainerCfg.WorkingDir), - sb.builderCfg.ContainerCfg.BaseImage, - "alpha", - "render-template", - "semver", - path.Join(sb.builderCfg.ContainerCfg.WorkingDir, semverConfig.Input)) + reader, err := os.Open(semverConfig.Input) + if err != nil { + return fmt.Errorf("error reading semver template: %v", err) + } + defer reader.Close() + + s := semvertemplate.Template{Registry: reg, Data: reader} - return build(containerCmd, path.Join(dir, semverConfig.Output), sb.builderCfg.OutputType) + dcfg, err := s.Render(ctx) + if err != nil { + return fmt.Errorf("error rendering semver template: %v", err) + } + + destPath := path.Join(sb.builderCfg.ContainerCfg.WorkingDir, dir, semverConfig.Output) + + return build(dcfg, destPath, sb.builderCfg.OutputType) } func (sb *SemverBuilder) Validate(dir string) error { - return validate(sb.builderCfg.ContainerCfg, path.Join(sb.builderCfg.CurrentDirectory, dir)) + return validate(sb.builderCfg.ContainerCfg, dir) } type RawBuilder struct { @@ -168,7 +179,7 @@ func NewRawBuilder(builderCfg BuilderConfig) *RawBuilder { } } -func (rb *RawBuilder) Build(dir string, td TemplateDefinition) error { +func (rb *RawBuilder) Build(ctx context.Context, _ image.Registry, dir string, td TemplateDefinition) error { if td.Schema != RawBuilderSchema { return fmt.Errorf("schema %q does not match the raw template builder schema %q", td.Schema, RawBuilderSchema) } @@ -196,21 +207,24 @@ func (rb *RawBuilder) Build(dir string, td TemplateDefinition) error { return fmt.Errorf("raw template configuration is invalid: %s", strings.Join(validationErrs, ",")) } - // build the container command - containerCmd := exec.Command(rb.builderCfg.ContainerCfg.ContainerTool, - "run", - "--rm", - "-v", - fmt.Sprintf("%s:%s:Z", rb.builderCfg.CurrentDirectory, rb.builderCfg.ContainerCfg.WorkingDir), - "--entrypoint=cat", // This assumes that the `cat` command is available in the container -- Should we also build a `... render-template raw` command to ensure consistent operation? Does OPM already have a way to render a raw FBC? - rb.builderCfg.ContainerCfg.BaseImage, - path.Join(rb.builderCfg.ContainerCfg.WorkingDir, rawConfig.Input)) + reader, err := os.Open(rawConfig.Input) + if err != nil { + return fmt.Errorf("error reading raw input file: %s, %v", rawConfig.Input, err) + } + defer reader.Close() + + dcfg, err := declcfg.LoadReader(reader) + if err != nil { + return fmt.Errorf("error parsing raw input file: %s, %v", rawConfig.Input, err) + } + + destPath := path.Join(rb.builderCfg.ContainerCfg.WorkingDir, dir, rawConfig.Output) - return build(containerCmd, path.Join(dir, rawConfig.Output), rb.builderCfg.OutputType) + return build(dcfg, destPath, rb.builderCfg.OutputType) } func (rb *RawBuilder) Validate(dir string) error { - return validate(rb.builderCfg.ContainerCfg, path.Join(rb.builderCfg.CurrentDirectory, dir)) + return validate(rb.builderCfg.ContainerCfg, dir) } type CustomBuilder struct { @@ -225,7 +239,7 @@ func NewCustomBuilder(builderCfg BuilderConfig) *CustomBuilder { } } -func (cb *CustomBuilder) Build(dir string, td TemplateDefinition) error { +func (cb *CustomBuilder) Build(ctx context.Context, reg image.Registry, dir string, td TemplateDefinition) error { if td.Schema != CustomBuilderSchema { return fmt.Errorf("schema %q does not match the custom template builder schema %q", td.Schema, CustomBuilderSchema) } @@ -254,15 +268,33 @@ func (cb *CustomBuilder) Build(dir string, td TemplateDefinition) error { } // build the command to execute cmd := exec.Command(customConfig.Command, customConfig.Args...) - cmd.Dir = cb.builderCfg.CurrentDirectory + cmd.Dir = cb.builderCfg.ContainerCfg.WorkingDir + + // custom template should output a valid FBC to STDOUT so we can + // build the FBC just like all the other templates. + v, err := cmd.Output() + if err != nil { + return fmt.Errorf("running command %q: %v", cmd.String(), err) + } + + reader := bytes.NewReader(v) + + dcfg, err := declcfg.LoadReader(reader) + cmdString := []string{customConfig.Command} + cmdString = append(cmdString, customConfig.Args...) + if err != nil { + return fmt.Errorf("error parsing custom command output: %s, %v", strings.Join(cmdString, "'"), err) + } + + destPath := path.Join(cb.builderCfg.ContainerCfg.WorkingDir, dir, customConfig.Output) // custom template should output a valid FBC to STDOUT so we can // build the FBC just like all the other templates. - return build(cmd, path.Join(dir, customConfig.Output), cb.builderCfg.OutputType) + return build(dcfg, destPath, cb.builderCfg.OutputType) } func (cb *CustomBuilder) Validate(dir string) error { - return validate(cb.builderCfg.ContainerCfg, path.Join(cb.builderCfg.CurrentDirectory, dir)) + return validate(cb.builderCfg.ContainerCfg, dir) } func writeDeclCfg(dcfg declcfg.DeclarativeConfig, w io.Writer, output string) error { @@ -277,45 +309,40 @@ func writeDeclCfg(dcfg declcfg.DeclarativeConfig, w io.Writer, output string) er } func validate(containerCfg ContainerConfig, dir string) error { - // build the container command - containerCmd := exec.Command(containerCfg.ContainerTool, - "run", - "--rm", - "-v", - fmt.Sprintf("%s:%s:Z", dir, containerCfg.WorkingDir), - containerCfg.BaseImage, - "validate", - containerCfg.WorkingDir) - - _, err := containerCmd.Output() + + path := path.Join(containerCfg.WorkingDir, dir) + s, err := os.Stat(path) if err != nil { - return fmt.Errorf("running command %q: %w", containerCmd.String(), err) + return fmt.Errorf("directory not found. validation path needs to be composed of ContainerConfig.WorkingDir+Component[].Destination.Path: %q: %v", path, err) + } + if !s.IsDir() { + return fmt.Errorf("%q is not a directory", path) } - return nil -} -func build(cmd *exec.Cmd, outPath string, outType string) error { - out, err := cmd.Output() - if err != nil { - return fmt.Errorf("running command %q: %w", cmd.String(), err) + if err := config.Validate(os.DirFS(path)); err != nil { + return fmt.Errorf("validation failure in path %q: %v", path, err) } + return nil +} - // parse out to dcfg - dcfg, err := declcfg.LoadReader(bytes.NewReader(out)) +func build(dcfg *declcfg.DeclarativeConfig, outPath string, outType string) error { + // create the destination for output, if it does not exist + outDir := filepath.Dir(outPath) + err := os.MkdirAll(outDir, 0o777) if err != nil { - return fmt.Errorf("parsing builder output: %w", err) + return fmt.Errorf("creating output directory %q: %v", outPath, err) } // write the dcfg file, err := os.Create(outPath) if err != nil { - return fmt.Errorf("creating output file %q: %w", outPath, err) + return fmt.Errorf("creating output file %q: %v", outPath, err) } defer file.Close() err = writeDeclCfg(*dcfg, file, outType) if err != nil { - return fmt.Errorf("writing to output file %q: %w", outPath, err) + return fmt.Errorf("writing to output file %q: %v", outPath, err) } return nil diff --git a/alpha/template/composite/builder_test.go b/alpha/template/composite/builder_test.go index 9c982d920..af7850b07 100644 --- a/alpha/template/composite/builder_test.go +++ b/alpha/template/composite/builder_test.go @@ -1,12 +1,14 @@ package composite import ( + "context" "fmt" "io" "os" "path" "testing" + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" "github.com/stretchr/testify/require" ) @@ -23,6 +25,12 @@ func TestBasicBuilder(t *testing.T) { validateAssertions func(t *testing.T, validateErr error) } + testDir := t.TempDir() + validConfigTemplate := `{ + "input": "%s", + "output": "%s" + }` + testCases := []testCase{ { name: "successful basic build yaml output", @@ -31,16 +39,13 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), templateDefinition: TemplateDefinition{ Schema: BasicBuilderSchema, - Config: []byte(`{ - "input": "components/basic.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/basic.yaml": basicYaml, @@ -69,16 +74,13 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "json", }), templateDefinition: TemplateDefinition{ Schema: BasicBuilderSchema, - Config: []byte(`{ - "input": "components/basic.yaml", - "output": "catalog.json" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.json")), }, files: map[string]string{ "components/basic.yaml": basicYaml, @@ -107,7 +109,7 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -123,30 +125,6 @@ func TestBasicBuilder(t *testing.T) { require.Contains(t, buildErr.Error(), "unmarshalling basic template config:") }, }, - { - name: "builder command failure", - validate: false, - basicBuilder: NewBasicBuilder(BuilderConfig{ - ContainerCfg: ContainerConfig{ - ContainerTool: "docker", - BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "basic", - }, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: BasicBuilderSchema, - Config: []byte(`{ - "input": "components/basic.yaml", - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "running command") - }, - }, { name: "invalid output type", validate: false, @@ -154,16 +132,13 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "invalid", }), templateDefinition: TemplateDefinition{ Schema: BasicBuilderSchema, - Config: []byte(`{ - "input": "components/basic.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/basic.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/basic.yaml": basicYaml, @@ -180,7 +155,7 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -200,7 +175,7 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -225,7 +200,7 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -250,7 +225,7 @@ func TestBasicBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/basic", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -268,10 +243,8 @@ func TestBasicBuilder(t *testing.T) { }, } - testDir := t.TempDir() - for i, tc := range testCases { - tc.basicBuilder.builderCfg.CurrentDirectory = testDir + tc.basicBuilder.builderCfg.InputDirectory = testDir t.Run(tc.name, func(t *testing.T) { outDir := fmt.Sprintf("basic-%d", i) outPath := path.Join(testDir, outDir) @@ -288,7 +261,16 @@ func TestBasicBuilder(t *testing.T) { require.NoError(t, err) } - buildErr := tc.basicBuilder.Build(outPath, tc.templateDefinition) + cacheDir, err := os.MkdirTemp("", "opm-registry-") + require.NoError(t, err) + + reg, err := containerdregistry.NewRegistry( + containerdregistry.WithCacheDir(cacheDir), + ) + defer reg.Destroy() + require.NoError(t, err) + + buildErr := tc.basicBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) tc.buildAssertions(t, outPath, buildErr) if tc.validate { @@ -454,6 +436,12 @@ func TestSemverBuilder(t *testing.T) { validateAssertions func(t *testing.T, validateErr error) } + testDir := t.TempDir() + validConfigTemplate := `{ + "input": "%s", + "output": "%s" + }` + testCases := []testCase{ { name: "successful semver build yaml output", @@ -462,16 +450,13 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), templateDefinition: TemplateDefinition{ Schema: SemverBuilderSchema, - Config: []byte(`{ - "input": "components/semver.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/semver.yaml": semverYaml, @@ -500,16 +485,13 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "json", }), templateDefinition: TemplateDefinition{ Schema: SemverBuilderSchema, - Config: []byte(`{ - "input": "components/semver.yaml", - "output": "catalog.json" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.json")), }, files: map[string]string{ "components/semver.yaml": semverYaml, @@ -538,7 +520,7 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -554,30 +536,6 @@ func TestSemverBuilder(t *testing.T) { require.Contains(t, buildErr.Error(), "unmarshalling semver template config:") }, }, - { - name: "builder command failure", - validate: false, - semverBuilder: NewSemverBuilder(BuilderConfig{ - ContainerCfg: ContainerConfig{ - ContainerTool: "docker", - BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "semver", - }, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: SemverBuilderSchema, - Config: []byte(`{ - "input": "components/semver.yaml", - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "running command") - }, - }, { name: "invalid output type", validate: false, @@ -585,16 +543,13 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "invalid", }), templateDefinition: TemplateDefinition{ Schema: SemverBuilderSchema, - Config: []byte(`{ - "input": "components/semver.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/semver.yaml": semverYaml, @@ -611,16 +566,13 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), templateDefinition: TemplateDefinition{ Schema: "olm.invalid", - Config: []byte(`{ - "input": "components/semver.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/semver.yaml"), "catalog.yaml")), }, files: map[string]string{}, buildAssertions: func(t *testing.T, dir string, buildErr error) { @@ -635,7 +587,7 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -660,7 +612,7 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -685,7 +637,7 @@ func TestSemverBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/semver", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -703,10 +655,8 @@ func TestSemverBuilder(t *testing.T) { }, } - testDir := t.TempDir() - for i, tc := range testCases { - tc.semverBuilder.builderCfg.CurrentDirectory = testDir + tc.semverBuilder.builderCfg.InputDirectory = testDir t.Run(tc.name, func(t *testing.T) { outDir := fmt.Sprintf("semver-%d", i) outPath := path.Join(testDir, outDir) @@ -721,9 +671,19 @@ func TestSemverBuilder(t *testing.T) { require.NoError(t, err) _, err = file.WriteString(fileContents) require.NoError(t, err) + // fmt.Printf("wrote file: %q\n", path.Join(testDir, fileName)) } - buildErr := tc.semverBuilder.Build(outPath, tc.templateDefinition) + cacheDir, err := os.MkdirTemp("", "opm-registry-") + require.NoError(t, err) + + reg, err := containerdregistry.NewRegistry( + containerdregistry.WithCacheDir(cacheDir), + ) + defer reg.Destroy() + require.NoError(t, err) + + buildErr := tc.semverBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) tc.buildAssertions(t, outPath, buildErr) if tc.validate { @@ -898,6 +858,12 @@ func TestRawBuilder(t *testing.T) { validateAssertions func(t *testing.T, validateErr error) } + testDir := t.TempDir() + validConfigTemplate := `{ + "input": "%s", + "output": "%s" + }` + testCases := []testCase{ { name: "successful raw build yaml output", @@ -906,16 +872,13 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), templateDefinition: TemplateDefinition{ Schema: RawBuilderSchema, - Config: []byte(`{ - "input": "components/raw.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/raw.yaml": rawYaml, @@ -944,16 +907,13 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "json", }), templateDefinition: TemplateDefinition{ Schema: RawBuilderSchema, - Config: []byte(`{ - "input": "components/raw.yaml", - "output": "catalog.json" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.json")), }, files: map[string]string{ "components/raw.yaml": rawYaml, @@ -982,7 +942,7 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -998,30 +958,6 @@ func TestRawBuilder(t *testing.T) { require.Contains(t, buildErr.Error(), "unmarshalling raw template config:") }, }, - { - name: "builder command failure", - validate: false, - rawBuilder: NewRawBuilder(BuilderConfig{ - ContainerCfg: ContainerConfig{ - ContainerTool: "docker", - BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "raw", - }, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: RawBuilderSchema, - Config: []byte(`{ - "input": "components/raw.yaml", - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "running command") - }, - }, { name: "invalid output type", validate: false, @@ -1029,16 +965,13 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "invalid", }), templateDefinition: TemplateDefinition{ Schema: RawBuilderSchema, - Config: []byte(`{ - "input": "components/raw.yaml", - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validConfigTemplate, path.Join(testDir, "components/raw.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/raw.yaml": semverYaml, @@ -1055,7 +988,7 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1075,7 +1008,7 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1100,7 +1033,7 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1125,7 +1058,7 @@ func TestRawBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/raw", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1143,10 +1076,8 @@ func TestRawBuilder(t *testing.T) { }, } - testDir := t.TempDir() - for i, tc := range testCases { - tc.rawBuilder.builderCfg.CurrentDirectory = testDir + tc.rawBuilder.builderCfg.InputDirectory = testDir t.Run(tc.name, func(t *testing.T) { outDir := fmt.Sprintf("raw-%d", i) outPath := path.Join(testDir, outDir) @@ -1163,7 +1094,16 @@ func TestRawBuilder(t *testing.T) { require.NoError(t, err) } - buildErr := tc.rawBuilder.Build(outPath, tc.templateDefinition) + cacheDir, err := os.MkdirTemp("", "opm-registry-") + require.NoError(t, err) + + reg, err := containerdregistry.NewRegistry( + containerdregistry.WithCacheDir(cacheDir), + ) + defer reg.Destroy() + require.NoError(t, err) + + buildErr := tc.rawBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) tc.buildAssertions(t, outPath, buildErr) if tc.validate { @@ -1361,6 +1301,13 @@ func TestCustomBuilder(t *testing.T) { validateAssertions func(t *testing.T, validateErr error) } + testDir := t.TempDir() + validTemplateConfig := `{ + "command": "%s", + "args": ["%s"], + "output": "%s" + }` + testCases := []testCase{ { name: "successful custom build yaml output", @@ -1369,17 +1316,13 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), templateDefinition: TemplateDefinition{ Schema: CustomBuilderSchema, - Config: []byte(`{ - "command": "cat", - "args": ["components/custom.yaml"], - "output": "catalog.yaml" - }`), + Config: []byte(fmt.Sprintf(validTemplateConfig, "cat", path.Join(testDir, "components/custom.yaml"), "catalog.yaml")), }, files: map[string]string{ "components/custom.yaml": customYaml, @@ -1408,17 +1351,13 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "json", }), templateDefinition: TemplateDefinition{ Schema: CustomBuilderSchema, - Config: []byte(`{ - "command": "cat", - "args": ["components/custom.yaml"], - "output": "catalog.json" - }`), + Config: []byte(fmt.Sprintf(validTemplateConfig, "cat", path.Join(testDir, "components/custom.yaml"), "catalog.json")), }, files: map[string]string{ "components/custom.yaml": customYaml, @@ -1447,7 +1386,7 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1463,31 +1402,6 @@ func TestCustomBuilder(t *testing.T) { require.Contains(t, buildErr.Error(), "unmarshalling custom template config:") }, }, - { - name: "builder command failure", - validate: false, - customBuilder: NewCustomBuilder(BuilderConfig{ - ContainerCfg: ContainerConfig{ - ContainerTool: "docker", - BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", - }, - OutputType: "yaml", - }), - templateDefinition: TemplateDefinition{ - Schema: CustomBuilderSchema, - Config: []byte(`{ - "command": "thiscommanddoesnotexist", - "args": [], - "output": "catalog.yaml" - }`), - }, - files: map[string]string{}, - buildAssertions: func(t *testing.T, dir string, buildErr error) { - require.Error(t, buildErr) - require.Contains(t, buildErr.Error(), "running command") - }, - }, { name: "invalid schema", validate: false, @@ -1495,7 +1409,7 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1519,7 +1433,7 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1545,7 +1459,7 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1571,7 +1485,7 @@ func TestCustomBuilder(t *testing.T) { ContainerCfg: ContainerConfig{ ContainerTool: "docker", BaseImage: "quay.io/operator-framework/opm:latest", - WorkingDir: "/custom", + WorkingDir: testDir, }, OutputType: "yaml", }), @@ -1590,10 +1504,8 @@ func TestCustomBuilder(t *testing.T) { }, } - testDir := t.TempDir() - for i, tc := range testCases { - tc.customBuilder.builderCfg.CurrentDirectory = testDir + tc.customBuilder.builderCfg.InputDirectory = testDir t.Run(tc.name, func(t *testing.T) { outDir := fmt.Sprintf("custom-%d", i) outPath := path.Join(testDir, outDir) @@ -1610,7 +1522,16 @@ func TestCustomBuilder(t *testing.T) { require.NoError(t, err) } - buildErr := tc.customBuilder.Build(outPath, tc.templateDefinition) + cacheDir, err := os.MkdirTemp("", "opm-registry-") + require.NoError(t, err) + + reg, err := containerdregistry.NewRegistry( + containerdregistry.WithCacheDir(cacheDir), + ) + defer reg.Destroy() + require.NoError(t, err) + + buildErr := tc.customBuilder.Build(context.Background(), reg, outDir, tc.templateDefinition) tc.buildAssertions(t, outPath, buildErr) if tc.validate { @@ -1800,5 +1721,5 @@ const customBuiltFbcJson = `{ func TestValidateFailure(t *testing.T) { err := validate(ContainerConfig{}, "") require.Error(t, err) - require.Contains(t, err.Error(), "running command") + require.Contains(t, err.Error(), "no such file or directory") } diff --git a/alpha/template/composite/composite.go b/alpha/template/composite/composite.go index 537db48af..422017c97 100644 --- a/alpha/template/composite/composite.go +++ b/alpha/template/composite/composite.go @@ -3,6 +3,8 @@ package composite import ( "context" "fmt" + + "github.com/operator-framework/operator-registry/pkg/image" ) type BuilderMap map[string]Builder @@ -11,6 +13,7 @@ type CatalogBuilderMap map[string]BuilderMap type Template struct { CatalogBuilders CatalogBuilderMap + Registry image.Registry } // TODO(everettraven): do we need the context here? If so, how should it be used? @@ -20,7 +23,7 @@ func (t *Template) Render(ctx context.Context, config *CompositeConfig, validate if builderMap, ok := t.CatalogBuilders[component.Name]; ok { if builder, ok := builderMap[component.Strategy.Template.Schema]; ok { // run the builder corresponding to the schema - err := builder.Build(component.Destination.Path, component.Strategy.Template) + err := builder.Build(ctx, t.Registry, component.Destination.Path, component.Strategy.Template) if err != nil { return fmt.Errorf("building component %q: %w", component.Name, err) } diff --git a/alpha/template/composite/composite_test.go b/alpha/template/composite/composite_test.go index 35bc75471..c2b28fac7 100644 --- a/alpha/template/composite/composite_test.go +++ b/alpha/template/composite/composite_test.go @@ -7,6 +7,7 @@ import ( "fmt" "testing" + "github.com/operator-framework/operator-registry/pkg/image" "github.com/stretchr/testify/require" ) @@ -20,7 +21,7 @@ const validateErr = "TestBuilder Validate() error" var _ Builder = &TestBuilder{} -func (tb *TestBuilder) Build(dir string, vd TemplateDefinition) error { +func (tb *TestBuilder) Build(ctx context.Context, reg image.Registry, dir string, vd TemplateDefinition) error { if tb.buildError { return errors.New(buildErr) } diff --git a/cmd/opm/alpha/template/composite.go b/cmd/opm/alpha/template/composite.go index 9cac25c6c..1d8b9df0c 100644 --- a/cmd/opm/alpha/template/composite.go +++ b/cmd/opm/alpha/template/composite.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/util/yaml" "github.com/operator-framework/operator-registry/alpha/template/composite" + "github.com/operator-framework/operator-registry/cmd/opm/internal/util" ) func newCompositeTemplateCmd() *cobra.Command { @@ -89,8 +90,9 @@ and a 'composite template' file`, BaseImage: catalog.Destination.BaseImage, WorkingDir: catalog.Destination.WorkingDir, }, - OutputType: output, - CurrentDirectory: wd, + OutputType: output, + // BUGBUG: JEK: This is a strong assumption that input is always local to execution which we need to eliminate in a later PR + InputDirectory: wd, }) if err != nil { log.Fatalf("getting builder %q for catalog %q: %s", schema, catalog.Name, err) @@ -116,6 +118,14 @@ and a 'composite template' file`, template.CatalogBuilders = catalogBuilderMap + reg, err := util.CreateCLIRegistry(cmd) + if err != nil { + log.Fatalf("creating containerd registry: %v", err) + } + defer reg.Destroy() + + template.Registry = reg + compositeData, err := os.Open(compositeFile) if err != nil { log.Fatalf("opening composite config file %q: %s", compositeFile, err)