diff --git a/cmd/podman/artifact/add.go b/cmd/podman/artifact/add.go new file mode 100644 index 0000000000..336e0b3708 --- /dev/null +++ b/cmd/podman/artifact/add.go @@ -0,0 +1,49 @@ +package artifact + +import ( + "fmt" + "github.com/containers/podman/v5/pkg/domain/entities" + + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/spf13/cobra" +) + +var ( + addCmd = &cobra.Command{ + Use: "add [options] PATH ARTIFACT", + Short: "Add an OCI artifact to the local store", + Long: "Add an OCI artifact to the local store from the local filesystem", + RunE: add, + Args: cobra.MinimumNArgs(2), + Example: `podman artifact add /tmp/foobar.txt quay.io/myimage/myartifact:latest`, + } + addFlag = addFlagType{} +) + +// TODO at some point force will be a required option; but this cannot be +// until we have artifacts being consumed by other parts of libpod like +// volumes +type addFlagType struct { +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: addCmd, + Parent: artifactCmd, + }) + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +func add(cmd *cobra.Command, args []string) error { + //_, err := registry.ImageEngine().ArtifactRm(registry.GetContext(), args[0], entities.ArtifactRemoveOptions{}) + //return err + report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1], entities.ArtifactAddoptions{}) + if err != nil { + return err + } + fmt.Println(report.NewBlobDigest) + return nil +} diff --git a/cmd/podman/artifact/artifact.go b/cmd/podman/artifact/artifact.go new file mode 100644 index 0000000000..d58918dae6 --- /dev/null +++ b/cmd/podman/artifact/artifact.go @@ -0,0 +1,25 @@ +package artifact + +import ( + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/cmd/podman/validate" + "github.com/spf13/cobra" +) + +var ( + json = registry.JSONLibrary() + // Command: podman _artifact_ + artifactCmd = &cobra.Command{ + Use: "artifact", + Short: "Manage OCI artifacts", + Long: "Manage OCI artifacts", + //PersistentPreRunE: validate.NoOp, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: artifactCmd, + }) +} diff --git a/cmd/podman/artifact/inspect.go b/cmd/podman/artifact/inspect.go new file mode 100644 index 0000000000..133cd62aa0 --- /dev/null +++ b/cmd/podman/artifact/inspect.go @@ -0,0 +1,69 @@ +package artifact + +import ( + "fmt" + "os" + + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + inspectCmd = &cobra.Command{ + Use: "inspect [options] [ARTIFACT...]", + Short: "Inspect an OCI artifact", + Long: "Provide details on an OCI artifact", + RunE: inspect, + Args: cobra.MinimumNArgs(1), + Example: `podman artifact inspect quay.io/myimage/myartifact:latest`, + } + inspectFlag = inspectFlagType{} +) + +type inspectFlagType struct { + format string + remote bool +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: inspectCmd, + Parent: artifactCmd, + }) + flags := inspectCmd.Flags() + formatFlagName := "format" + flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template") + remoteFlagName := "remote" + flags.BoolVar(&inspectFlag.remote, remoteFlagName, false, "Inspect the image on a container image registry") + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +func inspect(cmd *cobra.Command, args []string) error { + if inspectFlag.remote { + return fmt.Errorf("not implemented") + } + + if inspectFlag.format != "" { + return fmt.Errorf("not implemented") + } + + artifactOptions := entities.ArtifactInspectOptions{} + inspectData, err := registry.ImageEngine().ArtifactInspect(registry.GetContext(), args[0], artifactOptions) + if err != nil { + return err + } + return printJSON(inspectData) +} + +func printJSON(data interface{}) error { + enc := json.NewEncoder(os.Stdout) + // by default, json marshallers will force utf=8 from + // a string. + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + return enc.Encode(data) +} diff --git a/cmd/podman/artifact/list.go b/cmd/podman/artifact/list.go new file mode 100644 index 0000000000..0471de871f --- /dev/null +++ b/cmd/podman/artifact/list.go @@ -0,0 +1,70 @@ +package artifact + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" + units "github.com/docker/go-units" + "github.com/spf13/cobra" +) + +var ( + ListCmd = &cobra.Command{ + Use: "list [options]", + Aliases: []string{"ls"}, + Short: "List OCI artifacts", + Long: "List OCI artifacts in local store", + RunE: list, + Args: cobra.NoArgs, + Example: `podman artifact ls`, + } + ListFlag = inspectFlagType{} +) + +type listFlagType struct { + format string +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: ListCmd, + Parent: artifactCmd, + }) + flags := ListCmd.Flags() + formatFlagName := "format" + flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template") + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +func list(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("format") { + return fmt.Errorf("not implemented") + } + reports, err := registry.ImageEngine().ArtifactList(registry.GetContext(), entities.ArtifactListOptions{}) + if err != nil { + return err + } + + return writeTemplate(cmd, reports) +} + +func writeTemplate(cmd *cobra.Command, reports []*entities.ArtifactListReport) error { + fmt.Println("") + tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintln(tw, "NAME\tSIZE") + fmt.Fprintln(tw, "----\t----") + for _, art := range reports { + splitsees := strings.SplitN(art.List.Reference.StringWithinTransport(), ":", 2) + fmt.Fprintf(tw, "%s\t\t%s\n", splitsees[1], units.HumanSize(float64(art.TotalSize()))) + } + tw.Flush() + fmt.Println("") + return nil +} diff --git a/cmd/podman/artifact/pull.go b/cmd/podman/artifact/pull.go new file mode 100644 index 0000000000..fe22f07c73 --- /dev/null +++ b/cmd/podman/artifact/pull.go @@ -0,0 +1,193 @@ +package artifact + +import ( + "fmt" + "os" + + "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" + "github.com/containers/image/v5/types" + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/util" + "github.com/spf13/cobra" +) + +// pullOptionsWrapper wraps entities.ImagePullOptions and prevents leaking +// CLI-only fields into the API types. +type pullOptionsWrapper struct { + entities.ArtifactPullOptions + TLSVerifyCLI bool // CLI only + CredentialsCLI string + DecryptionKeys []string +} + +var ( + pullOptions = pullOptionsWrapper{} + pullDescription = `Pulls an artifact from a registry and stores it locally.` + + pullCmd = &cobra.Command{ + Use: "pull [options] [ARTIFACT...]", + Short: "Pull an OCI artifact", + Long: pullDescription, + RunE: artifactPull, + //PersistentPreRunE: devOnly, + Args: cobra.MaximumNArgs(1), + Example: `podman artifact pull quay.io/myimage/myartifact:latest`, + // TODO Autocomplete function needs to be done + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: pullCmd, + Parent: artifactCmd, + }) + pullFlags(pullCmd) + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +// pullFlags set the flags for the pull command. +func pullFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + credsFlagName := "creds" + flags.StringVar(&pullOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + // TODO I think these can be removed + /* + archFlagName := "arch" + flags.StringVar(&pullOptions.Arch, archFlagName, "", "Use `ARCH` instead of the architecture of the machine for choosing images") + _ = cmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteArch) + + + osFlagName := "os" + flags.StringVar(&pullOptions.OS, osFlagName, "", "Use `OS` instead of the running OS for choosing images") + _ = cmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) + + variantFlagName := "variant" + flags.StringVar(&pullOptions.Variant, variantFlagName, "", "Use VARIANT instead of the running architecture variant for choosing images") + _ = cmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) + + platformFlagName := "platform" + flags.String(platformFlagName, "", "Specify the platform for selecting the image. (Conflicts with arch and os)") + _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone) + + + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") + */ + + flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.BoolVar(&pullOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + authfileFlagName := "authfile" + flags.StringVar(&pullOptions.AuthFilePath, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + decryptionKeysFlagName := "decryption-key" + flags.StringArrayVar(&pullOptions.DecryptionKeys, decryptionKeysFlagName, nil, "Key needed to decrypt the image (e.g. /path/to/key.pem)") + _ = cmd.RegisterFlagCompletionFunc(decryptionKeysFlagName, completion.AutocompleteDefault) + + retryFlagName := "retry" + flags.Uint(retryFlagName, registry.RetryDefault(), "number of times to retry in case of failure when performing pull") + _ = cmd.RegisterFlagCompletionFunc(retryFlagName, completion.AutocompleteNone) + retryDelayFlagName := "retry-delay" + flags.String(retryDelayFlagName, registry.RetryDelayDefault(), "delay between retries in case of pull failures") + _ = cmd.RegisterFlagCompletionFunc(retryDelayFlagName, completion.AutocompleteNone) + + if registry.IsRemote() { + _ = flags.MarkHidden(decryptionKeysFlagName) + } else { + certDirFlagName := "cert-dir" + flags.StringVar(&pullOptions.CertDirPath, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + + signaturePolicyFlagName := "signature-policy" + flags.StringVar(&pullOptions.SignaturePolicyPath, signaturePolicyFlagName, "", "`Pathname` of signature policy file (not usually used)") + _ = flags.MarkHidden(signaturePolicyFlagName) + } +} + +func artifactPull(cmd *cobra.Command, args []string) error { + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + pullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!pullOptions.TLSVerifyCLI) + } + + if cmd.Flags().Changed("retry") { + retry, err := cmd.Flags().GetUint("retry") + if err != nil { + return err + } + + pullOptions.MaxRetries = &retry + } + + if cmd.Flags().Changed("retry-delay") { + val, err := cmd.Flags().GetString("retry-delay") + if err != nil { + return err + } + + pullOptions.RetryDelay = val + } + + if cmd.Flags().Changed("authfile") { + if err := auth.CheckAuthFile(pullOptions.AuthFilePath); err != nil { + return err + } + } + + // TODO Once we have a decision about the flag removal above, this should be safe to delete + /* + platform, err := cmd.Flags().GetString("platform") + if err != nil { + return err + } + if platform != "" { + if pullOptions.Arch != "" || pullOptions.OS != "" { + return errors.New("--platform option can not be specified with --arch or --os") + } + + specs := strings.Split(platform, "/") + pullOptions.OS = specs[0] // may be empty + if len(specs) > 1 { + pullOptions.Arch = specs[1] + if len(specs) > 2 { + pullOptions.Variant = specs[2] + } + } + } + */ + + if pullOptions.CredentialsCLI != "" { + creds, err := util.ParseRegistryCreds(pullOptions.CredentialsCLI) + if err != nil { + return err + } + pullOptions.Username = creds.Username + pullOptions.Password = creds.Password + } + + decConfig, err := cli.DecryptConfig(pullOptions.DecryptionKeys) + if err != nil { + return fmt.Errorf("unable to obtain decryption config: %w", err) + } + pullOptions.OciDecryptConfig = decConfig + + if !pullOptions.Quiet { + pullOptions.Writer = os.Stderr + } + + _, err = registry.ImageEngine().ArtifactPull(registry.GetContext(), args[0], pullOptions.ArtifactPullOptions) + return err + +} diff --git a/cmd/podman/artifact/push.go b/cmd/podman/artifact/push.go new file mode 100644 index 0000000000..e0020faeaf --- /dev/null +++ b/cmd/podman/artifact/push.go @@ -0,0 +1,240 @@ +package artifact + +import ( + "fmt" + "os" + + "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" + "github.com/containers/image/v5/types" + "github.com/containers/podman/v5/cmd/podman/common" + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/util" + "github.com/spf13/cobra" +) + +// pushOptionsWrapper wraps entities.ImagepushOptions and prevents leaking +// CLI-only fields into the API types. +type pushOptionsWrapper struct { + entities.ArtifactPushOptions + TLSVerifyCLI bool // CLI only + CredentialsCLI string + SignPassphraseFileCLI string + SignBySigstoreParamFileCLI string + EncryptionKeys []string + EncryptLayers []int + DigestFile string +} + +var ( + pushOptions = pushOptionsWrapper{} + pushDescription = `Push an OCI artifact from local storage to an image registry` + + pushCmd = &cobra.Command{ + Use: "push [options] [ARTIFACT...]", + Short: "Push an OCI artifact", + Long: pullDescription, + RunE: artifactPush, + //PersistentPreRunE: devOnly, + Args: cobra.MaximumNArgs(1), + Example: `podman artifact push quay.io/myimage/myartifact:latest`, + // TODO Autocomplete function needs to be done + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: pushCmd, + Parent: artifactCmd, + }) + pushFlags(pushCmd) + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +// pullFlags set the flags for the pull command. +func pushFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + // For now default All flag to true, for pushing of manifest lists + pushOptions.All = true + authfileFlagName := "authfile" + flags.StringVar(&pushOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + + certDirFlagName := "cert-dir" + flags.StringVar(&pushOptions.CertDir, certDirFlagName, "", "Path to a directory containing TLS certificates and keys") + _ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + + flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") + + credsFlagName := "creds" + flags.StringVar(&pushOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") + + digestfileFlagName := "digestfile" + flags.StringVar(&pushOptions.DigestFile, digestfileFlagName, "", "Write the digest of the pushed image to the specified file") + _ = cmd.RegisterFlagCompletionFunc(digestfileFlagName, completion.AutocompleteDefault) + + flags.BoolVar(&pushOptions.ForceCompressionFormat, "force-compression", false, "Use the specified compression algorithm even if the destination contains a differently-compressed variant already") + + formatFlagName := "format" + flags.StringVarP(&pushOptions.Format, formatFlagName, "f", "", "Manifest type (oci, v2s2, or v2s1) to use in the destination (default is manifest type of source, with fallbacks)") + _ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteManifestFormat) + + flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images") + flags.BoolVar(&pushOptions.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") + + retryFlagName := "retry" + flags.Uint(retryFlagName, registry.RetryDefault(), "number of times to retry in case of failure when performing push") + _ = cmd.RegisterFlagCompletionFunc(retryFlagName, completion.AutocompleteNone) + retryDelayFlagName := "retry-delay" + flags.String(retryDelayFlagName, registry.RetryDelayDefault(), "delay between retries in case of push failures") + _ = cmd.RegisterFlagCompletionFunc(retryDelayFlagName, completion.AutocompleteNone) + + signByFlagName := "sign-by" + flags.StringVar(&pushOptions.SignBy, signByFlagName, "", "Add a signature at the destination using the specified key") + _ = cmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + + signBySigstoreFlagName := "sign-by-sigstore" + flags.StringVar(&pushOptions.SignBySigstoreParamFileCLI, signBySigstoreFlagName, "", "Sign the image using a sigstore parameter file at `PATH`") + _ = cmd.RegisterFlagCompletionFunc(signBySigstoreFlagName, completion.AutocompleteDefault) + + signBySigstorePrivateKeyFlagName := "sign-by-sigstore-private-key" + flags.StringVar(&pushOptions.SignBySigstorePrivateKeyFile, signBySigstorePrivateKeyFlagName, "", "Sign the image using a sigstore private key at `PATH`") + _ = cmd.RegisterFlagCompletionFunc(signBySigstorePrivateKeyFlagName, completion.AutocompleteDefault) + + signPassphraseFileFlagName := "sign-passphrase-file" + flags.StringVar(&pushOptions.SignPassphraseFileCLI, signPassphraseFileFlagName, "", "Read a passphrase for signing an image from `PATH`") + _ = cmd.RegisterFlagCompletionFunc(signPassphraseFileFlagName, completion.AutocompleteDefault) + + flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + // TODO I think these two can be removed? + /* + compFormat := "compression-format" + flags.StringVar(&pushOptions.CompressionFormat, compFormat, compressionFormat(), "compression format to use") + _ = cmd.RegisterFlagCompletionFunc(compFormat, common.AutocompleteCompressionFormat) + + compLevel := "compression-level" + flags.Int(compLevel, compressionLevel(), "compression level to use") + _ = cmd.RegisterFlagCompletionFunc(compLevel, completion.AutocompleteNone) + + */ + + encryptionKeysFlagName := "encryption-key" + flags.StringArrayVar(&pushOptions.EncryptionKeys, encryptionKeysFlagName, nil, "Key with the encryption protocol to use to encrypt the image (e.g. jwe:/path/to/key.pem)") + _ = cmd.RegisterFlagCompletionFunc(encryptionKeysFlagName, completion.AutocompleteDefault) + + encryptLayersFlagName := "encrypt-layer" + flags.IntSliceVar(&pushOptions.EncryptLayers, encryptLayersFlagName, nil, "Layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified") + _ = cmd.RegisterFlagCompletionFunc(encryptLayersFlagName, completion.AutocompleteDefault) + + if registry.IsRemote() { + _ = flags.MarkHidden("cert-dir") + _ = flags.MarkHidden("compress") + _ = flags.MarkHidden("quiet") + _ = flags.MarkHidden(signByFlagName) + _ = flags.MarkHidden(signBySigstoreFlagName) + _ = flags.MarkHidden(signBySigstorePrivateKeyFlagName) + _ = flags.MarkHidden(signPassphraseFileFlagName) + _ = flags.MarkHidden(encryptionKeysFlagName) + _ = flags.MarkHidden(encryptLayersFlagName) + } else { + signaturePolicyFlagName := "signature-policy" + flags.StringVar(&pushOptions.SignaturePolicy, signaturePolicyFlagName, "", "Path to a signature-policy file") + _ = flags.MarkHidden(signaturePolicyFlagName) + } +} + +func artifactPush(cmd *cobra.Command, args []string) error { + source := args[0] + //destination := args[len(args)-1] + + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + pushOptions.SkipTLSVerify = types.NewOptionalBool(!pushOptions.TLSVerifyCLI) + } + + if cmd.Flags().Changed("authfile") { + if err := auth.CheckAuthFile(pushOptions.Authfile); err != nil { + return err + } + } + + if pushOptions.CredentialsCLI != "" { + creds, err := util.ParseRegistryCreds(pushOptions.CredentialsCLI) + if err != nil { + return err + } + pushOptions.Username = creds.Username + pushOptions.Password = creds.Password + } + + if !pushOptions.Quiet { + pushOptions.Writer = os.Stderr + } + + signingCleanup, err := common.PrepareSigning(&pushOptions.ImagePushOptions, + pushOptions.SignPassphraseFileCLI, pushOptions.SignBySigstoreParamFileCLI) + if err != nil { + return err + } + defer signingCleanup() + + encConfig, encLayers, err := cli.EncryptConfig(pushOptions.EncryptionKeys, pushOptions.EncryptLayers) + if err != nil { + return fmt.Errorf("unable to obtain encryption config: %w", err) + } + pushOptions.OciEncryptConfig = encConfig + pushOptions.OciEncryptLayers = encLayers + + if cmd.Flags().Changed("retry") { + retry, err := cmd.Flags().GetUint("retry") + if err != nil { + return err + } + + pushOptions.Retry = &retry + } + + if cmd.Flags().Changed("retry-delay") { + val, err := cmd.Flags().GetString("retry-delay") + if err != nil { + return err + } + + pushOptions.RetryDelay = val + } + + // TODO If not compression options are supported, we do not need the following + /* + if cmd.Flags().Changed("compression-level") { + val, err := cmd.Flags().GetInt("compression-level") + if err != nil { + return err + } + pushOptions.CompressionLevel = &val + } + + if cmd.Flags().Changed("compression-format") { + if !cmd.Flags().Changed("force-compression") { + // If `compression-format` is set and no value for `--force-compression` + // is selected then defaults to `true`. + pushOptions.ForceCompressionFormat = true + } + } + */ + + _, err = registry.ImageEngine().ArtifactPush(registry.GetContext(), source, pushOptions.ArtifactPushOptions) + return err +} diff --git a/cmd/podman/artifact/rm.go b/cmd/podman/artifact/rm.go new file mode 100644 index 0000000000..8ccc52a553 --- /dev/null +++ b/cmd/podman/artifact/rm.go @@ -0,0 +1,43 @@ +package artifact + +import ( + "github.com/containers/podman/v5/cmd/podman/registry" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + rmCmd = &cobra.Command{ + Use: "remove [options] [ARTIFACT...]", + Short: "Remove an OCI artifact", + Long: "Remove an OCI from local storage", + RunE: rm, + Aliases: []string{"rm"}, + Args: cobra.MinimumNArgs(1), + Example: `podman artifact remove quay.io/myimage/myartifact:latest`, + } + rmFlag = rmFlagType{} +) + +// TODO at some point force will be a required option; but this cannot be +// until we have artifacts being consumed by other parts of libpod like +// volumes +type rmFlagType struct { + force bool +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: rmCmd, + Parent: artifactCmd, + }) + + // TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this + // will also need to be reflected in the podman-artifact-inspect man page + // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) +} + +func rm(cmd *cobra.Command, args []string) error { + _, err := registry.ImageEngine().ArtifactRm(registry.GetContext(), args[0], entities.ArtifactRemoveOptions{}) + return err +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index dd5b984d8d..309a421529 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + _ "github.com/containers/podman/v5/cmd/podman/artifact" _ "github.com/containers/podman/v5/cmd/podman/completion" _ "github.com/containers/podman/v5/cmd/podman/farm" _ "github.com/containers/podman/v5/cmd/podman/generate" diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst index 6a81adfa89..29a41b8e33 100644 --- a/docs/source/Commands.rst +++ b/docs/source/Commands.rst @@ -5,6 +5,8 @@ Commands :doc:`Podman ` (Pod Manager) Global Options, Environment Variables, Exit Codes, Configuration Files, and more +:doc:`artifact ` Manage OCI artifacts + :doc:`attach ` Attach to a running container :doc:`auto-update ` Auto update containers according to their auto-update policy diff --git a/docs/source/markdown/podman-artifact-inspect.1.md b/docs/source/markdown/podman-artifact-inspect.1.md new file mode 100644 index 0000000000..f4a90ed536 --- /dev/null +++ b/docs/source/markdown/podman-artifact-inspect.1.md @@ -0,0 +1,51 @@ +% podman-artifact-inspect 1 + +## NAME +podman\-artifact\-inspect - Inspect an OCI artifact + +## SYNOPSIS +**podman artifact inspect** [*options*] [*name*] ... + +## DESCRIPTION + +Inspect one or more virtual machines + +Obtain greater detail about Podman virtual machines. More than one virtual machine can be +inspected at once. + +The default machine name is `podman-machine-default`. If a machine name is not specified as an argument, +then `podman-machine-default` will be inspected. + +Rootless only. + +## OPTIONS +#### **--format** + +Print results with a Go template. + + + +#### **--help** + +Print usage statement. + +#### **--remote** + +Instead of inspecting an OCI artifact in the local store, inspect it on an image registry. + +## EXAMPLES + +Inspect an OCI image in the local store. +``` +$ podman artifact inspect quay.io/myartifact/myml:latest +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)** + +## HISTORY +April 2024, Originally compiled by Brent Baude diff --git a/docs/source/markdown/podman-artifact.1.md b/docs/source/markdown/podman-artifact.1.md new file mode 100644 index 0000000000..f9aa086d6b --- /dev/null +++ b/docs/source/markdown/podman-artifact.1.md @@ -0,0 +1,26 @@ +% podman-artifact 1 + +## NAME +podman\-artifact - Manage OCI artifacts + +## SYNOPSIS +**podman artifact** *subcommand* + +## DESCRIPTION +`podman artifact` is a set of subcommands that manage OCI artifacts. + +OCI artifacts are common way to distribute files that are associated with OCI images and +containers. Podman is capable of managing (pulling, inspecting, pushing) these artifacts +from its local "artifact store". + +## SUBCOMMANDS + +| Command | Man Page | Description | +|---------|------------------------------------------------------------|-------------------------| +| inspect | [podman-artifact-inspect(1)](podman-artifact-inspect.1.md) | Inspect an OCI artifact | + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-artifact-inspect(1)](podman-artifact-inspect.1.md)** + +## HISTORY +Aug 2024, Originally compiled by Brent Baude diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 5484080618..e57245d84a 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -325,69 +325,70 @@ the exit codes follow the `chroot` standard, see below: ## COMMANDS -| Command | Description | -| ------------------------------------------------ | --------------------------------------------------------------------------- | -| [podman-attach(1)](podman-attach.1.md) | Attach to a running container. | -| [podman-auto-update(1)](podman-auto-update.1.md) | Auto update containers according to their auto-update policy | -| [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. | -| [podman-farm(1)](podman-farm.1.md) | Farm out builds to machines running podman for different architectures | -| [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | -| [podman-completion(1)](podman-completion.1.md) | Generate shell completion scripts | -| [podman-compose(1)](podman-compose.1.md) | Run Compose workloads via an external compose provider. | -| [podman-container(1)](podman-container.1.md) | Manage containers. | -| [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | -| [podman-create(1)](podman-create.1.md) | Create a new container. | -| [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | -| [podman-events(1)](podman-events.1.md) | Monitor Podman events | -| [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | -| [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | -| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. | -| [podman-healthcheck(1)](podman-healthcheck.1.md) | Manage healthchecks for containers | -| [podman-history(1)](podman-history.1.md) | Show the history of an image. | -| [podman-image(1)](podman-image.1.md) | Manage images. | -| [podman-images(1)](podman-images.1.md) | List images in local storage. | -| [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | -| [podman-info(1)](podman-info.1.md) | Display Podman related system information. | -| [podman-init(1)](podman-init.1.md) | Initialize one or more containers | -| [podman-inspect(1)](podman-inspect.1.md) | Display a container, image, volume, network, or pod's configuration. | -| [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | -| [podman-load(1)](podman-load.1.md) | Load image(s) from a tar archive into container storage. | -| [podman-login(1)](podman-login.1.md) | Log in to a container registry. | -| [podman-logout(1)](podman-logout.1.md) | Log out of a container registry. | -| [podman-logs(1)](podman-logs.1.md) | Display the logs of one or more containers. | -| [podman-machine(1)](podman-machine.1.md) | Manage Podman's virtual machine | -| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. | -| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. | -| [podman-network(1)](podman-network.1.md) | Manage Podman networks. | -| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | -| [podman-kube(1)](podman-kube.1.md) | Play containers, pods or volumes based on a structured input file. | -| [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. | -| [podman-port(1)](podman-port.1.md) | List port mappings for a container. | -| [podman-ps(1)](podman-ps.1.md) | Print out information about containers. | -| [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. | -| [podman-push(1)](podman-push.1.md) | Push an image, manifest list or image index from local storage to elsewhere.| -| [podman-rename(1)](podman-rename.1.md) | Rename an existing container. | -| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. | -| [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. | -| [podman-rmi(1)](podman-rmi.1.md) | Remove one or more locally stored images. | -| [podman-run(1)](podman-run.1.md) | Run a command in a new container. | -| [podman-save(1)](podman-save.1.md) | Save image(s) to an archive. | -| [podman-search(1)](podman-search.1.md) | Search a registry for an image. | -| [podman-secret(1)](podman-secret.1.md) | Manage podman secrets. | -| [podman-start(1)](podman-start.1.md) | Start one or more containers. | -| [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. | -| [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. | -| [podman-system(1)](podman-system.1.md) | Manage podman. | -| [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | -| [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | -| [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem. | -| [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | -| [podman-unshare(1)](podman-unshare.1.md) | Run a command inside of a modified user namespace. | -| [podman-untag(1)](podman-untag.1.md) | Remove one or more names from a locally-stored image. | -| [podman-update(1)](podman-update.1.md) | Update the configuration of a given container. | -| [podman-version(1)](podman-version.1.md) | Display the Podman version information. | -| [podman-volume(1)](podman-volume.1.md) | Simple management tool for volumes. | -| [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | +| Command | Description | +|--------------------------------------------------|------------------------------------------------------------------------------| +| [podman-artifact(1)](podman-artifact.1.md) | Manage OCI artifacts. | +| [podman-attach(1)](podman-attach.1.md) | Attach to a running container. | +| [podman-auto-update(1)](podman-auto-update.1.md) | Auto update containers according to their auto-update policy | +| [podman-build(1)](podman-build.1.md) | Build a container image using a Containerfile. | +| [podman-farm(1)](podman-farm.1.md) | Farm out builds to machines running podman for different architectures | +| [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | +| [podman-completion(1)](podman-completion.1.md) | Generate shell completion scripts | +| [podman-compose(1)](podman-compose.1.md) | Run Compose workloads via an external compose provider. | +| [podman-container(1)](podman-container.1.md) | Manage containers. | +| [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | +| [podman-create(1)](podman-create.1.md) | Create a new container. | +| [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | +| [podman-events(1)](podman-events.1.md) | Monitor Podman events | +| [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | +| [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | +| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. | +| [podman-healthcheck(1)](podman-healthcheck.1.md) | Manage healthchecks for containers | +| [podman-history(1)](podman-history.1.md) | Show the history of an image. | +| [podman-image(1)](podman-image.1.md) | Manage images. | +| [podman-images(1)](podman-images.1.md) | List images in local storage. | +| [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | +| [podman-info(1)](podman-info.1.md) | Display Podman related system information. | +| [podman-init(1)](podman-init.1.md) | Initialize one or more containers | +| [podman-inspect(1)](podman-inspect.1.md) | Display a container, image, volume, network, or pod's configuration. | +| [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | +| [podman-load(1)](podman-load.1.md) | Load image(s) from a tar archive into container storage. | +| [podman-login(1)](podman-login.1.md) | Log in to a container registry. | +| [podman-logout(1)](podman-logout.1.md) | Log out of a container registry. | +| [podman-logs(1)](podman-logs.1.md) | Display the logs of one or more containers. | +| [podman-machine(1)](podman-machine.1.md) | Manage Podman's virtual machine | +| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. | +| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. | +| [podman-network(1)](podman-network.1.md) | Manage Podman networks. | +| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | +| [podman-kube(1)](podman-kube.1.md) | Play containers, pods or volumes based on a structured input file. | +| [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. | +| [podman-port(1)](podman-port.1.md) | List port mappings for a container. | +| [podman-ps(1)](podman-ps.1.md) | Print out information about containers. | +| [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. | +| [podman-push(1)](podman-push.1.md) | Push an image, manifest list or image index from local storage to elsewhere. | +| [podman-rename(1)](podman-rename.1.md) | Rename an existing container. | +| [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. | +| [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. | +| [podman-rmi(1)](podman-rmi.1.md) | Remove one or more locally stored images. | +| [podman-run(1)](podman-run.1.md) | Run a command in a new container. | +| [podman-save(1)](podman-save.1.md) | Save image(s) to an archive. | +| [podman-search(1)](podman-search.1.md) | Search a registry for an image. | +| [podman-secret(1)](podman-secret.1.md) | Manage podman secrets. | +| [podman-start(1)](podman-start.1.md) | Start one or more containers. | +| [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. | +| [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. | +| [podman-system(1)](podman-system.1.md) | Manage podman. | +| [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | +| [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | +| [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem. | +| [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | +| [podman-unshare(1)](podman-unshare.1.md) | Run a command inside of a modified user namespace. | +| [podman-untag(1)](podman-untag.1.md) | Remove one or more names from a locally-stored image. | +| [podman-update(1)](podman-update.1.md) | Update the configuration of a given container. | +| [podman-version(1)](podman-version.1.md) | Display the Podman version information. | +| [podman-volume(1)](podman-volume.1.md) | Simple management tool for volumes. | +| [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | ## CONFIGURATION FILES diff --git a/go.mod b/go.mod index fa694e40a3..ec9ad48331 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/containers/podman/v5 // Warning: if there is a "toolchain" directive anywhere in this file (and most of the // time there shouldn't be), its version must be an exact match to the "go" directive. -go 1.22.6 +go 1.22.8 require ( github.com/BurntSushi/toml v1.4.0 @@ -195,11 +195,11 @@ require ( github.com/segmentio/ksuid v1.0.4 // indirect github.com/sigstore/fulcio v1.6.4 // indirect github.com/sigstore/rekor v1.3.6 // indirect - github.com/sigstore/sigstore v1.8.9 // indirect + github.com/sigstore/sigstore v1.8.10 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect - github.com/sylabs/sif/v2 v2.19.1 // indirect + github.com/sylabs/sif/v2 v2.20.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect @@ -221,7 +221,7 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect @@ -230,3 +230,5 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect ) + +replace github.com/containers/image/v5 => github.com/vrothberg/image/v5 v5.0.0-20241119121158-6481647ad504 diff --git a/go.sum b/go.sum index 5473d5ca6f..8528e903dd 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,6 @@ github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6J github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/gvisor-tap-vsock v0.8.0 h1:Z8ZEWb+Lio0d+lXexONdUWT4rm9lF91vH0g3ARnMy7o= github.com/containers/gvisor-tap-vsock v0.8.0/go.mod h1:LVwnMiNvhxyGfhaMEQcXKJhNnN4h8woB9U3wf8rYOPc= -github.com/containers/image/v5 v5.33.0 h1:6oPEFwTurf7pDTGw7TghqGs8K0+OvPtY/UyzU0B2DfE= -github.com/containers/image/v5 v5.33.0/go.mod h1:T7HpASmvnp2H1u4cyckMvCzLuYgpD18dSmabSw0AcHk= github.com/containers/libhvee v0.9.0 h1:5UxJMka1lDfxTeITA25Pd8QVVttJAG43eQS1Getw1tc= github.com/containers/libhvee v0.9.0/go.mod h1:p44VJd8jMIx3SRN1eM6PxfCEwXQE0lJ0dQppCAlzjPQ= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -462,8 +460,8 @@ github.com/sigstore/fulcio v1.6.4 h1:d86obfxUAG3Y6CYwOx1pdwCZwKmROB6w6927pKOVIRY github.com/sigstore/fulcio v1.6.4/go.mod h1:Y6bn3i3KGhXpaHsAtYP3Z4Np0+VzCo1fLv8Ci6mbPDs= github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= -github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= +github.com/sigstore/sigstore v1.8.10 h1:r4t+TYzJlG9JdFxMy+um9GZhZ2N1hBTyTex0AHEZxFs= +github.com/sigstore/sigstore v1.8.10/go.mod h1:BekjqxS5ZtHNJC4u3Q3Stvfx2eyisbW/lUZzmPU2u4A= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= @@ -490,8 +488,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/sylabs/sif/v2 v2.19.1 h1:1eeMmFc8elqJe60ZiWwXgL3gMheb0IP4GmNZ4q0IEA0= -github.com/sylabs/sif/v2 v2.19.1/go.mod h1:U1SUhvl8X1JIxAylC0DYz1fa/Xba6EMZD1dGPGBH83E= +github.com/sylabs/sif/v2 v2.20.0 h1:RfDHEltUrchZbp/XGcWaw3nRSbufoNWqvwmf91/Q2gI= +github.com/sylabs/sif/v2 v2.20.0/go.mod h1:z6dq3B7QXK0pD71n15kAapven+gE+PZAIPOewBTNDpU= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= @@ -522,6 +520,8 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vrothberg/image/v5 v5.0.0-20241119121158-6481647ad504 h1:9Vz00x2xVY6sWit68QauL73iw6KcSdh7D4+s5Z5REuc= +github.com/vrothberg/image/v5 v5.0.0-20241119121158-6481647ad504/go.mod h1:sYYpCezsfxAFMu5+9iXBNTJDe0A+6gT75ra3o7m2lm4= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -611,8 +611,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/domain/entities/artifact.go b/pkg/domain/entities/artifact.go new file mode 100644 index 0000000000..3a4e89deaf --- /dev/null +++ b/pkg/domain/entities/artifact.go @@ -0,0 +1,75 @@ +package entities + +import ( + "github.com/opencontainers/go-digest" + "io" + + "github.com/containers/image/v5/types" + encconfig "github.com/containers/ocicrypt/config" + "github.com/containers/podman/v5/pkg/libartifact" +) + +type ArtifactAddoptions struct { + ArtifactType string + StorePath string +} + +type ArtifactInspectOptions struct { + Remote bool + StorePath string +} + +type ArtifactListOptions struct { + ImagePushOptions + StorePath string +} + +type ArtifactPullOptions struct { + Architecture string + AuthFilePath string + CertDirPath string + InsecureSkipTLSVerify types.OptionalBool + MaxRetries *uint + OciDecryptConfig *encconfig.DecryptConfig + Password string + Quiet bool + RetryDelay string + SignaturePolicyPath string + StorePath string + Username string + Writer io.Writer +} + +type ArtifactPushOptions struct { + ImagePushOptions + CredentialsCLI string + DigestFile string + EncryptLayers []int + EncryptionKeys []string + SignBySigstoreParamFileCLI string + SignPassphraseFileCLI string + StorePath string + TLSVerifyCLI bool // CLI only +} + +type ArtifactRemoveOptions struct { + StorePath string +} + +type ArtifactPullReport struct{} + +type ArtifactPushReport struct{} + +type ArtifactInspectReport struct { + *libartifact.Artifact +} + +type ArtifactListReport struct { + *libartifact.Artifact +} + +type ArtifactAddReport struct { + NewBlobDigest *digest.Digest +} + +type ArtifactRemoveReport struct{} diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 65844f676f..0071609f74 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -9,6 +9,12 @@ import ( ) type ImageEngine interface { //nolint:interfacebloat + ArtifactAdd(ctx context.Context, path, name string, opts ArtifactAddoptions) (*ArtifactAddReport, error) + ArtifactInspect(ctx context.Context, name string, opts ArtifactInspectOptions) (*ArtifactInspectReport, error) + ArtifactList(ctx context.Context, opts ArtifactListOptions) ([]*ArtifactListReport, error) + ArtifactPull(ctx context.Context, name string, opts ArtifactPullOptions) (*ArtifactPullReport, error) + ArtifactPush(ctx context.Context, name string, opts ArtifactPushOptions) (*ArtifactPushReport, error) + ArtifactRm(ctx context.Context, name string, opts ArtifactRemoveOptions) (*ArtifactRemoveReport, error) Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error) Config(ctx context.Context) (*config.Config, error) Exists(ctx context.Context, nameOrID string) (*BoolReport, error) diff --git a/pkg/domain/infra/abi/artifact.go b/pkg/domain/infra/abi/artifact.go new file mode 100644 index 0000000000..1574711090 --- /dev/null +++ b/pkg/domain/infra/abi/artifact.go @@ -0,0 +1,104 @@ +package abi + +import ( + "context" + "os" + + "github.com/containers/common/libimage" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/libartifact" +) + +func ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddoptions) error { + return nil +} + +func (ir *ImageEngine) ArtifactInspect(ctx context.Context, name string, opts entities.ArtifactInspectOptions) (*entities.ArtifactInspectReport, error) { + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + art, err := artStore.Inspect(ctx, name) + if err != nil { + return nil, err + } + artInspectReport := entities.ArtifactInspectReport{ + Artifact: art, + } + return &artInspectReport, nil +} + +func (ir *ImageEngine) ArtifactList(ctx context.Context, opts entities.ArtifactListOptions) ([]*entities.ArtifactListReport, error) { + var reports []*entities.ArtifactListReport + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + lrs, err := artStore.List(ctx) + if err != nil { + return nil, err + } + for _, lr := range lrs { + artListReport := entities.ArtifactListReport{ + Artifact: lr, + } + reports = append(reports, &artListReport) + } + return reports, nil +} + +func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entities.ArtifactPullOptions) (*entities.ArtifactPullReport, error) { + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = opts.AuthFilePath + pullOptions.CertDirPath = opts.CertDirPath + pullOptions.Username = opts.Username + pullOptions.Password = opts.Password + //pullOptions.Architecture = opts.Arch + pullOptions.SignaturePolicyPath = opts.SignaturePolicyPath + pullOptions.InsecureSkipTLSVerify = opts.InsecureSkipTLSVerify + pullOptions.Writer = opts.Writer + pullOptions.OciDecryptConfig = opts.OciDecryptConfig + pullOptions.MaxRetries = opts.MaxRetries + + if !opts.Quiet && pullOptions.Writer == nil { + pullOptions.Writer = os.Stderr + } + + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + return nil, artStore.Pull(ctx, name) +} + +func (ir *ImageEngine) ArtifactRm(ctx context.Context, name string, opts entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) { + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + err = artStore.Remove(ctx, artStore.SystemContext, name) + return nil, err +} + +func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entities.ArtifactPushOptions) (*entities.ArtifactPushReport, error) { + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + + err = artStore.Push(ctx, name, name) + return &entities.ArtifactPushReport{}, err +} +func (ir *ImageEngine) ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddoptions) (*entities.ArtifactAddReport, error) { + artStore, err := libartifact.NewArtifactStore(opts.StorePath, ir.Libpod.SystemContext()) + if err != nil { + return nil, err + } + newBlobDigest, err := artStore.Add(ctx, name, path, opts.ArtifactType) + if err != nil { + return nil, err + } + return &entities.ArtifactAddReport{ + NewBlobDigest: newBlobDigest, + }, nil +} diff --git a/pkg/domain/infra/tunnel/artifact.go b/pkg/domain/infra/tunnel/artifact.go new file mode 100644 index 0000000000..e55027df68 --- /dev/null +++ b/pkg/domain/infra/tunnel/artifact.go @@ -0,0 +1,38 @@ +package tunnel + +import ( + "context" + "fmt" + + "github.com/containers/podman/v5/pkg/domain/entities" +) + +// TODO For now, no remote support has been added. We need the API to firm up first. + +func ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddoptions) error { + return fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactInspect(ctx context.Context, name string, opts entities.ArtifactInspectOptions) (*entities.ArtifactInspectReport, error) { + return nil, fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactList(ctx context.Context, opts entities.ArtifactListOptions) ([]*entities.ArtifactListReport, error) { + return nil, fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entities.ArtifactPullOptions) (*entities.ArtifactPullReport, error) { + return nil, fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactRm(ctx context.Context, name string, opts entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) { + return nil, fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entities.ArtifactPushOptions) (*entities.ArtifactPushReport, error) { + return nil, fmt.Errorf("not implemented") +} + +func (ir *ImageEngine) ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddoptions) (*entities.ArtifactAddReport, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/pkg/libartifact/artifact.go b/pkg/libartifact/artifact.go new file mode 100644 index 0000000000..11009b1763 --- /dev/null +++ b/pkg/libartifact/artifact.go @@ -0,0 +1,35 @@ +package libartifact + +import ( + "fmt" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/oci/layout" +) + +type Artifact struct { + List layout.ListResult + Manifests []manifest.OCI1 +} + +// TotalSize returns the total bytes of the all the artifact layers +func (a Artifact) TotalSize() int64 { + var s int64 + for _, artifact := range a.Manifests { + for _, layer := range artifact.Layers { + s += layer.Size + } + } + return s +} + +type ArtifactList []*Artifact + +// getByName returns an artifact, if present, by a given name +func (al ArtifactList) getByName(name string) (*Artifact, error) { + for _, artifact := range al { + if val, ok := artifact.List.ManifestDescriptor.Annotations[annotatedName]; ok && val == name { + return artifact, nil + } + } + return nil, fmt.Errorf("no artifact found with name %s", name) +} diff --git a/pkg/libartifact/config.go b/pkg/libartifact/config.go new file mode 100644 index 0000000000..43d971c693 --- /dev/null +++ b/pkg/libartifact/config.go @@ -0,0 +1,8 @@ +package libartifact + +var ( + // annotatedName is the label name where the artifact tag reference lives + annotatedName = "org.opencontainers.image.ref.name" +) + +type GetArtifactOptions struct{} diff --git a/pkg/libartifact/store.go b/pkg/libartifact/store.go new file mode 100644 index 0000000000..0144592e8a --- /dev/null +++ b/pkg/libartifact/store.go @@ -0,0 +1,361 @@ +package libartifact + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/common/libimage" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + "github.com/opencontainers/go-digest" + specV1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +var ( + // indexName is the name of the JSON file in root of the artifact store + // that describes the store's contents + indexName = "index.json" +) + +type ArtifactStore struct { + SystemContext *types.SystemContext + storePath string +} + +// NewArtifactStore is a constructor for artifact stores. Most artifact dealings depend on this. Store path is +// the filesystem location. +func NewArtifactStore(storePath string, sc *types.SystemContext) (*ArtifactStore, error) { + // storePath here is an override + if storePath == "" { + storeOptions, err := storage.DefaultStoreOptions() + if err != nil { + return nil, err + } + storePath = filepath.Join(storeOptions.GraphRoot, "artifacts") + } + artifactStore := &ArtifactStore{ + storePath: storePath, + SystemContext: sc, + } + + // if the storage dir does not exist, we need to create it. + baseDir := filepath.Dir(artifactStore.indexPath()) + if err := os.MkdirAll(baseDir, 0700); err != nil { + return nil, err + } + // if the index file is not present we need to create an empty one + _, err := os.Stat(artifactStore.indexPath()) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + if createErr := artifactStore.createEmptyManifest(); createErr != nil { + return nil, createErr + } + } + } + + return artifactStore, nil +} + +func (as ArtifactStore) Remove(ctx context.Context, sys *types.SystemContext, name string) error { + ir, err := layout.NewReference(as.storePath, name) + if err != nil { + return err + } + return ir.DeleteImage(ctx, sys) +} + +func (as ArtifactStore) Inspect(ctx context.Context, name string) (*Artifact, error) { + artifacts, err := as.getArtifacts(ctx, nil) + if err != nil { + return nil, err + } + return artifacts.getByName(name) +} + +func (as ArtifactStore) List(ctx context.Context) (ArtifactList, error) { + return as.getArtifacts(ctx, nil) +} + +func (as ArtifactStore) Pull(ctx context.Context, name string) error { + srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", name)) + if err != nil { + return err + } + destRef, err := layout.NewReference(as.storePath, name) + if err != nil { + return err + } + copyOpts := libimage.CopyOptions{ + Writer: os.Stdout, + } + + copyer, err := libimage.NewCopier(©Opts, as.SystemContext, nil) + if err != nil { + return err + } + _, err = copyer.Copy(ctx, srcRef, destRef) + if err != nil { + return err + } + return copyer.Close() +} + +func (as ArtifactStore) Push(ctx context.Context, src, dest string) error { + destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", dest)) + if err != nil { + return err + } + srcRef, err := layout.NewReference(as.storePath, src) + if err != nil { + return err + } + copyOpts := libimage.CopyOptions{ + Writer: os.Stdout, + } + copyer, err := libimage.NewCopier(©Opts, as.SystemContext, nil) + if err != nil { + return err + } + _, err = copyer.Copy(ctx, srcRef, destRef) + if err != nil { + return err + } + return copyer.Close() +} + +func (as ArtifactStore) Add(ctx context.Context, dest string, path, artifactType string) (*digest.Digest, error) { + // TODO + // 1. Check to make sure the artifact does not otherwise exist + + var ( + // TODO Needs to be located somewhere that makes sense + MediaTypeOCIArtifact = "application/vnd.github.com.containers.artifact" + ) + + var annotations = map[string]string{} + annotations[specV1.AnnotationTitle] = filepath.Base(path) + + ir, err := layout.NewReference(as.storePath, dest) + if err != nil { + return nil, err + } + + // Initialize artifact if not present + // sourcePath == dest + //ociDest, err := openOrCreateSourceImage(ctx, sourcePath) + //if err != nil { + // return err + //} + //defer ociDest.Close() + + // ociDest replaces imageDest + imageDest, err := ir.NewImageDestination(ctx, as.SystemContext) + if err != nil { + return nil, err + } + + // get the new artifact into the local store + newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path, nil) + if err != nil { + return nil, err + } + + //https://github.com/containers/buildah/blob/main/internal/source/add.go#L46 + is, err := ir.NewImageSource(ctx, as.SystemContext) + if err != nil { + return nil, err + } + + //ociSource, err := ociRef.NewImageSource(ctx, &types.SystemContext{}) + // initial digest is used to define whats need removed after we've written the updated + // manifest + artifactManifest, initialDigest, _, err := readManifestFromImageSource(ctx, is) + if err != nil { + return nil, err + } + + artifactManifest.Layers = append(artifactManifest.Layers, + specV1.Descriptor{ + MediaType: MediaTypeOCIArtifact, + Digest: newBlobDigest, + Size: newBlobSize, + Annotations: annotations, + }, + ) + + updatedDigest, updatedSize, err := writeManifest(ctx, artifactManifest, imageDest) + if err != nil { + return nil, err + } + + // Remove of the old blobs JSON file by the initialDigest name + // https://github.com/containers/buildah/blob/main/internal/source/source.go#L117-L121 + + manifestDescriptor := specV1.Descriptor{ + MediaType: specV1.MediaTypeImageManifest, // TODO: the media type should be configurable + Digest: *updatedDigest, + Size: updatedSize, + } + + // Update the artifact in index.json + //// We need to update the index.json on local storage + storeIndex, err := as.readIndex() + if err != nil { + return nil, err + } + + for i, m := range storeIndex.Manifests { + if &m.Digest == initialDigest { + storeIndex.Manifests[i] = manifestDescriptor + break + } + } + + if err := as.writeIndex(storeIndex); err != nil { + return nil, err + } + return updatedDigest, nil +} + +func (as ArtifactStore) readIndex() (specV1.Index, error) { + index := specV1.Index{} + rawData, err := os.ReadFile(as.indexPath()) + if err != nil { + return specV1.Index{}, err + } + err = json.Unmarshal(rawData, &index) + return index, err +} + +func (as ArtifactStore) writeIndex(index specV1.Index) error { + rawData, err := json.Marshal(&index) + if err != nil { + return err + } + return os.WriteFile(as.indexPath(), rawData, 0o644) +} + +func (as ArtifactStore) createEmptyManifest() error { + index := specV1.Index{} + rawData, err := json.Marshal(&index) + if err != nil { + return err + } + + return os.WriteFile(as.indexPath(), rawData, 0o644) +} + +func (as ArtifactStore) indexPath() string { + return filepath.Join(as.storePath, indexName) +} + +// getArtifacts returns an ArtifactList based on the artifact's store. The return error and +// unused opts is meant for future growth like filters, etc so the API does not change. +func (as ArtifactStore) getArtifacts(ctx context.Context, _ *GetArtifactOptions) (ArtifactList, error) { + var ( + al ArtifactList + ) + lrs, err := layout.List(as.storePath) + if err != nil { + return nil, err + } + for _, l := range lrs { + imgSrc, err := l.Reference.NewImageSource(ctx, as.SystemContext) + if err != nil { + return nil, err + } + manifests, err := getManifests(ctx, imgSrc, nil) + if err != nil { + return nil, err + } + + artifact := Artifact{ + List: l, + Manifests: manifests, + } + al = append(al, &artifact) + } + return al, nil +} + +// getManifests takes an imgSrc and starting digest (nil means "top") and collects all the manifests "under" +// it. this func calls itself recursively with a new startingDigest assuming that we are dealing with +// and index list +func getManifests(ctx context.Context, imgSrc types.ImageSource, startingDigest *digest.Digest) ([]manifest.OCI1, error) { + var ( + manifests []manifest.OCI1 + ) + b, manifestType, err := imgSrc.GetManifest(ctx, startingDigest) + if err != nil { + return nil, err + } + // this assumes that there are only single, and multi-images + if !manifest.MIMETypeIsMultiImage(manifestType) { + // these are the keepers + mani, err := manifest.OCI1FromManifest(b) + if err != nil { + return nil, err + } + manifests = append(manifests, *mani) + return manifests, nil + } + // We are dealing with an oci index list + maniList, err := manifest.OCI1IndexFromManifest(b) + if err != nil { + return nil, err + } + for _, m := range maniList.Manifests { + iterManifests, err := getManifests(ctx, imgSrc, &m.Digest) + if err != nil { + return nil, err + } + manifests = append(manifests, iterManifests...) + } + return manifests, nil +} + +// readManifestFromImageSource reads the manifest from the specified image +// source. Note that the manifest is expected to be an OCI v1 manifest. +// Taken from buildah source.go +func readManifestFromImageSource(ctx context.Context, src types.ImageSource) (*specV1.Manifest, *digest.Digest, int64, error) { + rawData, mimeType, err := src.GetManifest(ctx, nil) + if err != nil { + return nil, nil, -1, err + } + if mimeType != specV1.MediaTypeImageManifest { + return nil, nil, -1, fmt.Errorf("image %q is of type %q (expected: %q)", strings.TrimPrefix(src.Reference().StringWithinTransport(), "//"), mimeType, specV1.MediaTypeImageManifest) + } + + readManifest := specV1.Manifest{} + if err := json.Unmarshal(rawData, &readManifest); err != nil { + return nil, nil, -1, fmt.Errorf("reading manifest: %w", err) + } + + manifestDigest := digest.FromBytes(rawData) + return &readManifest, &manifestDigest, int64(len(rawData)), nil +} + +// writeManifest writes the specified OCI `manifest` to the source image at +// `ociDest`. +// Taken from buildah source.go +func writeManifest(ctx context.Context, manifest *specV1.Manifest, ociDest types.ImageDestination) (*digest.Digest, int64, error) { + rawData, err := json.Marshal(&manifest) + if err != nil { + return nil, -1, fmt.Errorf("marshalling manifest: %w", err) + } + + if err := ociDest.PutManifest(ctx, rawData, nil); err != nil { + return nil, -1, fmt.Errorf("writing manifest: %w", err) + } + + manifestDigest := digest.FromBytes(rawData) + return &manifestDigest, int64(len(rawData)), nil +} diff --git a/vendor/github.com/containers/image/v5/docker/distribution_error.go b/vendor/github.com/containers/image/v5/docker/distribution_error.go index 0a0064576a..622d21fb1c 100644 --- a/vendor/github.com/containers/image/v5/docker/distribution_error.go +++ b/vendor/github.com/containers/image/v5/docker/distribution_error.go @@ -24,7 +24,6 @@ import ( "slices" "github.com/docker/distribution/registry/api/errcode" - dockerChallenge "github.com/docker/distribution/registry/client/auth/challenge" ) // errNoErrorsInBody is returned when an HTTP response body parses to an empty @@ -114,10 +113,11 @@ func mergeErrors(err1, err2 error) error { // UnexpectedHTTPStatusError returned for response code outside of expected // range. func handleErrorResponse(resp *http.Response) error { - if resp.StatusCode >= 400 && resp.StatusCode < 500 { + switch { + case resp.StatusCode == http.StatusUnauthorized: // Check for OAuth errors within the `WWW-Authenticate` header first // See https://tools.ietf.org/html/rfc6750#section-3 - for _, c := range dockerChallenge.ResponseChallenges(resp) { + for _, c := range parseAuthHeader(resp.Header) { if c.Scheme == "bearer" { var err errcode.Error // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 @@ -138,6 +138,8 @@ func handleErrorResponse(resp *http.Response) error { return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body)) } } + fallthrough + case resp.StatusCode >= 400 && resp.StatusCode < 500: err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) if uErr, ok := err.(*unexpectedHTTPResponseError); ok && resp.StatusCode == 401 { return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) diff --git a/vendor/github.com/containers/image/v5/docker/docker_image_src.go b/vendor/github.com/containers/image/v5/docker/docker_image_src.go index 6e44ce0960..41ab9bfd16 100644 --- a/vendor/github.com/containers/image/v5/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image_src.go @@ -340,6 +340,10 @@ func handle206Response(streams chan io.ReadCloser, errs chan error, body io.Read } return } + if parts >= len(chunks) { + errs <- errors.New("too many parts returned by the server") + break + } s := signalCloseReader{ closed: make(chan struct{}), stream: p, diff --git a/vendor/github.com/containers/image/v5/oci/internal/oci_util.go b/vendor/github.com/containers/image/v5/oci/internal/oci_util.go index 53827b11af..ba4b20315a 100644 --- a/vendor/github.com/containers/image/v5/oci/internal/oci_util.go +++ b/vendor/github.com/containers/image/v5/oci/internal/oci_util.go @@ -6,6 +6,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" ) @@ -119,3 +120,31 @@ func validateScopeNonWindows(scope string) error { return nil } + +// parseOCIReferenceName parses the image from the oci reference. +func parseOCIReferenceName(image string) (img string, index int, err error) { + index = -1 + if strings.HasPrefix(image, "@") { + idx, err := strconv.Atoi(image[1:]) + if err != nil { + return "", index, fmt.Errorf("Invalid source index @%s: not an integer: %w", image[1:], err) + } + if idx < 0 { + return "", index, fmt.Errorf("Invalid source index @%d: must not be negative", idx) + } + index = idx + } else { + img = image + } + return img, index, nil +} + +// ParseReferenceIntoElements splits the oci reference into location, image name and source index if exists +func ParseReferenceIntoElements(reference string) (string, string, int, error) { + dir, image := SplitPathAndImage(reference) + image, index, err := parseOCIReferenceName(image) + if err != nil { + return "", "", -1, err + } + return dir, image, index, nil +} diff --git a/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go index 85374cecf0..4a3029e017 100644 --- a/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go @@ -18,10 +18,12 @@ import ( "github.com/containers/image/v5/internal/private" "github.com/containers/image/v5/internal/putblobdigest" "github.com/containers/image/v5/types" + reflinkCopy "github.com/containers/storage/drivers/copy" "github.com/containers/storage/pkg/fileutils" digest "github.com/opencontainers/go-digest" imgspec "github.com/opencontainers/image-spec/specs-go" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" ) type ociImageDestination struct { @@ -37,6 +39,9 @@ type ociImageDestination struct { // newImageDestination returns an ImageDestination for writing to an existing directory. func newImageDestination(sys *types.SystemContext, ref ociReference) (private.ImageDestination, error) { + if ref.sourceIndex != -1 { + return nil, fmt.Errorf("Destination reference must not contain a manifest index @%d", ref.sourceIndex) + } var index *imgspecv1.Index if indexExists(ref) { var err error @@ -159,7 +164,7 @@ func (d *ociImageDestination) PutBlobWithOptions(ctx context.Context, stream io. return private.UploadedBlob{}, err } - // need to explicitly close the file, since a rename won't otherwise not work on Windows + // need to explicitly close the file, since a rename won't otherwise work on Windows blobFile.Close() explicitClosed = true if err := os.Rename(blobFile.Name(), blobPath); err != nil { @@ -299,6 +304,102 @@ func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options pri return os.WriteFile(d.ref.indexPath(), indexJSON, 0644) } +// tryReflinkLocalFile attempts to reflink the specified file and digest it. +// If relinking does not work, reset to doing a verbatim copy of the file. +func tryReflinkLocalFile(dest *ociImageDestination, file string) (private.UploadedBlob, bool, error) { + fInfo, err := os.Stat(file) + if err != nil { + return private.UploadedBlob{}, false, err + } + + blobFile, err := os.CreateTemp(dest.ref.dir, "oci-put-blob") + if err != nil { + return private.UploadedBlob{}, false, err + } + blobName := blobFile.Name() + + copyRange := false + copyClone := true + err = reflinkCopy.CopyRegularToFile(file, blobFile, fInfo, ©Range, ©Clone) + if err != nil { + return private.UploadedBlob{}, false, err + } + + _, err = blobFile.Seek(0, 0) + if err != nil { + return private.UploadedBlob{}, false, err + } + + blobFile, err = os.Open(blobName) + if err != nil { + return private.UploadedBlob{}, false, err + } + blobDigest, err := digest.FromReader(blobFile) + if err != nil { + blobFile.Close() + return private.UploadedBlob{}, false, err + } + blobPath, err := dest.ref.blobPath(blobDigest, dest.sharedBlobDir) + if err != nil { + return private.UploadedBlob{}, false, err + } + if err := ensureParentDirectoryExists(blobPath); err != nil { + return private.UploadedBlob{}, false, err + } + + // need to explicitly close the file, since a rename won't otherwise work on Windows + blobFile.Close() + if err := os.Rename(blobName, blobPath); err != nil { + return private.UploadedBlob{}, false, err + } + + fileInfo, err := os.Stat(blobPath) + if err != nil { + return private.UploadedBlob{}, false, err + } + return private.UploadedBlob{Digest: blobDigest, Size: fileInfo.Size()}, false, nil +} + +// PutBlobFromLocalFileOptions is unused but may receive functionality in the future. +type PutBlobFromLocalFileOptions struct{} + +// PutBlobFromLocalFile arranges the data from path to be used as blob with digest. +// It computes, and returns, the digest and size of the used file. +// +// This function can be used instead of dest.PutBlob() where the ImageDestination requires PutBlob() to be called. +func PutBlobFromLocalFile(ctx context.Context, dest types.ImageDestination, file string, options *PutBlobFromLocalFileOptions) (digest.Digest, int64, error) { + d, ok := dest.(*ociImageDestination) + if !ok { + return "", -1, errors.New("internal error: PutBlobFromLocalFile called with a non-oci: destination") + } + + uploaded, fallback, err := tryReflinkLocalFile(d, file) + if err == nil { + return uploaded.Digest, uploaded.Size, nil + } else if fallback { + logrus.Debugf("Falling back to copying. Error trying to hardlink file: %v", err) + } else { + return "", -1, fmt.Errorf("trying to hardlink file: %w", err) + } + + // Fallback to copying the file + reader, err := os.Open(file) + if err != nil { + return "", -1, fmt.Errorf("opening %q: %w", file, err) + } + defer reader.Close() + + // This makes a full copy; instead, if possible, we could only digest the file and reflink (hard link?) + uploaded, err = d.PutBlobWithOptions(ctx, reader, types.BlobInfo{ + Digest: "", + Size: -1, + }, private.PutBlobOptions{}) + if err != nil { + return "", -1, err + } + return uploaded.Digest, uploaded.Size, nil +} + func ensureDirectoryExists(path string) error { if err := fileutils.Exists(path); err != nil && errors.Is(err, fs.ErrNotExist) { if err := os.MkdirAll(path, 0755); err != nil { diff --git a/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go b/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go index 816dfa7a1e..a35762188c 100644 --- a/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go @@ -61,22 +61,32 @@ type ociReference struct { // (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.) dir string // As specified by the user. May be relative, contain symlinks, etc. resolvedDir string // Absolute path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces. - // If image=="", it means the "only image" in the index.json is used in the case it is a source - // for destinations, the image name annotation "image.ref.name" is not added to the index.json + // If image=="" && sourceIndex==-1, it means the "only image" in the index.json is used in the case it is a source + // for destinations, the image name annotation "image.ref.name" is not added to the index.json. + // + // Must not be set if sourceIndex is set (the value is not -1). image string + // If not -1, a zero-based index of an image in the manifest index. Valid only for sources. + // Must not be set if image is set. + sourceIndex int } // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference. func ParseReference(reference string) (types.ImageReference, error) { - dir, image := internal.SplitPathAndImage(reference) - return NewReference(dir, image) + dir, image, index, err := internal.ParseReferenceIntoElements(reference) + if err != nil { + return nil, err + } + return newReference(dir, image, index) } -// NewReference returns an OCI reference for a directory and a image. +// newReference returns an OCI reference for a directory, and an image name annotation or sourceIndex. // +// If sourceIndex==-1, the index will not be valid to point out the source image, only image will be used. +// NewReference returns an OCI reference for a directory and a image. // We do not expose an API supplying the resolvedDir; we could, but recomputing it // is generally cheap enough that we prefer being confident about the properties of resolvedDir. -func NewReference(dir, image string) (types.ImageReference, error) { +func newReference(dir, image string, sourceIndex int) (types.ImageReference, error) { resolved, err := explicitfilepath.ResolvePathToFullyExplicit(dir) if err != nil { return nil, err @@ -90,7 +100,26 @@ func NewReference(dir, image string) (types.ImageReference, error) { return nil, err } - return ociReference{dir: dir, resolvedDir: resolved, image: image}, nil + if sourceIndex != -1 && sourceIndex < 0 { + return nil, fmt.Errorf("Invalid oci: layout reference: index @%d must not be negative", sourceIndex) + } + if sourceIndex != -1 && image != "" { + return nil, fmt.Errorf("Invalid oci: layout reference: cannot use both an image %s and a source index @%d", image, sourceIndex) + } + return ociReference{dir: dir, resolvedDir: resolved, image: image, sourceIndex: sourceIndex}, nil +} + +// NewIndexReference returns an OCI reference for a path and a zero-based source manifest index. +func NewIndexReference(dir string, sourceIndex int) (types.ImageReference, error) { + return newReference(dir, "", sourceIndex) +} + +// NewReference returns an OCI reference for a directory and a image. +// +// We do not expose an API supplying the resolvedDir; we could, but recomputing it +// is generally cheap enough that we prefer being confident about the properties of resolvedDir. +func NewReference(dir, image string) (types.ImageReference, error) { + return newReference(dir, image, -1) } func (ref ociReference) Transport() types.ImageTransport { @@ -103,7 +132,10 @@ func (ref ociReference) Transport() types.ImageTransport { // e.g. default attribute values omitted by the user may be filled in the return value, or vice versa. // WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix. func (ref ociReference) StringWithinTransport() string { - return fmt.Sprintf("%s:%s", ref.dir, ref.image) + if ref.sourceIndex == -1 { + return fmt.Sprintf("%s:%s", ref.dir, ref.image) + } + return fmt.Sprintf("%s:@%d", ref.dir, ref.sourceIndex) } // DockerReference returns a Docker reference associated with this reference @@ -187,14 +219,18 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, int, erro return imgspecv1.Descriptor{}, -1, err } - if ref.image == "" { - // return manifest if only one image is in the oci directory - if len(index.Manifests) != 1 { - // ask user to choose image when more than one image in the oci directory - return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage + switch { + case ref.image != "" && ref.sourceIndex != -1: + return imgspecv1.Descriptor{}, -1, fmt.Errorf("Internal error: Cannot have both ref %s and source index @%d", + ref.image, ref.sourceIndex) + + case ref.sourceIndex != -1: + if ref.sourceIndex >= len(index.Manifests) { + return imgspecv1.Descriptor{}, -1, fmt.Errorf("index %d is too large, only %d entries available", ref.sourceIndex, len(index.Manifests)) } - return index.Manifests[0], 0, nil - } else { + return index.Manifests[ref.sourceIndex], ref.sourceIndex, nil + + case ref.image != "": // if image specified, look through all manifests for a match var unsupportedMIMETypes []string for i, md := range index.Manifests { @@ -208,8 +244,16 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, int, erro if len(unsupportedMIMETypes) != 0 { return imgspecv1.Descriptor{}, -1, fmt.Errorf("reference %q matches unsupported manifest MIME types %q", ref.image, unsupportedMIMETypes) } + return imgspecv1.Descriptor{}, -1, ImageNotFoundError{ref} + + default: + // return manifest if only one image is in the oci directory + if len(index.Manifests) != 1 { + // ask user to choose image when more than one image in the oci directory + return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage + } + return index.Manifests[0], 0, nil } - return imgspecv1.Descriptor{}, -1, ImageNotFoundError{ref} } // LoadManifestDescriptor loads the manifest descriptor to be used to retrieve the image name diff --git a/vendor/github.com/containers/image/v5/oci/layout/reader.go b/vendor/github.com/containers/image/v5/oci/layout/reader.go new file mode 100644 index 0000000000..078ef68615 --- /dev/null +++ b/vendor/github.com/containers/image/v5/oci/layout/reader.go @@ -0,0 +1,52 @@ +package layout + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/containers/image/v5/types" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// This file is named reader.go for consistency with other transports +// handling of “image containers”, but we don’t actually need a stateful reader object. + +// ListResult wraps the image reference and the manifest for loading +type ListResult struct { + Reference types.ImageReference + ManifestDescriptor imgspecv1.Descriptor +} + +// List returns a slice of manifests included in the archive +func List(dir string) ([]ListResult, error) { + var res []ListResult + + indexJSON, err := os.ReadFile(filepath.Join(dir, imgspecv1.ImageIndexFile)) + if err != nil { + return nil, err + } + var index imgspecv1.Index + if err := json.Unmarshal(indexJSON, &index); err != nil { + return nil, err + } + + for manifestIndex, md := range index.Manifests { + refName := md.Annotations[imgspecv1.AnnotationRefName] + index := -1 + if refName == "" { + index = manifestIndex + } + ref, err := newReference(dir, refName, index) + if err != nil { + return nil, fmt.Errorf("error creating image reference: %w", err) + } + reference := ListResult{ + Reference: ref, + ManifestDescriptor: md, + } + res = append(res, reference) + } + return res, nil +} diff --git a/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go b/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go index c0b48dafa7..c0dc7b232b 100644 --- a/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go +++ b/vendor/github.com/containers/image/v5/signature/fulcio_cert_stub.go @@ -20,7 +20,7 @@ func (f *fulcioTrustRoot) validate() error { return errors.New("fulcio disabled at compile-time") } -func verifyRekorFulcio(rekorPublicKey *ecdsa.PublicKey, fulcioTrustRoot *fulcioTrustRoot, untrustedRekorSET []byte, +func verifyRekorFulcio(rekorPublicKeys []*ecdsa.PublicKey, fulcioTrustRoot *fulcioTrustRoot, untrustedRekorSET []byte, untrustedCertificateBytes []byte, untrustedIntermediateChainBytes []byte, untrustedBase64Signature string, untrustedPayloadBytes []byte) (crypto.PublicKey, error) { return nil, errors.New("fulcio disabled at compile-time") diff --git a/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go b/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go index 7c121cc2ee..2b20bbed2e 100644 --- a/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go +++ b/vendor/github.com/containers/image/v5/signature/internal/rekor_set_stub.go @@ -10,6 +10,6 @@ import ( // VerifyRekorSET verifies that unverifiedRekorSET is correctly signed by publicKey and matches the rest of the data. // Returns bundle upload time on success. -func VerifyRekorSET(publicKey *ecdsa.PublicKey, unverifiedRekorSET []byte, unverifiedKeyOrCertBytes []byte, unverifiedBase64Signature string, unverifiedPayloadBytes []byte) (time.Time, error) { +func VerifyRekorSET(publicKeys []*ecdsa.PublicKey, unverifiedRekorSET []byte, unverifiedKeyOrCertBytes []byte, unverifiedBase64Signature string, unverifiedPayloadBytes []byte) (time.Time, error) { return time.Time{}, NewInvalidSignatureError("rekor disabled at compile-time") } diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index 3743721fc3..7a16c8181e 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -6,12 +6,12 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 33 + VersionMinor = 34 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "" + VersionDev = "-dev" ) // Version is the specification version that the package types support. diff --git a/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go b/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go deleted file mode 100644 index 2c3ebe1653..0000000000 --- a/vendor/github.com/docker/distribution/registry/client/auth/challenge/addr.go +++ /dev/null @@ -1,27 +0,0 @@ -package challenge - -import ( - "net/url" - "strings" -) - -// FROM: https://golang.org/src/net/http/http.go -// Given a string of the form "host", "host:port", or "[ipv6::address]:port", -// return true if the string includes a port. -func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } - -// FROM: http://golang.org/src/net/http/transport.go -var portMap = map[string]string{ - "http": "80", - "https": "443", -} - -// canonicalAddr returns url.Host but always with a ":port" suffix -// FROM: http://golang.org/src/net/http/transport.go -func canonicalAddr(url *url.URL) string { - addr := url.Host - if !hasPort(addr) { - return addr + ":" + portMap[url.Scheme] - } - return addr -} diff --git a/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go b/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go deleted file mode 100644 index fe238210cd..0000000000 --- a/vendor/github.com/docker/distribution/registry/client/auth/challenge/authchallenge.go +++ /dev/null @@ -1,237 +0,0 @@ -package challenge - -import ( - "fmt" - "net/http" - "net/url" - "strings" - "sync" -) - -// Challenge carries information from a WWW-Authenticate response header. -// See RFC 2617. -type Challenge struct { - // Scheme is the auth-scheme according to RFC 2617 - Scheme string - - // Parameters are the auth-params according to RFC 2617 - Parameters map[string]string -} - -// Manager manages the challenges for endpoints. -// The challenges are pulled out of HTTP responses. Only -// responses which expect challenges should be added to -// the manager, since a non-unauthorized request will be -// viewed as not requiring challenges. -type Manager interface { - // GetChallenges returns the challenges for the given - // endpoint URL. - GetChallenges(endpoint url.URL) ([]Challenge, error) - - // AddResponse adds the response to the challenge - // manager. The challenges will be parsed out of - // the WWW-Authenicate headers and added to the - // URL which was produced the response. If the - // response was authorized, any challenges for the - // endpoint will be cleared. - AddResponse(resp *http.Response) error -} - -// NewSimpleManager returns an instance of -// Manger which only maps endpoints to challenges -// based on the responses which have been added the -// manager. The simple manager will make no attempt to -// perform requests on the endpoints or cache the responses -// to a backend. -func NewSimpleManager() Manager { - return &simpleManager{ - Challenges: make(map[string][]Challenge), - } -} - -type simpleManager struct { - sync.RWMutex - Challenges map[string][]Challenge -} - -func normalizeURL(endpoint *url.URL) { - endpoint.Host = strings.ToLower(endpoint.Host) - endpoint.Host = canonicalAddr(endpoint) -} - -func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) { - normalizeURL(&endpoint) - - m.RLock() - defer m.RUnlock() - challenges := m.Challenges[endpoint.String()] - return challenges, nil -} - -func (m *simpleManager) AddResponse(resp *http.Response) error { - challenges := ResponseChallenges(resp) - if resp.Request == nil { - return fmt.Errorf("missing request reference") - } - urlCopy := url.URL{ - Path: resp.Request.URL.Path, - Host: resp.Request.URL.Host, - Scheme: resp.Request.URL.Scheme, - } - normalizeURL(&urlCopy) - - m.Lock() - defer m.Unlock() - m.Challenges[urlCopy.String()] = challenges - return nil -} - -// Octet types from RFC 2616. -type octetType byte - -var octetTypes [256]octetType - -const ( - isToken octetType = 1 << iota - isSpace -) - -func init() { - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t octetType - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) - if strings.ContainsRune(" \t\r\n", rune(c)) { - t |= isSpace - } - if isChar && !isCtl && !isSeparator { - t |= isToken - } - octetTypes[c] = t - } -} - -// ResponseChallenges returns a list of authorization challenges -// for the given http Response. Challenges are only checked if -// the response status code was a 401. -func ResponseChallenges(resp *http.Response) []Challenge { - if resp.StatusCode == http.StatusUnauthorized { - // Parse the WWW-Authenticate Header and store the challenges - // on this endpoint object. - return parseAuthHeader(resp.Header) - } - - return nil -} - -func parseAuthHeader(header http.Header) []Challenge { - challenges := []Challenge{} - for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] { - v, p := parseValueAndParams(h) - if v != "" { - challenges = append(challenges, Challenge{Scheme: v, Parameters: p}) - } - } - return challenges -} - -func parseValueAndParams(header string) (value string, params map[string]string) { - params = make(map[string]string) - value, s := expectToken(header) - if value == "" { - return - } - value = strings.ToLower(value) - s = "," + skipSpace(s) - for strings.HasPrefix(s, ",") { - var pkey string - pkey, s = expectToken(skipSpace(s[1:])) - if pkey == "" { - return - } - if !strings.HasPrefix(s, "=") { - return - } - var pvalue string - pvalue, s = expectTokenOrQuoted(s[1:]) - if pvalue == "" { - return - } - pkey = strings.ToLower(pkey) - params[pkey] = pvalue - s = skipSpace(s) - } - return -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpace == 0 { - break - } - } - return s[i:] -} - -func expectToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isToken == 0 { - break - } - } - return s[:i], s[i:] -} - -func expectTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return expectToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} diff --git a/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go b/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go index dfc1f0c0e8..6714b3488e 100644 --- a/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go +++ b/vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go @@ -134,7 +134,7 @@ func (i *InteractiveIDTokenGetter) doOobFlow(cfg *oauth2.Config, stateToken stri fmt.Fprintln(i.GetOutput(), "Go to the following link in a browser:\n\n\t", authURL) fmt.Fprintf(i.GetOutput(), "Enter verification code: ") var code string - fmt.Fscanf(i.GetInput(), "%s", &code) + _, _ = fmt.Fscanf(i.GetInput(), "%s", &code) // New line in case read input doesn't move cursor to next line. fmt.Fprintln(i.GetOutput()) return code diff --git a/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go b/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go index 91dd430c1c..6fc80512fd 100644 --- a/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go +++ b/vendor/github.com/sylabs/sif/v2/pkg/sif/create.go @@ -23,21 +23,19 @@ var errAlignmentOverflow = errors.New("integer overflow when calculating alignme // nextAligned finds the next offset that satisfies alignment. func nextAligned(offset int64, alignment int) (int64, error) { - align64 := uint64(alignment) - offset64 := uint64(offset) + align64 := int64(alignment) - if align64 <= 0 || offset64%align64 == 0 { + if align64 <= 0 || offset%align64 == 0 { return offset, nil } - offset64 += (align64 - offset64%align64) + align64 -= offset % align64 - if offset64 > math.MaxInt64 { + if (math.MaxInt64 - offset) < align64 { return 0, errAlignmentOverflow } - //nolint:gosec // Overflow handled above. - return int64(offset64), nil + return offset + align64, nil } // writeDataObjectAt writes the data object described by di to ws, using time t, recording details diff --git a/vendor/golang.org/x/oauth2/README.md b/vendor/golang.org/x/oauth2/README.md index 781770c204..48dbb9d84c 100644 --- a/vendor/golang.org/x/oauth2/README.md +++ b/vendor/golang.org/x/oauth2/README.md @@ -5,15 +5,6 @@ oauth2 package contains a client implementation for OAuth 2.0 spec. -## Installation - -~~~~ -go get golang.org/x/oauth2 -~~~~ - -Or you can manually git clone the repository to -`$(go env GOPATH)/src/golang.org/x/oauth2`. - See pkg.go.dev for further documentation and examples. * [pkg.go.dev/golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) @@ -33,7 +24,11 @@ The main issue tracker for the oauth2 repository is located at https://github.com/golang/oauth2/issues. This repository uses Gerrit for code changes. To learn how to submit changes to -this repository, see https://golang.org/doc/contribute.html. In particular: +this repository, see https://go.dev/doc/contribute. + +The git repository is https://go.googlesource.com/oauth2. + +Note: * Excluding trivial changes, all contributions should be connected to an existing issue. * API changes must go through the [change proposal process](https://go.dev/s/proposal-process) before they can be accepted. diff --git a/vendor/modules.txt b/vendor/modules.txt index 72ac9a2e11..11c90117b5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -247,8 +247,8 @@ github.com/containers/conmon/runner/config # github.com/containers/gvisor-tap-vsock v0.8.0 ## explicit; go 1.22.0 github.com/containers/gvisor-tap-vsock/pkg/types -# github.com/containers/image/v5 v5.33.0 -## explicit; go 1.22.6 +# github.com/containers/image/v5 v5.33.0 => github.com/vrothberg/image/v5 v5.0.0-20241119121158-6481647ad504 +## explicit; go 1.22.8 github.com/containers/image/v5/copy github.com/containers/image/v5/directory github.com/containers/image/v5/directory/explicitfilepath @@ -464,7 +464,6 @@ github.com/distribution/reference ## explicit github.com/docker/distribution/registry/api/errcode github.com/docker/distribution/registry/api/v2 -github.com/docker/distribution/registry/client/auth/challenge # github.com/docker/docker v27.3.1+incompatible ## explicit github.com/docker/docker/api @@ -1011,8 +1010,8 @@ github.com/sigstore/rekor/pkg/generated/client/pubkey github.com/sigstore/rekor/pkg/generated/client/tlog github.com/sigstore/rekor/pkg/generated/models github.com/sigstore/rekor/pkg/util -# github.com/sigstore/sigstore v1.8.9 -## explicit; go 1.22.5 +# github.com/sigstore/sigstore v1.8.10 +## explicit; go 1.22.8 github.com/sigstore/sigstore/pkg/cryptoutils github.com/sigstore/sigstore/pkg/oauth github.com/sigstore/sigstore/pkg/oauthflow @@ -1042,8 +1041,8 @@ github.com/stefanberger/go-pkcs11uri ## explicit; go 1.17 github.com/stretchr/testify/assert github.com/stretchr/testify/require -# github.com/sylabs/sif/v2 v2.19.1 -## explicit; go 1.22.5 +# github.com/sylabs/sif/v2 v2.20.0 +## explicit; go 1.22.8 github.com/sylabs/sif/v2/pkg/sif # github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 ## explicit @@ -1220,7 +1219,7 @@ golang.org/x/net/internal/socks golang.org/x/net/internal/timeseries golang.org/x/net/proxy golang.org/x/net/trace -# golang.org/x/oauth2 v0.23.0 +# golang.org/x/oauth2 v0.24.0 ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal @@ -1391,3 +1390,4 @@ tags.cncf.io/container-device-interface/pkg/parser # tags.cncf.io/container-device-interface/specs-go v0.8.0 ## explicit; go 1.19 tags.cncf.io/container-device-interface/specs-go +# github.com/containers/image/v5 => github.com/vrothberg/image/v5 v5.0.0-20241119121158-6481647ad504