Skip to content
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
169 changes: 98 additions & 71 deletions alpha/template/composite/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -28,13 +34,13 @@ type ContainerConfig struct {
}

type BuilderConfig struct {
ContainerCfg ContainerConfig
OutputType string
CurrentDirectory string
ContainerCfg ContainerConfig
Comment thread
grokspawn marked this conversation as resolved.
OutputType string
InputDirectory string
Comment thread
grokspawn marked this conversation as resolved.
}

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
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Comment thread
grokspawn marked this conversation as resolved.
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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Comment thread
grokspawn marked this conversation as resolved.
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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Comment thread
grokspawn marked this conversation as resolved.
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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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...)
Comment thread
everettraven marked this conversation as resolved.
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 {
Expand All @@ -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
Expand Down
Loading