Skip to content

Commit

Permalink
feat: allow to run build commands
Browse files Browse the repository at this point in the history
And to be able to run commands, allow to embed a Dockerfile in the runx
manifest file.

Signed-off-by: Yves Brissaud <[email protected]>
  • Loading branch information
eunomie committed Oct 9, 2024
1 parent ec76bb3 commit d5e5b0f
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 33 deletions.
36 changes: 36 additions & 0 deletions examples/alpine-hello.runx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,39 @@ actions:
required: true
cmd: >
--rm {{.Ref}} echo hello {{opt "name"}}
- id: lint
desc: Run golangci-lint
type: run
shell:
pwd: pwd
gopath: go env GOPATH
gocache: go env GOCACHE
cmd: >
--rm
-v {{sh "pwd"}}:/app
-v {{sh "gopath"}}/pkg:/go/pkg
-v {{sh "gocache"}}:/cache/go
-e GOFLAGS=-buildvcs=false
-e GOCACHE=/cache/go
-e GOLANGCI_LINT_CACHE=/cache/go
-w /app
golangci/golangci-lint
golangci-lint run -v --timeout 5m
- id: build
type: build
dockerfile: Dockerfile
opts:
- name: bin_name
prompt: Please enter then name of the binary
required: true
- name: platforms
prompt: "Please enter the comma separated list of platforms (ex: linux/amd64,linux/arm64)"
cmd: >
-f {{.Dockerfile}}
--build-arg BIN_NAME={{opt "bin_name"}}
{{if opt "platforms"}}--platform {{opt "platforms"}}{{end}}
--target export-bin
--output type=local,dest=dist/
.
40 changes: 40 additions & 0 deletions internal/commands/decorate/decorate.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package decorate

import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"os"

"github.com/charmbracelet/huh/spinner"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"

"github.com/docker/cli/cli/command"
"github.com/eunomie/docker-runx/runkit"
Expand Down Expand Up @@ -48,6 +51,36 @@ func NewCmd(dockerCli command.Cli) *cobra.Command {
if err != nil {
return err
}

fullConfig := bytes.NewBuffer(config)

var cfg runkit.Config
if err = yaml.Unmarshal(config, &cfg); err != nil {
return err
}

files := bytes.NewBuffer(nil)
for _, a := range cfg.Actions {
if a.Dockerfile != "" {
dockerfile, err := os.ReadFile(a.Dockerfile)
if err != nil {
return err
}
if dockerfileContent, err := b64Encode(dockerfile, nil); err != nil {
return err
} else {
files.WriteString(fmt.Sprintf("- name: %s\n content: %s\n", a.Dockerfile, dockerfileContent))
}
}
}

if files.Len() > 0 {
fullConfig.WriteString("\n---\n")
fullConfig.WriteString("files:\n")
fullConfig.Write(files.Bytes())
}

config = fullConfig.Bytes()
}

if readmeFile != "" {
Expand Down Expand Up @@ -90,3 +123,10 @@ func NewCmd(dockerCli command.Cli) *cobra.Command {

return cmd
}

func b64Encode(content []byte, err error) (string, error) {
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(content), nil
}
3 changes: 2 additions & 1 deletion internal/commands/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ func getValuesLocal(src, action string) map[string]string {
}

func run(ctx context.Context, out io.Writer, src string, rk *runkit.RunKit, action string) error {
runnable, err := rk.GetRunnable(action)
runnable, cleanup, err := rk.GetRunnable(action)
defer cleanup()
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func envStr(env []string) string {
return " (required env: " + strings.Join(env, ", ") + ")"
}

func Ask(action runkit.Action, opts map[string]string) (map[string]string, error) {
func Ask(action *runkit.Action, opts map[string]string) (map[string]string, error) {
if len(action.Options) == 0 {
return nil, nil
}
Expand Down
10 changes: 6 additions & 4 deletions internal/runkit/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
)

const (
RunxAnnotation = "vnd.docker.reference.type"
RunxManifestType = "runx-manifest"
RunxConfigType = "application/vnd.runx.config+yaml"
RunxDocType = "application/vnd.runx.readme+txt"
RunxAnnotation = "vnd.docker.reference.type"
RunxManifestType = "runx-manifest"
RunxConfigType = "application/vnd.runx.config+yaml"
RunxDocType = "application/vnd.runx.readme+txt"
RunxFileNameAnnotation = "vnd.runx.filename"
RunxLayerFile = "application/vnd.runx.file.gzip"
)

func Image(runxConfig, runxDoc []byte) (v1.Image, *v1.Descriptor, error) {
Expand Down
46 changes: 43 additions & 3 deletions runkit/read.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package runkit

import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"strings"
Expand All @@ -15,6 +17,13 @@ import (
"github.com/eunomie/docker-runx/internal/runkit"
)

type Files struct {
Files []struct {
Name string `yaml:"name"`
Content string `yaml:"content"`
} `yaml:"files"`
}

func Get(ctx context.Context, src string) (*RunKit, error) {
var (
err error
Expand All @@ -25,8 +34,11 @@ func Get(ctx context.Context, src string) (*RunKit, error) {
layers []v1.Layer
runxConfig []byte
runxDoc []byte
files Files
config Config
rk RunKit
rk = RunKit{
Files: make(map[string]string),
}
remoteOpts = registry.WithOptions(ctx, nil)
ref, _ = name.ParseReference(src)
)
Expand Down Expand Up @@ -95,14 +107,38 @@ func Get(ctx context.Context, src string) (*RunKit, error) {
}

if len(runxConfig) != 0 {
dec := yaml.NewDecoder(bytes.NewReader(runxConfig))
dec.KnownFields(true)
// first, the runx config itself
if err = dec.Decode(&config); err != nil {
return nil, fmt.Errorf("could not decode runx config %s: %w", src, err)
}
// then, the optional files
if err = dec.Decode(&files); err != nil && err != io.EOF {
return nil, fmt.Errorf("could not decode runx files %s: %w", src, err)
} else {
for _, f := range files.Files {
c, err := b64Decode(f.Content)
if err != nil {
return nil, fmt.Errorf("could not decode runx file %s: %w", f.Name, err)
}
rk.Files[f.Name] = string(c)
}
}

if err = yaml.Unmarshal(runxConfig, &config); err != nil {
return nil, fmt.Errorf("could not unmarshal runx config %s: %w", src, err)
}
var actions []Action
// TODO: fix reading of multiline YAML strings
for _, a := range config.Actions {
// a := a
// TODO: fix reading of multiline YAML strings
a.Command = strings.ReplaceAll(a.Command, "\n", " ")

if a.Dockerfile != "" {
if c, ok := rk.Files[a.Dockerfile]; ok {
a.DockerfileContent = c
}
}
actions = append(actions, a)
}
config.Actions = actions
Expand All @@ -117,3 +153,7 @@ func Get(ctx context.Context, src string) (*RunKit, error) {

return &rk, nil
}

func b64Decode(content string) ([]byte, error) {
return base64.StdEncoding.DecodeString(content)
}
66 changes: 50 additions & 16 deletions runkit/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,48 @@ import (

type (
Runnable struct {
Command string
command string
args string
data TemplateData
Action Action
Command string
command string
args string
data TemplateData
Action *Action
dockerfile string
}

TemplateData struct {
Ref string
Env map[string]string
Opts map[string]string
Ref string
Env map[string]string
Opts map[string]string
Dockerfile string
}
)

func (rk *RunKit) GetRunnable(action string) (*Runnable, error) {
var noop = func() {}

func (rk *RunKit) GetRunnable(action string) (*Runnable, func(), error) {
for _, a := range rk.Config.Actions {
if a.ID == action {
return a.GetRunnable(rk.src)
}
}

return nil, fmt.Errorf("action %s not found", action)
return nil, noop, fmt.Errorf("action %s not found", action)
}

var rootCommands = map[ActionType]string{
ActionTypeRun: "docker run",
ActionTypeBuild: "docker buildx build",
}

func (action Action) GetRunnable(ref string) (*Runnable, error) {
if action.Type != ActionTypeRun {
return nil, fmt.Errorf("unsupported action type %s", action.Type)
func (action *Action) GetRunnable(ref string) (*Runnable, func(), error) {
rootCommand, ok := rootCommands[action.Type]
if !ok {
return nil, noop, fmt.Errorf("unsupported action type %s", action.Type)
}

runnable := Runnable{
Action: action,
command: "docker run",
command: rootCommand,
data: TemplateData{
Ref: ref,
Env: map[string]string{},
Expand All @@ -54,13 +64,37 @@ func (action Action) GetRunnable(ref string) (*Runnable, error) {

for _, env := range action.Env {
if v, ok := os.LookupEnv(env); !ok {
return nil, fmt.Errorf("environment variable %q is required", env)
return nil, noop, fmt.Errorf("environment variable %q is required", env)
} else {
runnable.data.Env[env] = v
}
}

return &runnable, nil
if action.DockerfileContent != "" {
f, err := os.CreateTemp("", "runx.*.Dockerfile")
if err != nil {
return nil, noop, err
}
if _, err := f.Write([]byte(action.DockerfileContent)); err != nil {
f.Close() //nolint:errcheck
return nil, noop, err
}
runnable.dockerfile = f.Name()
runnable.data.Dockerfile = f.Name()
if err := f.Close(); err != nil {
return nil, noop, err
}
}

return &runnable, runnable.cleanup(), nil
}

func (r *Runnable) cleanup() func() {
return func() {
if r.dockerfile != "" {
_ = os.Remove(r.dockerfile)
}
}
}

func (r *Runnable) compute() error {
Expand Down
20 changes: 12 additions & 8 deletions runkit/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type (
RunKit struct {
Config Config
Readme string
Files map[string]string

src string
}
Expand All @@ -14,13 +15,15 @@ type (
}

Action struct {
ID string `yaml:"id" json:"id"`
Desc string `yaml:"desc,omitempty" json:"desc,omitempty"`
Type ActionType `yaml:"type" json:"type"`
Command string `yaml:"cmd" json:"cmd,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Options []Opt `yaml:"opts,omitempty" json:"opts,omitempty"`
Shell map[string]string `yaml:"shell,omitempty" json:"shell,omitempty"`
ID string `yaml:"id" json:"id"`
Desc string `yaml:"desc,omitempty" json:"desc,omitempty"`
Type ActionType `yaml:"type" json:"type"`
Command string `yaml:"cmd" json:"cmd,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Options []Opt `yaml:"opts,omitempty" json:"opts,omitempty"`
Shell map[string]string `yaml:"shell,omitempty" json:"shell,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
DockerfileContent string
}

Opt struct {
Expand Down Expand Up @@ -49,5 +52,6 @@ type (
)

const (
ActionTypeRun ActionType = "run"
ActionTypeRun ActionType = "run"
ActionTypeBuild ActionType = "build"
)

0 comments on commit d5e5b0f

Please sign in to comment.