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  }