diff --git a/provider/helm/helm.go b/provider/helm/helm.go index db32acda..6399e38a 100644 --- a/provider/helm/helm.go +++ b/provider/helm/helm.go @@ -2,57 +2,179 @@ package helm import ( "fmt" - "os" - "k8s.io/helm/pkg/helm" - helm_env "k8s.io/helm/pkg/helm/environment" + "github.com/rusenask/keel/types" + "github.com/rusenask/keel/util/image" + + hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" rls "k8s.io/helm/pkg/proto/hapi/services" - "k8s.io/helm/pkg/tlsutil" + + log "github.com/Sirupsen/logrus" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm" ) -var ( - tlsCaCertFile string // path to TLS CA certificate file - tlsCertFile string // path to TLS certificate file - tlsKeyFile string // path to TLS key file - tlsVerify bool // enable TLS and verify remote certificates - tlsEnable bool // enable TLS +const ProviderName = "helm" - // kubeContext string - // tillerTunnel *kube.Tunnel - settings helm_env.EnvSettings -) +type Provider struct { + implementer Implementer -func newClient() helm.Interface { - options := []helm.Option{helm.Host(settings.TillerHost)} + events chan *types.Event + stop chan struct{} +} - if tlsVerify || tlsEnable { - tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true} - if tlsVerify { - tlsopts.CaCertFile = tlsCaCertFile - tlsopts.InsecureSkipVerify = false +func NewProvider(implementer Implementer) *Provider { + return &Provider{ + implementer: implementer, + events: make(chan *types.Event, 100), + stop: make(chan struct{}), + } +} + +func (p *Provider) GetName() string { + return ProviderName +} + +// Submit - submit event to provider +func (p *Provider) Submit(event types.Event) error { + p.events <- &event + return nil +} + +// Start - starts kubernetes provider, waits for events +func (p *Provider) Start() error { + return p.startInternal() +} + +// Stop - stops kubernetes provider +func (p *Provider) Stop() { + close(p.stop) +} + +func (p *Provider) startInternal() error { + for { + select { + case event := <-p.events: + log.WithFields(log.Fields{ + "repository": event.Repository.Name, + "tag": event.Repository.Tag, + "registry": event.Repository.Host, + }).Info("provider.helm: processing event") + err := p.processEvent(event) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "image": event.Repository.Name, + "tag": event.Repository.Tag, + }).Error("provider.helm: failed to process event") + } + case <-p.stop: + log.Info("provider.helm: got shutdown signal, stopping...") + return nil } - tlscfg, err := tlsutil.ClientConfig(tlsopts) + } +} + +func (p *Provider) processEvent(event *types.Event) (err error) { + + return nil +} + +func (p *Provider) getImpactedReleases(event *types.Event) ([]*rls.ListReleasesResponse, error) { + releaseList, err := p.implementer.ListReleases() + if err != nil { + return nil, err + } + + for _, release := range releaseList.Releases { + + ref, err := parseImage(release.Chart, release.Config) if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) + log.WithFields(log.Fields{ + "error": err, + }).Error("provider.helm: failed to get image name and tag from release") + continue } - options = append(options, helm.WithTLS(tlscfg)) + log.WithFields(log.Fields{ + "parsed_image_named": ref.Remote(), + }).Info("provider.helm: checking image") + } - return helm.NewClient(options...) + + return nil, nil } -type HelmImplementer struct { - client helm.Interface -} +// resp, err := u.client.UpdateRelease( +// u.release, +// chartPath, +// helm.UpdateValueOverrides(rawVals), +// helm.UpgradeDryRun(u.dryRun), +// helm.UpgradeRecreate(u.recreate), +// helm.UpgradeForce(u.force), +// helm.UpgradeDisableHooks(u.disableHooks), +// helm.UpgradeTimeout(u.timeout), +// helm.ResetValues(u.resetValues), +// helm.ReuseValues(u.reuseValues), +// helm.UpgradeWait(u.wait)) +// if err != nil { +// return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) +// } -func NewHelmImplementer() *HelmImplementer { - client := newClient() +func updateHelmRelease(implementer Implementer, releaseName string, chart *hapi_chart.Chart, rawVals string) error { - return &HelmImplementer{ - client: client, + resp, err := implementer.UpdateReleaseFromChart(releaseName, chart, + helm.UpdateValueOverrides([]byte(rawVals)), + helm.UpgradeDryRun(false), + helm.UpgradeRecreate(false), + helm.UpgradeForce(true), + helm.UpgradeDisableHooks(false), + helm.UpgradeTimeout(30), + helm.ResetValues(false), + helm.ReuseValues(true), + helm.UpgradeWait(true)) + + if err != nil { + return err } + + log.WithFields(log.Fields{ + "version": resp.Release.Version, + "release": releaseName, + }).Info("provider.helm: release updated") + return nil } -func (i *HelmImplementer) ListReleases(opts ...helm.ReleaseListOption) (*rls.ListReleasesResponse, error) { - return i.client.ListReleases(opts...) +func parseImage(chart *hapi_chart.Chart, config *hapi_chart.Config) (*image.Reference, error) { + vals, err := chartutil.ReadValues([]byte(config.Raw)) + if err != nil { + return nil, err + } + + log.Info(config.Raw) + + imageName, err := vals.PathValue("image.repository") + if err != nil { + return nil, err + } + + imageTag, err := vals.PathValue("image.tag") + if err != nil { + return nil, fmt.Errorf("failed to get image tag: %s", err) + } + + imageNameStr, ok := imageName.(string) + if !ok { + return nil, fmt.Errorf("failed to convert image name ref to string") + } + + imageTagStr, ok := imageTag.(string) + if !ok { + return nil, fmt.Errorf("failed to convert image tag ref to string") + } + + if imageTagStr != "" { + return image.Parse(imageNameStr + ":" + imageTagStr) + } + + return image.Parse(imageNameStr) } diff --git a/provider/helm/helm_test.go b/provider/helm/helm_test.go index 12e4833f..806c44b8 100644 --- a/provider/helm/helm_test.go +++ b/provider/helm/helm_test.go @@ -1,17 +1,52 @@ package helm import ( + "fmt" + "testing" ) -func TestImplementerList(t *testing.T) { - imp := NewHelmImplementer() +func TestParseImage(t *testing.T) { + imp := NewHelmImplementer("192.168.99.100:30083") + releases, err := imp.ListReleases() if err != nil { t.Fatalf("unexpected error: %s", err) } - if releases.Count == 0 { - t.Errorf("why no releases? ") + fmt.Println(releases.Count) + + for _, release := range releases.Releases { + ref, err := parseImage(release.Chart, release.Config) + if err != nil { + t.Errorf("failed to parse image, error: %s", err) + } + + fmt.Println(ref.Remote()) + } +} + +func TestUpdateRelease(t *testing.T) { + imp := NewHelmImplementer("192.168.99.100:30083") + + releases, err := imp.ListReleases() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + for _, release := range releases.Releases { + + ref, err := parseImage(release.Chart, release.Config) + if err != nil { + t.Errorf("failed to parse image, error: %s", err) + } + + fmt.Println(ref.Remote()) + + err = updateHelmRelease(imp, release.Name, release.Chart, "image.tag=0.0.11") + + if err != nil { + t.Errorf("failed to update release, error: %s", err) + } } } diff --git a/provider/helm/implementer.go b/provider/helm/implementer.go index 9a90ecfa..0c26f92c 100644 --- a/provider/helm/implementer.go +++ b/provider/helm/implementer.go @@ -1 +1,38 @@ package helm + +import ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + rls "k8s.io/helm/pkg/proto/hapi/services" +) + +var ( + TillerAddress = "tiller-deploy:44134" +) + +type Implementer interface { + ListReleases(opts ...helm.ReleaseListOption) (*rls.ListReleasesResponse, error) + UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...helm.UpdateOption) (*rls.UpdateReleaseResponse, error) +} + +type HelmImplementer struct { + client helm.Interface +} + +func NewHelmImplementer(address string) *HelmImplementer { + if address == "" { + address = TillerAddress + } + + return &HelmImplementer{ + client: helm.NewClient(helm.Host(address)), + } +} + +func (i *HelmImplementer) ListReleases(opts ...helm.ReleaseListOption) (*rls.ListReleasesResponse, error) { + return i.client.ListReleases(opts...) +} + +func (i *HelmImplementer) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...helm.UpdateOption) (*rls.UpdateReleaseResponse, error) { + return i.client.UpdateReleaseFromChart(rlsName, chart, opts...) +}