github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/cmd/tools/helm/repo_add.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 /* 18 NOTICE: This file's 'package' and some functionality has been modified / removed to fit within Jackal's package structure. 19 */ 20 21 // Package helm is a copy of the main package from helm to include a subset of the helm CLI in Jackal 22 package helm 23 24 import ( 25 "context" 26 "fmt" 27 "io" 28 "os" 29 "path/filepath" 30 "strings" 31 "time" 32 33 "github.com/defenseunicorns/pkg/helpers" 34 "github.com/gofrs/flock" 35 "github.com/pkg/errors" 36 "github.com/spf13/cobra" 37 "golang.org/x/term" 38 "sigs.k8s.io/yaml" 39 40 "helm.sh/helm/v3/cmd/helm/require" 41 "helm.sh/helm/v3/pkg/getter" 42 "helm.sh/helm/v3/pkg/repo" 43 ) 44 45 // Repositories that have been permanently deleted and no longer work 46 var deprecatedRepos = map[string]string{ 47 "//kubernetes-charts.storage.googleapis.com": "https://charts.helm.sh/stable", 48 "//kubernetes-charts-incubator.storage.googleapis.com": "https://charts.helm.sh/incubator", 49 } 50 51 type repoAddOptions struct { 52 name string 53 url string 54 username string 55 password string 56 passwordFromStdinOpt bool 57 passCredentialsAll bool 58 forceUpdate bool 59 allowDeprecatedRepos bool 60 61 certFile string 62 keyFile string 63 caFile string 64 insecureSkipTLSverify bool 65 66 repoFile string 67 repoCache string 68 69 // Deprecated, but cannot be removed until Helm 4 70 deprecatedNoUpdate bool 71 } 72 73 func newRepoAddCmd(out io.Writer) *cobra.Command { 74 o := &repoAddOptions{} 75 76 cmd := &cobra.Command{ 77 Use: "add [NAME] [URL]", 78 Short: "add a chart repository", 79 Args: require.ExactArgs(2), 80 RunE: func(_ *cobra.Command, args []string) error { 81 o.name = args[0] 82 o.url = args[1] 83 o.repoFile = settings.RepositoryConfig 84 o.repoCache = settings.RepositoryCache 85 86 return o.run(out) 87 }, 88 } 89 90 f := cmd.Flags() 91 f.StringVar(&o.username, "username", "", "chart repository username") 92 f.StringVar(&o.password, "password", "", "chart repository password") 93 f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin") 94 f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists") 95 f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.") 96 f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") 97 f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") 98 f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") 99 f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") 100 f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") 101 f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains") 102 103 return cmd 104 } 105 106 func (o *repoAddOptions) run(out io.Writer) error { 107 // Block deprecated repos 108 if !o.allowDeprecatedRepos { 109 for oldURL, newURL := range deprecatedRepos { 110 if strings.Contains(o.url, oldURL) { 111 return fmt.Errorf("repo %q is no longer available; try %q instead", o.url, newURL) 112 } 113 } 114 } 115 116 // Ensure the file directory exists as it is required for file locking 117 err := os.MkdirAll(filepath.Dir(o.repoFile), os.ModePerm) 118 if err != nil && !os.IsExist(err) { 119 return err 120 } 121 122 // Acquire a file lock for process synchronization 123 repoFileExt := filepath.Ext(o.repoFile) 124 var lockPath string 125 if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) { 126 lockPath = strings.TrimSuffix(o.repoFile, repoFileExt) + ".lock" 127 } else { 128 lockPath = o.repoFile + ".lock" 129 } 130 fileLock := flock.New(lockPath) 131 lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 132 defer cancel() 133 locked, err := fileLock.TryLockContext(lockCtx, time.Second) 134 if err == nil && locked { 135 defer fileLock.Unlock() 136 } 137 if err != nil { 138 return err 139 } 140 141 b, err := os.ReadFile(o.repoFile) 142 if err != nil && !os.IsNotExist(err) { 143 return err 144 } 145 146 var f repo.File 147 if err := yaml.Unmarshal(b, &f); err != nil { 148 return err 149 } 150 151 if o.username != "" && o.password == "" { 152 if o.passwordFromStdinOpt { 153 passwordFromStdin, err := io.ReadAll(os.Stdin) 154 if err != nil { 155 return err 156 } 157 password := strings.TrimSuffix(string(passwordFromStdin), "\n") 158 password = strings.TrimSuffix(password, "\r") 159 o.password = password 160 } else { 161 fd := int(os.Stdin.Fd()) 162 fmt.Fprint(out, "Password: ") 163 password, err := term.ReadPassword(fd) 164 fmt.Fprintln(out) 165 if err != nil { 166 return err 167 } 168 o.password = string(password) 169 } 170 } 171 172 c := repo.Entry{ 173 Name: o.name, 174 URL: o.url, 175 Username: o.username, 176 Password: o.password, 177 PassCredentialsAll: o.passCredentialsAll, 178 CertFile: o.certFile, 179 KeyFile: o.keyFile, 180 CAFile: o.caFile, 181 InsecureSkipTLSverify: o.insecureSkipTLSverify, 182 } 183 184 // Check if the repo name is legal 185 if strings.Contains(o.name, "/") { 186 return errors.Errorf("repository name (%s) contains '/', please specify a different name without '/'", o.name) 187 } 188 189 // If the repo exists do one of two things: 190 // 1. If the configuration for the name is the same continue without error 191 // 2. When the config is different require --force-update 192 if !o.forceUpdate && f.Has(o.name) { 193 existing := f.Get(o.name) 194 if c != *existing { 195 196 // The input coming in for the name is different from what is already 197 // configured. Return an error. 198 return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name) 199 } 200 201 // The add is idempotent so do nothing 202 fmt.Fprintf(out, "%q already exists with the same configuration, skipping\n", o.name) 203 return nil 204 } 205 206 r, err := repo.NewChartRepository(&c, getter.All(settings)) 207 if err != nil { 208 return err 209 } 210 211 if o.repoCache != "" { 212 r.CachePath = o.repoCache 213 } 214 if _, err := r.DownloadIndexFile(); err != nil { 215 return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", o.url) 216 } 217 218 f.Update(&c) 219 220 if err := f.WriteFile(o.repoFile, helpers.ReadWriteUser); err != nil { 221 return err 222 } 223 fmt.Fprintf(out, "%q has been added to your repositories\n", o.name) 224 return nil 225 }