commit
3198921af0
3
Makefile
3
Makefile
|
@ -77,7 +77,8 @@ alpha: image
|
||||||
e2e: install
|
e2e: install
|
||||||
cd tests && go test
|
cd tests && go test
|
||||||
|
|
||||||
run: install
|
run:
|
||||||
|
go install github.com/keel-hq/keel/cmd/keel
|
||||||
keel --no-incluster --ui-dir ui/dist
|
keel --no-incluster --ui-dir ui/dist
|
||||||
|
|
||||||
lint-ui:
|
lint-ui:
|
||||||
|
|
|
@ -90,4 +90,3 @@ func (c *ApprovalContext) Created() string {
|
||||||
c.AddHeader(ApprovalCreatedHeader)
|
c.AddHeader(ApprovalCreatedHeader)
|
||||||
return c.v.CreatedAt.String()
|
return c.v.CreatedAt.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ package hipchat
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/daneharrigan/hipchat"
|
"github.com/daneharrigan/hipchat"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XmppImplementer interface {
|
type XmppImplementer interface {
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"github.com/keel-hq/keel/bot"
|
"github.com/keel-hq/keel/bot"
|
||||||
"github.com/keel-hq/keel/constants"
|
"github.com/keel-hq/keel/constants"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
h "github.com/daneharrigan/hipchat"
|
h "github.com/daneharrigan/hipchat"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const connectionAttemptsDefault = 5
|
const connectionAttemptsDefault = 5
|
||||||
|
|
|
@ -14,27 +14,27 @@ func (b *Bot) RequestApproval(req *types.Approval) error {
|
||||||
req.Message,
|
req.Message,
|
||||||
types.LevelSuccess.Color(),
|
types.LevelSuccess.Color(),
|
||||||
[]slack.AttachmentField{
|
[]slack.AttachmentField{
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Approval required!",
|
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),
|
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,
|
Short: false,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Votes",
|
Title: "Votes",
|
||||||
Value: fmt.Sprintf("%d/%d", req.VotesReceived, req.VotesRequired),
|
Value: fmt.Sprintf("%d/%d", req.VotesReceived, req.VotesRequired),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Delta",
|
Title: "Delta",
|
||||||
Value: req.Delta(),
|
Value: req.Delta(),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Identifier",
|
Title: "Identifier",
|
||||||
Value: req.Identifier,
|
Value: req.Identifier,
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Provider",
|
Title: "Provider",
|
||||||
Value: req.Provider.String(),
|
Value: req.Provider.String(),
|
||||||
Short: true,
|
Short: true,
|
||||||
|
@ -50,22 +50,22 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
||||||
"All approvals received, thanks for voting!",
|
"All approvals received, thanks for voting!",
|
||||||
types.LevelInfo.Color(),
|
types.LevelInfo.Color(),
|
||||||
[]slack.AttachmentField{
|
[]slack.AttachmentField{
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "vote received!",
|
Title: "vote received!",
|
||||||
Value: "Waiting for remaining votes.",
|
Value: "Waiting for remaining votes.",
|
||||||
Short: false,
|
Short: false,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Votes",
|
Title: "Votes",
|
||||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Delta",
|
Title: "Delta",
|
||||||
Value: approval.Delta(),
|
Value: approval.Delta(),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Identifier",
|
Title: "Identifier",
|
||||||
Value: approval.Identifier,
|
Value: approval.Identifier,
|
||||||
Short: true,
|
Short: true,
|
||||||
|
@ -77,27 +77,27 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
||||||
"Change was rejected",
|
"Change was rejected",
|
||||||
types.LevelWarn.Color(),
|
types.LevelWarn.Color(),
|
||||||
[]slack.AttachmentField{
|
[]slack.AttachmentField{
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "change rejected",
|
Title: "change rejected",
|
||||||
Value: "Change was rejected.",
|
Value: "Change was rejected.",
|
||||||
Short: false,
|
Short: false,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Status",
|
Title: "Status",
|
||||||
Value: approval.Status().String(),
|
Value: approval.Status().String(),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Votes",
|
Title: "Votes",
|
||||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Delta",
|
Title: "Delta",
|
||||||
Value: approval.Delta(),
|
Value: approval.Delta(),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Identifier",
|
Title: "Identifier",
|
||||||
Value: approval.Identifier,
|
Value: approval.Identifier,
|
||||||
Short: true,
|
Short: true,
|
||||||
|
@ -109,22 +109,22 @@ func (b *Bot) ReplyToApproval(approval *types.Approval) error {
|
||||||
"All approvals received, thanks for voting!",
|
"All approvals received, thanks for voting!",
|
||||||
types.LevelSuccess.Color(),
|
types.LevelSuccess.Color(),
|
||||||
[]slack.AttachmentField{
|
[]slack.AttachmentField{
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "update approved!",
|
Title: "update approved!",
|
||||||
Value: "All approvals received, thanks for voting!",
|
Value: "All approvals received, thanks for voting!",
|
||||||
Short: false,
|
Short: false,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Votes",
|
Title: "Votes",
|
||||||
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Delta",
|
Title: "Delta",
|
||||||
Value: approval.Delta(),
|
Value: approval.Delta(),
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: "Identifier",
|
Title: "Identifier",
|
||||||
Value: approval.Identifier,
|
Value: approval.Identifier,
|
||||||
Short: true,
|
Short: true,
|
||||||
|
|
|
@ -150,7 +150,7 @@ func (b *Bot) postMessage(title, message, color string, fields []slack.Attachmen
|
||||||
params.IconURL = b.getBotUserIconURL()
|
params.IconURL = b.getBotUserIconURL()
|
||||||
|
|
||||||
attachements := []slack.Attachment{
|
attachements := []slack.Attachment{
|
||||||
slack.Attachment{
|
{
|
||||||
Fallback: message,
|
Fallback: message,
|
||||||
Color: color,
|
Color: color,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
|
|
|
@ -350,7 +350,7 @@ func TestProcessRejectedReply(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsApproval(t *testing.T) {
|
func TestIsApproval(t *testing.T) {
|
||||||
|
|
||||||
event := &slack.MessageEvent{
|
event := &slack.MessageEvent{
|
||||||
Msg: slack.Msg{
|
Msg: slack.Msg{
|
||||||
Channel: "approvals",
|
Channel: "approvals",
|
||||||
|
|
|
@ -3,7 +3,7 @@ name: keel
|
||||||
description: Open source, tool for automating Kubernetes deployment updates. Keel is stateless, robust and lightweight.
|
description: Open source, tool for automating Kubernetes deployment updates. Keel is stateless, robust and lightweight.
|
||||||
version: 0.8.22
|
version: 0.8.22
|
||||||
# Note that we use appVersion to get images tag, so make sure this is correct.
|
# Note that we use appVersion to get images tag, so make sure this is correct.
|
||||||
appVersion: 0.16.0
|
appVersion: 0.16.1
|
||||||
keywords:
|
keywords:
|
||||||
- kubernetes deployment
|
- kubernetes deployment
|
||||||
- helm release
|
- helm release
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
{{- if .Values.rbac.enabled }}
|
{{- if .Values.rbac.enabled }}
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
# apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ template "keel.name" . }}
|
name: {{ template "keel.name" . }}
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
name: {{ template "keel.name" . }}
|
# name: {{ template "keel.name" . }}
|
||||||
|
name: cluster-admin
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: {{ template "serviceAccount.name" . }}
|
name: {{ template "serviceAccount.name" . }}
|
||||||
|
|
|
@ -53,27 +53,33 @@ spec:
|
||||||
{{- if .Values.polling.enabled }}
|
{{- if .Values.polling.enabled }}
|
||||||
# Enable polling
|
# Enable polling
|
||||||
- name: POLL
|
- name: POLL
|
||||||
value: "1"
|
value: "true"
|
||||||
{{- else }}
|
{{- else }}
|
||||||
# Disable polling
|
# Disable polling
|
||||||
- name: POLL
|
- name: POLL
|
||||||
value: "0"
|
value: "false"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.helmProvider.enabled }}
|
{{- if .Values.helmProvider.enabled }}
|
||||||
|
{{- if eq .Values.helmProvider.version "v2" }}
|
||||||
# Enable/disable Helm provider
|
# Enable/disable Helm provider
|
||||||
- name: HELM_PROVIDER
|
- name: HELM_PROVIDER
|
||||||
value: "1"
|
value: "true"
|
||||||
- name: TILLER_NAMESPACE
|
- name: TILLER_NAMESPACE
|
||||||
value: "{{ .Values.helmProvider.tillerNamespace }}"
|
value: "{{ .Values.helmProvider.tillerNamespace }}"
|
||||||
- name: TILLER_ADDRESS
|
- name: TILLER_ADDRESS
|
||||||
value: "{{ .Values.helmProvider.tillerAddress }}"
|
value: "{{ .Values.helmProvider.tillerAddress }}"
|
||||||
|
{{- else if eq .Values.helmProvider.version "v3" }}
|
||||||
|
# Enable/disable Helm provider
|
||||||
|
- name: HELM3_PROVIDER
|
||||||
|
value: "true"
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.gcr.enabled }}
|
{{- if .Values.gcr.enabled }}
|
||||||
# Enable GCR with pub/sub support
|
# Enable GCR with pub/sub support
|
||||||
- name: PROJECT_ID
|
- name: PROJECT_ID
|
||||||
value: "{{ .Values.gcr.projectId }}"
|
value: "{{ .Values.gcr.projectId }}"
|
||||||
- name: PUBSUB
|
- name: PUBSUB
|
||||||
value: "1"
|
value: "true"
|
||||||
{{- if .Values.gcr.clusterName }}
|
{{- if .Values.gcr.clusterName }}
|
||||||
# Customize the cluster name, mainly useful when outside of GKE
|
# Customize the cluster name, mainly useful when outside of GKE
|
||||||
- name: CLUSTER_NAME
|
- name: CLUSTER_NAME
|
||||||
|
|
|
@ -18,6 +18,8 @@ polling:
|
||||||
# Helm provider support
|
# Helm provider support
|
||||||
helmProvider:
|
helmProvider:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# set to version "v3" for Helm v3
|
||||||
|
version: "v2"
|
||||||
tillerNamespace: "kube-system"
|
tillerNamespace: "kube-system"
|
||||||
# optional Tiller address (if portforwarder tunnel doesn't work),
|
# optional Tiller address (if portforwarder tunnel doesn't work),
|
||||||
# if you are using default configuration, setting it to
|
# if you are using default configuration, setting it to
|
||||||
|
@ -126,17 +128,17 @@ secret:
|
||||||
# Keel self-update
|
# Keel self-update
|
||||||
# uncomment lines below if you want Keel to automaticly
|
# uncomment lines below if you want Keel to automaticly
|
||||||
# self-update to the latest release version
|
# self-update to the latest release version
|
||||||
keel:
|
# keel:
|
||||||
# keel policy (all/major/minor/patch/force)
|
# # keel policy (all/major/minor/patch/force)
|
||||||
policy: patch
|
# policy: patch
|
||||||
# trigger type, defaults to events such as pubsub, webhooks
|
# # trigger type, defaults to events such as pubsub, webhooks
|
||||||
trigger: poll
|
# trigger: poll
|
||||||
# polling schedule
|
# # polling schedule
|
||||||
pollSchedule: "@every 3m"
|
# pollSchedule: "@every 3m"
|
||||||
# images to track and update
|
# # images to track and update
|
||||||
images:
|
# images:
|
||||||
- repository: image.repository
|
# - repository: image.repository
|
||||||
tag: image.tag
|
# tag: image.tag
|
||||||
|
|
||||||
# RBAC manifests management
|
# RBAC manifests management
|
||||||
rbac:
|
rbac:
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
netContext "golang.org/x/net/context"
|
|
||||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
kube "k8s.io/client-go/kubernetes"
|
kube "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
|
@ -32,6 +31,7 @@ import (
|
||||||
"github.com/keel-hq/keel/internal/workgroup"
|
"github.com/keel-hq/keel/internal/workgroup"
|
||||||
"github.com/keel-hq/keel/provider"
|
"github.com/keel-hq/keel/provider"
|
||||||
"github.com/keel-hq/keel/provider/helm"
|
"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/provider/kubernetes"
|
||||||
"github.com/keel-hq/keel/registry"
|
"github.com/keel-hq/keel/registry"
|
||||||
"github.com/keel-hq/keel/secrets"
|
"github.com/keel-hq/keel/secrets"
|
||||||
|
@ -58,6 +58,9 @@ import (
|
||||||
_ "github.com/keel-hq/keel/bot/slack"
|
_ "github.com/keel-hq/keel/bot/slack"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
// importing to ensure correct dependencies
|
||||||
|
_ "helm.sh/helm/v3/pkg/action"
|
||||||
)
|
)
|
||||||
|
|
||||||
// gcloud pubsub related config
|
// gcloud pubsub related config
|
||||||
|
@ -70,6 +73,7 @@ const (
|
||||||
EnvHelmProvider = "HELM_PROVIDER" // helm provider
|
EnvHelmProvider = "HELM_PROVIDER" // helm provider
|
||||||
EnvHelmTillerAddress = "TILLER_ADDRESS" // helm provider
|
EnvHelmTillerAddress = "TILLER_ADDRESS" // helm provider
|
||||||
EnvHelmTillerNamespace = "TILLER_NAMESPACE" // helm provider
|
EnvHelmTillerNamespace = "TILLER_NAMESPACE" // helm provider
|
||||||
|
EnvHelm3Provider = "HELM3_PROVIDER" // helm3 provider
|
||||||
EnvUIDir = "UI_DIR"
|
EnvUIDir = "UI_DIR"
|
||||||
|
|
||||||
// EnvDefaultDockerRegistryCfg - default registry configuration that can be passed into
|
// EnvDefaultDockerRegistryCfg - default registry configuration that can be passed into
|
||||||
|
@ -134,7 +138,7 @@ func main() {
|
||||||
notification.RegisterSender("auditor", auditLogger)
|
notification.RegisterSender("auditor", auditLogger)
|
||||||
|
|
||||||
// setting up triggers
|
// setting up triggers
|
||||||
ctx, cancel := netContext.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
notificationLevel := types.LevelInfo
|
notificationLevel := types.LevelInfo
|
||||||
|
@ -355,6 +359,23 @@ func setupProviders(opts *ProviderOpts) (providers provider.Providers) {
|
||||||
enabledProviders = append(enabledProviders, helmProvider)
|
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)
|
providers = provider.New(enabledProviders, opts.approvalsManager)
|
||||||
|
|
||||||
return providers
|
return providers
|
||||||
|
@ -426,7 +447,7 @@ func setupTriggers(ctx context.Context, opts *TriggerOpts) (teardown func()) {
|
||||||
go subManager.Start(ctx)
|
go subManager.Start(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv(EnvTriggerPoll) != "0" {
|
if os.Getenv(EnvTriggerPoll) != "0" || os.Getenv(EnvTriggerPoll) != "false" {
|
||||||
|
|
||||||
registryClient := registry.New()
|
registryClient := registry.New()
|
||||||
watcher := poll.NewRepositoryWatcher(opts.providers, registryClient)
|
watcher := poll.NewRepositoryWatcher(opts.providers, registryClient)
|
||||||
|
|
|
@ -92,11 +92,11 @@ func (s *sender) Send(event types.EventNotification) error {
|
||||||
params.IconURL = constants.KeelLogoURL
|
params.IconURL = constants.KeelLogoURL
|
||||||
|
|
||||||
attachements := []slack.Attachment{
|
attachements := []slack.Attachment{
|
||||||
slack.Attachment{
|
{
|
||||||
Fallback: event.Message,
|
Fallback: event.Message,
|
||||||
Color: event.Level.Color(),
|
Color: event.Level.Color(),
|
||||||
Fields: []slack.AttachmentField{
|
Fields: []slack.AttachmentField{
|
||||||
slack.AttachmentField{
|
{
|
||||||
Title: event.Type.String(),
|
Title: event.Type.String(),
|
||||||
Value: event.Message,
|
Value: event.Message,
|
||||||
Short: false,
|
Short: false,
|
||||||
|
|
184
go.mod
184
go.mod
|
@ -2,119 +2,73 @@ module github.com/keel-hq/keel
|
||||||
|
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
replace (
|
||||||
cloud.google.com/go v0.40.0
|
k8s.io/api => k8s.io/api v0.16.10
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
|
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.16.10
|
||||||
github.com/BurntSushi/toml v0.3.1
|
k8s.io/apimachinery => k8s.io/apimachinery v0.16.10
|
||||||
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e
|
k8s.io/apiserver => k8s.io/apiserver v0.16.10
|
||||||
github.com/Masterminds/goutils v1.1.0
|
k8s.io/cli-runtime => k8s.io/cli-runtime v0.16.10
|
||||||
github.com/Masterminds/semver v1.4.2
|
k8s.io/client-go => k8s.io/client-go v0.16.10
|
||||||
github.com/Masterminds/sprig v2.19.0+incompatible
|
k8s.io/cloud-provider => k8s.io/cloud-provider v0.16.10
|
||||||
github.com/PuerkitoBio/purell v1.1.1
|
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.16.10
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
|
k8s.io/code-generator => k8s.io/code-generator v0.16.10
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
k8s.io/component-base => k8s.io/component-base v0.16.10
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
|
k8s.io/cri-api => k8s.io/cri-api v0.16.10
|
||||||
github.com/aws/aws-sdk-go v1.20.0
|
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.16.10
|
||||||
github.com/beorn7/perks v1.0.0
|
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.16.10
|
||||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1
|
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.16.10
|
||||||
github.com/cyphar/filepath-securejoin v0.2.2
|
k8s.io/kube-proxy => k8s.io/kube-proxy v0.16.10
|
||||||
github.com/daneharrigan/hipchat v0.0.0-20170512185232-835dc879394a
|
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.16.10
|
||||||
github.com/davecgh/go-spew v1.1.1
|
k8s.io/kubectl => k8s.io/kubectl v0.16.10
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
k8s.io/kubelet => k8s.io/kubelet v0.16.10
|
||||||
github.com/docker/distribution v2.6.0-rc.1.0.20180227233429-6fca8d6e6713+incompatible
|
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.16.10
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20180612054059-a9fbbdc8dd87+incompatible
|
k8s.io/metrics => k8s.io/metrics v0.16.10
|
||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
|
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.16.10
|
||||||
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
|
replace (
|
||||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
|
helm.sh/helm/v3 => helm.sh/helm/v3 v3.1.2
|
||||||
github.com/ghodss/yaml v1.0.0
|
k8s.io/helm => k8s.io/helm v2.16.7+incompatible
|
||||||
github.com/go-openapi/jsonpointer v0.19.0
|
)
|
||||||
github.com/go-openapi/jsonreference v0.19.0
|
|
||||||
github.com/go-openapi/spec v0.19.0
|
replace k8s.io/kubernetes => k8s.io/kubernetes v1.16.10
|
||||||
github.com/go-openapi/swag v0.19.0
|
|
||||||
github.com/gobwas/glob v0.2.3
|
require (
|
||||||
github.com/gogo/protobuf v1.2.1
|
cloud.google.com/go/pubsub v1.4.0
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/golang/protobuf v1.3.1
|
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||||
github.com/google/btree v1.0.0
|
github.com/aws/aws-sdk-go v1.31.10
|
||||||
github.com/google/go-querystring v1.0.0
|
github.com/daneharrigan/hipchat v0.0.0-20170512185232-835dc879394a
|
||||||
github.com/google/gofuzz v1.0.0
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/google/uuid v1.1.1
|
github.com/docker/distribution v2.7.1+incompatible
|
||||||
github.com/googleapis/gnostic v0.2.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/gorilla/mux v1.7.2
|
github.com/golang/protobuf v1.4.2
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/hashicorp/golang-lru v0.5.1
|
github.com/jinzhu/gorm v1.9.12
|
||||||
github.com/huandu/xstrings v1.2.0
|
github.com/jmoiron/sqlx v1.2.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.7
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0
|
github.com/mfridman/tparse v0.7.4 // indirect
|
||||||
github.com/jinzhu/gorm v1.9.9
|
github.com/nlopes/slack v0.6.0
|
||||||
github.com/jinzhu/inflection v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
github.com/prometheus/client_golang v1.6.0
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 // indirect
|
||||||
github.com/json-iterator/go v1.1.6
|
github.com/rusenask/cron v1.1.0
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2
|
github.com/rusenask/docker-registry-client v0.0.0-20200210164146-049272422097
|
||||||
github.com/lib/pq v1.1.1
|
github.com/ryanuber/go-glob v1.0.0
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/mattn/go-sqlite3 v1.10.0
|
github.com/tbruyelle/hipchat-go v0.0.0-20170717082847-35aebc99209a
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
github.com/urfave/negroni v1.0.0
|
||||||
github.com/mfridman/tparse v0.7.4 // indirect
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0
|
google.golang.org/api v0.26.0
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
google.golang.org/grpc v1.29.1
|
||||||
github.com/modern-go/reflect2 v1.0.1
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||||
github.com/nlopes/slack v0.5.0
|
helm.sh/helm/v3 v3.0.0-00010101000000-000000000000
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
k8s.io/api v0.17.2
|
||||||
github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99
|
k8s.io/apimachinery v0.17.2
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
k8s.io/cli-runtime v0.17.2
|
||||||
github.com/pkg/errors v0.8.1
|
k8s.io/client-go v0.17.2
|
||||||
github.com/prometheus/client_golang v0.9.4
|
k8s.io/helm v0.0.0-00010101000000-000000000000
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
sigs.k8s.io/yaml v1.1.0
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,6 +101,7 @@ func (s *TriggerServer) Start() error {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"port": s.port,
|
"port": s.port,
|
||||||
}).Info("webhook trigger server starting...")
|
}).Info("webhook trigger server starting...")
|
||||||
|
|
||||||
return s.server.ListenAndServe()
|
return s.server.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// namespace/release name/version
|
// namespace/release name:version
|
||||||
func getIdentifier(namespace, name, version string) string {
|
func getIdentifier(plan *UpdatePlan) string {
|
||||||
return namespace + "/" + name + ":" + version
|
return fmt.Sprintf("%s/%s:%s", plan.Namespace, plan.Name, plan.NewVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (approvedPlans []*UpdatePlan) {
|
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,
|
"error": err,
|
||||||
"release_name": plan.Name,
|
"release_name": plan.Name,
|
||||||
"namespace": plan.Namespace,
|
"namespace": plan.Namespace,
|
||||||
|
"version": plan.NewVersion,
|
||||||
}).Error("provider.helm: failed to check approval status for deployment")
|
}).Error("provider.helm: failed to check approval status for deployment")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -36,7 +37,7 @@ func (p *Provider) checkForApprovals(event *types.Event, plans []*UpdatePlan) (a
|
||||||
|
|
||||||
// updateComplete is called after we successfully update resource
|
// updateComplete is called after we successfully update resource
|
||||||
func (p *Provider) updateComplete(plan *UpdatePlan) error {
|
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) {
|
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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
identifier := getIdentifier(plan.Namespace, plan.Name, plan.NewVersion)
|
identifier := getIdentifier(plan)
|
||||||
|
|
||||||
// checking for existing approval
|
// checking for existing approval
|
||||||
existing, err := p.approvalManager.Get(identifier)
|
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 {
|
for _, release := range releases {
|
||||||
// getting configuration
|
// getting configuration
|
||||||
|
|
||||||
|
if release.Chart.Metadata.Name == "" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
vals, err := values(release.Chart, release.Config)
|
vals, err := values(release.Chart, release.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
|
@ -344,7 +348,7 @@ func (p *Provider) applyPlans(plans []*UpdatePlan) error {
|
||||||
"error": err,
|
"error": err,
|
||||||
"name": plan.Name,
|
"name": plan.Name,
|
||||||
"namespace": plan.Namespace,
|
"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
|
var msg string
|
||||||
|
|
|
@ -113,13 +113,12 @@ keel:
|
||||||
images:
|
images:
|
||||||
- repository: image.repository
|
- repository: image.repository
|
||||||
tag: image.tag
|
tag: image.tag
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
@ -165,7 +164,7 @@ func TestGetChartPolicyFromProm(t *testing.T) {
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: promChartValues},
|
Values: &chart.Config{Raw: promChartValues},
|
||||||
|
@ -233,7 +232,7 @@ keel:
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
@ -276,7 +275,7 @@ image2:
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
@ -327,7 +326,7 @@ keel:
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
@ -385,7 +384,7 @@ func TestGetPolicyFromConfig(t *testing.T) {
|
||||||
|
|
||||||
func TestGetImagesFromConfig(t *testing.T) {
|
func TestGetImagesFromConfig(t *testing.T) {
|
||||||
vals, err := testingConfigYaml(&KeelChartConfig{Policy: "all", Images: []ImageDetails{
|
vals, err := testingConfigYaml(&KeelChartConfig{Policy: "all", Images: []ImageDetails{
|
||||||
ImageDetails{
|
{
|
||||||
RepositoryPath: "repopath",
|
RepositoryPath: "repopath",
|
||||||
TagPath: "tagpath",
|
TagPath: "tagpath",
|
||||||
},
|
},
|
||||||
|
@ -428,13 +427,14 @@ keel:
|
||||||
|
|
||||||
`
|
`
|
||||||
myChart := &chart.Chart{
|
myChart := &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
Metadata: &chart.Metadata{Name: "app-x"},
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: myChart,
|
Chart: myChart,
|
||||||
Config: &chart.Config{Raw: ""},
|
Config: &chart.Config{Raw: ""},
|
||||||
|
@ -596,7 +596,7 @@ keel:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypeDefault,
|
Trigger: types.TriggerTypeDefault,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||||
},
|
},
|
||||||
|
@ -610,7 +610,7 @@ keel:
|
||||||
Trigger: types.TriggerTypeDefault,
|
Trigger: types.TriggerTypeDefault,
|
||||||
NotificationChannels: []string{"chan1", "chan2"},
|
NotificationChannels: []string{"chan1", "chan2"},
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||||
},
|
},
|
||||||
|
@ -624,7 +624,7 @@ keel:
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
PollSchedule: "@every 30m",
|
PollSchedule: "@every 30m",
|
||||||
Images: []ImageDetails{
|
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),
|
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||||
},
|
},
|
||||||
|
@ -637,7 +637,7 @@ keel:
|
||||||
MatchPreRelease: false,
|
MatchPreRelease: false,
|
||||||
Trigger: types.TriggerTypeDefault,
|
Trigger: types.TriggerTypeDefault,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, false),
|
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, false),
|
||||||
},
|
},
|
||||||
|
@ -681,7 +681,7 @@ keel:
|
||||||
fakeImpl := &fakeImplementer{
|
fakeImpl := &fakeImplementer{
|
||||||
listReleasesResponse: &rls.ListReleasesResponse{
|
listReleasesResponse: &rls.ListReleasesResponse{
|
||||||
Releases: []*hapi_release5.Release{
|
Releases: []*hapi_release5.Release{
|
||||||
&hapi_release5.Release{
|
{
|
||||||
Name: "release-1",
|
Name: "release-1",
|
||||||
Chart: &chart.Chart{
|
Chart: &chart.Chart{
|
||||||
Values: &chart.Config{Raw: chartVals},
|
Values: &chart.Config{Raw: chartVals},
|
||||||
|
|
|
@ -65,15 +65,18 @@ keel:
|
||||||
`
|
`
|
||||||
|
|
||||||
helloWorldChart := &hapi_chart.Chart{
|
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{
|
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{
|
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 {
|
type args struct {
|
||||||
|
@ -111,7 +114,7 @@ keel:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{
|
{
|
||||||
RepositoryPath: "image.repository",
|
RepositoryPath: "image.repository",
|
||||||
TagPath: "image.tag",
|
TagPath: "image.tag",
|
||||||
},
|
},
|
||||||
|
@ -144,7 +147,7 @@ keel:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{
|
{
|
||||||
RepositoryPath: "image.repository",
|
RepositoryPath: "image.repository",
|
||||||
TagPath: "image.tag",
|
TagPath: "image.tag",
|
||||||
ReleaseNotes: "https://github.com/keel-hq/keel/releases",
|
ReleaseNotes: "https://github.com/keel-hq/keel/releases",
|
||||||
|
@ -271,21 +274,26 @@ image:
|
||||||
`
|
`
|
||||||
|
|
||||||
helloWorldChart := &hapi_chart.Chart{
|
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{
|
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{
|
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{
|
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{
|
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 {
|
type args struct {
|
||||||
|
@ -324,7 +332,7 @@ image:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||||
},
|
},
|
||||||
|
@ -381,7 +389,7 @@ image:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
{RepositoryPath: "image.repository", TagPath: "image.tag"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewForcePolicy(false),
|
Plc: policy.NewForcePolicy(false),
|
||||||
},
|
},
|
||||||
|
@ -425,7 +433,7 @@ image:
|
||||||
MatchPreRelease: true,
|
MatchPreRelease: true,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Images: []ImageDetails{
|
Images: []ImageDetails{
|
||||||
ImageDetails{RepositoryPath: "image.repository"},
|
{RepositoryPath: "image.repository"},
|
||||||
},
|
},
|
||||||
Plc: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
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 := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -37,7 +37,7 @@ func TestCheckRequestedApproval(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -88,7 +88,7 @@ func TestCheckRequestedApprovalAnnotation(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -113,7 +113,7 @@ func TestCheckRequestedApprovalAnnotation(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -171,7 +171,7 @@ func TestApprovedCheck(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -192,7 +192,7 @@ func TestApprovedCheck(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -252,7 +252,7 @@ func TestApprovalsCleanup(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -273,7 +273,7 @@ func TestApprovalsCleanup(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -141,7 +141,7 @@ func TestGetNamespaces(t *testing.T) {
|
||||||
fi := &fakeImplementer{
|
fi := &fakeImplementer{
|
||||||
namespaces: &v1.NamespaceList{
|
namespaces: &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -203,7 +203,7 @@ func TestGetImpacted(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -224,7 +224,7 @@ func TestGetImpacted(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -244,7 +244,7 @@ func TestGetImpacted(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -300,7 +300,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -322,7 +322,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -342,7 +342,7 @@ func TestGetImpactedPolicyAnnotations(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -403,7 +403,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -424,7 +424,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -444,7 +444,7 @@ func TestPrereleaseGetImpactedA(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -495,7 +495,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -516,7 +516,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -536,7 +536,7 @@ func TestPrereleaseGetImpactedB(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -582,7 +582,7 @@ func TestProcessEvent(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -608,7 +608,7 @@ func TestProcessEvent(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -634,7 +634,7 @@ func TestProcessEvent(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -665,7 +665,7 @@ func TestProcessEvent(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
Image: "gcr.io/v2-namespace/bye-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -710,7 +710,7 @@ func TestProcessEventBuildNumber(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -731,7 +731,7 @@ func TestProcessEventBuildNumber(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:10",
|
Image: "gcr.io/v2-namespace/hello-world:10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -773,7 +773,7 @@ func TestEventSent(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -794,7 +794,7 @@ func TestEventSent(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -841,7 +841,7 @@ func TestEventSentWithReleaseNotes(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -862,7 +862,7 @@ func TestEventSentWithReleaseNotes(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
Image: "gcr.io/v2-namespace/hello-world:10.0.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -914,7 +914,7 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -935,10 +935,10 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/greetings-world:1.1.1",
|
Image: "gcr.io/v2-namespace/greetings-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -958,7 +958,7 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1015,7 +1015,7 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -1036,10 +1036,10 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1060,7 +1060,7 @@ func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1117,7 +1117,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -1138,7 +1138,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/foo-world",
|
Image: "gcr.io/v2-namespace/foo-world",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1159,7 +1159,7 @@ func TestGetImpactedUntaggedImage(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1216,7 +1216,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -1237,7 +1237,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world",
|
Image: "gcr.io/v2-namespace/hello-world",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1258,7 +1258,7 @@ func TestGetImpactedUntaggedOneImage(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1316,7 +1316,7 @@ func TestTrackedImages(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -1336,12 +1336,12 @@ func TestTrackedImages(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImagePullSecrets: []v1.LocalObjectReference{
|
ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret",
|
Name: "very-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1380,7 +1380,7 @@ func TestTrackedImagesWithSecrets(t *testing.T) {
|
||||||
fp := &fakeImplementer{}
|
fp := &fakeImplementer{}
|
||||||
fp.namespaces = &v1.NamespaceList{
|
fp.namespaces = &v1.NamespaceList{
|
||||||
Items: []v1.Namespace{
|
Items: []v1.Namespace{
|
||||||
v1.Namespace{
|
{
|
||||||
meta_v1.TypeMeta{},
|
meta_v1.TypeMeta{},
|
||||||
meta_v1.ObjectMeta{Name: "xxxx"},
|
meta_v1.ObjectMeta{Name: "xxxx"},
|
||||||
v1.NamespaceSpec{},
|
v1.NamespaceSpec{},
|
||||||
|
@ -1403,12 +1403,12 @@ func TestTrackedImagesWithSecrets(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImagePullSecrets: []v1.LocalObjectReference{
|
ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret",
|
Name: "very-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world",
|
Image: "gcr.io/v2-namespace/hello-world",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -93,7 +93,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -125,7 +125,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/goodbye-world:earliest",
|
Image: "gcr.io/v2-namespace/goodbye-world:earliest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -163,7 +163,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:alpha",
|
Image: "gcr.io/v2-namespace/hello-world:alpha",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -201,7 +201,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:latest",
|
Image: "karolisr/keel:latest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -229,7 +229,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:0.2.0",
|
Image: "karolisr/keel:0.2.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -266,7 +266,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:master",
|
Image: "karolisr/keel:master",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -296,7 +296,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:master",
|
Image: "karolisr/keel:master",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -336,7 +336,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:latest-staging",
|
Image: "karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -366,7 +366,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:latest-staging",
|
Image: "karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -406,7 +406,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -437,7 +437,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -471,7 +471,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "karolisr/keel:latest-acceptance",
|
Image: "karolisr/keel:latest-acceptance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -511,7 +511,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -542,7 +542,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
Image: "eu.gcr.io/karolisr/keel:latest-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -581,7 +581,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:release-1",
|
Image: "eu.gcr.io/karolisr/keel:release-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -612,7 +612,7 @@ func TestProvider_checkForUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "eu.gcr.io/karolisr/keel:release-2",
|
Image: "eu.gcr.io/karolisr/keel:release-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -693,7 +693,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -721,7 +721,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -759,7 +759,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1",
|
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -796,7 +796,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1-staging",
|
Image: "gcr.io/v2-namespace/hello-prerelease:v1.1.1-staging",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -828,7 +828,7 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
Template: v1.PodTemplateSpec{
|
Template: v1.PodTemplateSpec{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -868,10 +868,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.1",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -899,10 +899,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -939,10 +939,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:latest",
|
Image: "gcr.io/v2-namespace/hello-world:latest",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -970,10 +970,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1013,10 +1013,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1047,10 +1047,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
Image: "yo-world:1.1.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1090,10 +1090,10 @@ func TestProvider_checkForUpdateSemver(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
Image: "gcr.io/v2-namespace/hello-world:1.1.2",
|
||||||
},
|
},
|
||||||
v1.Container{
|
{
|
||||||
Image: "yo-world:1.1.1",
|
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"
|
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.
|
That's it, see [Configuration](https://github.com/keel-hq/keel#configuration) section now.
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
|
@ -305,14 +305,11 @@ var tagsResp = `{
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
func TestGetDockerHubManyTags(t *testing.T) {
|
func TestGetDockerHubManyTags(t *testing.T) {
|
||||||
client := registry.New("https://quay.io", "", "")
|
client := registry.New("https://quay.io", "", "")
|
||||||
tags, err := client.Tags("coreos/prometheus-operator")
|
tags, err := client.Tags("coreos/prometheus-operator")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while getting repo: %s", err)
|
t.Errorf("error while getting repo: %s", err)
|
||||||
}
|
|
||||||
fmt.Println(tags)
|
|
||||||
}
|
}
|
||||||
|
fmt.Println(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ func TestGetSecret(t *testing.T) {
|
||||||
|
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(secretDataPayload),
|
dockerConfigKey: []byte(secretDataPayload),
|
||||||
},
|
},
|
||||||
|
@ -71,7 +71,7 @@ func TestGetDockerConfigJSONSecret(t *testing.T) {
|
||||||
|
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayload),
|
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayload),
|
||||||
},
|
},
|
||||||
|
@ -106,7 +106,7 @@ func TestGetDockerConfigJSONSecretUsernmePassword(t *testing.T) {
|
||||||
|
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
||||||
},
|
},
|
||||||
|
@ -142,7 +142,7 @@ func TestGetFromDefaultCredentials(t *testing.T) {
|
||||||
|
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
dockerConfigJSONKey: []byte(secretDockerConfigJSONPayloadWithUsernamePassword),
|
||||||
},
|
},
|
||||||
|
@ -215,9 +215,9 @@ func TestLookupHelmSecret(t *testing.T) {
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailablePods: &v1.PodList{
|
AvailablePods: &v1.PodList{
|
||||||
Items: []v1.Pod{
|
Items: []v1.Pod{
|
||||||
v1.Pod{
|
{
|
||||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret",
|
Name: "very-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -226,7 +226,7 @@ func TestLookupHelmSecret(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(fmt.Sprintf(secretDataPayloadEncoded, mustEncode("user-y:pass-y"))),
|
dockerConfigKey: []byte(fmt.Sprintf(secretDataPayloadEncoded, mustEncode("user-y:pass-y"))),
|
||||||
},
|
},
|
||||||
|
@ -263,9 +263,9 @@ func TestLookupHelmEncodedSecret(t *testing.T) {
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailablePods: &v1.PodList{
|
AvailablePods: &v1.PodList{
|
||||||
Items: []v1.Pod{
|
Items: []v1.Pod{
|
||||||
v1.Pod{
|
{
|
||||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret",
|
Name: "very-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -274,7 +274,7 @@ func TestLookupHelmEncodedSecret(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(secretDataPayload),
|
dockerConfigKey: []byte(secretDataPayload),
|
||||||
},
|
},
|
||||||
|
@ -311,10 +311,10 @@ func TestGetDirectHelmSecret(t *testing.T) {
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailablePods: &v1.PodList{
|
AvailablePods: &v1.PodList{
|
||||||
Items: []v1.Pod{
|
Items: []v1.Pod{
|
||||||
v1.Pod{
|
{
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
ImagePullSecrets: []v1.LocalObjectReference{
|
ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret-dont-look",
|
Name: "very-secret-dont-look",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -323,13 +323,13 @@ func TestGetDirectHelmSecret(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"myregistrysecret": &v1.Secret{
|
"myregistrysecret": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(secretDataPayload2),
|
dockerConfigKey: []byte(secretDataPayload2),
|
||||||
},
|
},
|
||||||
Type: v1.SecretTypeDockercfg,
|
Type: v1.SecretTypeDockercfg,
|
||||||
},
|
},
|
||||||
"very-secret-dont-look": &v1.Secret{
|
"very-secret-dont-look": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(secretDataPayload),
|
dockerConfigKey: []byte(secretDataPayload),
|
||||||
},
|
},
|
||||||
|
@ -367,9 +367,9 @@ func TestLookupHelmNoSecretsFound(t *testing.T) {
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailablePods: &v1.PodList{
|
AvailablePods: &v1.PodList{
|
||||||
Items: []v1.Pod{
|
Items: []v1.Pod{
|
||||||
v1.Pod{
|
{
|
||||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "very-secret",
|
Name: "very-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -411,9 +411,9 @@ func TestLookupWithPortedRegistry(t *testing.T) {
|
||||||
impl := &testutil.FakeK8sImplementer{
|
impl := &testutil.FakeK8sImplementer{
|
||||||
AvailablePods: &v1.PodList{
|
AvailablePods: &v1.PodList{
|
||||||
Items: []v1.Pod{
|
Items: []v1.Pod{
|
||||||
v1.Pod{
|
{
|
||||||
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
Spec: v1.PodSpec{ImagePullSecrets: []v1.LocalObjectReference{
|
||||||
v1.LocalObjectReference{
|
{
|
||||||
Name: "example.com",
|
Name: "example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -422,7 +422,7 @@ func TestLookupWithPortedRegistry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AvailableSecret: map[string]*v1.Secret{
|
AvailableSecret: map[string]*v1.Secret{
|
||||||
"example.com": &v1.Secret{
|
"example.com": {
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
dockerConfigKey: []byte(secretDataPayloadWithPort),
|
dockerConfigKey: []byte(secretDataPayloadWithPort),
|
||||||
},
|
},
|
||||||
|
|
|
@ -80,7 +80,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "keelhq/push-workflow-example:0.1.0-dev",
|
Image: "keelhq/push-workflow-example:0.1.0-dev",
|
||||||
},
|
},
|
||||||
|
@ -140,7 +140,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "keelhq/push-workflow-example:0.1.0",
|
Image: "keelhq/push-workflow-example:0.1.0",
|
||||||
},
|
},
|
||||||
|
@ -197,7 +197,7 @@ func TestPollingSemverUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "keelhq/push-workflow-example:0.3.0-alpha",
|
Image: "keelhq/push-workflow-example:0.3.0-alpha",
|
||||||
},
|
},
|
||||||
|
@ -335,7 +335,7 @@ func TestPollingPrivateRegistry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: v1.PullAlways,
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "karolisr/demo-webhook:0.0.1",
|
Image: "karolisr/demo-webhook:0.0.1",
|
||||||
|
@ -447,7 +447,7 @@ func TestPollingPrivateRegistry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: v1.PullAlways,
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "registry.gitlab.com/karolisr/keel:0.1.0",
|
Image: "registry.gitlab.com/karolisr/keel:0.1.0",
|
||||||
|
|
|
@ -101,7 +101,7 @@ func TestWebhooksSemverUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "karolisr/webhook-demo:0.0.14",
|
Image: "karolisr/webhook-demo:0.0.14",
|
||||||
},
|
},
|
||||||
|
@ -205,7 +205,7 @@ func TestWebhookHighIntegerUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "karolisr/webhook-demo:0.0.14",
|
Image: "karolisr/webhook-demo:0.0.14",
|
||||||
},
|
},
|
||||||
|
@ -278,6 +278,10 @@ func TestWebhookHighIntegerUpdate(t *testing.T) {
|
||||||
|
|
||||||
func TestApprovals(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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -335,7 +339,7 @@ func TestApprovals(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "karolisr/webhook-demo:0.0.14",
|
Image: "karolisr/webhook-demo:0.0.14",
|
||||||
},
|
},
|
||||||
|
@ -476,7 +480,7 @@ func TestApprovalsWithAuthentication(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
v1.Container{
|
{
|
||||||
Name: "wd-1",
|
Name: "wd-1",
|
||||||
Image: "karolisr/webhook-demo:0.0.14",
|
Image: "karolisr/webhook-demo:0.0.14",
|
||||||
},
|
},
|
||||||
|
@ -537,6 +541,10 @@ func TestApprovalsWithAuthentication(t *testing.T) {
|
||||||
t.Errorf("failed to make req: %s", err)
|
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
|
var approvals []*types.Approval
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
|
@ -55,14 +55,14 @@ func TestCheckDeployment(t *testing.T) {
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
PollSchedule: types.KeelPollDefaultSchedule,
|
PollSchedule: types.KeelPollDefaultSchedule,
|
||||||
},
|
},
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Image: imgB,
|
Image: imgB,
|
||||||
Provider: "fp",
|
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")
|
imgA, _ := image.Parse("528670773427.dkr.ecr.us-east-2.amazonaws.com/webhook-demo:master")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestWatchMultipleTagsWithSemver(t *testing.T) {
|
||||||
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
@ -227,7 +227,7 @@ func TestWatchMultipleTagsWithCredentialsHelper(t *testing.T) {
|
||||||
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
imgA, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1.1")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
|
|
@ -177,7 +177,7 @@ func TestWatchAllTagsJob(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:1.1.0")
|
reference, _ := image.Parse("foo/bar:1.1.0")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: reference,
|
Image: reference,
|
||||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeAll, true),
|
||||||
},
|
},
|
||||||
|
@ -221,7 +221,7 @@ func TestWatchAllTagsJobCurrentLatest(t *testing.T) {
|
||||||
reference, _ := image.Parse("foo/bar:latest")
|
reference, _ := image.Parse("foo/bar:latest")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: reference,
|
Image: reference,
|
||||||
Policy: policy.NewForcePolicy(true),
|
Policy: policy.NewForcePolicy(true),
|
||||||
},
|
},
|
||||||
|
@ -264,7 +264,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
@ -273,7 +273,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||||
},
|
},
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Image: imgB,
|
Image: imgB,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
@ -281,7 +281,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
Policy: policy.NewSemverPolicy(policy.SemverPolicyTypeMajor, true),
|
||||||
},
|
},
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Image: imgC,
|
Image: imgC,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
@ -289,7 +289,7 @@ func TestWatchMultipleTags(t *testing.T) {
|
||||||
Policy: policy.NewForcePolicy(true),
|
Policy: policy.NewForcePolicy(true),
|
||||||
},
|
},
|
||||||
|
|
||||||
&types.TrackedImage{
|
{
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Image: imgD,
|
Image: imgD,
|
||||||
Provider: "fp",
|
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")
|
imgA, _ := image.Parse("528670773427.dkr.ecr.us-east-2.amazonaws.com/webhook-demo:master")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: imgA,
|
Image: imgA,
|
||||||
Trigger: types.TriggerTypePoll,
|
Trigger: types.TriggerTypePoll,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
|
|
|
@ -82,7 +82,7 @@ func TestCheckDeployment(t *testing.T) {
|
||||||
img, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1")
|
img, _ := image.Parse("gcr.io/v2-namespace/hello-world:1.1")
|
||||||
fp := &fakeProvider{
|
fp := &fakeProvider{
|
||||||
images: []*types.TrackedImage{
|
images: []*types.TrackedImage{
|
||||||
&types.TrackedImage{
|
{
|
||||||
Image: img,
|
Image: img,
|
||||||
Provider: "fp",
|
Provider: "fp",
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,4 +13,4 @@ func ValidateID(id string) error {
|
||||||
return fmt.Errorf("image ID '%s' is invalid ", id)
|
return fmt.Errorf("image ID '%s' is invalid ", id)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue