github.com/theishshah/operator-sdk@v0.6.0/pkg/scaffold/helm/chart.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 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 package helm 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "github.com/operator-framework/operator-sdk/pkg/scaffold" 25 26 "github.com/iancoleman/strcase" 27 log "github.com/sirupsen/logrus" 28 "k8s.io/helm/pkg/chartutil" 29 "k8s.io/helm/pkg/downloader" 30 "k8s.io/helm/pkg/getter" 31 "k8s.io/helm/pkg/helm/environment" 32 "k8s.io/helm/pkg/helm/helmpath" 33 "k8s.io/helm/pkg/proto/hapi/chart" 34 "k8s.io/helm/pkg/repo" 35 ) 36 37 const ( 38 39 // HelmChartsDir is the relative directory within an SDK project where Helm 40 // charts are stored. 41 HelmChartsDir string = "helm-charts" 42 43 // DefaultAPIVersion is the Kubernetes CRD API Version used for fetched 44 // charts when the --api-version flag is not specified 45 DefaultAPIVersion string = "charts.helm.k8s.io/v1alpha1" 46 ) 47 48 // CreateChartOptions is used to configure how a Helm chart is scaffolded 49 // for a new Helm operator project. 50 type CreateChartOptions struct { 51 // ResourceAPIVersion defines the Kubernetes GroupVersion to be associated 52 // with the created chart. 53 ResourceAPIVersion string 54 55 // ResourceKind defines the Kubernetes Kind to be associated with the 56 // created chart. 57 ResourceKind string 58 59 // Chart is a chart reference for a local or remote chart. 60 Chart string 61 62 // Repo is a URL to a custom chart repository. 63 Repo string 64 65 // Version is the version of the chart to fetch. 66 Version string 67 } 68 69 // CreateChart scaffolds a new helm chart for the project rooted in projectDir 70 // based on the passed opts. 71 // 72 // It returns a scaffold.Resource that can be used by the caller to create 73 // other related files. opts.ResourceAPIVersion and opts.ResourceKind are 74 // used to create the resource and must be specified if opts.Chart is empty. 75 // 76 // If opts.Chart is not empty, opts.ResourceAPIVersion and opts.Kind can be 77 // left unset: opts.ResourceAPIVersion defaults to "charts.helm.k8s.io/v1alpha1" 78 // and opts.ResourceKind is deduced from the specified opts.Chart. 79 // 80 // CreateChart also returns a chart.Chart that references the newly created 81 // chart. 82 // 83 // If opts.Chart is empty, CreateChart scaffolds the default chart from helm's 84 // default template. 85 // 86 // If opts.Chart is a local file, CreateChart verifies that it is a valid helm 87 // chart archive and unpacks it into the project's helm charts directory. 88 // 89 // If opts.Chart is a local directory, CreateChart verifies that it is a valid 90 // helm chart directory and copies it into the project's helm charts directory. 91 // 92 // For any other value of opts.Chart, CreateChart attempts to fetch the helm chart 93 // from a remote repository. 94 // 95 // If opts.Repo is not specified, the following chart reference formats are supported: 96 // 97 // - <repoName>/<chartName>: Fetch the helm chart named chartName from the helm 98 // chart repository named repoName, as specified in the 99 // $HELM_HOME/repositories/repositories.yaml file. 100 // 101 // - <url>: Fetch the helm chart archive at the specified URL. 102 // 103 // If opts.Repo is specified, only one chart reference format is supported: 104 // 105 // - <chartName>: Fetch the helm chart named chartName in the helm chart repository 106 // specified by opts.Repo 107 // 108 // If opts.Version is not set, CreateChart will fetch the latest available version of 109 // the helm chart. Otherwise, CreateChart will fetch the specified version. 110 // opts.Version is not used when opts.Chart itself refers to a specific version, for 111 // example when it is a local path or a URL. 112 // 113 // CreateChart returns an error if an error occurs creating the scaffold.Resource or 114 // creating the chart. 115 func CreateChart(projectDir string, opts CreateChartOptions) (*scaffold.Resource, *chart.Chart, error) { 116 chartsDir := filepath.Join(projectDir, HelmChartsDir) 117 err := os.MkdirAll(chartsDir, 0755) 118 if err != nil { 119 return nil, nil, err 120 } 121 122 var ( 123 r *scaffold.Resource 124 c *chart.Chart 125 ) 126 127 // If we don't have a helm chart reference, scaffold the default chart 128 // from Helm's default template. Otherwise, fetch it. 129 if len(opts.Chart) == 0 { 130 r, c, err = scaffoldChart(chartsDir, opts.ResourceAPIVersion, opts.ResourceKind) 131 } else { 132 r, c, err = fetchChart(chartsDir, opts) 133 } 134 if err != nil { 135 return nil, nil, err 136 } 137 log.Infof("Created %s/%s/", HelmChartsDir, c.GetMetadata().GetName()) 138 return r, c, nil 139 } 140 141 func scaffoldChart(destDir, apiVersion, kind string) (*scaffold.Resource, *chart.Chart, error) { 142 r, err := scaffold.NewResource(apiVersion, kind) 143 if err != nil { 144 return nil, nil, err 145 } 146 147 chartfile := &chart.Metadata{ 148 // Many helm charts use hyphenated names, but we chose not to because 149 // of the issues related to how hyphens are interpreted in templates. 150 // See https://github.com/helm/helm/issues/2192 151 Name: r.LowerKind, 152 Description: "A Helm chart for Kubernetes", 153 Version: "0.1.0", 154 AppVersion: "1.0", 155 ApiVersion: chartutil.ApiVersionV1, 156 } 157 chartPath, err := chartutil.Create(chartfile, destDir) 158 if err != nil { 159 return nil, nil, err 160 } 161 162 chart, err := chartutil.LoadDir(chartPath) 163 if err != nil { 164 return nil, nil, err 165 } 166 return r, chart, nil 167 } 168 169 func fetchChart(destDir string, opts CreateChartOptions) (*scaffold.Resource, *chart.Chart, error) { 170 var ( 171 stat os.FileInfo 172 chart *chart.Chart 173 err error 174 ) 175 176 if stat, err = os.Stat(opts.Chart); err == nil { 177 chart, err = createChartFromDisk(destDir, opts.Chart, stat.IsDir()) 178 } else { 179 chart, err = createChartFromRemote(destDir, opts) 180 } 181 if err != nil { 182 return nil, nil, err 183 } 184 185 chartName := chart.GetMetadata().GetName() 186 if len(opts.ResourceAPIVersion) == 0 { 187 opts.ResourceAPIVersion = DefaultAPIVersion 188 } 189 if len(opts.ResourceKind) == 0 { 190 opts.ResourceKind = strcase.ToCamel(chartName) 191 } 192 193 r, err := scaffold.NewResource(opts.ResourceAPIVersion, opts.ResourceKind) 194 if err != nil { 195 return nil, nil, err 196 } 197 return r, chart, nil 198 } 199 200 func createChartFromDisk(destDir, source string, isDir bool) (*chart.Chart, error) { 201 var ( 202 chart *chart.Chart 203 err error 204 ) 205 206 // If source is a file or directory, attempt to load it 207 if isDir { 208 chart, err = chartutil.LoadDir(source) 209 } else { 210 chart, err = chartutil.LoadFile(source) 211 } 212 if err != nil { 213 return nil, err 214 } 215 216 // Save it into our project's helm-charts directory. 217 if err := chartutil.SaveDir(chart, destDir); err != nil { 218 return nil, err 219 } 220 return chart, nil 221 } 222 223 func createChartFromRemote(destDir string, opts CreateChartOptions) (*chart.Chart, error) { 224 helmHome, ok := os.LookupEnv(environment.HomeEnvVar) 225 if !ok { 226 helmHome = environment.DefaultHelmHome 227 } 228 getters := getter.All(environment.EnvSettings{}) 229 c := downloader.ChartDownloader{ 230 HelmHome: helmpath.Home(helmHome), 231 Out: os.Stderr, 232 Getters: getters, 233 } 234 235 if opts.Repo != "" { 236 chartURL, err := repo.FindChartInRepoURL(opts.Repo, opts.Chart, opts.Version, "", "", "", getters) 237 if err != nil { 238 return nil, err 239 } 240 opts.Chart = chartURL 241 } 242 243 tmpDir, err := ioutil.TempDir("", "osdk-helm-chart") 244 if err != nil { 245 return nil, err 246 } 247 defer func() { 248 if err := os.RemoveAll(tmpDir); err != nil { 249 log.Errorf("Failed to remove temporary directory %s: %s", tmpDir, err) 250 } 251 }() 252 253 chartArchive, _, err := c.DownloadTo(opts.Chart, opts.Version, tmpDir) 254 if err != nil { 255 // One of Helm's error messages directs users to run `helm init`, which 256 // installs tiller in a remote cluster. Since that's unnecessary and 257 // unhelpful, modify the error message to be relevant for operator-sdk. 258 if strings.Contains(err.Error(), "Couldn't load repositories file") { 259 return nil, fmt.Errorf("failed to load repositories file %s "+ 260 "(you might need to run `helm init --client-only` "+ 261 "to create and initialize it)", c.HelmHome.RepositoryFile()) 262 } 263 return nil, err 264 } 265 266 return createChartFromDisk(destDir, chartArchive, false) 267 }