github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/bindings/manifests/manifests.go (about)

     1  package manifests
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/containers/image/v5/manifest"
    11  	imageTypes "github.com/containers/image/v5/types"
    12  	"github.com/hanks177/podman/v4/pkg/auth"
    13  	"github.com/hanks177/podman/v4/pkg/bindings"
    14  	"github.com/hanks177/podman/v4/pkg/bindings/images"
    15  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    16  	"github.com/hanks177/podman/v4/pkg/errorhandling"
    17  	jsoniter "github.com/json-iterator/go"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // Create creates a manifest for the given name.  Optional images to be associated with
    22  // the new manifest can also be specified.  The all boolean specifies to add all entries
    23  // of a list if the name provided is a manifest list.  The ID of the new manifest list
    24  // is returned as a string.
    25  func Create(ctx context.Context, name string, images []string, options *CreateOptions) (string, error) {
    26  	var idr entities.IDResponse
    27  	if options == nil {
    28  		options = new(CreateOptions)
    29  	}
    30  	conn, err := bindings.GetClient(ctx)
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  	if len(name) < 1 {
    35  		return "", errors.New("creating a manifest requires at least one name argument")
    36  	}
    37  	params, err := options.ToParams()
    38  	if err != nil {
    39  		return "", err
    40  	}
    41  
    42  	for _, i := range images {
    43  		params.Add("images", i)
    44  	}
    45  
    46  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s", params, nil, name)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  	defer response.Body.Close()
    51  
    52  	return idr.ID, response.Process(&idr)
    53  }
    54  
    55  // Exists returns true if a given manifest list exists
    56  func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, error) {
    57  	conn, err := bindings.GetClient(ctx)
    58  	if err != nil {
    59  		return false, err
    60  	}
    61  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/exists", nil, nil, name)
    62  	if err != nil {
    63  		return false, err
    64  	}
    65  	defer response.Body.Close()
    66  
    67  	return response.IsSuccess(), nil
    68  }
    69  
    70  // Inspect returns a manifest list for a given name.
    71  func Inspect(ctx context.Context, name string, _ *InspectOptions) (*manifest.Schema2List, error) {
    72  	conn, err := bindings.GetClient(ctx)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	defer response.Body.Close()
    82  
    83  	var list manifest.Schema2List
    84  	return &list, response.Process(&list)
    85  }
    86  
    87  // Add adds a manifest to a given manifest list.  Additional options for the manifest
    88  // can also be specified.  The ID of the new manifest list is returned as a string
    89  func Add(ctx context.Context, name string, options *AddOptions) (string, error) {
    90  	if options == nil {
    91  		options = new(AddOptions)
    92  	}
    93  
    94  	optionsv4 := ModifyOptions{
    95  		All:           options.All,
    96  		Annotations:   options.Annotation,
    97  		Arch:          options.Arch,
    98  		Features:      options.Features,
    99  		Images:        options.Images,
   100  		OS:            options.OS,
   101  		OSFeatures:    nil,
   102  		OSVersion:     options.OSVersion,
   103  		Variant:       options.Variant,
   104  		Username:      options.Username,
   105  		Password:      options.Password,
   106  		Authfile:      options.Authfile,
   107  		SkipTLSVerify: options.SkipTLSVerify,
   108  	}
   109  	optionsv4.WithOperation("update")
   110  	return Modify(ctx, name, options.Images, &optionsv4)
   111  }
   112  
   113  // Remove deletes a manifest entry from a manifest list.  Both name and the digest to be
   114  // removed are mandatory inputs.  The ID of the new manifest list is returned as a string.
   115  func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) {
   116  	optionsv4 := new(ModifyOptions).WithOperation("remove")
   117  	return Modify(ctx, name, []string{digest}, optionsv4)
   118  }
   119  
   120  // Push takes a manifest list and pushes to a destination.  If the destination is not specified,
   121  // the name will be used instead.  If the optional all boolean is specified, all images specified
   122  // in the list will be pushed as well.
   123  func Push(ctx context.Context, name, destination string, options *images.PushOptions) (string, error) {
   124  	var idr entities.IDResponse
   125  	if options == nil {
   126  		options = new(images.PushOptions)
   127  	}
   128  	if len(destination) < 1 {
   129  		destination = name
   130  	}
   131  	conn, err := bindings.GetClient(ctx)
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  
   141  	params, err := options.ToParams()
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  	// SkipTLSVerify is special.  We need to delete the param added by
   146  	// ToParams() and change the key and flip the bool
   147  	if options.SkipTLSVerify != nil {
   148  		params.Del("SkipTLSVerify")
   149  		params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
   150  	}
   151  
   152  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/registry/%s", params, header, name, destination)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	defer response.Body.Close()
   157  
   158  	return idr.ID, response.Process(&idr)
   159  }
   160  
   161  // Modify modifies the given manifest list using options and the optional list of images
   162  func Modify(ctx context.Context, name string, images []string, options *ModifyOptions) (string, error) {
   163  	if options == nil || *options.Operation == "" {
   164  		return "", errors.New(`the field ModifyOptions.Operation must be set to either "update" or "remove"`)
   165  	}
   166  	options.WithImages(images)
   167  
   168  	conn, err := bindings.GetClient(ctx)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	opts, err := jsoniter.MarshalToString(options)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  	reader := strings.NewReader(opts)
   177  
   178  	header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  
   183  	params, err := options.ToParams()
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	// SkipTLSVerify is special.  We need to delete the param added by
   188  	// ToParams() and change the key and flip the bool
   189  	if options.SkipTLSVerify != nil {
   190  		params.Del("SkipTLSVerify")
   191  		params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
   192  	}
   193  
   194  	response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/manifests/%s", params, header, name)
   195  	if err != nil {
   196  		return "", err
   197  	}
   198  	defer response.Body.Close()
   199  
   200  	data, err := ioutil.ReadAll(response.Body)
   201  	if err != nil {
   202  		return "", errors.Wrap(err, "unable to process API response")
   203  	}
   204  
   205  	if response.IsSuccess() || response.IsRedirection() {
   206  		var report entities.ManifestModifyReport
   207  		if err = jsoniter.Unmarshal(data, &report); err != nil {
   208  			return "", errors.Wrap(err, "unable to decode API response")
   209  		}
   210  
   211  		err = errorhandling.JoinErrors(report.Errors)
   212  		if err != nil {
   213  			errModel := errorhandling.ErrorModel{
   214  				Because:      (errors.Cause(err)).Error(),
   215  				Message:      err.Error(),
   216  				ResponseCode: response.StatusCode,
   217  			}
   218  			return report.ID, &errModel
   219  		}
   220  		return report.ID, nil
   221  	}
   222  
   223  	errModel := errorhandling.ErrorModel{
   224  		ResponseCode: response.StatusCode,
   225  	}
   226  	if err = jsoniter.Unmarshal(data, &errModel); err != nil {
   227  		return "", errors.Wrap(err, "unable to decode API response")
   228  	}
   229  	return "", &errModel
   230  }
   231  
   232  // Annotate modifies the given manifest list using options and the optional list of images
   233  //
   234  // As of 4.0.0
   235  func Annotate(ctx context.Context, name string, images []string, options *ModifyOptions) (string, error) {
   236  	options.WithOperation("annotate")
   237  	return Modify(ctx, name, images, options)
   238  }