Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Implement OCI Index manipulation #719

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
170 changes: 102 additions & 68 deletions cmd/kraft/pkg/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/mattn/go-shellwords"
Expand All @@ -23,6 +24,8 @@ import (
"kraftkit.sh/packmanager"
"kraftkit.sh/tui/processtree"
"kraftkit.sh/unikraft/app"
"kraftkit.sh/unikraft/component"
"kraftkit.sh/unikraft/target"

"kraftkit.sh/cmd/kraft/pkg/list"
"kraftkit.sh/cmd/kraft/pkg/pull"
Expand All @@ -33,19 +36,19 @@ import (
)

type Pkg struct {
Architecture string `local:"true" long:"arch" short:"m" usage:"Filter the creation of the package by architecture of known targets"`
Args string `local:"true" long:"args" short:"a" usage:"Pass arguments that will be part of the running kernel's command line"`
Dbg bool `local:"true" long:"dbg" usage:"Package the debuggable (symbolic) kernel image instead of the stripped image"`
Force bool `local:"true" long:"force-format" usage:"Force the use of a packaging handler format"`
Format string `local:"true" long:"as" short:"M" usage:"Force the packaging despite possible conflicts" default:"auto"`
Initrd string `local:"true" long:"initrd" short:"i" usage:"Path to init ramdisk to bundle within the package (passing a path will automatically generate a CPIO image)"`
Kernel string `local:"true" long:"kernel" short:"k" usage:"Override the path to the unikernel image"`
Kraftfile string `long:"kraftfile" usage:"Set an alternative path of the Kraftfile"`
Name string `local:"true" long:"name" short:"n" usage:"Specify the name of the package"`
Output string `local:"true" long:"output" short:"o" usage:"Save the package at the following output"`
Platform string `local:"true" long:"plat" short:"p" usage:"Filter the creation of the package by platform of known targets"`
Target string `local:"true" long:"target" short:"t" usage:"Package a particular known target"`
WithKConfig bool `local:"true" long:"with-kconfig" usage:"Include the target .config"`
Architecture string `local:"true" long:"arch" short:"m" usage:"Filter the creation of the package by architecture of known targets"`
Args string `local:"true" long:"args" short:"a" usage:"Pass arguments that will be part of the running kernel's command line"`
Dbg bool `local:"true" long:"dbg" usage:"Package the debuggable (symbolic) kernel image instead of the stripped image"`
Force bool `local:"true" long:"force-format" usage:"Force the use of a packaging handler format"`
Format string `local:"true" long:"as" short:"M" usage:"Force the packaging despite possible conflicts" default:"auto"`
Initrd string `local:"true" long:"initrd" short:"i" usage:"Path to init ramdisk to bundle within the package (passing a path will automatically generate a CPIO image)"`
Kernel string `local:"true" long:"kernel" short:"k" usage:"Override the path to the unikernel image"`
Kraftfile string `long:"kraftfile" usage:"Set an alternative path of the Kraftfile"`
Name string `local:"true" long:"name" short:"n" usage:"Specify the name of the package"`
Output string `local:"true" long:"output" short:"o" usage:"Save the package at the following output"`
Platform string `local:"true" long:"plat" short:"p" usage:"Filter the creation of the package by platform of known targets"`
Targets []string `local:"true" long:"target" short:"t" usage:"Package a particular known target"`
WithKConfig bool `local:"true" long:"with-kconfig" usage:"Include the target .config"`
}

func New() *cobra.Command {
Expand All @@ -66,7 +69,17 @@ func New() *cobra.Command {
`, "`"),
Example: heredoc.Doc(`
# Package a project as an OCI archive and embed the target's KConfig.
$ kraft pkg --as oci --name unikraft.org/nginx:latest --with-kconfig`),
$ kraft pkg --as oci --name unikraft.org/nginx:latest --with-kconfig

# Package a project with a given target name
$ kraft pkg --as oci --name unikraft.org/nginx:latest --target my_target

# Package a project with a given platform and architecture
$ kraft pkg --as oci --name unikraft.org/nginx:latest --plat qemu --arch x86_64

# Package a project with multiple targets
$ kraft pkg --as oci --name unikraft.org/nginx:latest --target my_target --target my_other_target
`),
Annotations: map[string]string{
cmdfactory.AnnotationHelpGroup: "pkg",
},
Expand All @@ -86,7 +99,7 @@ func New() *cobra.Command {
}

func (opts *Pkg) Pre(cmd *cobra.Command, _ []string) error {
if (len(opts.Architecture) > 0 || len(opts.Platform) > 0) && len(opts.Target) > 0 {
if (len(opts.Architecture) > 0 || len(opts.Platform) > 0) && len(opts.Targets) > 0 {
return fmt.Errorf("the `--arch` and `--plat` options are not supported in addition to `--target`")
}

Expand All @@ -102,6 +115,22 @@ func (opts *Pkg) Pre(cmd *cobra.Command, _ []string) error {
return nil
}

func matchOption(targets []string, option string) bool {
for _, target := range targets {
name := target
if strings.Contains(target, "/") {
name = string(platform.PlatformByName(strings.Split(target, "/")[0])) +
"/" +
strings.Split(target, "/")[1]
}
if name == option {
return true
}
}

return false
}

func (opts *Pkg) Run(cmd *cobra.Command, args []string) error {
var err error
var workdir string
Expand Down Expand Up @@ -134,25 +163,31 @@ func (opts *Pkg) Run(cmd *cobra.Command, args []string) error {
}

var tree []*processtree.ProcessTreeItem
var targets []target.Target
var packageOpts []packmanager.PackOption

parallel := !config.G[config.KraftKit](ctx).NoParallel
norender := log.LoggerTypeFromString(config.G[config.KraftKit](ctx).Log.Type) != log.FANCY

// Generate a package for every matching requested target
// Generate a package with all matching requested targets
for _, targ := range project.Targets() {
// See: https://github.com/golang/go/wiki/CommonMistakes#using-reference-to-loop-iterator-variable
targ := targ

switch true {
case
// If no arguments are supplied
len(opts.Target) == 0 &&
len(opts.Targets) == 0 &&
len(opts.Architecture) == 0 &&
len(opts.Platform) == 0,

// If the --target flag is supplied and the target name match
len(opts.Target) > 0 &&
targ.Name() == opts.Target,
len(opts.Targets) > 0 &&
matchOption(opts.Targets, targ.Name()),

// If the --target flag is supplied and the target is a plat/arch combo
len(opts.Targets) > 0 &&
matchOption(opts.Targets, target.TargetPlatArchName(targ)),

// If only the --arch flag is supplied and the target's arch matches
len(opts.Architecture) > 0 &&
Expand All @@ -170,68 +205,67 @@ func (opts *Pkg) Run(cmd *cobra.Command, args []string) error {
targ.Architecture().Name() == opts.Architecture &&
targ.Platform().Name() == opts.Platform:

var format pack.PackageFormat
name := "packaging " + targ.Name()
if opts.Format != "auto" {
format = pack.PackageFormat(opts.Format)
} else if targ.Format().String() != "" {
format = targ.Format()
}
if format.String() != "auto" {
name += " (" + format.String() + ")"
}

cmdShellArgs, err := shellwords.Parse(opts.Args)
if err != nil {
return err
}

tree = append(tree, processtree.NewProcessTreeItem(
name,
targ.Architecture().Name()+"/"+targ.Platform().Name(),
func(ctx context.Context) error {
var err error
pm := packmanager.G(ctx)

// Switch the package manager the desired format for this target
if format != "auto" {
pm, err = pm.From(format)
if err != nil {
return err
}
}

popts := []packmanager.PackOption{
packmanager.PackArgs(cmdShellArgs...),
packmanager.PackInitrd(opts.Initrd),
packmanager.PackKConfig(opts.WithKConfig),
packmanager.PackName(opts.Name),
packmanager.PackOutput(opts.Output),
}

if ukversion, ok := targ.KConfig().Get(unikraft.UK_FULLVERSION); ok {
popts = append(popts,
packmanager.PackWithKernelVersion(ukversion.Value),
)
}

if _, err := pm.Pack(ctx, targ, popts...); err != nil {
return err
}

return nil
},
))
packageOpts = append(packageOpts,
packmanager.PackArgs(cmdShellArgs...),
packmanager.PackInitrd(opts.Initrd),
packmanager.PackKConfig(opts.WithKConfig),
packmanager.PackName(opts.Name),
packmanager.PackOutput(opts.Output),
)

if ukversion, ok := targ.KConfig().Get(unikraft.UK_FULLVERSION); ok {
packageOpts = append(packageOpts,
packmanager.PackWithKernelVersion(ukversion.Value),
)
}

targets = append(targets, targ)

default:
continue
}
}

var components []component.Component
var targetNames string

// Convert targets from []target.Target to []component.Component
for _, targ := range targets {
components = append(components, targ)
targetNames += targ.Name() + " "
}

tree = append(tree, processtree.NewProcessTreeItem(
opts.Output,
targetNames,
func(ctx context.Context) error {
pm := packmanager.G(ctx)

// Switch the package manager the desired format for this target
if opts.Format != "auto" {
pm, err = pm.From(pack.PackageFormat(opts.Format))
if err != nil {
return err
}
}

if _, err := pm.Pack(ctx, components, packageOpts...); err != nil {
return err
}

return nil
},
))

if len(tree) == 0 {
switch true {
case len(opts.Target) > 0:
return fmt.Errorf("no matching targets found for: %s", opts.Target)
case len(opts.Targets) > 0:
return fmt.Errorf("no matching targets found for: %s", opts.Targets)
case len(opts.Architecture) > 0 && len(opts.Platform) == 0:
return fmt.Errorf("no matching targets found for architecture: %s", opts.Architecture)
case len(opts.Architecture) == 0 && len(opts.Platform) > 0:
Expand Down
9 changes: 8 additions & 1 deletion cmd/kraft/pkg/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,19 @@ func (opts *Pull) Run(cmd *cobra.Command, args []string) error {
packmanager.WithSource(c.Source()),
packmanager.WithTypes(c.Type()),
packmanager.WithCache(opts.ForceCache),
packmanager.WithArchitecture(opts.Architecture),
packmanager.WithPlatform(opts.Platform),
},
})
}

// Is this a list (space delimetered) of packages to pull?
} else if len(args) > 0 {
for _, arg := range args {
pm, compatible, err := pm.IsCompatible(ctx, arg)
pm, compatible, err := pm.IsCompatible(ctx, arg,
packmanager.WithArchitecture(opts.Architecture),
packmanager.WithPlatform(opts.Platform),
)
if err != nil || !compatible {
continue
}
Expand All @@ -273,6 +278,8 @@ func (opts *Pull) Run(cmd *cobra.Command, args []string) error {
query: []packmanager.QueryOption{
packmanager.WithCache(opts.ForceCache),
packmanager.WithName(arg),
packmanager.WithArchitecture(opts.Architecture),
packmanager.WithPlatform(opts.Platform),
},
})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/kraft/pkg/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func (opts *Push) Run(cmd *cobra.Command, args []string) error {
if pm, compatible, err := pmananger.IsCompatible(ctx, ref); err == nil && compatible {
packages, err := pm.Catalog(ctx,
packmanager.WithCache(true),
packmanager.WithAllTargets(true),
packmanager.WithName(ref),
)
if err != nil {
Expand All @@ -138,7 +139,6 @@ func (opts *Push) Run(cmd *cobra.Command, args []string) error {
}

// Call push if it exists
// TODO push if it doesn't exist too
proc := paraprogress.NewProcess(
fmt.Sprintf("pushing %s",
ref,
Expand Down
4 changes: 4 additions & 0 deletions cmd/kraft/run/runner_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func (runner *runnerPackage) Prepare(ctx context.Context, opts *Run, machine *ma
packmanager.WithTypes(unikraft.ComponentTypeApp),
packmanager.WithName(runner.packName),
packmanager.WithCache(true),
packmanager.WithPlatform(string(opts.platform)),
packmanager.WithArchitecture(opts.Architecture),
)
if err != nil {
return err
Expand All @@ -88,6 +90,8 @@ func (runner *runnerPackage) Prepare(ctx context.Context, opts *Run, machine *ma
packmanager.WithTypes(unikraft.ComponentTypeApp),
packmanager.WithName(runner.packName),
packmanager.WithCache(false),
packmanager.WithPlatform(string(opts.platform)),
packmanager.WithArchitecture(opts.Architecture),
)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion manifest/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (m *manifestManager) RemoveSource(ctx context.Context, source string) error
return nil
}

func (m *manifestManager) Pack(ctx context.Context, c component.Component, opts ...packmanager.PackOption) ([]pack.Package, error) {
func (m *manifestManager) Pack(ctx context.Context, c []component.Component, opts ...packmanager.PackOption) ([]pack.Package, error) {
return nil, fmt.Errorf("not implemented manifest.manager.Pack")
}

Expand Down
Loading