Skip to content

Commit

Permalink
feat: support oci charts
Browse files Browse the repository at this point in the history
Signed-off-by: Luis Davim <luis.davim@jet.com>
  • Loading branch information
Luis Davim committed May 19, 2021
1 parent 039f714 commit f2890f4
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/how_to/helm_repos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Following list contains guides on how to define helm repositories
- [Using private repos with basic auth](basic_auth.md)
- [Using pre-configured repos](pre_configured.md)
- [Using local charts](local.md)
- [Using OCI registries](oci.md)
34 changes: 34 additions & 0 deletions docs/how_to/helm_repos/oci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
version: v3.7.0
---

# Using OCI registries for helm charts

Helmsman allows you to use charts stored in OCI registries.

You need to export the following env variables:

- `HELM_EXPERIMENTAL_OCI=1`

if the registry requires authentication, you must login before running Helmsman

```sh
helm registry login -u myuser my-registry.local
```

```toml
[apps]
[apps.my-app]
chart = "oci://my-registry.local/my-chart"
version = "1.0.0"
```

```yaml
#...
apps:
my-app:
chart: oci://my-registry.local/my-chart
version: 1.0.0
```
For more information, read the [helm registries documentation](https://helm.sh/docs/topics/registries/).
13 changes: 13 additions & 0 deletions internal/app/helm_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ func updateChartDep(chartPath string) error {
return nil
}

// helmExportChart pulls chart and exports it to the specified destination
func helmExportChart(chart, dest string) error {
cmd := helmCmd([]string{"chart", "pull", chart}, "Pulling chart [ "+chart+" ] to local registry cache")
if _, err := cmd.Exec(); err != nil {
return err
}
cmd = helmCmd([]string{"chart", "export", chart, "-d", dest}, "Exporting chart [ "+chart+" ] to "+dest)
if _, err := cmd.Exec(); err != nil {
return err
}
return nil
}

// addHelmRepos adds repositories to Helm if they don't exist already.
// Helm does not mind if a repo with the same name exists. It treats it as an update.
func addHelmRepos(repos map[string]string) error {
Expand Down
5 changes: 4 additions & 1 deletion internal/app/state_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ func (s *state) expand(relativeToFile string) {
repoName := strings.Split(r.Chart, "/")[0]
_, isRepo := s.HelmRepos[repoName]
isRepo = isRepo || stringInSlice(repoName, s.PreconfiguredHelmRepos)
// if there is no repo for the chart, we assume it's intended to be a local path
// if there is no repo for the chart, we assume it's intended to be a local path or url
if !isRepo {
if strings.HasPrefix(r.Chart, "oci://") && !strings.HasSuffix(r.Chart, r.Version) {
r.Chart = fmt.Sprintf("%s:%s", r.Chart, r.Version)
}
r.Chart, _ = resolveOnePath(r.Chart, dir, downloadDest)
}
}
Expand Down
36 changes: 25 additions & 11 deletions internal/app/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ func stringInSlice(a string, list []string) bool {
// and downloads/fetches the file locally into helmsman temp directory and returns
// its absolute path
func resolveOnePath(file string, dir string, downloadDest string) (string, error) {
if destFile, err := ioutil.TempFile(downloadDest, fmt.Sprintf("*%s", path.Base(file))); err != nil {
destFile, err := ioutil.TempFile(downloadDest, fmt.Sprintf("*%s", path.Base(file)))
if err != nil {
return "", err
} else {
_ = destFile.Close()
return filepath.Abs(downloadFile(file, dir, destFile.Name()))
}
destFile.Close()
return filepath.Abs(downloadFile(file, dir, destFile.Name()))
}

// createTempDir creates a temp directory in a specific location with a pattern
Expand Down Expand Up @@ -208,6 +208,11 @@ func downloadFile(file string, dir string, outfile string) string {
}

switch u.Scheme {
case "oci":
dest := filepath.Dir(outfile)
fileName := strings.Split(filepath.Base(file), ":")[0]
helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest)
return filepath.Join(dest, fileName)
case "https", "http":
if err := downloadFileFromURL(file, outfile); err != nil {
log.Fatal(err.Error())
Expand Down Expand Up @@ -446,18 +451,27 @@ func isLocalChart(chart string) bool {

// isValidCert checks if a certificate/key path/URI is valid
func isValidCert(value string) bool {
if _, err := os.Stat(value); err != nil {
_, err1 := url.ParseRequestURI(value)
if err1 != nil || (!strings.HasPrefix(value, "s3://") && !strings.HasPrefix(value, "gs://") && !strings.HasPrefix(value, "az://")) {
if _, err := os.Stat(value); err == nil {
return true
}
u, err := url.ParseRequestURI(value)
if err != nil {
return false
}
switch u.Scheme {
case "http", "https", "s3", "gs", "az":
if !isOfType(u.Path, []string{".cert", ".key", ".pem", ".crt"}) {
return false
}
return true
default:
return false
}
return true
}

// isValidFile checks if the file exists in the given path or accessible via http and is of allowed file extension (e.g. yaml, json ...)
func isValidFile(filePath string, allowedFileTypes []string) error {
if strings.HasPrefix(filePath, "http") {
if strings.HasPrefix(filePath, "http") || strings.HasPrefix(filePath, "s3://") || strings.HasPrefix(filePath, "gs://") || strings.HasPrefix(filePath, "az://") {
if _, err := url.ParseRequestURI(filePath); err != nil {
return fmt.Errorf("%s must be valid URL path to a raw file", filePath)
}
Expand All @@ -475,11 +489,11 @@ func checkVersion(version, constraint string) bool {
return false
}

jsonConstraint, err := semver.NewConstraint(constraint)
c, err := semver.NewConstraint(constraint)
if err != nil {
return false
}
return jsonConstraint.Check(v)
return c.Check(v)
}

// notify MSTeams sends a JSON formatted message to MSTeams channel over a webhook url
Expand Down

0 comments on commit f2890f4

Please sign in to comment.