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
2 changes: 2 additions & 0 deletions cmd/unikraft/testdata/TestGolden/build/help

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions cmd/unikraft/testdata/TestGolden/images/help

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion internal/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type BuildCmd struct {
NoCache bool `help:"Do not use cache when building the image."`
Secret []string `help:"Secret to expose to the build (format: \"id=mysecret[,src=/local/secret]\")."`
SSH []string `help:"SSH agent socket or keys to expose to the build (format: \"default|<id>[=<socket>|<key>[,<key>]]\")."`

Insecure []string `help:"Allow insecure (HTTP/unverified TLS) connections to registries. Specify hostnames to restrict, or omit to apply to all." type:"optional"`
}

func (BuildCmd) Examples() []kingkong.Example {
Expand Down Expand Up @@ -125,7 +127,16 @@ func (c *BuildCmd) Run(ctx context.Context, cfg *config.Config) error {
return err
}

access, err := images.Accessor(ctx)
var opts []images.AccessorOpt
if c.Insecure != nil {
if len(c.Insecure) > 0 {
opts = append(opts, images.WithInsecureRegistry(c.Insecure...))
} else {
opts = append(opts, images.WithInsecureRegistries())
Comment thread
jedevc marked this conversation as resolved.
}
}

access, err := images.Accessor(ctx, opts...)
if err != nil {
return err
}
Expand Down
55 changes: 51 additions & 4 deletions internal/cmd/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ import (
type ImagesCmd struct {
cmd.ResourceCmd[ImageEntry]
cmd.ListableResourceCmd[ImageEntry]
cmd.GettableResourceCmd[Image]
cmd.DeletableResourceCmd[Image]

Copy ImagesCopyCmd `cmd:"" help:"Copy images."`
Get ImagesGetCmd `cmd:"" help:"Inspect an image." aliases:"inspect,show"`
Delete ImagesDeleteCmd `cmd:"" help:"Remove an image." aliases:"rm,remove"`
Copy ImagesCopyCmd `cmd:"" help:"Copy images."`
}

type Image struct {
Expand Down Expand Up @@ -640,9 +640,47 @@ func (ImageEntry) loadFromPlatform(image platform.Image, metro *config.Metro) ([
return results, nil
}

type ImagesGetCmd struct {
cmd.ResourceGetCmd[Image]
Insecure []string `help:"Allow insecure (HTTP/unverified TLS) connections to registries. Specify hostnames to restrict, or omit to apply to all." type:"optional"`
}

func (c *ImagesGetCmd) Run(ctx context.Context, stdio config.Stdio, sandbox *resource.Sandbox) error {
if c.Insecure != nil {
var opts []images.AccessorOpt
if len(c.Insecure) > 0 {
opts = append(opts, images.WithInsecureRegistry(c.Insecure...))
} else {
opts = append(opts, images.WithInsecureRegistries())
}
ctx = images.WithInsecureContext(ctx, opts...)
}
return c.ResourceGetCmd.Run(ctx, stdio, sandbox)
}

type ImagesDeleteCmd struct {
cmd.ResourceRemoveCmd[Image]
Insecure []string `help:"Allow insecure (HTTP/unverified TLS) connections to registries. Specify hostnames to restrict, or omit to apply to all." type:"optional"`
}

func (c *ImagesDeleteCmd) Run(ctx context.Context, stdio config.Stdio, sandbox *resource.Sandbox) error {
if c.Insecure != nil {
var opts []images.AccessorOpt
if len(c.Insecure) > 0 {
opts = append(opts, images.WithInsecureRegistry(c.Insecure...))
} else {
opts = append(opts, images.WithInsecureRegistries())
}
ctx = images.WithInsecureContext(ctx, opts...)
}
return c.ResourceRemoveCmd.Run(ctx, stdio, sandbox)
}

type ImagesCopyCmd struct {
Source string `arg:"" help:"Source image reference."`
Dest string `arg:"" help:"Destination image reference."`

Insecure []string `help:"Allow insecure (HTTP/unverified TLS) connections to registries. Specify hostnames to restrict, or omit to apply to all." type:"optional"`
}

func (cmd ImagesCopyCmd) Examples() []kingkong.Example {
Expand All @@ -669,7 +707,16 @@ func (cmd ImagesCopyCmd) Examples() []kingkong.Example {
}

func (cmd ImagesCopyCmd) Run(ctx context.Context) error {
access, err := images.Accessor(ctx)
var opts []images.AccessorOpt
if cmd.Insecure != nil {
if len(cmd.Insecure) > 0 {
opts = append(opts, images.WithInsecureRegistry(cmd.Insecure...))
} else {
opts = append(opts, images.WithInsecureRegistries())
}
}

access, err := images.Accessor(ctx, opts...)
if err != nil {
return err
}
Expand Down
43 changes: 41 additions & 2 deletions internal/images/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,33 @@ var defaultRegistries = []string{
"index.unikraft.io",
}

func Accessor(ctx context.Context) (*imagespec.Accessor, error) {
type insecureContextKey struct{}

// WithInsecureContext returns a context that carries insecure registry options,
// which are picked up by Accessor.
func WithInsecureContext(ctx context.Context, opts ...AccessorOpt) context.Context {
return context.WithValue(ctx, insecureContextKey{}, opts)
}

func Accessor(ctx context.Context, opts ...AccessorOpt) (*imagespec.Accessor, error) {
if len(opts) == 0 {
if ctxOpts, ok := ctx.Value(insecureContextKey{}).([]AccessorOpt); ok {
opts = ctxOpts
}
}

var o accessorOpts
for _, opt := range opts {
opt(&o)
}

cfg := config.FromContextOrDefault(ctx)
profile, err := cfg.CurrentProfile()
if err != nil {
return nil, err
}

options := resolverOptions(profile)
options := resolverOptions(profile, o.insecureRegistries, o.allInsecure)
resolver := docker.NewResolver(options)
return imagespec.NewAccessor(
imagespec.WithResolver(resolver),
Expand All @@ -41,6 +60,26 @@ func Accessor(ctx context.Context) (*imagespec.Accessor, error) {
), nil
}

// AccessorOpt is a functional option for configuring an Accessor.
type AccessorOpt func(*accessorOpts)

type accessorOpts struct {
insecureRegistries []string
allInsecure bool
}

func WithInsecureRegistry(hosts ...string) AccessorOpt {
return func(o *accessorOpts) {
o.insecureRegistries = hosts
}
}

func WithInsecureRegistries() AccessorOpt {
return func(o *accessorOpts) {
o.allInsecure = true
}
}

func ParseNormalizedNamed(key string) (reference.Named, error) {
return ParseNormalizedNamedMetro(nil, key)
}
Expand Down
20 changes: 14 additions & 6 deletions internal/images/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"slices"
"strings"

"github.com/containerd/containerd/v2/core/remotes"
"github.com/containerd/containerd/v2/core/remotes/docker"
dockerconfig "github.com/docker/cli/cli/config"

Expand All @@ -22,7 +21,7 @@ import (
"unikraft.com/cli/internal/version"
)

func resolverOptions(profile *config.Profile) docker.ResolverOptions {
func resolverOptions(profile *config.Profile, insecureRegistries []string, allInsecure bool) docker.ResolverOptions {
headers := http.Header{}
headers.Set("User-Agent", version.UserAgent())

Expand All @@ -34,7 +33,17 @@ func resolverOptions(profile *config.Profile) docker.ResolverOptions {
}
}

isInsecureRegistry := func(host string) bool {
if allInsecure {
return true
}
return slices.Contains(insecureRegistries, host)
}

httpHost := func(host string) (bool, error) {
if isInsecureRegistry(host) {
return true, nil
}
for _, index := range indexes {
if host == index.Host {
return index.HTTP, nil
Expand All @@ -43,6 +52,9 @@ func resolverOptions(profile *config.Profile) docker.ResolverOptions {
return false, nil
}
insecureHost := func(host string) (bool, error) {
if isInsecureRegistry(host) {
return true, nil
}
for _, index := range indexes {
if host == index.Host {
return index.Insecure, nil
Expand Down Expand Up @@ -93,10 +105,6 @@ func resolverOptions(profile *config.Profile) docker.ResolverOptions {
}
}

func Resolver(profile *config.Profile) remotes.Resolver {
return docker.NewResolver(resolverOptions(profile))
}

func fallbackHost(registryHosts ...docker.RegistryHosts) docker.RegistryHosts {
return func(host string) ([]docker.RegistryHost, error) {
var allHosts []docker.RegistryHost
Expand Down
18 changes: 6 additions & 12 deletions internal/x/kong/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,15 @@ func Optional() kong.MapperFunc {
// NOTE: this works with:
// --optional
// --optional=value
// --optional value
// -ovalue
// --optional --other-flag (does not consume --other-flag)
// but not with:
// --optional notvalue
// because the latter is ambiguous with positional arguments.
// but NOT with:
// --optional value
// -o value
// because the latter forms would be ambiguous with positional arguments.
return func(ctx *kong.DecodeContext, target reflect.Value) error {
switch tok := ctx.Scan.Peek(); tok.Type {
case kong.FlagValueToken:
r := kong.NewRegistry().RegisterDefaults()
return r.ForValue(target).Decode(ctx, target)
case kong.UntypedToken:
// Don't consume tokens that look like flags (e.g. --sort, -s).
if s, ok := tok.Value.(string); ok && len(s) > 0 && s[0] == '-' {
return nil
}
case kong.FlagValueToken, kong.ShortFlagTailToken:
r := kong.NewRegistry().RegisterDefaults()
return r.ForValue(target).Decode(ctx, target)
default:
Expand Down
19 changes: 17 additions & 2 deletions internal/x/kong/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ func TestOptional(t *testing.T) {
type CLI struct {
Watch *time.Duration `short:"w" long:"watch" type:"optional"`
Sort string `long:"sort"`
Arg string `arg:"" optional:""`
}

tests := []struct {
name string
args []string
wantWatch *time.Duration
wantSort string
wantArg string
}{
{
name: "no flags",
Expand All @@ -40,8 +42,8 @@ func TestOptional(t *testing.T) {
wantSort: "",
},
{
name: "watch with value",
args: []string{"-w", "5s"},
name: "short watch with inline value",
args: []string{"-w5s"},
wantWatch: new(5 * time.Second),
wantSort: "",
},
Expand Down Expand Up @@ -69,6 +71,18 @@ func TestOptional(t *testing.T) {
wantWatch: new(5 * time.Second),
wantSort: "name",
},
{
name: "space-separated long does not consume value",
args: []string{"--watch", "5s"},
wantWatch: new(time.Duration),
wantArg: "5s",
},
{
name: "space-separated short does not consume value",
args: []string{"-w", "5s"},
wantWatch: new(time.Duration),
wantArg: "5s",
},
}

for _, tt := range tests {
Expand All @@ -87,6 +101,7 @@ func TestOptional(t *testing.T) {
assert.Equal(t, *tt.wantWatch, *cli.Watch)
}
assert.Equal(t, tt.wantSort, cli.Sort)
assert.Equal(t, tt.wantArg, cli.Arg)
})
}
}
Loading