diff --git a/internal/cli/kraft/pkg/packager_kraftfile_runtime.go b/internal/cli/kraft/pkg/packager_kraftfile_runtime.go index dbadd6e67..83dc87dde 100644 --- a/internal/cli/kraft/pkg/packager_kraftfile_runtime.go +++ b/internal/cli/kraft/pkg/packager_kraftfile_runtime.go @@ -332,6 +332,14 @@ func (p *packagerKraftfileRuntime) Pack(ctx context.Context, opts *PkgOptions, a return nil, err } + if len(opts.Readme) == 0 { + opts.Readme = opts.Project.Readme() + } else { + if readmeBytes, err := os.ReadFile(opts.Readme); err == nil { + opts.Readme = string(readmeBytes) + } + } + var result []pack.Package norender := log.LoggerTypeFromString(config.G[config.KraftKit](ctx).Log.Type) != log.FANCY @@ -352,6 +360,7 @@ func (p *packagerKraftfileRuntime) Pack(ctx context.Context, opts *PkgOptions, a packmanager.PackKConfig(!opts.NoKConfig), packmanager.PackName(opts.Name), packmanager.PackOutput(opts.Output), + packmanager.PackReadme(opts.Readme), ) if ukversion, ok := targ.KConfig().Get(unikraft.UK_FULLVERSION); ok { diff --git a/internal/cli/kraft/pkg/packager_kraftfile_unikraft.go b/internal/cli/kraft/pkg/packager_kraftfile_unikraft.go index 58705508f..9a260bc31 100644 --- a/internal/cli/kraft/pkg/packager_kraftfile_unikraft.go +++ b/internal/cli/kraft/pkg/packager_kraftfile_unikraft.go @@ -8,6 +8,7 @@ package pkg import ( "context" "fmt" + "os" "strings" "github.com/mattn/go-shellwords" @@ -117,6 +118,14 @@ func (p *packagerKraftfileUnikraft) Pack(ctx context.Context, opts *PkgOptions, return nil, err } + if len(opts.Readme) == 0 { + opts.Readme = opts.Project.Readme() + } else { + if readmeBytes, err := os.ReadFile(opts.Readme); err == nil { + opts.Readme = string(readmeBytes) + } + } + // When i > 0, we have already applied the merge strategy. Now, for all // targets, we actually do wish to merge these because they are part of // the same execution lifecycle. @@ -136,6 +145,7 @@ func (p *packagerKraftfileUnikraft) Pack(ctx context.Context, opts *PkgOptions, packmanager.PackKConfig(!opts.NoKConfig), packmanager.PackName(opts.Name), packmanager.PackOutput(opts.Output), + packmanager.PackReadme(opts.Readme), ) if ukversion, ok := targ.KConfig().Get(unikraft.UK_FULLVERSION); ok { diff --git a/internal/cli/kraft/pkg/pkg.go b/internal/cli/kraft/pkg/pkg.go index aa9ab3fab..0ab9b701c 100644 --- a/internal/cli/kraft/pkg/pkg.go +++ b/internal/cli/kraft/pkg/pkg.go @@ -48,6 +48,7 @@ type PkgOptions struct { Platform string `local:"true" long:"plat" short:"p" usage:"Filter the creation of the package by platform of known targets"` Project app.Application `noattribute:"true"` Push bool `local:"true" long:"push" short:"P" usage:"Push the package on if successfully packaged"` + Readme string `local:"true" long:"readme" usage:"Verbatim text or path to a README file to include in the package"` Rootfs string `local:"true" long:"rootfs" usage:"Specify a path to use as root file system (can be volume or initramfs)"` Strategy packmanager.MergeStrategy `noattribute:"true"` Target string `local:"true" long:"target" short:"t" usage:"Package a particular known target"` diff --git a/oci/annotations.go b/oci/annotations.go index 349530da9..a82794d72 100644 --- a/oci/annotations.go +++ b/oci/annotations.go @@ -11,6 +11,7 @@ const ( AnnotationURL = "org.unikraft.image.url" AnnotationCreated = "org.unikraft.image.created" AnnotationDescription = "org.unikraft.image.description" + AnnotationReadme = "org.unikraft.image.readme" AnnotationKernelPath = "org.unikraft.kernel.image" AnnotationKernelVersion = "org.unikraft.kernel.version" AnnotationKernelInitrdPath = "org.unikraft.kernel.initrd" diff --git a/oci/pack.go b/oci/pack.go index 2f98f415b..6863ede56 100644 --- a/oci/pack.go +++ b/oci/pack.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "crypto/tls" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -72,6 +73,7 @@ type ociPackage struct { kernelDbg string initrd initrd.Initrd command []string + readme string original *ociPackage } @@ -100,6 +102,7 @@ func NewPackageFromTarget(ctx context.Context, targ target.Target, opts ...packm kernel: targ.Kernel(), kernelDbg: targ.KernelDbg(), command: popts.Args(), + readme: popts.Readme(), } // It is possible that `NewPackageFromTarget` is called with an existing @@ -380,6 +383,11 @@ func NewPackageFromTarget(ctx context.Context, targ target.Target, opts ...packm return nil, fmt.Errorf("could not add manifest to index: %w", err) } + if len(ocipack.readme) > 0 { + encoded := base64.StdEncoding.EncodeToString([]byte(ocipack.readme)) + ocipack.index.SetAnnotation(ctx, AnnotationReadme, encoded) + } + if _, err = ocipack.index.Save(ctx, ocipack.ref.String(), nil); err != nil { return nil, fmt.Errorf("could not save index: %w", err) } diff --git a/packmanager/pack_options.go b/packmanager/pack_options.go index 21c8f6ca2..22572534b 100644 --- a/packmanager/pack_options.go +++ b/packmanager/pack_options.go @@ -17,6 +17,7 @@ type PackOptions struct { kernelSourceFiles bool kernelVersion string name string + readme string output string mergeStrategy MergeStrategy } @@ -81,6 +82,11 @@ func (popts *PackOptions) Name() string { return popts.name } +// Readme returns the readme of the package. +func (popts *PackOptions) Readme() string { + return popts.readme +} + // Output returns the location of the package. func (popts *PackOptions) Output() string { return popts.output @@ -167,6 +173,13 @@ func PackName(name string) PackOption { } } +// PackReadme sets the readme of the package. +func PackReadme(readme string) PackOption { + return func(popts *PackOptions) { + popts.readme = readme + } +} + // PackOutput sets the location of the output artifact package. func PackOutput(output string) PackOption { return func(popts *PackOptions) { diff --git a/schema/v0.6.json b/schema/v0.6.json index a9a7a293e..7964033ca 100644 --- a/schema/v0.6.json +++ b/schema/v0.6.json @@ -11,6 +11,8 @@ "/^name$/": { "type": "string" }, + "/^readme$/": { "type": "string" }, + "/^outdir$/": { "type": "string" }, "/^template$/": { diff --git a/unikraft/app/application.go b/unikraft/app/application.go index 5f5fd1456..4848f1ba8 100644 --- a/unikraft/app/application.go +++ b/unikraft/app/application.go @@ -39,6 +39,9 @@ import ( type Application interface { component.Component + // Readme contains the application's README text. + Readme() string + // WorkingDir returns the path to the application's working directory WorkingDir() string @@ -150,6 +153,7 @@ type Application interface { type application struct { name string + readme string version string source string path string @@ -173,6 +177,10 @@ func (app application) Name() string { return app.name } +func (app application) Readme() string { + return app.readme +} + func (app application) String() string { return app.name } diff --git a/unikraft/app/application_options.go b/unikraft/app/application_options.go index 0ca8d9399..33764f5ed 100644 --- a/unikraft/app/application_options.go +++ b/unikraft/app/application_options.go @@ -70,6 +70,14 @@ func WithName(name string) ApplicationOption { } } +// WithReadme sets the application's readme +func WithReadme(readme string) ApplicationOption { + return func(ac *application) error { + ac.readme = readme + return nil + } +} + // WithVersion sets the application version func WithVersion(version string) ApplicationOption { return func(ac *application) error { diff --git a/unikraft/app/loader.go b/unikraft/app/loader.go index 010626d22..dcda15a2a 100644 --- a/unikraft/app/loader.go +++ b/unikraft/app/loader.go @@ -20,6 +20,7 @@ package app import ( "context" "fmt" + "os" "strings" interp "github.com/compose-spec/compose-go/interpolation" @@ -43,6 +44,17 @@ func NewApplicationFromInterface(ctx context.Context, iface map[string]interface } } + if r, ok := iface["readme"]; ok { + app.readme, ok = r.(string) + if !ok { + return nil, errors.New("readme must be a string") + } + + if readmeBytes, err := os.ReadFile(app.readme); err == nil { + app.readme = string(readmeBytes) + } + } + app.name = name app.path = popts.workdir diff --git a/unikraft/app/project.go b/unikraft/app/project.go index a8b08056a..aaca22ce2 100644 --- a/unikraft/app/project.go +++ b/unikraft/app/project.go @@ -163,6 +163,7 @@ func NewProjectFromOptions(ctx context.Context, opts ...ProjectOption) (Applicat project, err := NewApplicationFromOptions( WithName(projectName), + WithReadme(app.readme), WithWorkingDir(popts.workdir), WithFilename(app.filename), WithOutDir(app.outDir),