//go:build !remote package libpod import ( "errors" "fmt" "net/http" "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/types" "github.com/containers/podman/v5/libpod" "github.com/containers/podman/v5/pkg/api/handlers/utils" api "github.com/containers/podman/v5/pkg/api/types" "github.com/containers/podman/v5/pkg/auth" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/infra/abi" domain_utils "github.com/containers/podman/v5/pkg/domain/utils" libartifact_types "github.com/containers/podman/v5/pkg/libartifact/types" "github.com/docker/distribution/registry/api/errcode" "github.com/gorilla/schema" ) func InspectArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) name := utils.GetName(r) imageEngine := abi.ImageEngine{Libpod: runtime} report, err := imageEngine.ArtifactInspect(r.Context(), name, entities.ArtifactInspectOptions{}) if err != nil { if errors.Is(err, libartifact_types.ErrArtifactNotExist) { utils.ArtifactNotFound(w, name, err) return } else { utils.InternalServerError(w, err) return } } utils.WriteResponse(w, http.StatusOK, report) } func ListArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) imageEngine := abi.ImageEngine{Libpod: runtime} artifacts, err := imageEngine.ArtifactList(r.Context(), entities.ArtifactListOptions{}) if err != nil { utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusOK, artifacts) } func PullArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Name string `schema:"name"` Retry uint `schema:"retry"` RetryDelay string `schema:"retryDelay"` TLSVerify types.OptionalBool `schema:"tlsVerify"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if query.Name == "" { utils.Error(w, http.StatusBadRequest, errors.New("name parameter is required")) return } artifactsPullOptions := entities.ArtifactPullOptions{} // If TLS verification is explicitly specified (True or False) in the query, // set the InsecureSkipTLSVerify option accordingly. // If TLSVerify was not set in the query, OptionalBoolUndefined is used and // handled later based off the target registry configuration. switch query.TLSVerify { case types.OptionalBoolTrue: artifactsPullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(false) case types.OptionalBoolFalse: artifactsPullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(true) case types.OptionalBoolUndefined: // If the user doesn't define TLSVerify in the query, do nothing and pass // it to the backend code to handle. default: // Should never happen panic("Unexpected handling occurred for TLSVerify") } if _, found := r.URL.Query()["retry"]; found { artifactsPullOptions.MaxRetries = &query.Retry } if len(query.RetryDelay) != 0 { artifactsPullOptions.RetryDelay = query.RetryDelay } authConf, authfile, err := auth.GetCredentials(r) if err != nil { utils.Error(w, http.StatusBadRequest, err) return } defer auth.RemoveAuthfile(authfile) artifactsPullOptions.AuthFilePath = authfile if authConf != nil { artifactsPullOptions.Username = authConf.Username artifactsPullOptions.Password = authConf.Password artifactsPullOptions.IdentityToken = authConf.IdentityToken } imageEngine := abi.ImageEngine{Libpod: runtime} artifacts, err := imageEngine.ArtifactPull(r.Context(), query.Name, artifactsPullOptions) if err != nil { var errcd errcode.ErrorCoder // Check to see if any of the wrapped errors is an errcode.ErrorCoder returned from the registry if errors.As(err, &errcd) { 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()) 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()) return } } utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusOK, artifacts) } func RemoveArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) imageEngine := abi.ImageEngine{Libpod: runtime} name := utils.GetName(r) artifacts, err := imageEngine.ArtifactRm(r.Context(), name, entities.ArtifactRemoveOptions{}) if err != nil { if errors.Is(err, libartifact_types.ErrArtifactNotExist) { utils.ArtifactNotFound(w, name, err) return } utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusOK, artifacts) } func AddArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Name string `schema:"name"` FileName string `schema:"fileName"` FileMIMEType string `schema:"fileMIMEType"` Annotations []string `schema:"annotations"` ArtifactMIMEType string `schema:"artifactMIMEType"` Append bool `schema:"append"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if query.Name == "" || query.FileName == "" { utils.Error(w, http.StatusBadRequest, errors.New("name and file parameters are required")) return } annotations, err := domain_utils.ParseAnnotations(query.Annotations) if err != nil { utils.Error(w, http.StatusBadRequest, err) return } artifactAddOptions := &entities.ArtifactAddOptions{ Append: query.Append, Annotations: annotations, ArtifactType: query.ArtifactMIMEType, FileType: query.FileMIMEType, } artifactBlobs := []entities.ArtifactBlob{{ BlobReader: r.Body, FileName: query.FileName, }} imageEngine := abi.ImageEngine{Libpod: runtime} artifacts, err := imageEngine.ArtifactAdd(r.Context(), query.Name, artifactBlobs, artifactAddOptions) if err != nil { if errors.Is(err, libartifact_types.ErrArtifactNotExist) { utils.ArtifactNotFound(w, query.Name, err) return } utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusCreated, artifacts) } func PushArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Retry uint `schema:"retry"` RetryDelay string `schema:"retrydelay"` TLSVerify types.OptionalBool `schema:"tlsVerify"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, errors.New("name parameter is required")) return } name := utils.GetName(r) artifactsPushOptions := entities.ArtifactPushOptions{} // If TLS verification is explicitly specified (True or False) in the query, // set the SkipTLSVerify option accordingly. // If TLSVerify was not set in the query, OptionalBoolUndefined is used and // handled later based off the target registry configuration. switch query.TLSVerify { case types.OptionalBoolTrue: artifactsPushOptions.SkipTLSVerify = types.NewOptionalBool(false) case types.OptionalBoolFalse: artifactsPushOptions.SkipTLSVerify = types.NewOptionalBool(true) case types.OptionalBoolUndefined: // If the user doesn't define TLSVerify in the query, do nothing and pass // it to the backend code to handle. default: // Should never happen panic("Unexpected handling occurred for TLSVerify") } if _, found := r.URL.Query()["retry"]; found { artifactsPushOptions.Retry = &query.Retry } if len(query.RetryDelay) != 0 { artifactsPushOptions.RetryDelay = query.RetryDelay } authConf, authfile, err := auth.GetCredentials(r) if err != nil { utils.Error(w, http.StatusBadRequest, err) return } defer auth.RemoveAuthfile(authfile) if authConf != nil { artifactsPushOptions.Username = authConf.Username artifactsPushOptions.Password = authConf.Password } imageEngine := abi.ImageEngine{Libpod: runtime} artifacts, err := imageEngine.ArtifactPush(r.Context(), name, artifactsPushOptions) if err != nil { var errcd errcode.ErrorCoder // Check to see if any of the wrapped errors is an errcode.ErrorCoder returned from the registry if errors.As(err, &errcd) { 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()) return } } var notFoundErr layout.ImageNotFoundError if errors.As(err, ¬FoundErr) { utils.ArtifactNotFound(w, name, notFoundErr) return } utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusOK, artifacts) } func ExtractArtifact(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { Digest string `schema:"digest"` Title string `schema:"title"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } extractOpts := entities.ArtifactExtractOptions{ Title: query.Title, Digest: query.Digest, } name := utils.GetName(r) imageEngine := abi.ImageEngine{Libpod: runtime} err := imageEngine.ArtifactExtractTarStream(r.Context(), w, name, &extractOpts) if err != nil { if errors.Is(err, libartifact_types.ErrArtifactNotExist) { utils.ArtifactNotFound(w, name, err) return } utils.InternalServerError(w, err) return } }