mirror of
https://github.com/containers/podman.git
synced 2025-10-20 20:54:45 +08:00
feat: add Podman artifact support to Go bindings and remote clients
Add the Go bindings implementation necessary to support Artifacts. Implement the tunnel interface that consumes the Artifacts Go bindings. With this patch, users of the Podman remote clients will now be able to manage OCI artifacts via the Podman CLI and Podman machine. Jira: https://issues.redhat.com/browse/RUN-2714# Signed-off-by: Lewis Roy <lewis@redhat.com>
This commit is contained in:
@ -23,20 +23,17 @@ var (
|
||||
Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt
|
||||
podman artifact add --file-type text/yaml quay.io/myimage/myartifact:latest /tmp/foobar.yaml
|
||||
podman artifact add --append quay.io/myimage/myartifact:latest /tmp/foobar.tar.gz`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
|
||||
type artifactAddOptions struct {
|
||||
ArtifactType string
|
||||
Annotations []string
|
||||
Append bool
|
||||
FileType string
|
||||
// AddOptionsWrapper wraps entities.ArtifactsAddOptions and prevents leaking
|
||||
// CLI-only fields into the API types.
|
||||
type AddOptionsWrapper struct {
|
||||
entities.ArtifactAddOptions
|
||||
AnnotationsCLI []string // CLI only
|
||||
}
|
||||
|
||||
var (
|
||||
addOpts artifactAddOptions
|
||||
)
|
||||
var addOpts AddOptionsWrapper
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
@ -46,34 +43,36 @@ func init() {
|
||||
flags := addCmd.Flags()
|
||||
|
||||
annotationFlagName := "annotation"
|
||||
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified files of artifact")
|
||||
flags.StringArrayVar(&addOpts.AnnotationsCLI, annotationFlagName, nil, "set an `annotation` for the specified files of artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone)
|
||||
|
||||
addTypeFlagName := "type"
|
||||
flags.StringVar(&addOpts.ArtifactType, addTypeFlagName, "", "Use type to describe an artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(addTypeFlagName, completion.AutocompleteNone)
|
||||
addMIMETypeFlagName := "type"
|
||||
flags.StringVar(&addOpts.ArtifactMIMEType, addMIMETypeFlagName, "", "Use type to describe an artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(addMIMETypeFlagName, completion.AutocompleteNone)
|
||||
|
||||
appendFlagName := "append"
|
||||
flags.BoolVarP(&addOpts.Append, appendFlagName, "a", false, "Append files to an existing artifact")
|
||||
|
||||
fileTypeFlagName := "file-type"
|
||||
flags.StringVarP(&addOpts.FileType, fileTypeFlagName, "", "", "Set file type to use for the artifact (layer)")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(fileTypeFlagName, completion.AutocompleteNone)
|
||||
fileMIMETypeFlagName := "file-type"
|
||||
flags.StringVarP(&addOpts.FileMIMEType, fileMIMETypeFlagName, "", "", "Set file type to use for the artifact (layer)")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(fileMIMETypeFlagName, completion.AutocompleteNone)
|
||||
}
|
||||
|
||||
func add(cmd *cobra.Command, args []string) error {
|
||||
artifactName := args[0]
|
||||
blobs := args[1:]
|
||||
opts := new(entities.ArtifactAddOptions)
|
||||
|
||||
annots, err := utils.ParseAnnotations(addOpts.Annotations)
|
||||
annots, err := utils.ParseAnnotations(addOpts.AnnotationsCLI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Annotations = annots
|
||||
opts.ArtifactType = addOpts.ArtifactType
|
||||
opts.Append = addOpts.Append
|
||||
opts.FileType = addOpts.FileType
|
||||
|
||||
opts := entities.ArtifactAddOptions{
|
||||
Annotations: annots,
|
||||
ArtifactMIMEType: addOpts.ArtifactMIMEType,
|
||||
Append: addOpts.Append,
|
||||
FileMIMEType: addOpts.FileMIMEType,
|
||||
}
|
||||
|
||||
artifactBlobs := make([]entities.ArtifactBlob, 0, len(blobs))
|
||||
|
||||
|
@ -6,16 +6,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// Command: podman _artifact_
|
||||
artifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Manage OCI artifacts",
|
||||
Long: "Manage OCI artifacts",
|
||||
RunE: validate.SubCommandExists,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
// Command: podman _artifact_
|
||||
var artifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Manage OCI artifacts",
|
||||
Long: "Manage OCI artifacts",
|
||||
RunE: validate.SubCommandExists,
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
|
@ -18,7 +18,6 @@ var (
|
||||
ValidArgsFunction: common.AutocompleteArtifactAdd,
|
||||
Example: `podman artifact Extract quay.io/myimage/myartifact:latest /tmp/foobar.txt
|
||||
podman artifact Extract quay.io/myimage/myartifact:latest /home/paul/mydir`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
|
||||
@ -43,7 +42,7 @@ func init() {
|
||||
}
|
||||
|
||||
func extract(cmd *cobra.Command, args []string) error {
|
||||
err := registry.ImageEngine().ArtifactExtract(registry.Context(), args[0], args[1], &extractOpts)
|
||||
err := registry.ImageEngine().ArtifactExtract(registry.Context(), args[0], args[1], extractOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ var (
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: common.AutocompleteArtifacts,
|
||||
Example: `podman artifact inspect quay.io/myimage/myartifact:latest`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,6 @@ var (
|
||||
Args: validate.NoArgs,
|
||||
ValidArgsFunction: completion.AutocompleteNone,
|
||||
Example: `podman artifact ls`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
listFlag = listFlagType{}
|
||||
)
|
||||
|
@ -36,7 +36,6 @@ var (
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: common.AutocompleteArtifacts,
|
||||
Example: `podman artifact pull quay.io/myimage/myartifact:latest`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -40,7 +40,6 @@ var (
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: common.AutocompleteArtifacts,
|
||||
Example: `podman artifact push quay.io/myimage/myartifact:latest`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -23,7 +23,6 @@ var (
|
||||
ValidArgsFunction: common.AutocompleteArtifacts,
|
||||
Example: `podman artifact rm quay.io/myimage/myartifact:latest
|
||||
podman artifact rm -a`,
|
||||
Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
|
||||
}
|
||||
|
||||
rmOptions = entities.ArtifactRemoveOptions{}
|
||||
|
@ -127,12 +127,12 @@ func PullArtifact(w http.ResponseWriter, r *http.Request) {
|
||||
rc := errcd.ErrorCode().Descriptor().HTTPStatusCode
|
||||
// Check if the returned error is 401 StatusUnauthorized indicating the request was unauthorized
|
||||
if rc == http.StatusUnauthorized {
|
||||
utils.Error(w, http.StatusUnauthorized, errcd.ErrorCode())
|
||||
utils.Error(w, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
// Check if the returned error is 404 StatusNotFound indicating the artifact was not found
|
||||
if rc == http.StatusNotFound {
|
||||
utils.Error(w, http.StatusNotFound, errcd.ErrorCode())
|
||||
utils.Error(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -191,11 +191,11 @@ func AddArtifact(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
artifactAddOptions := &entities.ArtifactAddOptions{
|
||||
Append: query.Append,
|
||||
Annotations: annotations,
|
||||
ArtifactType: query.ArtifactMIMEType,
|
||||
FileType: query.FileMIMEType,
|
||||
artifactAddOptions := entities.ArtifactAddOptions{
|
||||
Append: query.Append,
|
||||
Annotations: annotations,
|
||||
ArtifactMIMEType: query.ArtifactMIMEType,
|
||||
FileMIMEType: query.FileMIMEType,
|
||||
}
|
||||
|
||||
artifactBlobs := []entities.ArtifactBlob{{
|
||||
@ -283,7 +283,7 @@ func PushArtifact(w http.ResponseWriter, r *http.Request) {
|
||||
rc := errcd.ErrorCode().Descriptor().HTTPStatusCode
|
||||
// Check if the returned error is 401 indicating the request was unauthorized
|
||||
if rc == 401 {
|
||||
utils.Error(w, 401, errcd.ErrorCode())
|
||||
utils.Error(w, 401, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -306,8 +306,9 @@ func ExtractArtifact(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
|
||||
query := struct {
|
||||
Digest string `schema:"digest"`
|
||||
Title string `schema:"title"`
|
||||
Digest string `schema:"digest"`
|
||||
Title string `schema:"title"`
|
||||
ExcludeTitle bool `schema:"excludetitle"`
|
||||
}{}
|
||||
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
@ -316,15 +317,16 @@ func ExtractArtifact(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
extractOpts := entities.ArtifactExtractOptions{
|
||||
Title: query.Title,
|
||||
Digest: query.Digest,
|
||||
Title: query.Title,
|
||||
Digest: query.Digest,
|
||||
ExcludeTitle: query.ExcludeTitle,
|
||||
}
|
||||
|
||||
name := utils.GetName(r)
|
||||
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
|
||||
err := imageEngine.ArtifactExtractTarStream(r.Context(), w, name, &extractOpts)
|
||||
err := imageEngine.ArtifactExtractTarStream(r.Context(), w, name, extractOpts)
|
||||
if err != nil {
|
||||
if errors.Is(err, libartifact_types.ErrArtifactNotExist) {
|
||||
utils.ArtifactNotFound(w, name, err)
|
||||
|
@ -242,9 +242,15 @@ func (s *APIServer) registerArtifactHandlers(r *mux.Router) error {
|
||||
// in: query
|
||||
// description: Only extract blob with the given digest
|
||||
// type: string
|
||||
// - name: excludeTitle
|
||||
// in: query
|
||||
// description: When extracting a single Artifact blob, don't use the blob title as the filename in the tar
|
||||
// type: boolean
|
||||
// responses:
|
||||
// 200:
|
||||
// description: Extract successful
|
||||
// schema:
|
||||
// type: file
|
||||
// 400:
|
||||
// $ref: "#/responses/badParamError"
|
||||
// 404:
|
||||
|
42
pkg/bindings/artifacts/add.go
Normal file
42
pkg/bindings/artifacts/add.go
Normal file
@ -0,0 +1,42 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||
)
|
||||
|
||||
func Add(ctx context.Context, artifactName string, blobName string, artifactBlob io.Reader, options *AddOptions) (*entitiesTypes.ArtifactAddReport, error) {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options == nil {
|
||||
options = new(AddOptions)
|
||||
}
|
||||
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.Set("name", artifactName)
|
||||
params.Set("fileName", blobName)
|
||||
|
||||
response, err := conn.DoRequest(ctx, artifactBlob, http.MethodPost, "/artifacts/add", params, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var artifactAddReport entitiesTypes.ArtifactAddReport
|
||||
if err := response.Process(&artifactAddReport); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &artifactAddReport, nil
|
||||
}
|
113
pkg/bindings/artifacts/extract.go
Normal file
113
pkg/bindings/artifacts/extract.go
Normal file
@ -0,0 +1,113 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
)
|
||||
|
||||
func Extract(ctx context.Context, artifactName string, target string, options *ExtractOptions) error {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting client: %w", err)
|
||||
}
|
||||
|
||||
if options == nil {
|
||||
options = new(ExtractOptions)
|
||||
}
|
||||
|
||||
// Check if target is a directory to know if we can copy more than one blob
|
||||
targetIsDirectory := false
|
||||
stat, err := os.Stat(target)
|
||||
if err == nil {
|
||||
targetIsDirectory = stat.IsDir()
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("stat target %q failed: %w", target, err)
|
||||
}
|
||||
|
||||
// If the target is not a directory, request API to return the blob without title.
|
||||
// If a blob has a malicious title it will be returned from the API without it
|
||||
// as the file will be written to the provided target
|
||||
if !targetIsDirectory {
|
||||
options.WithExcludeTitle(true)
|
||||
}
|
||||
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return fmt.Errorf("converting options to params: %w", err)
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/artifacts/%s/extract", params, nil, artifactName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if !response.IsSuccess() {
|
||||
return response.Process(nil)
|
||||
}
|
||||
|
||||
multipleBlobs := false
|
||||
tr := tar.NewReader(response.Body)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break // End of archive
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !targetIsDirectory && multipleBlobs {
|
||||
return fmt.Errorf("the artifact consists of several blobs and the target %q is not a directory and neither digest or title was specified to only copy a single blob", target)
|
||||
}
|
||||
|
||||
// If destination isn't a file, extract to target/filename
|
||||
fileTarget := target
|
||||
if targetIsDirectory {
|
||||
fileTarget = filepath.Join(target, header.Name)
|
||||
}
|
||||
|
||||
if header.Typeflag == tar.TypeReg {
|
||||
err = extractFile(tr, fileTarget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Signal that the first blob has been extracted so we can return an error if more
|
||||
// than one blob are being extracted when target is not a directory.
|
||||
multipleBlobs = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFile(tr *tar.Reader, fileTarget string) (retErr error) {
|
||||
outFile, err := os.Create(fileTarget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use an anonymous function to enable capturing the error from
|
||||
// outFile.Close() upon returning.
|
||||
defer func() {
|
||||
cErr := outFile.Close()
|
||||
if retErr == nil {
|
||||
retErr = cErr
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(outFile, tr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract blob to %s: %w", fileTarget, err)
|
||||
}
|
||||
return nil
|
||||
}
|
29
pkg/bindings/artifacts/inspect.go
Normal file
29
pkg/bindings/artifacts/inspect.go
Normal file
@ -0,0 +1,29 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||
)
|
||||
|
||||
func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*types.ArtifactInspectReport, error) {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/artifacts/%s/json", nil, nil, nameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var inspectedData types.ArtifactInspectReport
|
||||
if err := response.Process(&inspectedData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &inspectedData, nil
|
||||
}
|
30
pkg/bindings/artifacts/list.go
Normal file
30
pkg/bindings/artifacts/list.go
Normal file
@ -0,0 +1,30 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
)
|
||||
|
||||
// List returns a list of artifacts in local storage.
|
||||
func List(ctx context.Context, options *ListOptions) ([]*entities.ArtifactListReport, error) {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/artifacts/json", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var artifactSummary []*entities.ArtifactListReport
|
||||
if err := response.Process(&artifactSummary); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return artifactSummary, nil
|
||||
}
|
52
pkg/bindings/artifacts/pull.go
Normal file
52
pkg/bindings/artifacts/pull.go
Normal file
@ -0,0 +1,52 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
imageTypes "github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v5/pkg/auth"
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
)
|
||||
|
||||
func Pull(ctx context.Context, name string, options *PullOptions) (*entities.ArtifactPullReport, error) {
|
||||
if options == nil {
|
||||
options = new(PullOptions)
|
||||
}
|
||||
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Set("name", name)
|
||||
|
||||
header, err := auth.MakeXRegistryAuthHeader(
|
||||
&imageTypes.SystemContext{
|
||||
AuthFilePath: options.GetAuthfile(),
|
||||
},
|
||||
options.GetUsername(),
|
||||
options.GetPassword(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/artifacts/pull", params, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var report entities.ArtifactPullReport
|
||||
if err := response.Process(&report); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &report, nil
|
||||
}
|
45
pkg/bindings/artifacts/push.go
Normal file
45
pkg/bindings/artifacts/push.go
Normal file
@ -0,0 +1,45 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
imageTypes "github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v5/pkg/auth"
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
)
|
||||
|
||||
func Push(ctx context.Context, name string, options *PushOptions) (*entities.ArtifactPushReport, error) {
|
||||
if options == nil {
|
||||
options = new(PushOptions)
|
||||
}
|
||||
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/artifacts/%s/push", params, header, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var report entities.ArtifactPushReport
|
||||
if err := response.Process(&report); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &report, nil
|
||||
}
|
30
pkg/bindings/artifacts/remove.go
Normal file
30
pkg/bindings/artifacts/remove.go
Normal file
@ -0,0 +1,30 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
)
|
||||
|
||||
// Remove removes an artifact from local storage.
|
||||
func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ArtifactRemoveReport, error) {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/artifacts/%s", nil, nil, nameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var artifactRemoveReport entities.ArtifactRemoveReport
|
||||
if err := response.Process(&artifactRemoveReport); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &artifactRemoveReport, nil
|
||||
}
|
89
pkg/bindings/artifacts/types.go
Normal file
89
pkg/bindings/artifacts/types.go
Normal file
@ -0,0 +1,89 @@
|
||||
package artifacts
|
||||
|
||||
import "io"
|
||||
|
||||
// PullOptions are optional options for pulling images
|
||||
//
|
||||
//go:generate go run ../generator/generator.go PullOptions
|
||||
type PullOptions struct {
|
||||
// Authfile is the path to the authentication file.
|
||||
Authfile *string `schema:"-"`
|
||||
// Password for authenticating against the registry.
|
||||
Password *string `schema:"-"`
|
||||
// ProgressWriter is a writer where pull progress are sent.
|
||||
ProgressWriter *io.Writer `schema:"-"`
|
||||
// Quiet can be specified to suppress pull progress when pulling.
|
||||
Quiet *bool
|
||||
// Retry number of times to retry pull in case of failure
|
||||
Retry *uint
|
||||
// RetryDelay between retries in case of pull failures
|
||||
RetryDelay *string
|
||||
// SkipTLSVerify to skip HTTPS and certificate verification.
|
||||
TlsVerify *bool
|
||||
// Username for authenticating against the registry.
|
||||
Username *string `schema:"-"`
|
||||
}
|
||||
|
||||
// PushOptions are optional options for pushing images
|
||||
//
|
||||
//go:generate go run ../generator/generator.go PushOptions
|
||||
type PushOptions struct {
|
||||
// Authfile is the path to the authentication file.
|
||||
Authfile *string `schema:"-"`
|
||||
// Password for authenticating against the registry.
|
||||
Password *string `schema:"-"`
|
||||
// Quiet can be specified to suppress pull progress when pulling.
|
||||
Quiet *bool
|
||||
// Retry number of times to retry pull in case of failure
|
||||
Retry *uint
|
||||
// RetryDelay between retries in case of pull failures
|
||||
RetryDelay *string
|
||||
// SkipTLSVerify to skip HTTPS and certificate verification.
|
||||
TlsVerify *bool
|
||||
// Username for authenticating against the registry.
|
||||
Username *string `schema:"-"`
|
||||
}
|
||||
|
||||
// RemoveOptions are optional options for removing images
|
||||
//
|
||||
//go:generate go run ../generator/generator.go RemoveOptions
|
||||
type RemoveOptions struct {
|
||||
// Remove all artifacts
|
||||
All *bool
|
||||
}
|
||||
|
||||
// AddOptions are optional options for removing images
|
||||
//
|
||||
//go:generate go run ../generator/generator.go AddOptions
|
||||
type AddOptions struct {
|
||||
Annotations []string
|
||||
ArtifactMIMEType *string
|
||||
Append *bool
|
||||
FileMIMEType *string
|
||||
}
|
||||
|
||||
// ExtractOptions
|
||||
//
|
||||
//go:generate go run ../generator/generator.go ExtractOptions
|
||||
type ExtractOptions struct {
|
||||
// Title annotation value to extract only a single blob matching that name.
|
||||
// Conflicts with Digest. Optional.
|
||||
Title *string
|
||||
// Digest of the blob to extract.
|
||||
// Conflicts with Title. Optional.
|
||||
Digest *string
|
||||
// ExcludeTitle option allows single blobs to be exported
|
||||
// with their title/filename empty. Optional.
|
||||
// Default: False
|
||||
ExcludeTitle *bool
|
||||
}
|
||||
|
||||
// ListOptions
|
||||
//
|
||||
//go:generate go run ../generator/generator.go ListOptions
|
||||
type ListOptions struct{}
|
||||
|
||||
// InspectOptions
|
||||
//
|
||||
//go:generate go run ../generator/generator.go InspectOptions
|
||||
type InspectOptions struct{}
|
78
pkg/bindings/artifacts/types_add_options.go
Normal file
78
pkg/bindings/artifacts/types_add_options.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *AddOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *AddOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithAnnotations set field Annotations to given value
|
||||
func (o *AddOptions) WithAnnotations(value []string) *AddOptions {
|
||||
o.Annotations = value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAnnotations returns value of field Annotations
|
||||
func (o *AddOptions) GetAnnotations() []string {
|
||||
if o.Annotations == nil {
|
||||
var z []string
|
||||
return z
|
||||
}
|
||||
return o.Annotations
|
||||
}
|
||||
|
||||
// WithArtifactMIMEType set field ArtifactMIMEType to given value
|
||||
func (o *AddOptions) WithArtifactMIMEType(value string) *AddOptions {
|
||||
o.ArtifactMIMEType = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetArtifactMIMEType returns value of field ArtifactMIMEType
|
||||
func (o *AddOptions) GetArtifactMIMEType() string {
|
||||
if o.ArtifactMIMEType == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.ArtifactMIMEType
|
||||
}
|
||||
|
||||
// WithAppend set field Append to given value
|
||||
func (o *AddOptions) WithAppend(value bool) *AddOptions {
|
||||
o.Append = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAppend returns value of field Append
|
||||
func (o *AddOptions) GetAppend() bool {
|
||||
if o.Append == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.Append
|
||||
}
|
||||
|
||||
// WithFileMIMEType set field FileMIMEType to given value
|
||||
func (o *AddOptions) WithFileMIMEType(value string) *AddOptions {
|
||||
o.FileMIMEType = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetFileMIMEType returns value of field FileMIMEType
|
||||
func (o *AddOptions) GetFileMIMEType() string {
|
||||
if o.FileMIMEType == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.FileMIMEType
|
||||
}
|
63
pkg/bindings/artifacts/types_extract_options.go
Normal file
63
pkg/bindings/artifacts/types_extract_options.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *ExtractOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *ExtractOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithTitle set field Title to given value
|
||||
func (o *ExtractOptions) WithTitle(value string) *ExtractOptions {
|
||||
o.Title = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetTitle returns value of field Title
|
||||
func (o *ExtractOptions) GetTitle() string {
|
||||
if o.Title == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Title
|
||||
}
|
||||
|
||||
// WithDigest set field Digest to given value
|
||||
func (o *ExtractOptions) WithDigest(value string) *ExtractOptions {
|
||||
o.Digest = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetDigest returns value of field Digest
|
||||
func (o *ExtractOptions) GetDigest() string {
|
||||
if o.Digest == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Digest
|
||||
}
|
||||
|
||||
// WithExcludeTitle set field ExcludeTitle to given value
|
||||
func (o *ExtractOptions) WithExcludeTitle(value bool) *ExtractOptions {
|
||||
o.ExcludeTitle = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetExcludeTitle returns value of field ExcludeTitle
|
||||
func (o *ExtractOptions) GetExcludeTitle() bool {
|
||||
if o.ExcludeTitle == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.ExcludeTitle
|
||||
}
|
18
pkg/bindings/artifacts/types_inspect_options.go
Normal file
18
pkg/bindings/artifacts/types_inspect_options.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *InspectOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *InspectOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
18
pkg/bindings/artifacts/types_list_options.go
Normal file
18
pkg/bindings/artifacts/types_list_options.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *ListOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *ListOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
139
pkg/bindings/artifacts/types_pull_options.go
Normal file
139
pkg/bindings/artifacts/types_pull_options.go
Normal file
@ -0,0 +1,139 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *PullOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *PullOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithAuthfile set field Authfile to given value
|
||||
func (o *PullOptions) WithAuthfile(value string) *PullOptions {
|
||||
o.Authfile = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAuthfile returns value of field Authfile
|
||||
func (o *PullOptions) GetAuthfile() string {
|
||||
if o.Authfile == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Authfile
|
||||
}
|
||||
|
||||
// WithPassword set field Password to given value
|
||||
func (o *PullOptions) WithPassword(value string) *PullOptions {
|
||||
o.Password = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetPassword returns value of field Password
|
||||
func (o *PullOptions) GetPassword() string {
|
||||
if o.Password == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Password
|
||||
}
|
||||
|
||||
// WithProgressWriter set field ProgressWriter to given value
|
||||
func (o *PullOptions) WithProgressWriter(value io.Writer) *PullOptions {
|
||||
o.ProgressWriter = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetProgressWriter returns value of field ProgressWriter
|
||||
func (o *PullOptions) GetProgressWriter() io.Writer {
|
||||
if o.ProgressWriter == nil {
|
||||
var z io.Writer
|
||||
return z
|
||||
}
|
||||
return *o.ProgressWriter
|
||||
}
|
||||
|
||||
// WithQuiet set field Quiet to given value
|
||||
func (o *PullOptions) WithQuiet(value bool) *PullOptions {
|
||||
o.Quiet = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetQuiet returns value of field Quiet
|
||||
func (o *PullOptions) GetQuiet() bool {
|
||||
if o.Quiet == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.Quiet
|
||||
}
|
||||
|
||||
// WithRetry set field Retry to given value
|
||||
func (o *PullOptions) WithRetry(value uint) *PullOptions {
|
||||
o.Retry = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetRetry returns value of field Retry
|
||||
func (o *PullOptions) GetRetry() uint {
|
||||
if o.Retry == nil {
|
||||
var z uint
|
||||
return z
|
||||
}
|
||||
return *o.Retry
|
||||
}
|
||||
|
||||
// WithRetryDelay set field RetryDelay to given value
|
||||
func (o *PullOptions) WithRetryDelay(value string) *PullOptions {
|
||||
o.RetryDelay = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetRetryDelay returns value of field RetryDelay
|
||||
func (o *PullOptions) GetRetryDelay() string {
|
||||
if o.RetryDelay == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.RetryDelay
|
||||
}
|
||||
|
||||
// WithTlsVerify set field TlsVerify to given value
|
||||
func (o *PullOptions) WithTlsVerify(value bool) *PullOptions {
|
||||
o.TlsVerify = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetTlsVerify returns value of field TlsVerify
|
||||
func (o *PullOptions) GetTlsVerify() bool {
|
||||
if o.TlsVerify == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.TlsVerify
|
||||
}
|
||||
|
||||
// WithUsername set field Username to given value
|
||||
func (o *PullOptions) WithUsername(value string) *PullOptions {
|
||||
o.Username = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetUsername returns value of field Username
|
||||
func (o *PullOptions) GetUsername() string {
|
||||
if o.Username == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Username
|
||||
}
|
123
pkg/bindings/artifacts/types_push_options.go
Normal file
123
pkg/bindings/artifacts/types_push_options.go
Normal file
@ -0,0 +1,123 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *PushOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *PushOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithAuthfile set field Authfile to given value
|
||||
func (o *PushOptions) WithAuthfile(value string) *PushOptions {
|
||||
o.Authfile = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAuthfile returns value of field Authfile
|
||||
func (o *PushOptions) GetAuthfile() string {
|
||||
if o.Authfile == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Authfile
|
||||
}
|
||||
|
||||
// WithPassword set field Password to given value
|
||||
func (o *PushOptions) WithPassword(value string) *PushOptions {
|
||||
o.Password = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetPassword returns value of field Password
|
||||
func (o *PushOptions) GetPassword() string {
|
||||
if o.Password == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Password
|
||||
}
|
||||
|
||||
// WithQuiet set field Quiet to given value
|
||||
func (o *PushOptions) WithQuiet(value bool) *PushOptions {
|
||||
o.Quiet = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetQuiet returns value of field Quiet
|
||||
func (o *PushOptions) GetQuiet() bool {
|
||||
if o.Quiet == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.Quiet
|
||||
}
|
||||
|
||||
// WithRetry set field Retry to given value
|
||||
func (o *PushOptions) WithRetry(value uint) *PushOptions {
|
||||
o.Retry = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetRetry returns value of field Retry
|
||||
func (o *PushOptions) GetRetry() uint {
|
||||
if o.Retry == nil {
|
||||
var z uint
|
||||
return z
|
||||
}
|
||||
return *o.Retry
|
||||
}
|
||||
|
||||
// WithRetryDelay set field RetryDelay to given value
|
||||
func (o *PushOptions) WithRetryDelay(value string) *PushOptions {
|
||||
o.RetryDelay = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetRetryDelay returns value of field RetryDelay
|
||||
func (o *PushOptions) GetRetryDelay() string {
|
||||
if o.RetryDelay == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.RetryDelay
|
||||
}
|
||||
|
||||
// WithTlsVerify set field TlsVerify to given value
|
||||
func (o *PushOptions) WithTlsVerify(value bool) *PushOptions {
|
||||
o.TlsVerify = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetTlsVerify returns value of field TlsVerify
|
||||
func (o *PushOptions) GetTlsVerify() bool {
|
||||
if o.TlsVerify == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.TlsVerify
|
||||
}
|
||||
|
||||
// WithUsername set field Username to given value
|
||||
func (o *PushOptions) WithUsername(value string) *PushOptions {
|
||||
o.Username = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetUsername returns value of field Username
|
||||
func (o *PushOptions) GetUsername() string {
|
||||
if o.Username == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Username
|
||||
}
|
33
pkg/bindings/artifacts/types_remove_options.go
Normal file
33
pkg/bindings/artifacts/types_remove_options.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *RemoveOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *RemoveOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithAll set field All to given value
|
||||
func (o *RemoveOptions) WithAll(value bool) *RemoveOptions {
|
||||
o.All = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAll returns value of field All
|
||||
func (o *RemoveOptions) GetAll() bool {
|
||||
if o.All == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.All
|
||||
}
|
@ -5,18 +5,18 @@ import (
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
entityTypes "github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||
"github.com/containers/podman/v5/pkg/libartifact"
|
||||
"github.com/opencontainers/go-digest"
|
||||
entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||
)
|
||||
|
||||
type ArtifactAddOptions struct {
|
||||
Annotations map[string]string
|
||||
ArtifactType string
|
||||
Append bool
|
||||
FileType string
|
||||
Annotations map[string]string
|
||||
ArtifactMIMEType string
|
||||
Append bool
|
||||
FileMIMEType string
|
||||
}
|
||||
|
||||
type ArtifactAddReport = entitiesTypes.ArtifactAddReport
|
||||
|
||||
type ArtifactExtractOptions struct {
|
||||
// Title annotation value to extract only a single blob matching that name.
|
||||
// Conflicts with Digest. Optional.
|
||||
@ -24,13 +24,13 @@ type ArtifactExtractOptions struct {
|
||||
// Digest of the blob to extract.
|
||||
// Conflicts with Title. Optional.
|
||||
Digest string
|
||||
// ExcludeTitle option allows single blobs to be exported
|
||||
// with their title/filename empty. Optional.
|
||||
// Default: False
|
||||
ExcludeTitle bool
|
||||
}
|
||||
|
||||
type ArtifactBlob struct {
|
||||
BlobReader io.Reader
|
||||
BlobFilePath string
|
||||
FileName string
|
||||
}
|
||||
type ArtifactBlob = entitiesTypes.ArtifactBlob
|
||||
|
||||
type ArtifactInspectOptions struct {
|
||||
// Note: Remote is not currently implemented but will be used for
|
||||
@ -38,8 +38,9 @@ type ArtifactInspectOptions struct {
|
||||
Remote bool
|
||||
}
|
||||
|
||||
type ArtifactListOptions struct {
|
||||
}
|
||||
type ArtifactListOptions struct{}
|
||||
|
||||
type ArtifactListReport = entitiesTypes.ArtifactListReport
|
||||
|
||||
type ArtifactPullOptions struct {
|
||||
// containers-auth.json(5) file to use when authenticating against
|
||||
@ -79,6 +80,8 @@ type ArtifactPullOptions struct {
|
||||
IdentityToken string `json:"identitytoken,omitempty"`
|
||||
}
|
||||
|
||||
type ArtifactPullReport = entitiesTypes.ArtifactPullReport
|
||||
|
||||
type ArtifactPushOptions struct {
|
||||
ImagePushOptions
|
||||
CredentialsCLI string
|
||||
@ -90,29 +93,13 @@ type ArtifactPushOptions struct {
|
||||
TLSVerifyCLI bool // CLI only
|
||||
}
|
||||
|
||||
type ArtifactPushReport = entitiesTypes.ArtifactPushReport
|
||||
|
||||
type ArtifactRemoveOptions struct {
|
||||
// Remove all artifacts
|
||||
All bool
|
||||
}
|
||||
|
||||
type ArtifactPullReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
type ArtifactRemoveReport = entitiesTypes.ArtifactRemoveReport
|
||||
|
||||
type ArtifactPushReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
|
||||
type ArtifactInspectReport = entityTypes.ArtifactInspectReport
|
||||
|
||||
type ArtifactListReport struct {
|
||||
*libartifact.Artifact
|
||||
}
|
||||
|
||||
type ArtifactAddReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
|
||||
type ArtifactRemoveReport struct {
|
||||
ArtifactDigests []*digest.Digest
|
||||
}
|
||||
type ArtifactInspectReport = entitiesTypes.ArtifactInspectReport
|
||||
|
@ -10,9 +10,9 @@ import (
|
||||
)
|
||||
|
||||
type ImageEngine interface { //nolint:interfacebloat
|
||||
ArtifactAdd(ctx context.Context, name string, artifactBlobs []ArtifactBlob, opts *ArtifactAddOptions) (*ArtifactAddReport, error)
|
||||
ArtifactExtract(ctx context.Context, name string, target string, opts *ArtifactExtractOptions) error
|
||||
ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts *ArtifactExtractOptions) error
|
||||
ArtifactAdd(ctx context.Context, name string, artifactBlobs []ArtifactBlob, opts ArtifactAddOptions) (*ArtifactAddReport, error)
|
||||
ArtifactExtract(ctx context.Context, name string, target string, opts ArtifactExtractOptions) error
|
||||
ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts ArtifactExtractOptions) 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)
|
||||
|
@ -1,8 +1,39 @@
|
||||
package types
|
||||
|
||||
import "github.com/containers/podman/v5/pkg/libartifact"
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/libartifact"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type ArtifactInspectReport struct {
|
||||
*libartifact.Artifact
|
||||
Digest string
|
||||
}
|
||||
|
||||
type ArtifactBlob struct {
|
||||
BlobReader io.Reader
|
||||
BlobFilePath string
|
||||
FileName string
|
||||
}
|
||||
|
||||
type ArtifactAddReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
|
||||
type ArtifactRemoveReport struct {
|
||||
ArtifactDigests []*digest.Digest
|
||||
}
|
||||
|
||||
type ArtifactListReport struct {
|
||||
*libartifact.Artifact
|
||||
}
|
||||
|
||||
type ArtifactPushReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
|
||||
type ArtifactPullReport struct {
|
||||
ArtifactDigest *digest.Digest
|
||||
}
|
||||
|
@ -197,21 +197,17 @@ func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entit
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, artifactBlobs []entities.ArtifactBlob, opts *entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, artifactBlobs []entities.ArtifactBlob, opts entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
artStore, err := ir.Libpod.ArtifactStore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.Annotations == nil {
|
||||
opts.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
addOptions := types.AddOptions{
|
||||
Annotations: opts.Annotations,
|
||||
ArtifactType: opts.ArtifactType,
|
||||
Append: opts.Append,
|
||||
FileType: opts.FileType,
|
||||
Annotations: opts.Annotations,
|
||||
ArtifactMIMEType: opts.ArtifactMIMEType,
|
||||
Append: opts.Append,
|
||||
FileMIMEType: opts.FileMIMEType,
|
||||
}
|
||||
|
||||
artifactDigest, err := artStore.Add(ctx, name, artifactBlobs, &addOptions)
|
||||
@ -223,35 +219,33 @@ func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, artifactBlo
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactExtract(ctx context.Context, name string, target string, opts *entities.ArtifactExtractOptions) error {
|
||||
func (ir *ImageEngine) ArtifactExtract(ctx context.Context, name string, target string, opts entities.ArtifactExtractOptions) error {
|
||||
artStore, err := ir.Libpod.ArtifactStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extractOpt := &types.ExtractOptions{
|
||||
extractOpt := types.ExtractOptions{
|
||||
FilterBlobOptions: types.FilterBlobOptions{
|
||||
Digest: opts.Digest,
|
||||
Title: opts.Title,
|
||||
},
|
||||
}
|
||||
|
||||
return artStore.Extract(ctx, name, target, extractOpt)
|
||||
return artStore.Extract(ctx, name, target, &extractOpt)
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts *entities.ArtifactExtractOptions) error {
|
||||
if opts == nil {
|
||||
opts = &entities.ArtifactExtractOptions{}
|
||||
}
|
||||
func (ir *ImageEngine) ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts entities.ArtifactExtractOptions) error {
|
||||
artStore, err := ir.Libpod.ArtifactStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extractOpt := &types.ExtractOptions{
|
||||
extractOpt := types.ExtractOptions{
|
||||
FilterBlobOptions: types.FilterBlobOptions{
|
||||
Digest: opts.Digest,
|
||||
Title: opts.Title,
|
||||
},
|
||||
ExcludeTitle: opts.ExcludeTitle,
|
||||
}
|
||||
|
||||
return artStore.ExtractTarStream(ctx, w, name, extractOpt)
|
||||
return artStore.ExtractTarStream(ctx, w, name, &extractOpt)
|
||||
}
|
||||
|
@ -2,42 +2,119 @@ package tunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v5/pkg/bindings/artifacts"
|
||||
"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 (ir *ImageEngine) ArtifactExtract(_ context.Context, name string, target string, opts entities.ArtifactExtractOptions) error {
|
||||
options := artifacts.ExtractOptions{
|
||||
Digest: &opts.Digest,
|
||||
Title: &opts.Title,
|
||||
ExcludeTitle: &opts.ExcludeTitle,
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactExtract(ctx context.Context, name string, target string, opts *entities.ArtifactExtractOptions) error {
|
||||
return artifacts.Extract(ir.ClientCtx, name, target, &options)
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactExtractTarStream(_ context.Context, w io.Writer, name string, opts entities.ArtifactExtractOptions) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts *entities.ArtifactExtractOptions) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
func (ir *ImageEngine) ArtifactInspect(_ context.Context, name string, opts entities.ArtifactInspectOptions) (*entities.ArtifactInspectReport, error) {
|
||||
return artifacts.Inspect(ir.ClientCtx, name, &artifacts.InspectOptions{})
|
||||
}
|
||||
|
||||
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(_ context.Context, opts entities.ArtifactListOptions) ([]*entities.ArtifactListReport, error) {
|
||||
return artifacts.List(ir.ClientCtx, &artifacts.ListOptions{})
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactList(ctx context.Context, opts entities.ArtifactListOptions) ([]*entities.ArtifactListReport, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
func (ir *ImageEngine) ArtifactPull(_ context.Context, name string, opts entities.ArtifactPullOptions) (*entities.ArtifactPullReport, error) {
|
||||
options := artifacts.PullOptions{
|
||||
Username: &opts.Username,
|
||||
Password: &opts.Password,
|
||||
Quiet: &opts.Quiet,
|
||||
RetryDelay: &opts.RetryDelay,
|
||||
Retry: opts.MaxRetries,
|
||||
}
|
||||
|
||||
switch opts.InsecureSkipTLSVerify {
|
||||
case types.OptionalBoolTrue:
|
||||
options.WithTlsVerify(false)
|
||||
case types.OptionalBoolFalse:
|
||||
options.WithTlsVerify(true)
|
||||
}
|
||||
|
||||
return artifacts.Pull(ir.ClientCtx, name, &options)
|
||||
}
|
||||
|
||||
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(_ context.Context, name string, opts entities.ArtifactRemoveOptions) (*entities.ArtifactRemoveReport, error) {
|
||||
if opts.All {
|
||||
// Note: This will be added when artifacts remove all endpoint is implemented
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
return artifacts.Remove(ir.ClientCtx, name, &artifacts.RemoveOptions{})
|
||||
}
|
||||
|
||||
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(_ context.Context, name string, opts entities.ArtifactPushOptions) (*entities.ArtifactPushReport, error) {
|
||||
options := artifacts.PushOptions{
|
||||
Username: &opts.Username,
|
||||
Password: &opts.Password,
|
||||
Quiet: &opts.Quiet,
|
||||
RetryDelay: &opts.RetryDelay,
|
||||
Retry: opts.Retry,
|
||||
}
|
||||
|
||||
switch opts.SkipTLSVerify {
|
||||
case types.OptionalBoolTrue:
|
||||
options.WithTlsVerify(false)
|
||||
case types.OptionalBoolFalse:
|
||||
options.WithTlsVerify(true)
|
||||
}
|
||||
|
||||
return artifacts.Push(ir.ClientCtx, name, &options)
|
||||
}
|
||||
|
||||
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(_ context.Context, name string, artifactBlob []entities.ArtifactBlob, opts entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
var artifactAddReport *entities.ArtifactAddReport
|
||||
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, artifactBlob []entities.ArtifactBlob, opts *entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
options := artifacts.AddOptions{
|
||||
Append: &opts.Append,
|
||||
ArtifactMIMEType: &opts.ArtifactMIMEType,
|
||||
FileMIMEType: &opts.FileMIMEType,
|
||||
}
|
||||
|
||||
for k, v := range opts.Annotations {
|
||||
options.Annotations = append(options.Annotations, k+"="+v)
|
||||
}
|
||||
|
||||
for i, blob := range artifactBlob {
|
||||
if i > 0 {
|
||||
// When adding more than 1 blob, set append true after the first
|
||||
options.WithAppend(true)
|
||||
}
|
||||
f, err := os.Open(blob.BlobFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
artifactAddReport, err = artifacts.Add(ir.ClientCtx, name, blob.FileName, f, &options)
|
||||
if err != nil && i > 0 {
|
||||
_, recoverErr := artifacts.Remove(ir.ClientCtx, name, &artifacts.RemoveOptions{})
|
||||
if recoverErr != nil {
|
||||
return nil, fmt.Errorf("failed to cleanup unfinished artifact add: %w", errors.Join(err, recoverErr))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return artifactAddReport, nil
|
||||
}
|
||||
|
@ -217,8 +217,8 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []en
|
||||
return nil, ErrEmptyArtifactName
|
||||
}
|
||||
|
||||
if options.Append && len(options.ArtifactType) > 0 {
|
||||
return nil, errors.New("append option is not compatible with ArtifactType option")
|
||||
if options.Append && len(options.ArtifactMIMEType) > 0 {
|
||||
return nil, errors.New("append option is not compatible with type option")
|
||||
}
|
||||
|
||||
// currently we don't allow override of the filename ; if a user requirement emerges,
|
||||
@ -256,7 +256,7 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []en
|
||||
artifactManifest = specV1.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: ManifestSchemaVersion},
|
||||
MediaType: specV1.MediaTypeImageManifest,
|
||||
ArtifactType: options.ArtifactType,
|
||||
ArtifactType: options.ArtifactMIMEType,
|
||||
// TODO This should probably be configurable once the CLI is capable
|
||||
Config: specV1.DescriptorEmptyJSON,
|
||||
Layers: make([]specV1.Descriptor, 0),
|
||||
@ -314,13 +314,13 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []en
|
||||
annotations[specV1.AnnotationTitle] = artifactBlob.FileName
|
||||
|
||||
newLayer := specV1.Descriptor{
|
||||
MediaType: options.FileType,
|
||||
MediaType: options.FileMIMEType,
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
// If we did not receive an override for the layer's mediatype, use
|
||||
// detection to determine it.
|
||||
if options.FileType == "" {
|
||||
if options.FileMIMEType == "" {
|
||||
artifactBlob.BlobReader, newLayer.MediaType, err = determineBlobMIMEType(artifactBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -449,6 +449,7 @@ func (as ArtifactStore) BlobMountPaths(ctx context.Context, nameOrDigest string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, err := layout.GetLocalBlobPath(ctx, imgSrc, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -466,6 +467,7 @@ func (as ArtifactStore) BlobMountPaths(ctx context.Context, nameOrDigest string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, err := layout.GetLocalBlobPath(ctx, imgSrc, l.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -525,6 +527,7 @@ func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return copyTrustedImageBlobToFile(ctx, imgSrc, digest, filepath.Join(target, filename))
|
||||
}
|
||||
|
||||
@ -534,6 +537,7 @@ func (as ArtifactStore) Extract(ctx context.Context, nameOrDigest string, target
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = copyTrustedImageBlobToFile(ctx, imgSrc, l.Digest, filepath.Join(target, filename))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -555,9 +559,6 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO
|
||||
}
|
||||
defer imgSrc.Close()
|
||||
|
||||
tw := tar.NewWriter(w)
|
||||
defer tw.Close()
|
||||
|
||||
// Return early if only a single blob is requested via title or digest
|
||||
if len(options.Digest) > 0 || len(options.Title) > 0 {
|
||||
digest, err := findDigest(arty, &options.FilterBlobOptions)
|
||||
@ -569,11 +570,17 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO
|
||||
// so we do not have to get the actual title annotation form the blob.
|
||||
// Passing options.Title is enough because we know it is empty when digest
|
||||
// is set as we only allow either one.
|
||||
filename, err := generateArtifactBlobName(options.Title, digest)
|
||||
if err != nil {
|
||||
return err
|
||||
var filename string
|
||||
if !options.ExcludeTitle {
|
||||
filename, err = generateArtifactBlobName(options.Title, digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tw := tar.NewWriter(w)
|
||||
defer tw.Close()
|
||||
|
||||
err = copyTrustedImageBlobToTarStream(ctx, imgSrc, digest, filename, tw)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -582,13 +589,40 @@ func (as ArtifactStore) ExtractTarStream(ctx context.Context, w io.Writer, nameO
|
||||
return nil
|
||||
}
|
||||
|
||||
artifactBlobCount := len(arty.Manifest.Layers)
|
||||
|
||||
type blob struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
}
|
||||
blobs := make([]blob, 0, artifactBlobCount)
|
||||
|
||||
// Gather blob details and return error on any illegal names
|
||||
for _, l := range arty.Manifest.Layers {
|
||||
title := l.Annotations[specV1.AnnotationTitle]
|
||||
filename, err := generateArtifactBlobName(title, l.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
digest := l.Digest
|
||||
var name string
|
||||
|
||||
if artifactBlobCount != 1 || !options.ExcludeTitle {
|
||||
name, err = generateArtifactBlobName(title, digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = copyTrustedImageBlobToTarStream(ctx, imgSrc, l.Digest, filename, tw)
|
||||
|
||||
blobs = append(blobs, blob{
|
||||
name: name,
|
||||
digest: digest,
|
||||
})
|
||||
}
|
||||
|
||||
// Wrap io.Writer in a tar.Writer
|
||||
tw := tar.NewWriter(w)
|
||||
defer tw.Close()
|
||||
|
||||
// Write each blob to tar.Writer then close
|
||||
for _, b := range blobs {
|
||||
err := copyTrustedImageBlobToTarStream(ctx, imgSrc, b.digest, b.name, tw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -612,7 +646,7 @@ func generateArtifactBlobName(title string, digest digest.Digest) (string, error
|
||||
// We must use os.IsPathSeparator() as on Windows it checks both "\\" and "/".
|
||||
for i := 0; i < len(filename); i++ {
|
||||
if os.IsPathSeparator(filename[i]) {
|
||||
return "", fmt.Errorf("invalid name: %q cannot contain %c", filename, filename[i])
|
||||
return "", fmt.Errorf("invalid name: %q cannot contain %c: %w", filename, filename[i], libartTypes.ErrArtifactBlobTitleInvalid)
|
||||
}
|
||||
}
|
||||
return filename, nil
|
||||
@ -733,9 +767,7 @@ func (as ArtifactStore) indexPath() string {
|
||||
// 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, _ *libartTypes.GetArtifactOptions) (libartifact.ArtifactList, error) {
|
||||
var (
|
||||
al libartifact.ArtifactList
|
||||
)
|
||||
var al libartifact.ArtifactList
|
||||
|
||||
lrs, err := layout.List(as.storePath)
|
||||
if err != nil {
|
||||
|
@ -6,13 +6,13 @@ type GetArtifactOptions struct{}
|
||||
|
||||
// AddOptions are additional descriptors of an artifact file
|
||||
type AddOptions struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
ArtifactType string `json:",omitempty"`
|
||||
// append option is not compatible with ArtifactType option
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
ArtifactMIMEType string `json:",omitempty"`
|
||||
// append option is not compatible with ArtifactMIMEType option
|
||||
Append bool `json:",omitempty"`
|
||||
// FileType describes the media type for the layer. It is an override
|
||||
// for the standard detection
|
||||
FileType string `json:",omitempty"`
|
||||
FileMIMEType string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// FilterBlobOptions options used to filter for a single blob in an artifact
|
||||
@ -27,6 +27,10 @@ type FilterBlobOptions struct {
|
||||
|
||||
type ExtractOptions struct {
|
||||
FilterBlobOptions
|
||||
// ExcludeTitle option allows single blobs to be exported
|
||||
// with their title/filename empty. Optional.
|
||||
// Default: False
|
||||
ExcludeTitle bool
|
||||
}
|
||||
|
||||
type BlobMountPathOptions struct {
|
||||
|
@ -5,8 +5,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrArtifactUnamed = errors.New("artifact is unnamed")
|
||||
ErrArtifactNotExist = errors.New("artifact does not exist")
|
||||
ErrArtifactAlreadyExists = errors.New("artifact already exists")
|
||||
ErrArtifactFileExists = errors.New("file already exists in artifact")
|
||||
ErrArtifactUnamed = errors.New("artifact is unnamed")
|
||||
ErrArtifactNotExist = errors.New("artifact does not exist")
|
||||
ErrArtifactAlreadyExists = errors.New("artifact already exists")
|
||||
ErrArtifactFileExists = errors.New("file already exists in artifact")
|
||||
ErrArtifactBlobTitleInvalid = errors.New("artifact blob title invalid")
|
||||
)
|
||||
|
@ -218,7 +218,7 @@ class ArtifactTestCase(APITestCase):
|
||||
# Assert return error response is json and contains correct message
|
||||
self.assertEqual(
|
||||
rjson["cause"],
|
||||
"append option is not compatible with ArtifactType option",
|
||||
"append option is not compatible with type option",
|
||||
)
|
||||
|
||||
def test_add_with_append_to_missing_artifact_fails(self):
|
||||
@ -354,7 +354,7 @@ class ArtifactTestCase(APITestCase):
|
||||
# Assert correct response code
|
||||
self.assertEqual(r.status_code, 200, r.text)
|
||||
|
||||
# Assert return error response is json and contains correct message
|
||||
# Assert return response is json and contains correct message
|
||||
self.assertIn("sha256:", rjson["ArtifactDigest"])
|
||||
|
||||
def test_pull_with_retry(self):
|
||||
@ -397,7 +397,7 @@ class ArtifactTestCase(APITestCase):
|
||||
# Assert return error response is json and contains correct message
|
||||
self.assertEqual(
|
||||
rjson["cause"],
|
||||
"unauthorized",
|
||||
"unauthorized: access to the requested resource is not authorized",
|
||||
)
|
||||
|
||||
def test_pull_missing_fails(self):
|
||||
@ -413,9 +413,9 @@ class ArtifactTestCase(APITestCase):
|
||||
self.assertEqual(r.status_code, 404, r.text)
|
||||
|
||||
# Assert return error response is json and contains correct message
|
||||
self.assertEqual(
|
||||
rjson["cause"],
|
||||
self.assertIn(
|
||||
"manifest unknown",
|
||||
rjson["cause"],
|
||||
)
|
||||
|
||||
def test_remove(self):
|
||||
@ -459,9 +459,9 @@ class ArtifactTestCase(APITestCase):
|
||||
self.assertEqual(r.status_code, 401, r.text)
|
||||
|
||||
# Assert return error response is json and contains correct message
|
||||
self.assertEqual(
|
||||
self.assertIn(
|
||||
"authentication required",
|
||||
rjson["cause"],
|
||||
"unauthorized",
|
||||
)
|
||||
|
||||
def test_push_bad_param(self):
|
||||
|
@ -12,10 +12,6 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("Podman artifact mount", func() {
|
||||
BeforeEach(func() {
|
||||
SkipIfRemote("artifacts are not supported on the remote client yet due to being in development still")
|
||||
})
|
||||
|
||||
It("podman artifact mount single blob", func() {
|
||||
podmanTest.PodmanExitCleanly("artifact", "pull", ARTIFACT_SINGLE)
|
||||
|
||||
|
@ -21,10 +21,6 @@ const (
|
||||
)
|
||||
|
||||
var _ = Describe("Podman artifact", func() {
|
||||
BeforeEach(func() {
|
||||
SkipIfRemote("artifacts are not supported on the remote client yet due to being in development still")
|
||||
})
|
||||
|
||||
It("podman artifact ls", func() {
|
||||
artifact1File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -67,7 +63,6 @@ var _ = Describe("Podman artifact", func() {
|
||||
noHeaderOutput := noHeaderSession.OutputToStringArray()
|
||||
Expect(noHeaderOutput).To(HaveLen(2))
|
||||
Expect(noHeaderOutput).ToNot(ContainElement("REPOSITORY"))
|
||||
|
||||
})
|
||||
|
||||
It("podman artifact simple add", func() {
|
||||
@ -133,7 +128,11 @@ var _ = Describe("Podman artifact", func() {
|
||||
retrySession := podmanTest.Podman([]string{"artifact", "pull", "--retry", "1", "--retry-delay", "100ms", "127.0.0.1/mybadimagename"})
|
||||
retrySession.WaitWithDefaultTimeout()
|
||||
Expect(retrySession).Should(ExitWithError(125, "connect: connection refused"))
|
||||
Expect(retrySession.ErrorToString()).To(ContainSubstring("retrying in 100ms ..."))
|
||||
|
||||
// TODO: This can be removed once Artifact API supports streaming
|
||||
if !IsRemote() {
|
||||
Expect(retrySession.ErrorToString()).To(ContainSubstring("retrying in 100ms ..."))
|
||||
}
|
||||
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -202,12 +201,15 @@ var _ = Describe("Podman artifact", func() {
|
||||
multipleArgs.WaitWithDefaultTimeout()
|
||||
Expect(multipleArgs).Should(ExitWithError(125, "Error: too many arguments: only accepts one artifact name or digest"))
|
||||
|
||||
// Remove all
|
||||
podmanTest.PodmanExitCleanly("artifact", "rm", "-a")
|
||||
// TODO: This should be removed once Artifact API remove endpoint supports the "all" flag
|
||||
if !IsRemote() {
|
||||
// Remove all
|
||||
podmanTest.PodmanExitCleanly("artifact", "rm", "-a")
|
||||
|
||||
// There should be no artifacts in the store
|
||||
rmAll := podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading")
|
||||
Expect(rmAll.OutputToString()).To(BeEmpty())
|
||||
// There should be no artifacts in the store
|
||||
rmAll := podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading")
|
||||
Expect(rmAll.OutputToString()).To(BeEmpty())
|
||||
}
|
||||
})
|
||||
|
||||
It("podman artifact inspect with full or partial digest", func() {
|
||||
@ -220,7 +222,6 @@ var _ = Describe("Podman artifact", func() {
|
||||
|
||||
podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest)
|
||||
podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest[:12])
|
||||
|
||||
})
|
||||
|
||||
It("podman artifact extract single", func() {
|
||||
@ -535,7 +536,7 @@ var _ = Describe("Podman artifact", func() {
|
||||
|
||||
failSession := podmanTest.Podman([]string{"artifact", "add", "--type", artifactType, "--append", artifact1Name, artifact3File})
|
||||
failSession.WaitWithDefaultTimeout()
|
||||
Expect(failSession).Should(ExitWithError(125, "Error: append option is not compatible with ArtifactType option"))
|
||||
Expect(failSession).Should(ExitWithError(125, "Error: append option is not compatible with type option"))
|
||||
})
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user