Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Merge pull request #141 from fluxcd/helm-v3/repo-management
Browse files Browse the repository at this point in the history
Support import of repositories for Helm v2 _and_ v3
  • Loading branch information
hiddeco committed Dec 11, 2019
2 parents ca9c8ba + 1f267a7 commit 9752271
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 12 deletions.
40 changes: 31 additions & 9 deletions cmd/helm-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ var (

listenAddr *string

versionedHelmRepositoryIndexes *[]string

enabledHelmVersions *[]string
defaultHelmVersion *string
)
Expand Down Expand Up @@ -120,11 +122,13 @@ func init() {
gitPollInterval = fs.Duration("git-poll-interval", 5*time.Minute, "period on which to poll git chart sources for changes")
gitDefaultRef = fs.String("git-default-ref", "master", "ref to clone chart from if ref is unspecified in a HelmRelease")

versionedHelmRepositoryIndexes = fs.StringSlice("helm-repository-import", nil, "Targeted version and the path of the Helm repository index to import, i.e. v3:/tmp/v3/index.yaml,v2:/tmp/v2/index.yaml")

enabledHelmVersions = fs.StringSlice("enabled-helm-versions", []string{v2.VERSION, v3.VERSION}, "Helm versions supported by this operator instance")
}

func main() {
// Explicitly initialize klog to enable stderr logging,
// explicitly initialize klog to enable stderr logging,
// and parse our own flags.
klog.InitFlags(nil)
fs.Parse(os.Args)
Expand All @@ -134,7 +138,7 @@ func main() {
os.Exit(0)
}

// Support enabling the Helm supported versions through an
// support enabling the Helm supported versions through an
// environment variable.
helmVersionEnv := getEnvAsSlice("HELM_VERSION", []string{})
if len(helmVersionEnv) > 0 && !fs.Changed("enabled-helm-versions") {
Expand Down Expand Up @@ -171,6 +175,7 @@ func main() {

mainLogger := log.With(logger, "component", "helm-operator")

// build Kubernetes clients
cfg, err := clientcmd.BuildConfigFromFlags(*master, *kubeconfig)
if err != nil {
mainLogger.Log("error", fmt.Sprintf("error building kubeconfig: %v", err))
Expand All @@ -189,11 +194,14 @@ func main() {
os.Exit(1)
}

// initialize versioned Helm clients
helmClients := &helm.Clients{}
for _, v := range *enabledHelmVersions {
versionedLogger := log.With(logger, "component", "helm", "version", v)

switch v {
case v2.VERSION:
helmClients.Add(v2.VERSION, v2.New(log.With(logger, "component", "helm", "version", "v2"), kubeClient, v2.TillerOptions{
helmClients.Add(v2.VERSION, v2.New(versionedLogger, kubeClient, v2.TillerOptions{
Host: *tillerIP,
Port: *tillerPort,
Namespace: *tillerNamespace,
Expand All @@ -205,14 +213,10 @@ func main() {
TLSHostname: *tillerTLSHostname,
}))
case v3.VERSION:
client := v3.New(log.With(logger, "component", "helm", "version", "v3"), cfg)
// TODO(hidde): remove hardcoded path
if err := client.(*v3.HelmV3).RepositoryImport("/var/fluxd/helm/repository/repositories.yaml"); err != nil {
mainLogger.Log("warning", "failed to import Helm chart repositories from path", "err", err)
}
client := v3.New(versionedLogger, cfg)
helmClients.Add(v3.VERSION, client)
default:
mainLogger.Log("error", fmt.Sprintf("%s is not a supported Helm version, ignoring...", v))
mainLogger.Log("error", fmt.Sprintf("unsupported Helm version: %s", v))
continue
}

Expand All @@ -222,6 +226,24 @@ func main() {
}
}

// import Helm chart repositories from provided indexes
for _, i := range *versionedHelmRepositoryIndexes {
parts := strings.Split(i, ":")
if len(parts) != 2 {
mainLogger.Log("error", fmt.Sprintf("invalid version/path pair: %s, expected format is [version]:[path]", i))
continue
}
v, p := parts[0], parts[1]
client, ok := helmClients.Load(v)
if !ok {
mainLogger.Log("error", fmt.Sprintf("no Helm client found for version: %s", v))
continue
}
if err := client.RepositoryImport(p); err != nil {
mainLogger.Log("error", fmt.Sprintf("failed to import Helm chart repositories for %s from %s: %v", v, p, err))
}
}

// setup shared informer for HelmReleases
nsOpt := ifinformers.WithNamespace(*namespace)
ifInformerFactory := ifinformers.NewSharedInformerFactoryWithOptions(ifClient, *chartsSyncInterval, nsOpt)
Expand Down
3 changes: 3 additions & 0 deletions docs/references/operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ take action accordingly.
| `--kubeconfig` | | Path to a kubeconfig. Only required if out-of-cluster.
| `--master` | | The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
| `--allow-namespace` | | If set, this limits the scope to a single namespace. if not specified, all namespaces will be watched.
| **Helm options**
| `--enabled-helm-versions` | `v2,v3` | The Helm client versions supported by this operator instance
| `--helm-repository-import` | | Targeted version and the path of the Helm repository index to import, i.e. `v3:/tmp/v3/index.yaml,v2:/tmp/v2/index.yaml`
| **Tiller options**
| `--tiller-ip` | | Tiller IP address. Only required if out-of-cluster.
| `--tiller-port` | | Tiller port.
Expand Down
4 changes: 4 additions & 0 deletions pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type Client interface {
History(releaseName string, opts HistoryOptions) ([]*Release, error)
Rollback(releaseName string, opts RollbackOptions) (*Release, error)
DependencyUpdate(chartPath string) error
RepositoryIndex() error
RepositoryAdd(name, url, username, password, certFile, keyFile, caFile string) error
RepositoryRemove(name string) error
RepositoryImport(path string) error
Uninstall(releaseName string, opts UninstallOptions) error
Version() string
}
21 changes: 18 additions & 3 deletions pkg/helm/v2/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v2
import (
"errors"
"fmt"
"os"
"time"

"github.com/go-kit/kit/log"
Expand All @@ -11,13 +12,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
helmv2 "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/tlsutil"

"github.com/fluxcd/helm-operator/pkg/helm"
)

const VERSION = "v2"

var (
repositoryConfig = helmHome().RepositoryFile()
repositoryCache = helmHome().Cache()
)

// TillerOptions holds configuration options for tiller
type TillerOptions struct {
Host string
Expand All @@ -31,7 +39,7 @@ type TillerOptions struct {
TLSHostname string
}

// HelmV2 provides access to the Client client, while adhering
// HelmV2 provides access to the Helm v2 client, while adhering
// to the generic Client interface
type HelmV2 struct {
client *helmv2.Client
Expand Down Expand Up @@ -97,11 +105,11 @@ func newHelmClient(kubeClient *kubernetes.Clientset, opts TillerOptions) (*helmv
if opts.TLSHostname != "" {
tlsopts.ServerName = opts.TLSHostname
}
tlscfg, err := tlsutil.ClientConfig(tlsopts)
tlsCfg, err := tlsutil.ClientConfig(tlsopts)
if err != nil {
return nil, "", err
}
options = append(options, helmv2.WithTLS(tlscfg))
options = append(options, helmv2.WithTLS(tlsCfg))
}

return helmv2.NewClient(options...), host, nil
Expand All @@ -128,3 +136,10 @@ func statusMessageErr(err error) error {
}
return err
}

func helmHome() helmpath.Home {
if v, ok := os.LookupEnv("HELM_HOME"); ok {
return helmpath.Home(v)
}
return helmpath.Home(environment.DefaultHelmHome)
}
142 changes: 142 additions & 0 deletions pkg/helm/v2/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package v2

import (
"os"
"sync"

"github.com/pkg/errors"

"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/repo"
)

var (
repositoryConfigLock sync.RWMutex
getters = getter.Providers{{
Schemes: []string{"http", "https"},
New: func(URL, CertFile, KeyFile, CAFile string) (getter.Getter, error) {
return getter.NewHTTPGetter(URL, CertFile, KeyFile, CAFile)
},
}}
)

func (h *HelmV2) RepositoryIndex() error {
repositoryConfigLock.RLock()
defer repositoryConfigLock.RUnlock()

f, err := loadRepositoryConfig()
if err != nil {
return err
}

var wg sync.WaitGroup
for _, c := range f.Repositories {
r, err := repo.NewChartRepository(c, getters)
if err != nil {
return err
}
wg.Add(1)
go func(r *repo.ChartRepository) {
if err := r.DownloadIndexFile(repositoryCache); err != nil {
h.logger.Log("error", "unable to get an update from the chart repository", "url", r.Config.URL, "err", err)
} else {
h.logger.Log("info", "successfully got an update from the chart repository", "url", r.Config.URL)
}
wg.Done()
}(r)
}
wg.Wait()
return nil
}

func (h *HelmV2) RepositoryAdd(name, url, username, password, certFile, keyFile, caFile string) error {
repositoryConfigLock.Lock()
defer repositoryConfigLock.Unlock()

f, err := loadRepositoryConfig()
if err != nil {
return err
}

c := &repo.Entry{
Name: name,
URL: url,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,
}
f.Add(c)

if f.Has(name) {
return errors.New("chart repository with name %s already exists")
}

r, err := repo.NewChartRepository(c, getters)
if err != nil {
return err
}
if err = r.DownloadIndexFile(repositoryCache); err != nil {
return err
}

return f.WriteFile(repositoryConfig, 0644)
}

func (h *HelmV2) RepositoryRemove(name string) error {
repositoryConfigLock.Lock()
defer repositoryConfigLock.Unlock()

f, err := repo.LoadRepositoriesFile(repositoryConfig)
if err != nil {
return err
}
f.Remove(name)

return f.WriteFile(repositoryConfig, 0644)
}

func (h *HelmV2) RepositoryImport(path string) error {
s, err := repo.LoadRepositoriesFile(path)
if err != nil {
return err
}

repositoryConfigLock.Lock()
defer repositoryConfigLock.Unlock()

t, err := loadRepositoryConfig()
if err != nil {
return err
}

for _, c := range s.Repositories {
if t.Has(c.Name) {
h.logger.Log("error", "repository with name already exists", "name", c.Name, "url", c.URL)
continue
}
r, err := repo.NewChartRepository(c, getters)
if err != nil {
h.logger.Log("error", err, "name", c.Name, "url", c.URL)
continue
}
if err := r.DownloadIndexFile(repositoryCache); err != nil {
h.logger.Log("error", err, "name", c.Name, "url", c.URL)
continue
}

t.Add(c)
h.logger.Log("info", "successfully imported repository", "name", c.Name, "url", c.URL)
}

return t.WriteFile(repositoryConfig, 0644)
}

func loadRepositoryConfig() (*repo.RepoFile, error) {
r, err := repo.LoadRepositoriesFile(repositoryConfig)
if err != nil && !os.IsNotExist(errors.Cause(err)) {
return nil, err
}
return r, nil
}

0 comments on commit 9752271

Please sign in to comment.