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  }