Skip to content

Commit

Permalink
Merge pull request #14 from eunomie/cache
Browse files Browse the repository at this point in the history
feat: cache runx readme and config locally
  • Loading branch information
eunomie authored Oct 14, 2024
2 parents 2fc21e8 + 23b0000 commit ae4dab6
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 62 deletions.
5 changes: 3 additions & 2 deletions internal/commands/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
src string
action string
lc = runkit.GetLocalConfig()
cache = runkit.NewLocalCache(dockerCli)
)

switch len(args) {
Expand Down Expand Up @@ -83,14 +84,14 @@ func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
Type(spinner.Globe).
Title(" Fetching runx details...").
Action(func() {
rk, err = runkit.Get(cmd.Context(), src)
rk, err = runkit.Get(cmd.Context(), cache, src)
if err != nil {
_, _ = fmt.Fprintln(dockerCli.Err(), err)
os.Exit(1)
}
}).Run()
} else {
rk, err = runkit.Get(cmd.Context(), src)
rk, err = runkit.Get(cmd.Context(), cache, src)
}
if err != nil {
return err
Expand Down
83 changes: 83 additions & 0 deletions runkit/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package runkit

import (
"os"
"path/filepath"

"github.com/docker/cli/cli/command"
"github.com/eunomie/docker-runx/internal/constants"
)

const (
runxConfigFile = "runx.yaml"
runxDocFile = "README.md"
)

var subCacheDir = filepath.Join(constants.SubCommandName, "cache", "sha256")

type LocalCache struct {
cacheDir string
}

func NewLocalCache(cli command.Cli) *LocalCache {
rootDir := filepath.Dir(cli.ConfigFile().Filename)
cacheDir := filepath.Join(rootDir, subCacheDir)

return &LocalCache{
cacheDir: cacheDir,
}
}

func (c *LocalCache) Get(digest string) (*RunKit, error) {
rk := &RunKit{
Files: make(map[string]string),
}
found := false

configFile := filepath.Join(c.cacheDir, digest, runxConfigFile)
if runxConfig, err := os.ReadFile(configFile); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
if err = decodeConfig(rk, digest, runxConfig); err != nil {
return nil, err
}
found = true
}

readmeFile := filepath.Join(c.cacheDir, digest, runxDocFile)
if runxDoc, err := os.ReadFile(readmeFile); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
rk.Readme = string(runxDoc)
found = true
}

if found {
return rk, nil
}
return nil, nil
}

func (c *LocalCache) Set(digest string, runxConfig, runxDoc []byte) error {
digestDir := filepath.Join(c.cacheDir, digest)
if err := os.MkdirAll(digestDir, 0o755); err != nil {
return err
}
if len(runxConfig) > 0 {
configFile := filepath.Join(c.cacheDir, digest, runxConfigFile)
if err := os.WriteFile(configFile, runxConfig, 0o644); err != nil {
return err
}
}
if len(runxDoc) > 0 {
readmeFile := filepath.Join(c.cacheDir, digest, runxDocFile)
if err := os.WriteFile(readmeFile, runxDoc, 0o644); err != nil {
return err
}
}
return nil
}
154 changes: 94 additions & 60 deletions runkit/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,40 @@ import (
"github.com/eunomie/docker-runx/internal/registry"
)

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

Cache interface {
Get(digest string) (*RunKit, error)
Set(digest string, runxConfig, runxDoc []byte) error
}
)

func Get(ctx context.Context, src string) (*RunKit, error) {
func Get(ctx context.Context, cache Cache, src string) (*RunKit, error) {
var (
err error
index v1.ImageIndex
desc *remote.Descriptor
manifest v1.Descriptor
runxImg v1.Image
layers []v1.Layer
runxConfig []byte
runxDoc []byte
files Files
config Config
rk = RunKit{
err error
desc *v1.Descriptor
indexDigest string
cached *RunKit
index v1.ImageIndex
manifest v1.Descriptor
runxImg v1.Image
layers []v1.Layer
runxConfig []byte
runxDoc []byte
rk = &RunKit{
Files: make(map[string]string),
}
remoteOpts = registry.WithOptions(ctx, nil)
ref, _ = name.ParseReference(src)
)

desc, err = remote.Get(ref, remoteOpts...)
desc, err = remote.Head(ref, remoteOpts...)
if err != nil {
return nil, fmt.Errorf("could not get image %s: %w", src, err)
}
Expand All @@ -51,6 +58,13 @@ func Get(ctx context.Context, src string) (*RunKit, error) {
return nil, fmt.Errorf("image %s can't be read by 'docker runx': should be an index", src)
}

indexDigest = desc.Digest.String()

cached, err = cache.Get(indexDigest)
if err == nil && cached != nil {
return cached, nil
}

index, err = remote.Index(ref, remoteOpts...)
if err != nil {
return nil, fmt.Errorf("could not get index %s: %w", src, err)
Expand Down Expand Up @@ -106,47 +120,10 @@ 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
for _, a := range config.Actions {
// 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
}
}

if config.Default == a.ID {
a.isDefault = true
}

actions = append(actions, a)
err = decodeConfig(rk, src, runxConfig)
if err != nil {
return nil, err
}
config.Actions = actions
rk.Config = config
}

if len(runxDoc) != 0 {
Expand All @@ -155,9 +132,66 @@ func Get(ctx context.Context, src string) (*RunKit, error) {

rk.src = src

return &rk, nil
err = cache.Set(indexDigest, runxConfig, runxDoc)
if err != nil {
// TODO: log error
return rk, nil
}

return rk, nil
}

func b64Decode(content string) ([]byte, error) {
return base64.StdEncoding.DecodeString(content)
}

func decodeConfig(rk *RunKit, src string, runxConfig []byte) error {
var (
config Config
err error
files Files
)
dec := yaml.NewDecoder(bytes.NewReader(runxConfig))
dec.KnownFields(true)
// first, the runx config itself
if err = dec.Decode(&config); err != nil {
return 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 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 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 fmt.Errorf("could not unmarshal runx config %s: %w", src, err)
}
var actions []Action
for _, a := range config.Actions {
// 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
}
}

if config.Default == a.ID {
a.isDefault = true
}

actions = append(actions, a)
}
config.Actions = actions
rk.Config = config

return nil
}

0 comments on commit ae4dab6

Please sign in to comment.