commit
3198921af0
3
Makefile
3
Makefile
|
@ -77,7 +77,8 @@ alpha: image
|
|||
e2e: install
|
||||
cd tests && go test
|
||||
|
||||
run: install
|
||||
run:
|
||||
go install github.com/keel-hq/keel/cmd/keel
|
||||
keel --no-incluster --ui-dir ui/dist
|
||||
|
||||
lint-ui:
|
||||
|
|
|
@ -90,4 +90,3 @@ func (c *ApprovalContext) Created() string {
|
|||
c.AddHeader(ApprovalCreatedHeader)
|
||||
return c.v.CreatedAt.String()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package hipchat
|
|||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/daneharrigan/hipchat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type XmppImplementer interface {
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"github.com/keel-hq/keel/bot"
|
||||
"github.com/keel-hq/keel/constants"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
h "github.com/daneharrigan/hipchat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const connectionAttemptsDefault = 5
|
||||
|
|
|
@ -14,27 +14,27 @@ func (b *Bot) RequestApproval(req *types.Approval) error {
|
|||
req.Message,
|
||||
types.LevelSuccess.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Approval required!",
|
||||
Value: req.Message + "\n" + fmt.Sprintf("To vote for change type '%s approve %s' to reject it: '%s reject %s'.", b.name, req.Identifier, b.name, req.Identifier),
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", req.VotesReceived, req.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Delta",
|
||||
Value: req.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Identifier",
|
||||
Value: req.Identifier,
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Provider",
|
||||
Value: req.Provider.String(),
|
||||
Short: true,
|
||||
|
@ -50,22 +50,22 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
|||
"All approvals received, thanks for voting!",
|
||||
types.LevelInfo.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "vote received!",
|
||||
Value: "Waiting for remaining votes.",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
|
@ -77,27 +77,27 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
|||
"Change was rejected",
|
||||
types.LevelWarn.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "change rejected",
|
||||
Value: "Change was rejected.",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Status",
|
||||
Value: approval.Status().String(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
|
@ -109,22 +109,22 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
|||
"All approvals received, thanks for voting!",
|
||||
types.LevelSuccess.Color(),
|
||||
[]slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "update approved!",
|
||||
Value: "All approvals received, thanks for voting!",
|
||||
Short: false,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Votes",
|
||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Delta",
|
||||
Value: approval.Delta(),
|
||||
Short: true,
|
||||
},
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: "Identifier",
|
||||
Value: approval.Identifier,
|
||||
Short: true,
|
||||
|
|
|
@ -150,7 +150,7 @@ func (b *Bot) postMessage(title, message, color string, fields []slack.Attachmen
|
|||
params.IconURL = b.getBotUserIconURL()
|
||||
|
||||
attachements := []slack.Attachment{
|
||||
slack.Attachment{
|
||||
{
|
||||
Fallback: message,
|
||||
Color: color,
|
||||
Fields: fields,
|
||||
|
|
|
@ -350,7 +350,7 @@ func TestProcessRejectedReply(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsApproval(t *testing.T) {
|
||||
|
||||
|
||||
event := &slack.MessageEvent{
|
||||
Msg: slack.Msg{
|
||||
Channel: "approvals",
|
||||
|
|
|
@ -3,7 +3,7 @@ name: keel
|
|||
description: Open source, tool for automating Kubernetes deployment updates. Keel is stateless, robust and lightweight.
|
||||
version: 0.8.22
|
||||
# Note that we use appVersion to get images tag, so make sure this is correct.
|
||||
appVersion: 0.16.0
|
||||
appVersion: 0.16.1
|
||||
keywords:
|
||||
- kubernetes deployment
|
||||
- helm release
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
{{- if .Values.rbac.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
# apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ template "keel.name" . }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ template "keel.name" . }}
|
||||
# name: {{ template "keel.name" . }}
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ template "serviceAccount.name" . }}
|
||||
|
|
|
@ -53,27 +53,33 @@ spec:
|
|||
{{- if .Values.polling.enabled }}
|
||||
# Enable polling
|
||||
- name: POLL
|
||||
value: "1"
|
||||
value: "true"
|
||||
{{- else }}
|
||||
# Disable polling
|
||||
- name: POLL
|
||||
value: "0"
|
||||
value: "false"
|
||||
{{- end }}
|
||||
{{- if .Values.helmProvider.enabled }}
|
||||
{{- if eq .Values.helmProvider.version "v2" }}
|
||||
# Enable/disable Helm provider
|
||||
- name: HELM_PROVIDER
|
||||
value: "1"
|
||||
value: "true"
|
||||
- name: TILLER_NAMESPACE
|
||||
value: "{{ .Values.helmProvider.tillerNamespace }}"
|
||||
- name: TILLER_ADDRESS
|
||||
value: "{{ .Values.helmProvider.tillerAddress }}"
|
||||
{{- else if eq .Values.helmProvider.version "v3" }}
|
||||
# Enable/disable Helm provider
|
||||
- name: HELM3_PROVIDER
|
||||
value: "true"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.gcr.enabled }}
|
||||
# Enable GCR with pub/sub support
|
||||
- name: PROJECT_ID
|
||||
value: "{{ .Values.gcr.projectId }}"
|
||||
- name: PUBSUB
|
||||
value: "1"
|
||||
value: "true"
|
||||
{{- if .Values.gcr.clusterName }}
|
||||
# Customize the cluster name, mainly useful when outside of GKE
|
||||
- name: CLUSTER_NAME
|
||||
|
|
|
@ -18,6 +18,8 @@ polling:
|
|||
# Helm provider support
|
||||
helmProvider:
|
||||
enabled: true
|
||||
# set to version "v3" for Helm v3
|
||||
version: "v2"
|
||||
tillerNamespace: "kube-system"
|
||||
# optional Tiller address (if portforwarder tunnel doesn't work),
|
||||
# if you are using default configuration, setting it to
|
||||
|
@ -126,17 +128,17 @@ secret:
|
|||
# Keel self-update
|
||||
# uncomment lines below if you want Keel to automaticly
|
||||
# self-update to the latest release version
|
||||
keel:
|
||||
# keel policy (all/major/minor/patch/force)
|
||||
policy: patch
|
||||
# trigger type, defaults to events such as pubsub, webhooks
|
||||
trigger: poll
|
||||
# polling schedule
|
||||
pollSchedule: "@every 3m"
|
||||
# images to track and update
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
# keel:
|
||||
# # keel policy (all/major/minor/patch/force)
|
||||
# policy: patch
|
||||
# # trigger type, defaults to events such as pubsub, webhooks
|
||||
# trigger: poll
|
||||
# # polling schedule
|
||||
# pollSchedule: "@every 3m"
|
||||
# # images to track and update
|
||||
# images:
|
||||
# - repository: image.repository
|
||||
# tag: image.tag
|
||||
|
||||
# RBAC manifests management
|
||||
rbac:
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
netContext "golang.org/x/net/context"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
kube "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
|
@ -32,6 +31,7 @@ import (
|
|||
"github.com/keel-hq/keel/internal/workgroup"
|
||||
"github.com/keel-hq/keel/provider"
|
||||
"github.com/keel-hq/keel/provider/helm"
|
||||
"github.com/keel-hq/keel/provider/helm3"
|
||||
"github.com/keel-hq/keel/provider/kubernetes"
|
||||
"github.com/keel-hq/keel/registry"
|
||||
"github.com/keel-hq/keel/secrets"
|
||||
|
@ -58,6 +58,9 @@ import (
|
|||
_ "github.com/keel-hq/keel/bot/slack"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
// importing to ensure correct dependencies
|
||||
_ "helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
// gcloud pubsub related config
|
||||
|
@ -70,6 +73,7 @@ const (
|
|||
EnvHelmProvider = "HELM_PROVIDER" // helm provider
|
||||
EnvHelmTillerAddress = "TILLER_ADDRESS" // helm provider
|
||||
EnvHelmTillerNamespace = "TILLER_NAMESPACE" // helm provider
|
||||
EnvHelm3Provider = "HELM3_PROVIDER" // helm3 provider
|
||||
EnvUIDir = "UI_DIR"
|
||||
|
||||
// EnvDefaultDockerRegistryCfg - default registry configuration that can be passed into
|
||||
|
@ -134,7 +138,7 @@ func main() {
|
|||
notification.RegisterSender("auditor", auditLogger)
|
||||
|
||||
// setting up triggers
|
||||
ctx, cancel := netContext.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
notificationLevel := types.LevelInfo
|
||||
|
@ -355,6 +359,23 @@ func setupProviders(opts *ProviderOpts) (providers provider.Providers) {
|
|||
enabledProviders = append(enabledProviders, helmProvider)
|
||||
}
|
||||
|
||||
if os.Getenv(EnvHelm3Provider) == "1" || os.Getenv(EnvHelm3Provider) == "true" {
|
||||
helm3Implementer := helm3.NewHelm3Implementer()
|
||||
helm3Provider := helm3.NewProvider(helm3Implementer, opts.sender, opts.approvalsManager)
|
||||
|
||||
go func() {
|
||||
err := helm3Provider.Start()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Fatal("helm3 provider stopped with an error")
|
||||
}
|
||||
}()
|
||||
|
||||
enabledProviders = append(enabledProviders, helm3Provider)
|
||||
|
||||
}
|
||||
|
||||
providers = provider.New(enabledProviders, opts.approvalsManager)
|
||||
|
||||
return providers
|
||||
|
@ -426,7 +447,7 @@ func setupTriggers(ctx context.Context, opts *TriggerOpts) (teardown func()) {
|
|||
go subManager.Start(ctx)
|
||||
}
|
||||
|
||||
if os.Getenv(EnvTriggerPoll) != "0" {
|
||||
if os.Getenv(EnvTriggerPoll) != "0" || os.Getenv(EnvTriggerPoll) != "false" {
|
||||
|
||||
registryClient := registry.New()
|
||||
watcher := poll.NewRepositoryWatcher(opts.providers, registryClient)
|
||||
|
|
|
@ -92,11 +92,11 @@ func (s *sender) Send(event types.EventNotification) error {
|
|||
params.IconURL = constants.KeelLogoURL
|
||||
|
||||
attachements := []slack.Attachment{
|
||||
slack.Attachment{
|
||||
{
|
||||
Fallback: event.Message,
|
||||
Color: event.Level.Color(),
|
||||
Fields: []slack.AttachmentField{
|
||||
slack.AttachmentField{
|
||||
{
|
||||
Title: event.Type.String(),
|
||||
Value: event.Message,
|
||||
Short: false,
|
||||
|
|
184
go.mod
184
go.mod
|
@ -2,119 +2,73 @@ module github.com/keel-hq/keel
|
|||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.40.0
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e
|
||||
github.com/Masterminds/goutils v1.1.0
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/Masterminds/sprig v2.19.0+incompatible
|
||||
github.com/PuerkitoBio/purell v1.1.1
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
|
||||
github.com/aws/aws-sdk-go v1.20.0
|
||||
github.com/beorn7/perks v1.0.0
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/daneharrigan/hipchat v0.0.0-20170512185232-835dc879394a
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/docker/distribution v2.6.0-rc.1.0.20180227233429-6fca8d6e6713+incompatible
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20180612054059-a9fbbdc8dd87+incompatible
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible
|
||||
github.com/evanphx/json-patch v3.0.0+incompatible
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-openapi/jsonpointer v0.19.0
|
||||
github.com/go-openapi/jsonreference v0.19.0
|
||||
github.com/go-openapi/spec v0.19.0
|
||||
github.com/go-openapi/swag v0.19.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogo/protobuf v1.2.1
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/google/btree v1.0.0
|
||||
github.com/google/go-querystring v1.0.0
|
||||
github.com/google/gofuzz v1.0.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/googleapis/gnostic v0.2.0
|
||||
github.com/gorilla/mux v1.7.2
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||
github.com/hashicorp/golang-lru v0.5.1
|
||||
github.com/huandu/xstrings v1.2.0
|
||||
github.com/imdario/mergo v0.3.7
|
||||
github.com/inconshreveable/mousetrap v1.0.0
|
||||
github.com/jinzhu/gorm v1.9.9
|
||||
github.com/jinzhu/inflection v1.0.0
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/json-iterator/go v1.1.6
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2
|
||||
github.com/lib/pq v1.1.1
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||
github.com/mfridman/tparse v0.7.4 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||
github.com/modern-go/reflect2 v1.0.1
|
||||
github.com/nlopes/slack v0.5.0
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v0.9.4
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
||||
github.com/prometheus/common v0.4.1
|
||||
github.com/prometheus/procfs v0.0.2
|
||||
github.com/rubenv/sql-migrate v0.0.0-20190327083759-54bad0a9b051
|
||||
github.com/rusenask/cron v1.1.0
|
||||
github.com/rusenask/docker-registry-client v0.0.0-20190426095143-dbc590492100
|
||||
github.com/russross/blackfriday v1.5.2
|
||||
github.com/ryanuber/go-glob v0.0.0-20160226084822-572520ed46db
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/tbruyelle/hipchat-go v0.0.0-20160921153256-749fb9e14beb
|
||||
github.com/urfave/negroni v0.3.0
|
||||
go.opencensus.io v0.22.0
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/sys v0.0.0-20190613124609-5ed2794edfdc
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
google.golang.org/api v0.6.0
|
||||
google.golang.org/appengine v1.6.1
|
||||
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3
|
||||
google.golang.org/grpc v1.21.1
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/gorp.v1 v1.7.2
|
||||
gopkg.in/inf.v0 v0.9.1
|
||||
gopkg.in/square/go-jose.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
k8s.io/api v0.0.0-20190516230258-a675ac48af67
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190516231611-bf6753f2aa24
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||
k8s.io/apiserver v0.0.0-20190516230822-f89599b3f645
|
||||
k8s.io/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5
|
||||
k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible
|
||||
k8s.io/cloud-provider v0.0.0-20190323031113-9c9d72d1bf90
|
||||
k8s.io/helm v2.14.1+incompatible
|
||||
k8s.io/klog v0.3.3
|
||||
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208
|
||||
k8s.io/kubernetes v1.14.2
|
||||
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.16.10
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.16.10
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.16.10
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.16.10
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.16.10
|
||||
k8s.io/client-go => k8s.io/client-go v0.16.10
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.16.10
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.16.10
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.16.10
|
||||
k8s.io/component-base => k8s.io/component-base v0.16.10
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.16.10
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.16.10
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.16.10
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.16.10
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.16.10
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.16.10
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.16.10
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.16.10
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.16.10
|
||||
k8s.io/metrics => k8s.io/metrics v0.16.10
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.16.10
|
||||
)
|
||||
|
||||
replace (
|
||||
helm.sh/helm/v3 => helm.sh/helm/v3 v3.1.2
|
||||
k8s.io/helm => k8s.io/helm v2.16.7+incompatible
|
||||
)
|
||||
|
||||
replace k8s.io/kubernetes => k8s.io/kubernetes v1.16.10
|
||||
|
||||
require (
|
||||
cloud.google.com/go/pubsub v1.4.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/aws/aws-sdk-go v1.31.10
|
||||
github.com/daneharrigan/hipchat v0.0.0-20170512185232-835dc879394a
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/jinzhu/gorm v1.9.12
|
||||
github.com/jmoiron/sqlx v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mfridman/tparse v0.7.4 // indirect
|
||||
github.com/nlopes/slack v0.6.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 // indirect
|
||||
github.com/rusenask/cron v1.1.0
|
||||
github.com/rusenask/docker-registry-client v0.0.0-20200210164146-049272422097
|
||||
github.com/ryanuber/go-glob v1.0.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tbruyelle/hipchat-go v0.0.0-20170717082847-35aebc99209a
|
||||
github.com/urfave/negroni v1.0.0
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
google.golang.org/api v0.26.0
|
||||
google.golang.org/grpc v1.29.1
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
helm.sh/helm/v3 v3.0.0-00010101000000-000000000000
|
||||
k8s.io/api v0.17.2
|
||||
k8s.io/apimachinery v0.17.2
|
||||
k8s.io/cli-runtime v0.17.2
|
||||
k8s.io/client-go v0.17.2
|
||||
k8s.io/helm v0.0.0-00010101000000-000000000000
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
|
|
|
@ -101,6 +101,7 @@ func (s *TriggerServer) Start() error {
|
|||
log.WithFields(log.Fields{
|
||||
"port": s.port,
|
||||
}).Info("webhook trigger server starting...")
|
||||
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// namespace/release name/version
|
||||
func getIdentifier(namespace, name, version string) string {
|
||||
return namespace + "/" + name + ":" + version
|
||||
// namespace/release name:version
|
||||
func getIdentifier(plan *UpdatePlan) string {
|
||||
return fmt.Sprintf("%s/%s:%s", plan.Namespace, plan.Name, plan.NewVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (approvedPlans []*UpdatePlan) {
|
||||
|
@ -24,6 +24,7 @@ func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (a
|
|||
"error": err,
|
||||
"release_name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
"version": plan.NewVersion,
|
||||
}).Error("provider.helm: failed to check approval status for deployment")
|
||||
continue
|
||||
}
|
||||
|
@ -36,7 +37,7 @@ func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (a
|
|||
|
||||
// updateComplete is called after we successfully update resource
|
||||
func (p *Provider) updateComplete(plan *UpdatePlan) error {
|
||||
return p.approvalManager.Archive(getIdentifier(plan.Namespace, plan.Name, plan.NewVersion))
|
||||
return p.approvalManager.Archive(getIdentifier(plan))
|
||||
}
|
||||
|
||||
func (p *Provider) isApproved(event *types.Event, plan *UpdatePlan) (bool, error) {
|
||||
|
@ -44,7 +45,7 @@ func (p *Provider) isApproved(event *types.Event, plan *UpdatePlan) (bool, error
|
|||
return true, nil
|
||||
}
|
||||
|
||||
identifier := getIdentifier(plan.Namespace, plan.Name, plan.NewVersion)
|
||||
identifier := getIdentifier(plan)
|
||||
|
||||
// checking for existing approval
|
||||
existing, err := p.approvalManager.Get(identifier)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -181,6 +181,10 @@ func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) {
|
|||
|
||||
for _, release := range releases {
|
||||
// getting configuration
|
||||
|
||||
if release.Chart.Metadata.Name == "" {
|
||||
return nil, err
|
||||
}
|
||||
vals, err := values(release.Chart, release.Config)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
@ -344,7 +348,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
|||
"error": err,
|
||||
"name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
}).Warn("provider.helm: got error while resetting approvals counter after successful update")
|
||||
}).Debug("provider.helm: got error while resetting approvals counter after successful update")
|
||||
}
|
||||
|
||||
var msg string
|
||||
|
|
|
@ -113,13 +113,12 @@ keel:
|
|||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
|
@ -165,7 +164,7 @@ func TestGetChartPolicyFromProm(t *testing.T) {
|
|||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: promChartValues},
|
||||
|
@ -233,7 +232,7 @@ keel:
|
|||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
|
@ -276,7 +275,7 @@ image2:
|
|||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
|
@ -327,7 +326,7 @@ keel:
|
|||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
|
@ -385,7 +384,7 @@ func TestGetPolicyFromConfig(t *testing.T) {
|
|||
|
||||
func TestGetImagesFromConfig(t *testing.T) {
|
||||
vals, err := testingConfigYaml(&KeelChartConfig{Policy: "all", Images: []ImageDetails{
|
||||
ImageDetails{
|
||||
{
|
||||
RepositoryPath: "repopath",
|
||||
TagPath: "tagpath",
|
||||
},
|
||||
|
@ -428,13 +427,14 @@ keel:
|
|||
|
||||
`
|
||||
myChart := &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
Metadata: &chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: &chart.Config{Raw: ""},
|
||||
|
@ -596,7 +596,7 @@ keel:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
|
@ -610,7 +610,7 @@ keel:
|
|||
Trigger: types.TriggerTypeDefault,
|
||||
NotificationChannels: []string{"chan1", "chan2"},
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
|
@ -624,7 +624,7 @@ keel:
|
|||
Trigger: types.TriggerTypePoll,
|
||||
PollSchedule: "@every 30m",
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag", ImagePullSecret: "such-secret"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag", ImagePullSecret: "such-secret"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
|
@ -637,7 +637,7 @@ keel:
|
|||
MatchPreRelease: false,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, false),
|
||||
},
|
||||
|
@ -681,7 +681,7 @@ keel:
|
|||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: &rls.ListReleasesResponse{
|
||||
Releases: []*hapi_release5.Release{
|
||||
&hapi_release5.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: &chart.Chart{
|
||||
Values: &chart.Config{Raw: chartVals},
|
||||
|
|
|
@ -65,15 +65,18 @@ keel:
|
|||
`
|
||||
|
||||
helloWorldChart := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyForce},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyForce},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldChartPolicyMajor := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyMajor},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyMajor},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldChartPolicyMajorReleaseNotes := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyForceReleaseNotes},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesPolicyForceReleaseNotes},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
|
@ -111,7 +114,7 @@ keel:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{
|
||||
{
|
||||
RepositoryPath: "image.repository",
|
||||
TagPath: "image.tag",
|
||||
},
|
||||
|
@ -144,7 +147,7 @@ keel:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{
|
||||
{
|
||||
RepositoryPath: "image.repository",
|
||||
TagPath: "image.tag",
|
||||
ReleaseNotes: "https://github.com/keel-hq/keel/releases",
|
||||
|
@ -271,21 +274,26 @@ image:
|
|||
`
|
||||
|
||||
helloWorldChart := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesA},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesA},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldNonSemverChart := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesB},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesB},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
helloWorldNonSemverNoForceChart := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNonSemverNoForce},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNonSemverNoForce},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
helloWorldNoTagChart := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNoTag},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNoTag},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldNoKeelCfg := &hapi_chart.Chart{
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNoKeelCfg},
|
||||
Values: &hapi_chart.Config{Raw: chartValuesNoKeelCfg},
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
|
@ -324,7 +332,7 @@ image:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
|
@ -381,7 +389,7 @@ image:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewForcePolicy(false),
|
||||
},
|
||||
|
@ -425,7 +433,7 @@ image:
|
|||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
ImageDetails{RepositoryPath: "image.repository"},
|
||||
{RepositoryPath: "image.repository"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/keel-hq/keel/pkg/store"
|
||||
"github.com/keel-hq/keel/types"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// namespace/release name:version
|
||||
func getIdentifier(plan *UpdatePlan) string {
|
||||
return fmt.Sprintf("%s/%s:%s", plan.Namespace, plan.Name, plan.NewVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (approvedPlans []*UpdatePlan) {
|
||||
approvedPlans = []*UpdatePlan{}
|
||||
for _, plan := range plans {
|
||||
approved, err := p.isApproved(event, plan)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"release_name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
"version": plan.NewVersion,
|
||||
}).Error("provider.helm3: failed to check approval status for deployment")
|
||||
continue
|
||||
}
|
||||
if approved {
|
||||
approvedPlans = append(approvedPlans, plan)
|
||||
}
|
||||
}
|
||||
return approvedPlans
|
||||
}
|
||||
|
||||
// updateComplete is called after we successfully update resource
|
||||
func (p *Provider) updateComplete(plan *UpdatePlan) error {
|
||||
return p.approvalManager.Archive(getIdentifier(plan))
|
||||
}
|
||||
|
||||
func (p *Provider) isApproved(event *types.Event, plan *UpdatePlan) (bool, error) {
|
||||
if plan.Config.Approvals == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
identifier := getIdentifier(plan)
|
||||
|
||||
// checking for existing approval
|
||||
existing, err := p.approvalManager.Get(identifier)
|
||||
if err != nil {
|
||||
if err == store.ErrRecordNotFound {
|
||||
|
||||
// if approval doesn't exist and trigger wasn't existing approval fulfillment -
|
||||
// create a new one, otherwise if several deployments rely on the same image, it would just be
|
||||
// requesting approvals in a loop
|
||||
if event.TriggerName == types.TriggerTypeApproval.String() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if plan.Config.ApprovalDeadline == 0 {
|
||||
plan.Config.ApprovalDeadline = types.KeelApprovalDeadlineDefault
|
||||
}
|
||||
|
||||
// creating new one
|
||||
approval := &types.Approval{
|
||||
Provider: types.ProviderTypeHelm,
|
||||
Identifier: identifier,
|
||||
Event: event,
|
||||
CurrentVersion: plan.CurrentVersion,
|
||||
NewVersion: plan.NewVersion,
|
||||
VotesRequired: plan.Config.Approvals,
|
||||
VotesReceived: 0,
|
||||
Rejected: false,
|
||||
Deadline: time.Now().Add(time.Duration(plan.Config.ApprovalDeadline) * time.Hour),
|
||||
}
|
||||
|
||||
approval.Message = fmt.Sprintf("New image is available for release %s/%s (%s).",
|
||||
plan.Namespace,
|
||||
plan.Name,
|
||||
approval.Delta(),
|
||||
)
|
||||
|
||||
return false, p.approvalManager.Create(approval)
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return existing.Status() == types.ApprovalStatusApproved, nil
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/keel-hq/keel/types"
|
||||
"github.com/keel-hq/keel/util/image"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ErrKeelConfigNotFound - default error when keel configuration for chart is not defined
|
||||
var ErrKeelConfigNotFound = errors.New("keel configuration not found")
|
||||
|
||||
// getImages - get images from chart values
|
||||
func getImages(vals chartutil.Values) ([]*types.TrackedImage, error) {
|
||||
var images []*types.TrackedImage
|
||||
|
||||
keelCfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
if err == ErrPolicyNotSpecified {
|
||||
// nothing to do
|
||||
return images, nil
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("provider.helm3: failed to get keel configuration for release")
|
||||
// ignoring this release, no keel config found
|
||||
return nil, ErrKeelConfigNotFound
|
||||
}
|
||||
|
||||
for _, imageDetails := range keelCfg.Images {
|
||||
imageRef, err := parseImage(vals, &imageDetails)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repository_name": imageDetails.RepositoryPath,
|
||||
"repository_tag": imageDetails.TagPath,
|
||||
}).Error("provider.helm3: failed to parse image")
|
||||
continue
|
||||
}
|
||||
|
||||
trackedImage := &types.TrackedImage{
|
||||
Image: imageRef,
|
||||
PollSchedule: keelCfg.PollSchedule,
|
||||
Trigger: keelCfg.Trigger,
|
||||
Policy: keelCfg.Plc,
|
||||
}
|
||||
|
||||
if imageDetails.ImagePullSecret != "" {
|
||||
trackedImage.Secrets = append(trackedImage.Secrets, imageDetails.ImagePullSecret)
|
||||
}
|
||||
|
||||
images = append(images, trackedImage)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func getPlanValues(newVersion *types.Version, ref *image.Reference, imageDetails *ImageDetails) (path, value string) {
|
||||
// if tag is not supplied, then user specified full image name
|
||||
if imageDetails.TagPath == "" {
|
||||
return imageDetails.RepositoryPath, getUpdatedImage(ref, newVersion.String())
|
||||
}
|
||||
return imageDetails.TagPath, newVersion.String()
|
||||
}
|
||||
|
||||
func getUnversionedPlanValues(newTag string, ref *image.Reference, imageDetails *ImageDetails) (path, value string) {
|
||||
// if tag is not supplied, then user specified full image name
|
||||
if imageDetails.TagPath == "" {
|
||||
return imageDetails.RepositoryPath, getUpdatedImage(ref, newTag)
|
||||
}
|
||||
return imageDetails.TagPath, newTag
|
||||
}
|
||||
|
||||
func getUpdatedImage(ref *image.Reference, version string) string {
|
||||
// updating image
|
||||
if ref.Registry() == image.DefaultRegistryHostname {
|
||||
return fmt.Sprintf("%s:%s", ref.ShortName(), version)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ref.Repository(), version)
|
||||
}
|
||||
|
||||
func parseImage(vals chartutil.Values, details *ImageDetails) (*image.Reference, error) {
|
||||
if details.RepositoryPath == "" {
|
||||
return nil, fmt.Errorf("repository name path cannot be empty")
|
||||
}
|
||||
|
||||
imageName, err := getValueAsString(vals, details.RepositoryPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// getting image tag
|
||||
imageTag, err := getValueAsString(vals, details.TagPath)
|
||||
if err != nil {
|
||||
// failed to find tag, returning anyway
|
||||
return image.Parse(imageName)
|
||||
}
|
||||
|
||||
return image.Parse(imageName + ":" + imageTag)
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/keel-hq/keel/internal/policy"
|
||||
"github.com/keel-hq/keel/types"
|
||||
"github.com/keel-hq/keel/util/image"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
var chartValuesA = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
func mustParse(name string) *image.Reference {
|
||||
img, err := image.Parse(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func Test_getImages(t *testing.T) {
|
||||
vals, _ := chartutil.ReadValues([]byte(chartValuesA))
|
||||
img, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.0")
|
||||
|
||||
promVals, _ := chartutil.ReadValues([]byte(promChartValues))
|
||||
|
||||
type args struct {
|
||||
vals chartutil.Values
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*types.TrackedImage
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "hello-world image",
|
||||
args: args{
|
||||
vals: vals,
|
||||
},
|
||||
want: []*types.TrackedImage{
|
||||
{
|
||||
Image: img,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "prom config from https://raw.githubusercontent.com/helm/charts/master/stable/prometheus-operator/values.yaml",
|
||||
args: args{
|
||||
vals: promVals,
|
||||
},
|
||||
want: []*types.TrackedImage{
|
||||
{
|
||||
Image: mustParse("quay.io/prometheus/alertmanager:v0.16.2"),
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
{
|
||||
Image: mustParse("quay.io/coreos/prometheus-operator:v0.29.0"),
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
{
|
||||
Image: mustParse("quay.io/prometheus/prometheus:v2.7.2"),
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
got, err := getImages(tt.args.vals)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getImages() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getImages() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var promChartValues = `# Default values for prometheus-operator.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# keel config
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: alertmanager.alertmanagerSpec.image.repository
|
||||
tag: alertmanager.alertmanagerSpec.image.tag
|
||||
- repository: prometheusOperator.image.repository
|
||||
tag: prometheusOperator.image.tag
|
||||
- repository: prometheus.prometheusSpec.image.repository
|
||||
tag: prometheus.prometheusSpec.image.tag
|
||||
|
||||
alertmanager:
|
||||
enabled: true
|
||||
alertmanagerSpec:
|
||||
image:
|
||||
repository: quay.io/prometheus/alertmanager
|
||||
tag: v0.16.2
|
||||
|
||||
prometheusOperator:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/coreos/prometheus-operator
|
||||
tag: v0.29.0
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
prometheus:
|
||||
enabled: true
|
||||
prometheusSpec:
|
||||
image:
|
||||
repository: quay.io/prometheus/prometheus
|
||||
tag: v2.7.2
|
||||
`
|
|
@ -0,0 +1,462 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/keel-hq/keel/approvals"
|
||||
"github.com/keel-hq/keel/internal/policy"
|
||||
"github.com/keel-hq/keel/types"
|
||||
"github.com/keel-hq/keel/util/image"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/keel-hq/keel/extension/notification"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
|
||||
_ "helm.sh/helm/v3/pkg/action"
|
||||
_ "helm.sh/helm/v3/pkg/release"
|
||||
|
||||
hapi_chart "helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
var helm3VersionedUpdatesCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "helm3_versioned_updates_total",
|
||||
Help: "How many versioned helm3 charts were updated, partitioned by chart name.",
|
||||
},
|
||||
[]string{"chart"},
|
||||
)
|
||||
|
||||
var helm3UnversionedUpdatesCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "helm3_unversioned_updates_total",
|
||||
Help: "How many unversioned helm3 charts were updated, partitioned by chart name.",
|
||||
},
|
||||
[]string{"chart"},
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(helm3VersionedUpdatesCounter)
|
||||
prometheus.MustRegister(helm3UnversionedUpdatesCounter)
|
||||
}
|
||||
|
||||
// ErrPolicyNotSpecified helm related errors
|
||||
var (
|
||||
ErrPolicyNotSpecified = errors.New("policy not specified")
|
||||
)
|
||||
|
||||
// Manager - high level interface into helm provider related data used by
|
||||
// triggers
|
||||
type Manager interface {
|
||||
Images() ([]*image.Reference, error)
|
||||
}
|
||||
|
||||
// ProviderName - helm provider name
|
||||
const ProviderName = "helm3"
|
||||
|
||||
// DefaultUpdateTimeout - update timeout in seconds
|
||||
// const DefaultUpdateTimeout = 300
|
||||
|
||||
// UpdatePlan - release update plan
|
||||
type UpdatePlan struct {
|
||||
Namespace string
|
||||
Name string
|
||||
|
||||
Config *KeelChartConfig
|
||||
|
||||
// chart
|
||||
Chart *hapi_chart.Chart
|
||||
|
||||
// values to update path=value
|
||||
Values map[string]string
|
||||
|
||||
// Current (last seen cluster version)
|
||||
CurrentVersion string
|
||||
// New version that's already in the deployment
|
||||
NewVersion string
|
||||
|
||||
// ReleaseNotes is a slice of combined release notes.
|
||||
ReleaseNotes []string
|
||||
|
||||
// used as fix to bug in chartutil.coalesce v3.1.2
|
||||
EmptyConfig bool
|
||||
}
|
||||
|
||||
// keel:
|
||||
// # keel policy (all/major/minor/patch/force)
|
||||
// policy: all
|
||||
// # trigger type, defaults to events such as pubsub, webhooks
|
||||
// trigger: poll
|
||||
// pollSchedule: "@every 2m"
|
||||
// # images to track and update
|
||||
// images:
|
||||
// - repository: image.repository
|
||||
// tag: image.tag
|
||||
|
||||
// Root - root element of the values yaml
|
||||
type Root struct {
|
||||
Keel KeelChartConfig `json:"keel"`
|
||||
}
|
||||
|
||||
// KeelChartConfig - keel related configuration taken from values.yaml
|
||||
type KeelChartConfig struct {
|
||||
Policy string `json:"policy"`
|
||||
MatchTag bool `json:"matchTag"`
|
||||
MatchPreRelease bool `json:"matchPreRelease"`
|
||||
Trigger types.TriggerType `json:"trigger"`
|
||||
PollSchedule string `json:"pollSchedule"`
|
||||
Approvals int `json:"approvals"` // Minimum required approvals
|
||||
ApprovalDeadline int `json:"approvalDeadline"` // Deadline in hours
|
||||
Images []ImageDetails `json:"images"`
|
||||
NotificationChannels []string `json:"notificationChannels"` // optional notification channels
|
||||
|
||||
Plc policy.Policy `json:"-"`
|
||||
}
|
||||
|
||||
// ImageDetails - image details
|
||||
type ImageDetails struct {
|
||||
RepositoryPath string `json:"repository"`
|
||||
TagPath string `json:"tag"`
|
||||
DigestPath string `json:"digest"`
|
||||
ReleaseNotes string `json:"releaseNotes"`
|
||||
ImagePullSecret string `json:"imagePullSecret"`
|
||||
}
|
||||
|
||||
// Provider - helm3 provider, responsible for managing release updates
|
||||
type Provider struct {
|
||||
implementer Implementer
|
||||
|
||||
sender notification.Sender
|
||||
|
||||
approvalManager approvals.Manager
|
||||
|
||||
events chan *types.Event
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// NewProvider - create new Helm provider
|
||||
func NewProvider(implementer Implementer, sender notification.Sender, approvalManager approvals.Manager) *Provider {
|
||||
return &Provider{
|
||||
implementer: implementer,
|
||||
approvalManager: approvalManager,
|
||||
sender: sender,
|
||||
events: make(chan *types.Event, 100),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// GetName - get provider name
|
||||
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)
|
||||
}
|
||||
|
||||
// TrackedImages - returns tracked images from all releases that have keel configuration
|
||||
func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) {
|
||||
var trackedImages []*types.TrackedImage
|
||||
|
||||
releases, err := p.implementer.ListReleases()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
// getting configuration
|
||||
vals, err := values(release.Chart, release.Config)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"release": release.Name,
|
||||
"namespace": release.Namespace,
|
||||
}).Error("provider.helm3: failed to get values.yaml for release")
|
||||
continue
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"release": release.Name,
|
||||
"namespace": release.Namespace,
|
||||
}).Debug("provider.helm3: failed to get config for release")
|
||||
continue
|
||||
}
|
||||
|
||||
if cfg.PollSchedule == "" {
|
||||
cfg.PollSchedule = types.KeelPollDefaultSchedule
|
||||
}
|
||||
// used to check pod secrets
|
||||
selector := fmt.Sprintf("app=%s,release=%s", release.Chart.Metadata.Name, release.Name)
|
||||
|
||||
releaseImages, err := getImages(vals)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"release": release.Name,
|
||||
"namespace": release.Namespace,
|
||||
}).Error("provider.helm3: failed to get images for release")
|
||||
continue
|
||||
}
|
||||
|
||||
for _, img := range releaseImages {
|
||||
img.Meta = map[string]string{
|
||||
"selector": selector,
|
||||
"helm.sh/chart": fmt.Sprintf("%s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version),
|
||||
}
|
||||
img.Namespace = release.Namespace
|
||||
img.Provider = ProviderName
|
||||
trackedImages = append(trackedImages, img)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return trackedImages, nil
|
||||
}
|
||||
|
||||
func (p *Provider) startInternal() error {
|
||||
for {
|
||||
select {
|
||||
case event := <-p.events:
|
||||
err := p.processEvent(event)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"image": event.Repository.Name,
|
||||
"tag": event.Repository.Tag,
|
||||
}).Error("provider.helm3: failed to process event")
|
||||
}
|
||||
case <-p.stop:
|
||||
log.Info("provider.helm3: got shutdown signal, stopping...")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) processEvent(event *types.Event) (err error) {
|
||||
plans, err := p.createUpdatePlans(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
approved := p.checkForApprovals(event, plans)
|
||||
|
||||
return p.applyPlans(approved)
|
||||
}
|
||||
|
||||
func (p *Provider) createUpdatePlans(event *types.Event) ([]*UpdatePlan, error) {
|
||||
var plans []*UpdatePlan
|
||||
|
||||
releases, err := p.implementer.ListReleases()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
|
||||
plan, update, err := checkRelease(&event.Repository, release.Namespace, release.Name, release.Chart, release.Config)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"name": release.Name,
|
||||
"namespace": release.Namespace,
|
||||
}).Error("provider.helm3: failed to process versioned release")
|
||||
continue
|
||||
}
|
||||
|
||||
if update {
|
||||
helm3VersionedUpdatesCounter.With(prometheus.Labels{"chart": fmt.Sprintf("%s/%s", release.Namespace, release.Name)}).Inc()
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
}
|
||||
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
||||
for _, plan := range plans {
|
||||
|
||||
p.sender.Send(types.EventNotification{
|
||||
ResourceKind: "chart",
|
||||
Identifier: fmt.Sprintf("%s/%s/%s", "chart", plan.Namespace, plan.Name),
|
||||
Name: "update release",
|
||||
Message: fmt.Sprintf("Preparing to update release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", ")),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationPreReleaseUpdate,
|
||||
Level: types.LevelDebug,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
Metadata: map[string]string{
|
||||
"provider": p.GetName(),
|
||||
"namespace": plan.Namespace,
|
||||
"name": plan.Name,
|
||||
},
|
||||
})
|
||||
|
||||
// err := updateHelmRelease(p.implementer, plan.Name, plan.Chart, plan.Values)
|
||||
err := updateHelmRelease(p.implementer, plan.Name, plan.Chart, plan.Values, plan.Namespace, plan.EmptyConfig)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
}).Error("provider.helm3: failed to apply plan")
|
||||
|
||||
p.sender.Send(types.EventNotification{
|
||||
ResourceKind: "chart",
|
||||
Identifier: fmt.Sprintf("%s/%s/%s", "chart", plan.Namespace, plan.Name),
|
||||
Name: "update release",
|
||||
Message: fmt.Sprintf("Release update failed %s/%s %s->%s (%s), error: %s", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "), err),
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelError,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
Metadata: map[string]string{
|
||||
"provider": p.GetName(),
|
||||
"namespace": plan.Namespace,
|
||||
"name": plan.Name,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
err = p.updateComplete(plan)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"name": plan.Name,
|
||||
"namespace": plan.Namespace,
|
||||
}).Debug("provider.helm3: got error while resetting approvals counter after successful update")
|
||||
}
|
||||
|
||||
var msg string
|
||||
if len(plan.ReleaseNotes) == 0 {
|
||||
msg = fmt.Sprintf("Successfully updated release %s/%s %s->%s (%s)", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "))
|
||||
} else {
|
||||
msg = fmt.Sprintf("Successfully updated release %s/%s %s->%s (%s). Release notes: %s", plan.Namespace, plan.Name, plan.CurrentVersion, plan.NewVersion, strings.Join(mapToSlice(plan.Values), ", "), strings.Join(plan.ReleaseNotes, ", "))
|
||||
}
|
||||
|
||||
p.sender.Send(types.EventNotification{
|
||||
ResourceKind: "chart",
|
||||
Identifier: fmt.Sprintf("%s/%s/%s", "chart", plan.Namespace, plan.Name),
|
||||
Name: "update release",
|
||||
Message: msg,
|
||||
CreatedAt: time.Now(),
|
||||
Type: types.NotificationReleaseUpdate,
|
||||
Level: types.LevelSuccess,
|
||||
Channels: plan.Config.NotificationChannels,
|
||||
Metadata: map[string]string{
|
||||
"provider": p.GetName(),
|
||||
"namespace": plan.Namespace,
|
||||
"name": plan.Name,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateHelmRelease(implementer Implementer, releaseName string, chart *hapi_chart.Chart, overrideValues map[string]string, namespace string, opts ...bool) error {
|
||||
|
||||
// set reuse values to false if currentRelease.config is nil
|
||||
emptyConfig := false
|
||||
if len(opts) == 1 && opts[0] {
|
||||
emptyConfig = opts[0]
|
||||
}
|
||||
resp, err := implementer.UpdateReleaseFromChart(releaseName, chart, overrideValues, namespace, emptyConfig)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"version": resp.Version,
|
||||
"release": releaseName,
|
||||
"overrideValues": overrideValues,
|
||||
}).Info("provider.helm3: release updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapToSlice(values map[string]string) []string {
|
||||
converted := []string{}
|
||||
for k, v := range values {
|
||||
concat := k + "=" + v
|
||||
converted = append(converted, concat)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
// parse
|
||||
func convertToYaml(values []string) ([]byte, error) {
|
||||
base := map[string]interface{}{}
|
||||
for _, value := range values {
|
||||
if err := strvals.ParseInto(value, base); err != nil {
|
||||
return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return yaml.Marshal(base)
|
||||
}
|
||||
|
||||
func getValueAsString(vals chartutil.Values, path string) (string, error) {
|
||||
valinterface, err := vals.PathValue(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
valString := fmt.Sprintf("%v", valinterface)
|
||||
|
||||
return valString, nil
|
||||
}
|
||||
|
||||
// func values(chart *hapi_chart.Chart, config *hapi_chart.Config) (chartutil.Values, error) {
|
||||
func values(chart *hapi_chart.Chart, config map[string]interface{}) (chartutil.Values, error) {
|
||||
return chartutil.CoalesceValues(chart, config)
|
||||
}
|
||||
|
||||
func getKeelConfig(vals chartutil.Values) (*KeelChartConfig, error) {
|
||||
yamlFull, err := vals.YAML()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vals config, error: %s", err)
|
||||
}
|
||||
|
||||
var r Root
|
||||
// Default MatchPreRelease to true if not present (backward compatibility)
|
||||
r.Keel.MatchPreRelease = true
|
||||
err = yaml.Unmarshal([]byte(yamlFull), &r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse keel config: %s", err)
|
||||
}
|
||||
|
||||
if r.Keel.Policy == "" {
|
||||
return nil, ErrPolicyNotSpecified
|
||||
}
|
||||
|
||||
cfg := r.Keel
|
||||
|
||||
cfg.Plc = policy.GetPolicy(cfg.Policy, &policy.Options{MatchTag: cfg.MatchTag, MatchPreRelease: cfg.MatchPreRelease})
|
||||
|
||||
return &cfg, nil
|
||||
}
|
|
@ -0,0 +1,742 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/keel-hq/keel/approvals"
|
||||
"github.com/keel-hq/keel/extension/notification"
|
||||
"github.com/keel-hq/keel/internal/policy"
|
||||
"github.com/keel-hq/keel/pkg/store/sql"
|
||||
"github.com/keel-hq/keel/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
func newTestingUtils() (*sql.SQLStore, func()) {
|
||||
dir, err := ioutil.TempDir("", "whstoretest")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tmpfn := filepath.Join(dir, "gorm.db")
|
||||
// defer
|
||||
store, err := sql.New(sql.Opts{DatabaseType: "sqlite3", URI: tmpfn})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
teardown := func() {
|
||||
os.RemoveAll(dir) // clean up
|
||||
}
|
||||
|
||||
return store, teardown
|
||||
}
|
||||
|
||||
func approver() (*approvals.DefaultManager, func()) {
|
||||
store, teardown := newTestingUtils()
|
||||
return approvals.New(&approvals.Opts{
|
||||
Store: store,
|
||||
}), teardown
|
||||
}
|
||||
|
||||
type fakeSender struct {
|
||||
sentEvent types.EventNotification
|
||||
}
|
||||
|
||||
func (s *fakeSender) Configure(cfg *notification.Config) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *fakeSender) Send(event types.EventNotification) error {
|
||||
s.sentEvent = event
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeImplementer struct {
|
||||
listReleasesResponse []*release.Release
|
||||
|
||||
// updated info
|
||||
updatedRlsName string
|
||||
updatedChart *chart.Chart
|
||||
// updatedOptions []helm.UpdateOption
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) ListReleases() ([]*release.Release, error) {
|
||||
return i.listReleasesResponse, nil
|
||||
}
|
||||
|
||||
func (i *fakeImplementer) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, vals map[string]string, namespace string, opts ...bool) (*release.Release, error) {
|
||||
// func (i *fakeImplementer) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...helm.UpdateOption) (*rls.UpdateReleaseResponse, error) {
|
||||
i.updatedRlsName = rlsName
|
||||
i.updatedChart = chart
|
||||
// i.updatedOptions = opts
|
||||
|
||||
return &release.Release{
|
||||
Name: rlsName,
|
||||
Chart: chart,
|
||||
Version: 2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// helper function to generate keel configuration
|
||||
func testingConfigYaml(cfg *KeelChartConfig) (vals chartutil.Values, err error) {
|
||||
root := &Root{Keel: *cfg}
|
||||
bts, err := yaml.Marshal(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chartutil.ReadValues(bts)
|
||||
}
|
||||
|
||||
// helper function to generate chart.Chart object from string
|
||||
func testingStringToChart(raw string) (myChart *chart.Chart, err error) {
|
||||
chartVals, err := chartutil.ReadValues([]byte(raw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
myChart = &chart.Chart{
|
||||
Values: chartVals,
|
||||
Metadata: &chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
return myChart, nil
|
||||
}
|
||||
|
||||
func TestGetChartPolicy(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
releases, err := fakeImpl.ListReleases()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
t.Fatalf("unexpexted error: releases should not be empty")
|
||||
}
|
||||
|
||||
policyFound := false
|
||||
|
||||
for _, release := range releases {
|
||||
|
||||
vals, err := values(release.Chart, release.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get values: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Plc.Name() == policy.SemverPolicyTypeAll.String() {
|
||||
policyFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !policyFound {
|
||||
t.Errorf("policy not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChartPolicyFromProm(t *testing.T) {
|
||||
|
||||
myChart, err := testingStringToChart(promChartValues)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
releases, err := fakeImpl.ListReleases()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
policyFound := false
|
||||
|
||||
for _, release := range releases {
|
||||
|
||||
vals, err := values(release.Chart, release.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get values: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Plc.Name() == policy.SemverPolicyTypeAll.String() {
|
||||
policyFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !policyFound {
|
||||
t.Errorf("policy not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrackedReleases(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: chart-x
|
||||
where:
|
||||
city: kaunas
|
||||
title: hmm
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/bye-world
|
||||
tag: 1.1.0
|
||||
|
||||
image2:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.2.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
approver, teardown := approver()
|
||||
defer teardown()
|
||||
prov := NewProvider(fakeImpl, &fakeSender{}, approver)
|
||||
|
||||
tracked, _ := prov.TrackedImages()
|
||||
|
||||
if tracked[0].Image.Remote() != "gcr.io/v2-namespace/bye-world:1.1.0" {
|
||||
t.Errorf("unexpected image: %s", tracked[0].Image.Remote())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrackedReleasesWithoutKeelConfig(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: chart-x
|
||||
where:
|
||||
city: kaunas
|
||||
title: hmm
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/bye-world
|
||||
tag: 1.1.0
|
||||
|
||||
image2:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.2.0
|
||||
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
approver, teardown := approver()
|
||||
defer teardown()
|
||||
prov := NewProvider(fakeImpl, &fakeSender{}, approver)
|
||||
|
||||
tracked, _ := prov.TrackedImages()
|
||||
|
||||
if len(tracked) != 0 {
|
||||
t.Errorf("didn't expect to find any tracked releases, found: %d", len(tracked))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetTrackedReleasesTotallyNonStandard(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: chart-x
|
||||
where:
|
||||
city: kaunas
|
||||
title: hmm
|
||||
ihavemyownstandard:
|
||||
repo: gcr.io/v2-namespace/bye-world
|
||||
version: 1.1.0
|
||||
|
||||
image2:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.2.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: ihavemyownstandard.repo
|
||||
tag: ihavemyownstandard.version
|
||||
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
approver, teardown := approver()
|
||||
defer teardown()
|
||||
prov := NewProvider(fakeImpl, &fakeSender{}, approver)
|
||||
|
||||
tracked, _ := prov.TrackedImages()
|
||||
|
||||
if tracked[0].Image.Remote() != "gcr.io/v2-namespace/bye-world:1.1.0" {
|
||||
t.Errorf("unexpected image: %s", tracked[0].Image.Remote())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTriggerFromConfig(t *testing.T) {
|
||||
vals, err := testingConfigYaml(&KeelChartConfig{Trigger: types.TriggerTypePoll, Policy: "all"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load testdata: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Trigger != types.TriggerTypePoll {
|
||||
t.Errorf("invalid trigger: %s", cfg.Trigger)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPolicyFromConfig(t *testing.T) {
|
||||
vals, err := testingConfigYaml(&KeelChartConfig{Policy: "all"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load testdata: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
// if cfg.Policy != types.PolicyTypeAll {
|
||||
if cfg.Plc.Name() != policy.SemverPolicyTypeAll.String() {
|
||||
t.Errorf("invalid policy: %s", cfg.Policy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesFromConfig(t *testing.T) {
|
||||
vals, err := testingConfigYaml(&KeelChartConfig{Policy: "all", Images: []ImageDetails{
|
||||
{
|
||||
RepositoryPath: "repopath",
|
||||
TagPath: "tagpath",
|
||||
},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load testdata: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Images[0].RepositoryPath != "repopath" {
|
||||
t.Errorf("invalid repo path: %s", cfg.Images[0].RepositoryPath)
|
||||
}
|
||||
|
||||
if cfg.Images[0].TagPath != "tagpath" {
|
||||
t.Errorf("invalid tag path: %s", cfg.Images[0].TagPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRelease(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: karolisr/webhook-demo
|
||||
tag: 0.0.10
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
approver, teardown := approver()
|
||||
defer teardown()
|
||||
provider := NewProvider(fakeImpl, &fakeSender{}, approver)
|
||||
|
||||
err = provider.processEvent(&types.Event{
|
||||
Repository: types.Repository{
|
||||
Name: "karolisr/webhook-demo",
|
||||
Tag: "0.0.11",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("failed to process event, error: %s", err)
|
||||
}
|
||||
|
||||
// checking updated release
|
||||
if fakeImpl.updatedChart != myChart {
|
||||
t.Errorf("wrong chart updated")
|
||||
}
|
||||
|
||||
if fakeImpl.updatedRlsName != "release-1" {
|
||||
t.Errorf("unexpected release updated: %s", fakeImpl.updatedRlsName)
|
||||
}
|
||||
}
|
||||
|
||||
var pollingValues = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
pollSchedule: "@every 12m"
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
func TestGetPollingSchedule(t *testing.T) {
|
||||
vals, _ := chartutil.ReadValues([]byte(pollingValues))
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get config: %s", err)
|
||||
}
|
||||
|
||||
if cfg.PollSchedule != "@every 12m" {
|
||||
t.Errorf("unexpected polling schedule: %s", cfg.PollSchedule)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getKeelConfig(t *testing.T) {
|
||||
|
||||
var valuesBasicStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
valuesBasic, _ := chartutil.ReadValues([]byte(valuesBasicStr))
|
||||
|
||||
var valuesChannelsStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
notificationChannels:
|
||||
- chan1
|
||||
- chan2
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
valuesChannels, _ := chartutil.ReadValues([]byte(valuesChannelsStr))
|
||||
|
||||
var valuesPollStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: major
|
||||
trigger: poll
|
||||
pollSchedule: "@every 30m"
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
imagePullSecret: such-secret
|
||||
`
|
||||
valuesPoll, _ := chartutil.ReadValues([]byte(valuesPollStr))
|
||||
|
||||
var valuesNoMatchPreReleaseStr = `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
matchPreRelease: false
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
valuesNoMatchPreRelease, _ := chartutil.ReadValues([]byte(valuesNoMatchPreReleaseStr))
|
||||
|
||||
type args struct {
|
||||
vals chartutil.Values
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *KeelChartConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "correct config",
|
||||
args: args{vals: valuesBasic},
|
||||
want: &KeelChartConfig{
|
||||
Policy: "all",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom notification channels",
|
||||
args: args{vals: valuesChannels},
|
||||
want: &KeelChartConfig{
|
||||
Policy: "all",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
NotificationChannels: []string{"chan1", "chan2"},
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct polling config",
|
||||
args: args{vals: valuesPoll},
|
||||
want: &KeelChartConfig{
|
||||
Policy: "major",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
PollSchedule: "@every 30m",
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag", ImagePullSecret: "such-secret"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disable matchPreRelease",
|
||||
args: args{vals: valuesNoMatchPreRelease},
|
||||
want: &KeelChartConfig{
|
||||
Policy: "all",
|
||||
MatchPreRelease: false,
|
||||
Trigger: types.TriggerTypeDefault,
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, false),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := getKeelConfig(tt.args.vals)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getKeelConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getKeelConfig() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChartMatchTag(t *testing.T) {
|
||||
|
||||
chartVals := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
matchTag: true
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
myChart, err := testingStringToChart(chartVals)
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
fakeImpl := &fakeImplementer{
|
||||
listReleasesResponse: []*release.Release{
|
||||
{
|
||||
Name: "release-1",
|
||||
Chart: myChart,
|
||||
Config: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
releases, err := fakeImpl.ListReleases()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
policyFound := false
|
||||
|
||||
for _, release := range releases {
|
||||
|
||||
vals, err := values(release.Chart, release.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get values: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get image paths: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Plc.Name() == policy.SemverPolicyTypeAll.String() {
|
||||
policyFound = true
|
||||
}
|
||||
if !cfg.MatchTag {
|
||||
t.Errorf("expected to find 'matchTag' == true ")
|
||||
}
|
||||
}
|
||||
|
||||
if !policyFound {
|
||||
t.Errorf("policy not found")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
// "helm.sh/helm/v3/pkg/cli"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
// to do:
|
||||
// * update to latest chart package
|
||||
// * udpate the paramateres for the function
|
||||
|
||||
const DefaultUpdateTimeout = 300
|
||||
|
||||
// Implementer - generic helm implementer used to abstract actual implementation
|
||||
type Implementer interface {
|
||||
// ListReleases(opts ...helm.ReleaseListOption) ([]*release.Release, error)
|
||||
ListReleases() ([]*release.Release, error)
|
||||
UpdateReleaseFromChart(rlsName string, chart *chart.Chart, vals map[string]string, namespace string, opts ...bool) (*release.Release, error)
|
||||
}
|
||||
|
||||
// Helm3Implementer - actual helm3 implementer
|
||||
type Helm3Implementer struct {
|
||||
// actionConfig *action.Configuration
|
||||
HelmDriver string
|
||||
KubeContext string
|
||||
KubeToken string
|
||||
KubeAPIServer string
|
||||
}
|
||||
|
||||
// NewHelm3Implementer - get new helm implementer
|
||||
func NewHelm3Implementer() *Helm3Implementer {
|
||||
return &Helm3Implementer{
|
||||
HelmDriver: os.Getenv("HELM_DRIVER"),
|
||||
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
|
||||
KubeToken: os.Getenv("HELM_KUBETOKEN"),
|
||||
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
|
||||
}
|
||||
}
|
||||
|
||||
// ListReleases - list available releases
|
||||
func (i *Helm3Implementer) ListReleases() ([]*release.Release, error) {
|
||||
actionConfig := i.generateConfig("")
|
||||
client := action.NewList(actionConfig)
|
||||
results, err := client.Run()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Fatal("helm3: failed to list release")
|
||||
return []*release.Release{}, err
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// UpdateReleaseFromChart - update release from chart
|
||||
func (i *Helm3Implementer) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, vals map[string]string, namespace string, opts ...bool) (*release.Release, error) {
|
||||
actionConfig := i.generateConfig(namespace)
|
||||
client := action.NewUpgrade(actionConfig)
|
||||
client.Namespace = namespace
|
||||
client.Force = true
|
||||
client.Timeout = DefaultUpdateTimeout
|
||||
client.ReuseValues = true
|
||||
|
||||
// set reuse values to false if currentRelease.config is nil (temp fix for bug in chartutil.coalesce v3.1.2)
|
||||
if len(opts) == 1 && opts[0] {
|
||||
client.ReuseValues = false
|
||||
}
|
||||
|
||||
convertedVals := convertToInterface(vals)
|
||||
|
||||
// returns the new release
|
||||
results, err := client.Run(rlsName, chart, convertedVals)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Fatal("helm3: failed to update release from chart")
|
||||
return nil, err
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (i *Helm3Implementer) generateConfig(namespace string) *action.Configuration {
|
||||
// settings := cli.New()
|
||||
config := &genericclioptions.ConfigFlags{
|
||||
Namespace: &namespace,
|
||||
Context: &i.KubeContext,
|
||||
BearerToken: &i.KubeToken,
|
||||
APIServer: &i.KubeAPIServer,
|
||||
}
|
||||
|
||||
actionConfig := &action.Configuration{}
|
||||
|
||||
if err := actionConfig.Init(config, namespace, i.HelmDriver, log.Printf); err != nil {
|
||||
log.Printf("%+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return actionConfig
|
||||
}
|
||||
|
||||
// convert map[string]string to map[string]interface
|
||||
// converts:
|
||||
// map[string]string{"image.tag": "0.1.0"}
|
||||
// to:
|
||||
// map[string]interface{"image": map[string]interface{"tag": "0.1.0"}}
|
||||
func convertToInterface(values map[string]string) map[string]interface{} {
|
||||
converted := make(map[string]interface{})
|
||||
for key, value := range values {
|
||||
keys := strings.SplitN(key, ".", 2)
|
||||
if len(keys) == 1 {
|
||||
converted[key] = value
|
||||
} else if len(keys) == 2 {
|
||||
converted[keys[0]] = convertToInterface(map[string]string{
|
||||
keys[1]: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestImplementerList(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
imp := NewHelm3Implementer()
|
||||
releases, err := imp.ListReleases()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if len(releases) == 0 {
|
||||
t.Errorf("why no releases? ")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"github.com/keel-hq/keel/internal/policy"
|
||||
"github.com/keel-hq/keel/types"
|
||||
"github.com/keel-hq/keel/util/image"
|
||||
|
||||
hapi_chart "helm.sh/helm/v3/pkg/chart"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func checkRelease(repo *types.Repository, namespace, name string, chart *hapi_chart.Chart, config map[string]interface{}) (plan *UpdatePlan, shouldUpdateRelease bool, err error) {
|
||||
|
||||
plan = &UpdatePlan{
|
||||
Chart: chart,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
Values: make(map[string]string),
|
||||
EmptyConfig: config == nil,
|
||||
}
|
||||
|
||||
eventRepoRef, err := image.Parse(repo.String())
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repository_name": repo.Name,
|
||||
}).Error("provider.helm3: failed to parse event repository name")
|
||||
return
|
||||
}
|
||||
|
||||
// getting configuration
|
||||
vals, err := values(chart, config)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("provider.helm3: failed to get values.yaml for release")
|
||||
return
|
||||
}
|
||||
|
||||
keelCfg, err := getKeelConfig(vals)
|
||||
if err != nil {
|
||||
if err == ErrPolicyNotSpecified {
|
||||
// nothing to do
|
||||
return plan, false, nil
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
}).Error("provider.helm3: failed to get keel configuration for release")
|
||||
// ignoring this release, no keel config found
|
||||
return plan, false, nil
|
||||
}
|
||||
log.Infof("policy for release %s/%s parsed: %s", namespace, name, keelCfg.Plc.Name())
|
||||
|
||||
if keelCfg.Plc.Type() == policy.PolicyTypeNone {
|
||||
// policy is not set, ignoring release
|
||||
return plan, false, nil
|
||||
}
|
||||
|
||||
// checking for impacted images
|
||||
for _, imageDetails := range keelCfg.Images {
|
||||
imageRef, err := parseImage(vals, &imageDetails)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repository_name": imageDetails.RepositoryPath,
|
||||
"repository_tag": imageDetails.TagPath,
|
||||
}).Error("provider.helm3: failed to parse image")
|
||||
continue
|
||||
}
|
||||
|
||||
if imageRef.Repository() != eventRepoRef.Repository() {
|
||||
log.WithFields(log.Fields{
|
||||
"parsed_image_name": imageRef.Remote(),
|
||||
"target_image_name": repo.Name,
|
||||
}).Debug("provider.helm3: images do not match, ignoring")
|
||||
continue
|
||||
}
|
||||
|
||||
shouldUpdate, err := keelCfg.Plc.ShouldUpdate(imageRef.Tag(), eventRepoRef.Tag())
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"error": err,
|
||||
"repository_name": imageDetails.RepositoryPath,
|
||||
"repository_tag": imageDetails.TagPath,
|
||||
}).Error("provider.helm3: got error while checking whether update the chart")
|
||||
continue
|
||||
}
|
||||
|
||||
if !shouldUpdate {
|
||||
log.WithFields(log.Fields{
|
||||
"parsed_image_name": imageRef.Remote(),
|
||||
"target_image_name": repo.Name,
|
||||
"policy": keelCfg.Plc.Name(),
|
||||
}).Info("provider.helm3: ignoring")
|
||||
continue
|
||||
}
|
||||
|
||||
if imageDetails.DigestPath != "" {
|
||||
plan.Values[imageDetails.DigestPath] = repo.Digest
|
||||
log.WithFields(log.Fields{
|
||||
"image_details_digestPath": imageDetails.DigestPath,
|
||||
"target_image_digest": repo.Digest,
|
||||
}).Debug("provider.helm3: setting image Digest")
|
||||
}
|
||||
|
||||
path, value := getUnversionedPlanValues(repo.Tag, imageRef, &imageDetails)
|
||||
plan.Values[path] = value
|
||||
plan.NewVersion = repo.Tag
|
||||
plan.CurrentVersion = imageRef.Tag()
|
||||
plan.Config = keelCfg
|
||||
shouldUpdateRelease = true
|
||||
if imageDetails.ReleaseNotes != "" {
|
||||
plan.ReleaseNotes = append(plan.ReleaseNotes, imageDetails.ReleaseNotes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return plan, shouldUpdateRelease, nil
|
||||
}
|
|
@ -0,0 +1,513 @@
|
|||
package helm3
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/keel-hq/keel/internal/policy"
|
||||
"github.com/keel-hq/keel/types"
|
||||
|
||||
hapi_chart "helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
func Test_checkUnversionedRelease(t *testing.T) {
|
||||
chartValuesPolicyForce := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: force
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
chartValuesPolicyForceReleaseNotes := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: force
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
releaseNotes: https://github.com/keel-hq/keel/releases
|
||||
|
||||
`
|
||||
|
||||
chartValuesPolicyMajor := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: major
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
|
||||
chartValuesPolicyForceVal, err := chartutil.ReadValues([]byte(chartValuesPolicyForce))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
chartValuesPolicyMajorVal, err := chartutil.ReadValues([]byte(chartValuesPolicyMajor))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
chartValuesPolicyForceReleaseNotesVal, err := chartutil.ReadValues([]byte(chartValuesPolicyForceReleaseNotes))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
helloWorldChart := &hapi_chart.Chart{
|
||||
// Values: &hapi_chart.Config{Raw: chartValuesPolicyForce},
|
||||
Values: chartValuesPolicyForceVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldChartPolicyMajor := &hapi_chart.Chart{
|
||||
Values: chartValuesPolicyMajorVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldChartPolicyMajorReleaseNotes := &hapi_chart.Chart{
|
||||
Values: chartValuesPolicyForceReleaseNotesVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
repo *types.Repository
|
||||
namespace string
|
||||
name string
|
||||
chart *hapi_chart.Chart
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPlan *UpdatePlan
|
||||
wantShouldUpdateRelease bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "correct force update",
|
||||
args: args{
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "latest"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChart,
|
||||
Values: map[string]string{"image.tag": "latest"},
|
||||
CurrentVersion: "1.1.0",
|
||||
NewVersion: "latest",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: "force",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
{
|
||||
RepositoryPath: "image.repository",
|
||||
TagPath: "image.tag",
|
||||
},
|
||||
},
|
||||
Plc: policy.NewForcePolicy(false),
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "correct force update, with release notes",
|
||||
args: args{
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.2.0"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChartPolicyMajorReleaseNotes,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChartPolicyMajorReleaseNotes,
|
||||
Values: map[string]string{"image.tag": "1.2.0"},
|
||||
CurrentVersion: "1.1.0",
|
||||
NewVersion: "1.2.0",
|
||||
ReleaseNotes: []string{"https://github.com/keel-hq/keel/releases"},
|
||||
Config: &KeelChartConfig{
|
||||
Policy: "force",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
{
|
||||
RepositoryPath: "image.repository",
|
||||
TagPath: "image.tag",
|
||||
ReleaseNotes: "https://github.com/keel-hq/keel/releases",
|
||||
},
|
||||
},
|
||||
Plc: policy.NewForcePolicy(false),
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "update without force",
|
||||
args: args{
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "latest"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChartPolicyMajor,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChartPolicyMajor,
|
||||
Values: map[string]string{}},
|
||||
wantShouldUpdateRelease: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPlan, gotShouldUpdateRelease, err := checkRelease(tt.args.repo, tt.args.namespace, tt.args.name, tt.args.chart, tt.args.config)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkRelease() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotPlan, tt.wantPlan) {
|
||||
t.Errorf("checkRelease() gotPlan = %v, want %v", gotPlan, tt.wantPlan)
|
||||
}
|
||||
if gotShouldUpdateRelease != tt.wantShouldUpdateRelease {
|
||||
t.Errorf("checkRelease() gotShouldUpdateRelease = %v, want %v", gotShouldUpdateRelease, tt.wantShouldUpdateRelease)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkRelease(t *testing.T) {
|
||||
|
||||
chartValuesA := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.1.0
|
||||
|
||||
keel:
|
||||
policy: all
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
// non semver existing
|
||||
chartValuesB := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: alpha
|
||||
|
||||
keel:
|
||||
policy: force
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
|
||||
`
|
||||
chartValuesNonSemverNoForce := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: alpha
|
||||
|
||||
keel:
|
||||
policy: major
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
tag: image.tag
|
||||
`
|
||||
|
||||
chartValuesNoTag := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world:1.0.0
|
||||
|
||||
keel:
|
||||
policy: major
|
||||
trigger: poll
|
||||
images:
|
||||
- repository: image.repository
|
||||
`
|
||||
|
||||
chartValuesNoKeelCfg := `
|
||||
name: al Rashid
|
||||
where:
|
||||
city: Basrah
|
||||
title: caliph
|
||||
image:
|
||||
repository: gcr.io/v2-namespace/hello-world
|
||||
tag: 1.0.0
|
||||
`
|
||||
|
||||
chartValuesAVal, err := chartutil.ReadValues([]byte(chartValuesA))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
chartValuesBVal, err := chartutil.ReadValues([]byte(chartValuesB))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
chartValuesNonSemverNoForceVal, err := chartutil.ReadValues([]byte(chartValuesNonSemverNoForce))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
chartValuesNoTagVal, err := chartutil.ReadValues([]byte(chartValuesNoTag))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
chartValuesNoKeelCfgVal, err := chartutil.ReadValues([]byte(chartValuesNoKeelCfg))
|
||||
if err != nil {
|
||||
t.Errorf("chartutil.ReadValues error = %v", err)
|
||||
}
|
||||
|
||||
helloWorldChart := &hapi_chart.Chart{
|
||||
Values: chartValuesAVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldNonSemverChart := &hapi_chart.Chart{
|
||||
Values: chartValuesBVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
helloWorldNonSemverNoForceChart := &hapi_chart.Chart{
|
||||
Values: chartValuesNonSemverNoForceVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
helloWorldNoTagChart := &hapi_chart.Chart{
|
||||
Values: chartValuesNoTagVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
helloWorldNoKeelCfg := &hapi_chart.Chart{
|
||||
Values: chartValuesNoKeelCfgVal,
|
||||
Metadata: &hapi_chart.Metadata{Name: "app-x"},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
repo *types.Repository
|
||||
namespace string
|
||||
name string
|
||||
chart *hapi_chart.Chart
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPlan *UpdatePlan
|
||||
wantShouldUpdateRelease bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "correct version bump",
|
||||
args: args{
|
||||
// newVersion: unsafeGetVersion("1.1.2"),
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.2"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldChart,
|
||||
Values: map[string]string{"image.tag": "1.1.2"},
|
||||
NewVersion: "1.1.2",
|
||||
CurrentVersion: "1.1.0",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: "all",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "correct but same version",
|
||||
args: args{
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.0"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1", Chart: helloWorldChart, Values: map[string]string{}},
|
||||
wantShouldUpdateRelease: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "different image",
|
||||
args: args{
|
||||
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/bye-world", Tag: "1.1.5"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1", Chart: helloWorldChart, Values: map[string]string{}},
|
||||
wantShouldUpdateRelease: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "non semver existing version",
|
||||
args: args{
|
||||
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.0"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldNonSemverChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1",
|
||||
Chart: helloWorldNonSemverChart,
|
||||
Values: map[string]string{"image.tag": "1.1.0"},
|
||||
NewVersion: "1.1.0",
|
||||
CurrentVersion: "alpha",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: "force",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||
},
|
||||
Plc: policy.NewForcePolicy(false),
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "non semver no force, should not add to plan",
|
||||
args: args{
|
||||
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.0"},
|
||||
namespace: "default",
|
||||
name: "release-1",
|
||||
chart: helloWorldNonSemverNoForceChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1", Chart: helloWorldNonSemverNoForceChart, Values: map[string]string{}},
|
||||
wantShouldUpdateRelease: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "semver no tag",
|
||||
args: args{
|
||||
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.0"},
|
||||
namespace: "default",
|
||||
name: "release-1-no-tag",
|
||||
chart: helloWorldNoTagChart,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{
|
||||
Namespace: "default",
|
||||
Name: "release-1-no-tag",
|
||||
Chart: helloWorldNoTagChart,
|
||||
Values: map[string]string{"image.repository": "gcr.io/v2-namespace/hello-world:1.1.0"},
|
||||
NewVersion: "1.1.0",
|
||||
CurrentVersion: "1.0.0",
|
||||
Config: &KeelChartConfig{
|
||||
Policy: "major",
|
||||
MatchPreRelease: true,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Images: []ImageDetails{
|
||||
{RepositoryPath: "image.repository"},
|
||||
},
|
||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
},
|
||||
wantShouldUpdateRelease: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no keel config",
|
||||
args: args{
|
||||
|
||||
repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "1.1.0"},
|
||||
namespace: "default",
|
||||
name: "release-1-no-tag",
|
||||
chart: helloWorldNoKeelCfg,
|
||||
config: make(map[string]interface{}),
|
||||
},
|
||||
wantPlan: &UpdatePlan{Namespace: "default", Name: "release-1-no-tag", Chart: helloWorldNoKeelCfg, Values: map[string]string{}},
|
||||
wantShouldUpdateRelease: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotPlan, gotShouldUpdateRelease, err := checkRelease(tt.args.repo, tt.args.namespace, tt.args.name, tt.args.chart, tt.args.config)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkRelease() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotPlan, tt.wantPlan) {
|
||||
t.Errorf("checkRelease() gotPlan = %v, want %v", gotPlan, tt.wantPlan)
|
||||
}
|
||||
if gotShouldUpdateRelease != tt.wantShouldUpdateRelease {
|
||||
t.Errorf("checkRelease() gotShouldUpdateRelease = %v, want %v", gotShouldUpdateRelease, tt.wantShouldUpdateRelease)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ func TestCheckRequestedApproval(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -37,7 +37,7 @@ func TestCheckRequestedApproval(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -88,7 +88,7 @@ func TestCheckRequestedApprovalAnnotation(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -113,7 +113,7 @@ func TestCheckRequestedApprovalAnnotation(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -171,7 +171,7 @@ func TestApprovedCheck(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -192,7 +192,7 @@ func TestApprovedCheck(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -252,7 +252,7 @@ func TestApprovalsCleanup(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -273,7 +273,7 @@ func TestApprovalsCleanup(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -141,7 +141,7 @@ func TestGetNamespaces(t *testing.T) {
|
|||
fi := &fakeImplementer{
|
||||
namespaces: &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -203,7 +203,7 @@ func TestGetImpacted(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -224,7 +224,7 @@ func TestGetImpacted(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -244,7 +244,7 @@ func TestGetImpacted(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -300,7 +300,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -322,7 +322,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -342,7 +342,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -403,7 +403,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -424,7 +424,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
||||
},
|
||||
},
|
||||
|
@ -444,7 +444,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -495,7 +495,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -516,7 +516,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
||||
},
|
||||
},
|
||||
|
@ -536,7 +536,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -582,7 +582,7 @@ func TestProcessEvent(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -608,7 +608,7 @@ func TestProcessEvent(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -634,7 +634,7 @@ func TestProcessEvent(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -665,7 +665,7 @@ func TestProcessEvent(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -710,7 +710,7 @@ func TestProcessEventBuildNumber(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -731,7 +731,7 @@ func TestProcessEventBuildNumber(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:10",
|
||||
},
|
||||
},
|
||||
|
@ -773,7 +773,7 @@ func TestEventSent(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -794,7 +794,7 @@ func TestEventSent(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
||||
},
|
||||
},
|
||||
|
@ -841,7 +841,7 @@ func TestEventSentWithReleaseNotes(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -862,7 +862,7 @@ func TestEventSentWithReleaseNotes(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
||||
},
|
||||
},
|
||||
|
@ -914,7 +914,7 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -935,10 +935,10 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/greetings-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -958,7 +958,7 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1015,7 +1015,7 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -1036,10 +1036,10 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1060,7 +1060,7 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1117,7 +1117,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -1138,7 +1138,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/foo-world",
|
||||
},
|
||||
},
|
||||
|
@ -1159,7 +1159,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1216,7 +1216,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -1237,7 +1237,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world",
|
||||
},
|
||||
},
|
||||
|
@ -1258,7 +1258,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1316,7 +1316,7 @@ func TestTrackedImages(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -1336,12 +1336,12 @@ func TestTrackedImages(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
||||
},
|
||||
},
|
||||
ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret",
|
||||
},
|
||||
},
|
||||
|
@ -1380,7 +1380,7 @@ func TestTrackedImagesWithSecrets(t *testing.T) {
|
|||
fp := &fakeImplementer{}
|
||||
fp.namespaces = &v1.NamespaceList{
|
||||
Items: []v1.Namespace{
|
||||
v1.Namespace{
|
||||
{
|
||||
meta_v1.TypeMeta{},
|
||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||
v1.NamespaceSpec{},
|
||||
|
@ -1403,12 +1403,12 @@ func TestTrackedImagesWithSecrets(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
||||
},
|
||||
},
|
||||
ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world",
|
||||
},
|
||||
},
|
||||
|
@ -93,7 +93,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||
},
|
||||
},
|
||||
|
@ -125,7 +125,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/goodbye-world:earliest",
|
||||
},
|
||||
},
|
||||
|
@ -163,7 +163,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:alpha",
|
||||
},
|
||||
},
|
||||
|
@ -201,7 +201,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:latest",
|
||||
},
|
||||
},
|
||||
|
@ -229,7 +229,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:0.2.0",
|
||||
},
|
||||
},
|
||||
|
@ -266,7 +266,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:master",
|
||||
},
|
||||
},
|
||||
|
@ -296,7 +296,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:master",
|
||||
},
|
||||
},
|
||||
|
@ -336,7 +336,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -366,7 +366,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -406,7 +406,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -437,7 +437,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -471,7 +471,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "karolisr/keel:latest-acceptance",
|
||||
},
|
||||
},
|
||||
|
@ -511,7 +511,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -542,7 +542,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||
},
|
||||
},
|
||||
|
@ -581,7 +581,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:release-1",
|
||||
},
|
||||
},
|
||||
|
@ -612,7 +612,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "eu.gcr.io/karolisr/keel:release-2",
|
||||
},
|
||||
},
|
||||
|
@ -693,7 +693,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -721,7 +721,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
},
|
||||
|
@ -759,7 +759,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -796,7 +796,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1-staging",
|
||||
},
|
||||
},
|
||||
|
@ -828,7 +828,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -868,10 +868,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -899,10 +899,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -939,10 +939,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -970,10 +970,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1013,10 +1013,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1047,10 +1047,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
@ -1090,10 +1090,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||
},
|
||||
v1.Container{
|
||||
{
|
||||
Image: "yo-world:1.1.1",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -119,6 +119,12 @@ If you work mostly with regular Kubernetes manifests, you can install Keel witho
|
|||
helm upgrade --install keel --namespace=keel keel/keel --set helmProvider.enabled="false"
|
||||
```
|
||||
|
||||
To install for Helm v3, set helmProvider.version="v3" (default is "v2"):
|
||||
|
||||
```bash
|
||||
helm install keel keel/keel --set helmProvider.version="v3"
|
||||
```
|
||||
|
||||
That's it, see [Configuration](https://github.com/keel-hq/keel#configuration) section now.
|
||||
|
||||
### Quick Start
|
||||
|
|
|
@ -305,14 +305,11 @@ var tagsResp = `{
|
|||
]
|
||||
}`
|
||||
|
||||
func TestGetDockerHubManyTags(t *testing.T) {
|
||||
client := registry.New("https://quay.io", "", "")
|
||||
tags, err := client.Tags("coreos/prometheus-operator")
|
||||
if err != nil {
|
||||
t.Errorf("error while getting repo: %s", err)
|
||||
}
|
||||
fmt.Println(tags)
|
||||
func TestGetDockerHubManyTags(t *testing.T) {
|
||||
client := registry.New("https://quay.io", "", "")
|
||||
tags, err := client.Tags("coreos/prometheus-operator")
|
||||
if err != nil {
|
||||
t.Errorf("error while getting repo: %s", err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fmt.Println(tags)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestGetSecret(t *testing.T) {
|
|||
|
||||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(secretDataPayload),
|
||||
},
|
||||
|
@ -71,7 +71,7 @@ func TestGetDockerConfigJSONSecret(t *testing.T) {
|
|||
|
||||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayload),
|
||||
},
|
||||
|
@ -106,7 +106,7 @@ func TestGetDockerConfigJSONSecretUsernmePassword(t *testing.T) {
|
|||
|
||||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
||||
},
|
||||
|
@ -142,7 +142,7 @@ func TestGetFromDefaultCredentials(t *testing.T) {
|
|||
|
||||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
||||
},
|
||||
|
@ -215,9 +215,9 @@ func TestLookupHelmSecret(t *testing.T) {
|
|||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailablePods: &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
v1.Pod{
|
||||
{
|
||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret",
|
||||
},
|
||||
},
|
||||
|
@ -226,7 +226,7 @@ func TestLookupHelmSecret(t *testing.T) {
|
|||
},
|
||||
},
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(fmt.Sprintf(secretDataPayloadEncoded, mustEncode("user-y:pass-y"))),
|
||||
},
|
||||
|
@ -263,9 +263,9 @@ func TestLookupHelmEncodedSecret(t *testing.T) {
|
|||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailablePods: &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
v1.Pod{
|
||||
{
|
||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret",
|
||||
},
|
||||
},
|
||||
|
@ -274,7 +274,7 @@ func TestLookupHelmEncodedSecret(t *testing.T) {
|
|||
},
|
||||
},
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(secretDataPayload),
|
||||
},
|
||||
|
@ -311,10 +311,10 @@ func TestGetDirectHelmSecret(t *testing.T) {
|
|||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailablePods: &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
v1.Pod{
|
||||
{
|
||||
Spec: v1.PodSpec{
|
||||
ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret-dont-look",
|
||||
},
|
||||
},
|
||||
|
@ -323,13 +323,13 @@ func TestGetDirectHelmSecret(t *testing.T) {
|
|||
},
|
||||
},
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"myregistrysecret": &v1.Secret{
|
||||
"myregistrysecret": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(secretDataPayload2),
|
||||
},
|
||||
Type: v1.SecretTypeDockercfg,
|
||||
},
|
||||
"very-secret-dont-look": &v1.Secret{
|
||||
"very-secret-dont-look": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(secretDataPayload),
|
||||
},
|
||||
|
@ -367,9 +367,9 @@ func TestLookupHelmNoSecretsFound(t *testing.T) {
|
|||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailablePods: &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
v1.Pod{
|
||||
{
|
||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "very-secret",
|
||||
},
|
||||
},
|
||||
|
@ -411,9 +411,9 @@ func TestLookupWithPortedRegistry(t *testing.T) {
|
|||
impl := &testutil.FakeK8sImplementer{
|
||||
AvailablePods: &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
v1.Pod{
|
||||
{
|
||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||
v1.LocalObjectReference{
|
||||
{
|
||||
Name: "example.com",
|
||||
},
|
||||
},
|
||||
|
@ -422,7 +422,7 @@ func TestLookupWithPortedRegistry(t *testing.T) {
|
|||
},
|
||||
},
|
||||
AvailableSecret: map[string]*v1.Secret{
|
||||
"example.com": &v1.Secret{
|
||||
"example.com": {
|
||||
Data: map[string][]byte{
|
||||
dockerConfigKey: []byte(secretDataPayloadWithPort),
|
||||
},
|
||||
|
|
|
@ -80,7 +80,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "keelhq/push-workflow-example:0.1.0-dev",
|
||||
},
|
||||
|
@ -140,7 +140,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "keelhq/push-workflow-example:0.1.0",
|
||||
},
|
||||
|
@ -197,7 +197,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "keelhq/push-workflow-example:0.3.0-alpha",
|
||||
},
|
||||
|
@ -335,7 +335,7 @@ func TestPollingPrivateRegistry(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Name: "wd-1",
|
||||
Image: "karolisr/demo-webhook:0.0.1",
|
||||
|
@ -447,7 +447,7 @@ func TestPollingPrivateRegistry(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Name: "wd-1",
|
||||
Image: "registry.gitlab.com/karolisr/keel:0.1.0",
|
||||
|
|
|
@ -101,7 +101,7 @@ func TestWebhooksSemverUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "karolisr/webhook-demo:0.0.14",
|
||||
},
|
||||
|
@ -205,7 +205,7 @@ func TestWebhookHighIntegerUpdate(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "karolisr/webhook-demo:0.0.14",
|
||||
},
|
||||
|
@ -278,6 +278,10 @@ func TestWebhookHighIntegerUpdate(t *testing.T) {
|
|||
|
||||
func TestApprovals(t *testing.T) {
|
||||
|
||||
// approvals endpoint shouldn't work without at least basic auth
|
||||
// see http.go:134
|
||||
t.Skip()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
|
@ -335,7 +339,7 @@ func TestApprovals(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "karolisr/webhook-demo:0.0.14",
|
||||
},
|
||||
|
@ -476,7 +480,7 @@ func TestApprovalsWithAuthentication(t *testing.T) {
|
|||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
v1.Container{
|
||||
{
|
||||
Name: "wd-1",
|
||||
Image: "karolisr/webhook-demo:0.0.14",
|
||||
},
|
||||
|
@ -537,6 +541,10 @@ func TestApprovalsWithAuthentication(t *testing.T) {
|
|||
t.Errorf("failed to make req: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected %d, got: %d", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
var approvals []*types.Approval
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
|
|
@ -55,14 +55,14 @@ func TestCheckDeployment(t *testing.T) {
|
|||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
PollSchedule: types.KeelPollDefaultSchedule,
|
||||
},
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Image: imgB,
|
||||
Provider: "fp",
|
||||
|
@ -144,7 +144,7 @@ func TestCheckECRDeployment(t *testing.T) {
|
|||
imgA, _ := image.Parse("528670773427.dkr.ecr.us-east-2.amazonaws.com/webhook-demo:master")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
|
|
|
@ -23,7 +23,7 @@ func TestWatchMultipleTagsWithSemver(t *testing.T) {
|
|||
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
|
@ -227,7 +227,7 @@ func TestWatchMultipleTagsWithCredentialsHelper(t *testing.T) {
|
|||
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestWatchAllTagsJob(t *testing.T) {
|
|||
reference, _ := image.Parse("foo/bar:1.1.0")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: reference,
|
||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||
},
|
||||
|
@ -221,7 +221,7 @@ func TestWatchAllTagsJobCurrentLatest(t *testing.T) {
|
|||
reference, _ := image.Parse("foo/bar:latest")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: reference,
|
||||
Policy: policy.NewForcePolicy(true),
|
||||
},
|
||||
|
@ -264,7 +264,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
|||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
|
@ -273,7 +273,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
|||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Image: imgB,
|
||||
Provider: "fp",
|
||||
|
@ -281,7 +281,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
|||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||
},
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Image: imgC,
|
||||
Provider: "fp",
|
||||
|
@ -289,7 +289,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
|||
Policy: policy.NewForcePolicy(true),
|
||||
},
|
||||
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Image: imgD,
|
||||
Provider: "fp",
|
||||
|
@ -422,7 +422,7 @@ func TestWatchTagJobLatestECR(t *testing.T) {
|
|||
imgA, _ := image.Parse("528670773427.dkr.ecr.us-east-2.amazonaws.com/webhook-demo:master")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: imgA,
|
||||
Trigger: types.TriggerTypePoll,
|
||||
Provider: "fp",
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestCheckDeployment(t *testing.T) {
|
|||
img, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1")
|
||||
fp := &fakeProvider{
|
||||
images: []*types.TrackedImage{
|
||||
&types.TrackedImage{
|
||||
{
|
||||
Image: img,
|
||||
Provider: "fp",
|
||||
},
|
||||
|
|
|
@ -13,4 +13,4 @@ func ValidateID(id string) error {
|
|||
return fmt.Errorf("image ID '%s' is invalid ", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue