mirror of
https://github.com/containers/podman.git
synced 2025-10-20 20:54:45 +08:00

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>
339 lines
10 KiB
Go
339 lines
10 KiB
Go
//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, 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, err)
|
|
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,
|
|
ArtifactMIMEType: query.ArtifactMIMEType,
|
|
FileMIMEType: 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, err)
|
|
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"`
|
|
ExcludeTitle bool `schema:"excludetitle"`
|
|
}{}
|
|
|
|
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,
|
|
ExcludeTitle: query.ExcludeTitle,
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|