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