github.com/codefresh-io/kcfi@v0.0.0-20230301195427-c1578715cc46/pkg/helm-internal/experimental/registry/client.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package registry // import "helm.sh/helm/v3/internal/experimental/registry" 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "net/http" 25 "sort" 26 27 auth "github.com/deislabs/oras/pkg/auth/docker" 28 "github.com/deislabs/oras/pkg/oras" 29 "github.com/gosuri/uitable" 30 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 31 "github.com/pkg/errors" 32 33 "helm.sh/helm/v3/pkg/chart" 34 "helm.sh/helm/v3/pkg/helmpath" 35 ) 36 37 const ( 38 // CredentialsFileBasename is the filename for auth credentials file 39 CredentialsFileBasename = "config.json" 40 ) 41 42 type ( 43 // Client works with OCI-compliant registries and local Helm chart cache 44 Client struct { 45 debug bool 46 out io.Writer 47 authorizer *Authorizer 48 resolver *Resolver 49 cache *Cache 50 } 51 ) 52 53 // NewClient returns a new registry client with config 54 func NewClient(opts ...ClientOption) (*Client, error) { 55 client := &Client{ 56 out: ioutil.Discard, 57 } 58 for _, opt := range opts { 59 opt(client) 60 } 61 // set defaults if fields are missing 62 if client.authorizer == nil { 63 credentialsFile := helmpath.CachePath("registry", CredentialsFileBasename) 64 authClient, err := auth.NewClient(credentialsFile) 65 if err != nil { 66 return nil, err 67 } 68 client.authorizer = &Authorizer{ 69 Client: authClient, 70 } 71 } 72 if client.resolver == nil { 73 resolver, err := client.authorizer.Resolver(context.Background(), http.DefaultClient, false) 74 if err != nil { 75 return nil, err 76 } 77 client.resolver = &Resolver{ 78 Resolver: resolver, 79 } 80 } 81 if client.cache == nil { 82 cache, err := NewCache( 83 CacheOptDebug(client.debug), 84 CacheOptWriter(client.out), 85 CacheOptRoot(helmpath.CachePath("registry", CacheRootDir)), 86 ) 87 if err != nil { 88 return nil, err 89 } 90 client.cache = cache 91 } 92 return client, nil 93 } 94 95 // Login logs into a registry 96 func (c *Client) Login(hostname string, username string, password string, insecure bool) error { 97 err := c.authorizer.Login(ctx(c.out, c.debug), hostname, username, password, insecure) 98 if err != nil { 99 return err 100 } 101 fmt.Fprintf(c.out, "Login succeeded\n") 102 return nil 103 } 104 105 // Logout logs out of a registry 106 func (c *Client) Logout(hostname string) error { 107 err := c.authorizer.Logout(ctx(c.out, c.debug), hostname) 108 if err != nil { 109 return err 110 } 111 fmt.Fprintln(c.out, "Logout succeeded") 112 return nil 113 } 114 115 // PushChart uploads a chart to a registry 116 func (c *Client) PushChart(ref *Reference) error { 117 r, err := c.cache.FetchReference(ref) 118 if err != nil { 119 return err 120 } 121 if !r.Exists { 122 return errors.New(fmt.Sprintf("Chart not found: %s", r.Name)) 123 } 124 fmt.Fprintf(c.out, "The push refers to repository [%s]\n", r.Repo) 125 c.printCacheRefSummary(r) 126 layers := []ocispec.Descriptor{*r.ContentLayer} 127 _, err = oras.Push(ctx(c.out, c.debug), c.resolver, r.Name, c.cache.Provider(), layers, 128 oras.WithConfig(*r.Config), oras.WithNameValidation(nil)) 129 if err != nil { 130 return err 131 } 132 s := "" 133 numLayers := len(layers) 134 if 1 < numLayers { 135 s = "s" 136 } 137 fmt.Fprintf(c.out, 138 "%s: pushed to remote (%d layer%s, %s total)\n", r.Tag, numLayers, s, byteCountBinary(r.Size)) 139 return nil 140 } 141 142 // PullChart downloads a chart from a registry 143 func (c *Client) PullChart(ref *Reference) error { 144 if ref.Tag == "" { 145 return errors.New("tag explicitly required") 146 } 147 existing, err := c.cache.FetchReference(ref) 148 if err != nil { 149 return err 150 } 151 fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo) 152 manifest, _, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), c.cache.Ingester(), 153 oras.WithPullEmptyNameAllowed(), 154 oras.WithAllowedMediaTypes(KnownMediaTypes()), 155 oras.WithContentProvideIngester(c.cache.ProvideIngester())) 156 if err != nil { 157 return err 158 } 159 err = c.cache.AddManifest(ref, &manifest) 160 if err != nil { 161 return err 162 } 163 r, err := c.cache.FetchReference(ref) 164 if err != nil { 165 return err 166 } 167 if !r.Exists { 168 return errors.New(fmt.Sprintf("Chart not found: %s", r.Name)) 169 } 170 c.printCacheRefSummary(r) 171 if !existing.Exists { 172 fmt.Fprintf(c.out, "Status: Downloaded newer chart for %s\n", ref.FullName()) 173 } else { 174 fmt.Fprintf(c.out, "Status: Chart is up to date for %s\n", ref.FullName()) 175 } 176 return err 177 } 178 179 // SaveChart stores a copy of chart in local cache 180 func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error { 181 r, err := c.cache.StoreReference(ref, ch) 182 if err != nil { 183 return err 184 } 185 c.printCacheRefSummary(r) 186 err = c.cache.AddManifest(ref, r.Manifest) 187 if err != nil { 188 return err 189 } 190 fmt.Fprintf(c.out, "%s: saved\n", r.Tag) 191 return nil 192 } 193 194 // LoadChart retrieves a chart object by reference 195 func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) { 196 r, err := c.cache.FetchReference(ref) 197 if err != nil { 198 return nil, err 199 } 200 if !r.Exists { 201 return nil, errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName())) 202 } 203 c.printCacheRefSummary(r) 204 return r.Chart, nil 205 } 206 207 // RemoveChart deletes a locally saved chart 208 func (c *Client) RemoveChart(ref *Reference) error { 209 r, err := c.cache.DeleteReference(ref) 210 if err != nil { 211 return err 212 } 213 if !r.Exists { 214 return errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName())) 215 } 216 fmt.Fprintf(c.out, "%s: removed\n", r.Tag) 217 return nil 218 } 219 220 // PrintChartTable prints a list of locally stored charts 221 func (c *Client) PrintChartTable() error { 222 table := uitable.New() 223 table.MaxColWidth = 60 224 table.AddRow("REF", "NAME", "VERSION", "DIGEST", "SIZE", "CREATED") 225 rows, err := c.getChartTableRows() 226 if err != nil { 227 return err 228 } 229 for _, row := range rows { 230 table.AddRow(row...) 231 } 232 fmt.Fprintln(c.out, table.String()) 233 return nil 234 } 235 236 // printCacheRefSummary prints out chart ref summary 237 func (c *Client) printCacheRefSummary(r *CacheRefSummary) { 238 fmt.Fprintf(c.out, "ref: %s\n", r.Name) 239 fmt.Fprintf(c.out, "digest: %s\n", r.Digest.Hex()) 240 fmt.Fprintf(c.out, "size: %s\n", byteCountBinary(r.Size)) 241 fmt.Fprintf(c.out, "name: %s\n", r.Chart.Metadata.Name) 242 fmt.Fprintf(c.out, "version: %s\n", r.Chart.Metadata.Version) 243 } 244 245 // getChartTableRows returns rows in uitable-friendly format 246 func (c *Client) getChartTableRows() ([][]interface{}, error) { 247 rr, err := c.cache.ListReferences() 248 if err != nil { 249 return nil, err 250 } 251 refsMap := map[string]map[string]string{} 252 for _, r := range rr { 253 refsMap[r.Name] = map[string]string{ 254 "name": r.Chart.Metadata.Name, 255 "version": r.Chart.Metadata.Version, 256 "digest": shortDigest(r.Digest.Hex()), 257 "size": byteCountBinary(r.Size), 258 "created": timeAgo(r.CreatedAt), 259 } 260 } 261 // Sort and convert to format expected by uitable 262 rows := make([][]interface{}, len(refsMap)) 263 keys := make([]string, 0, len(refsMap)) 264 for key := range refsMap { 265 keys = append(keys, key) 266 } 267 sort.Strings(keys) 268 for i, key := range keys { 269 rows[i] = make([]interface{}, 6) 270 rows[i][0] = key 271 ref := refsMap[key] 272 for j, k := range []string{"name", "version", "digest", "size", "created"} { 273 rows[i][j+1] = ref[k] 274 } 275 } 276 return rows, nil 277 }