Skip to content

Commit 83b59da

Browse files
committed
feat: read runx actions from a remote image
Access to an image and reads the configuration manifest to extract runnable actions. docker runx IMAGE Signed-off-by: Yves Brissaud <[email protected]>
1 parent 0120382 commit 83b59da

File tree

8 files changed

+131
-9
lines changed

8 files changed

+131
-9
lines changed

docs/reference/docker_runx.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
command: docker runx
22
short: Docker Run, better
33
long: Docker Run, better
4-
usage: docker runx [command]
4+
usage: docker runx [IMAGE]
55
pname: docker
66
plink: docker.yaml
77
cname:

examples/alpine-hello.runx.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
actions:
22
- id: hello
33
type: run
4-
cmds: --rm {{.Ref}} echo hello
4+
cmd: --rm {{.Ref}} echo hello

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/google/go-containerregistry v0.20.2
1010
github.com/spf13/cobra v1.8.1
1111
github.com/spf13/pflag v1.0.5
12+
gopkg.in/yaml.v2 v2.4.0
1213
)
1314

1415
require (

internal/commands/root/root.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/spf13/cobra"
7+
"gopkg.in/yaml.v2"
78

89
"github.com/docker/cli/cli"
910
"github.com/docker/cli/cli-plugins/plugin"
@@ -12,23 +13,28 @@ import (
1213
"github.com/eunomie/docker-runx/internal/commands/help"
1314
"github.com/eunomie/docker-runx/internal/commands/version"
1415
"github.com/eunomie/docker-runx/internal/constants"
16+
"github.com/eunomie/docker-runx/runkit"
1517
)
1618

1719
func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
1820
var (
1921
name = commandName(isPlugin)
2022
cmd = &cobra.Command{
21-
Use: fmt.Sprintf("%s [command]", name),
23+
Use: fmt.Sprintf("%s [IMAGE]", name),
2224
Short: "Docker Run, better",
2325
RunE: func(cmd *cobra.Command, args []string) error {
2426
if len(args) == 0 {
2527
return cmd.Help()
2628
}
27-
_ = cmd.Help()
28-
return cli.StatusError{
29-
StatusCode: 1,
30-
Status: fmt.Sprintf("unknown command: %q", args[0]),
29+
30+
src := args[0]
31+
32+
actions, err := runkit.Get(cmd.Context(), src)
33+
if err != nil {
34+
return err
3135
}
36+
37+
return yaml.NewEncoder(cmd.OutOrStdout()).Encode(actions)
3238
},
3339
}
3440
)

internal/runkit/image.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
)
1111

1212
const (
13+
RunxAnnotation = "vnd.docker.reference.type"
1314
RunxManifestType = "runx-manifest"
1415
RunxConfigType = "application/vnd.runx.config+yaml"
1516
)
@@ -34,7 +35,7 @@ func Image(runxConfig []byte) (v1.Image, *v1.Descriptor, error) {
3435
desc, _ := partial.Descriptor(img)
3536
desc.Platform = config.Platform()
3637
desc.Annotations = make(map[string]string)
37-
desc.Annotations["vnd.docker.reference.type"] = RunxManifestType
38+
desc.Annotations[RunxAnnotation] = RunxManifestType
3839

3940
return img, desc, nil
4041
}

runkit/decorate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func Decorate(ctx context.Context, src, dest string, runxConfig []byte) error {
2121
index v1.ImageIndex
2222
remoteOpts = registry.WithOptions(ctx, nil)
2323
ref, _ = name.ParseReference(src)
24+
desc *remote.Descriptor
2425
destRef, _ = name.ParseReference(dest)
2526
runxImage, runxDesc, err = runkit.Image(runxConfig)
2627
)
@@ -29,7 +30,7 @@ func Decorate(ctx context.Context, src, dest string, runxConfig []byte) error {
2930
return fmt.Errorf("could not create runx image: %w", err)
3031
}
3132

32-
desc, err := remote.Get(ref, remoteOpts...)
33+
desc, err = remote.Get(ref, remoteOpts...)
3334
if err != nil {
3435
return fmt.Errorf("could not get image %s: %w", src, err)
3536
}

runkit/read.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package runkit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
8+
"github.com/google/go-containerregistry/pkg/name"
9+
v1 "github.com/google/go-containerregistry/pkg/v1"
10+
"github.com/google/go-containerregistry/pkg/v1/remote"
11+
"gopkg.in/yaml.v2"
12+
13+
"github.com/eunomie/docker-runx/internal/registry"
14+
"github.com/eunomie/docker-runx/internal/runkit"
15+
)
16+
17+
func Get(ctx context.Context, src string) (*RunKit, error) {
18+
var (
19+
err error
20+
index v1.ImageIndex
21+
desc *remote.Descriptor
22+
manifest v1.Descriptor
23+
runxImg v1.Image
24+
layers []v1.Layer
25+
runxConfig []byte
26+
actions RunKit
27+
remoteOpts = registry.WithOptions(ctx, nil)
28+
ref, _ = name.ParseReference(src)
29+
)
30+
31+
desc, err = remote.Get(ref, remoteOpts...)
32+
if err != nil {
33+
return nil, fmt.Errorf("could not get image %s: %w", src, err)
34+
}
35+
36+
if !desc.MediaType.IsIndex() {
37+
return nil, fmt.Errorf("image %s can't be read by 'docker runx': should be an index", src)
38+
}
39+
40+
index, err = remote.Index(ref, remoteOpts...)
41+
if err != nil {
42+
return nil, fmt.Errorf("could not get index %s: %w", src, err)
43+
}
44+
45+
found := false
46+
manifests, _ := index.IndexManifest()
47+
for _, m := range manifests.Manifests {
48+
if a, ok := m.Annotations[runkit.RunxAnnotation]; ok && a == runkit.RunxManifestType {
49+
manifest = m
50+
found = true
51+
break
52+
}
53+
}
54+
55+
if !found {
56+
return nil, fmt.Errorf("image %s can't be read by 'docker runx': no runx manifest found", src)
57+
}
58+
runxImg, err = index.Image(manifest.Digest)
59+
if err != nil {
60+
return nil, fmt.Errorf("could not get runx image %s: %w", src, err)
61+
}
62+
63+
layers, err = runxImg.Layers()
64+
if err != nil {
65+
return nil, fmt.Errorf("could not get runx layers %s: %w", src, err)
66+
}
67+
68+
for _, l := range layers {
69+
if mt, err := l.MediaType(); err == nil && mt == runkit.RunxConfigType {
70+
dataReader, err := l.Uncompressed()
71+
if err != nil {
72+
return nil, fmt.Errorf("could not read runx actions %s: %w", src, err)
73+
}
74+
runxConfig, err = io.ReadAll(dataReader)
75+
if err != nil {
76+
return nil, fmt.Errorf("could not read runx actions %s: %w", src, err)
77+
}
78+
break
79+
}
80+
}
81+
82+
if len(runxConfig) == 0 {
83+
return nil, fmt.Errorf("image %s can't be read by 'docker runx': no runx actions found", src)
84+
}
85+
86+
if err = yaml.Unmarshal(runxConfig, &actions); err != nil {
87+
return nil, fmt.Errorf("could not unmarshal runx actions %s: %w", src, err)
88+
}
89+
90+
actions.src = src
91+
92+
return &actions, nil
93+
}

runkit/types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package runkit
2+
3+
type (
4+
RunKit struct {
5+
Actions []Action `yaml:"actions" json:"actions"`
6+
src string
7+
}
8+
9+
Action struct {
10+
ID string `yaml:"id" json:"id"`
11+
Type ActionType `yaml:"type" json:"type"`
12+
Command string `yaml:"cmd" json:"cmd,omitempty"`
13+
}
14+
15+
ActionType string
16+
)
17+
18+
const (
19+
ActionTypeRun ActionType = "run"
20+
)

0 commit comments

Comments
 (0)