1
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
36 type OCIPusher struct {
37 opts options
38 }
39
40
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
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
121 Transport: &http.Transport{
122 Proxy: http.ProxyFromEnvironment,
123 DialContext: (&net.Dialer{
124
125
126
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