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 }