...

Source file src/helm.sh/helm/v3/pkg/pusher/ocipusher.go

Documentation: helm.sh/helm/v3/pkg/pusher

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package pusher
    17  
    18  import (
    19  	"fmt"
    20  	"net"
    21  	"net/http"
    22  	"os"
    23  	"path"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  
    29  	"helm.sh/helm/v3/internal/tlsutil"
    30  	"helm.sh/helm/v3/pkg/chart/loader"
    31  	"helm.sh/helm/v3/pkg/registry"
    32  	"helm.sh/helm/v3/pkg/time/ctime"
    33  )
    34  
    35  // OCIPusher is the default OCI backend handler
    36  type OCIPusher struct {
    37  	opts options
    38  }
    39  
    40  // Push performs a Push from repo.Pusher.
    41  func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error {
    42  	for _, opt := range options {
    43  		opt(&pusher.opts)
    44  	}
    45  	return pusher.push(chartRef, href)
    46  }
    47  
    48  func (pusher *OCIPusher) push(chartRef, href string) error {
    49  	stat, err := os.Stat(chartRef)
    50  	if err != nil {
    51  		if os.IsNotExist(err) {
    52  			return errors.Errorf("%s: no such file", chartRef)
    53  		}
    54  		return err
    55  	}
    56  	if stat.IsDir() {
    57  		return errors.New("cannot push directory, must provide chart archive (.tgz)")
    58  	}
    59  
    60  	meta, err := loader.Load(chartRef)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	client := pusher.opts.registryClient
    66  	if client == nil {
    67  		c, err := pusher.newRegistryClient()
    68  		if err != nil {
    69  			return err
    70  		}
    71  		client = c
    72  	}
    73  
    74  	chartBytes, err := os.ReadFile(chartRef)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	var pushOpts []registry.PushOption
    80  	provRef := fmt.Sprintf("%s.prov", chartRef)
    81  	if _, err := os.Stat(provRef); err == nil {
    82  		provBytes, err := os.ReadFile(provRef)
    83  		if err != nil {
    84  			return err
    85  		}
    86  		pushOpts = append(pushOpts, registry.PushOptProvData(provBytes))
    87  	}
    88  
    89  	ref := fmt.Sprintf("%s:%s",
    90  		path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name),
    91  		meta.Metadata.Version)
    92  
    93  	chartCreationTime := ctime.Created(stat)
    94  	pushOpts = append(pushOpts, registry.PushOptCreationTime(chartCreationTime.Format(time.RFC3339)))
    95  
    96  	_, err = client.Push(chartBytes, ref, pushOpts...)
    97  	return err
    98  }
    99  
   100  // NewOCIPusher constructs a valid OCI client as a Pusher
   101  func NewOCIPusher(ops ...Option) (Pusher, error) {
   102  	var client OCIPusher
   103  
   104  	for _, opt := range ops {
   105  		opt(&client.opts)
   106  	}
   107  
   108  	return &client, nil
   109  }
   110  
   111  func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) {
   112  	if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify {
   113  		tlsConf, err := tlsutil.NewClientTLS(pusher.opts.certFile, pusher.opts.keyFile, pusher.opts.caFile, pusher.opts.insecureSkipTLSverify)
   114  		if err != nil {
   115  			return nil, errors.Wrap(err, "can't create TLS config for client")
   116  		}
   117  
   118  		registryClient, err := registry.NewClient(
   119  			registry.ClientOptHTTPClient(&http.Client{
   120  				// From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87
   121  				Transport: &http.Transport{
   122  					Proxy: http.ProxyFromEnvironment,
   123  					DialContext: (&net.Dialer{
   124  						// By default we wrap the transport in retries, so reduce the
   125  						// default dial timeout to 5s to avoid 5x 30s of connection
   126  						// timeouts when doing the "ping" on certain http registries.
   127  						Timeout:   5 * time.Second,
   128  						KeepAlive: 30 * time.Second,
   129  					}).DialContext,
   130  					ForceAttemptHTTP2:     true,
   131  					MaxIdleConns:          100,
   132  					IdleConnTimeout:       90 * time.Second,
   133  					TLSHandshakeTimeout:   10 * time.Second,
   134  					ExpectContinueTimeout: 1 * time.Second,
   135  					TLSClientConfig:       tlsConf,
   136  				},
   137  			}),
   138  			registry.ClientOptEnableCache(true),
   139  		)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  		return registryClient, nil
   144  	}
   145  
   146  	opts := []registry.ClientOption{registry.ClientOptEnableCache(true)}
   147  	if pusher.opts.plainHTTP {
   148  		opts = append(opts, registry.ClientOptPlainHTTP())
   149  	}
   150  
   151  	registryClient, err := registry.NewClient(opts...)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return registryClient, nil
   156  }
   157  

View as plain text