Implement event recording
- emit Kubernetes events for reconciliation actions - forward events to notification controller - remove the Profile API/CRD
This commit is contained in:
parent
570d2ea05e
commit
2ebd5b6450
|
@ -22,7 +22,6 @@ RUN go mod download
|
|||
COPY main.go main.go
|
||||
COPY api/ api/
|
||||
COPY controllers/ controllers/
|
||||
COPY internal/ internal/
|
||||
|
||||
# build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o kustomize-controller main.go
|
||||
|
|
61
README.md
61
README.md
|
@ -22,7 +22,7 @@ Features:
|
|||
* prunes the Kubernetes objects removed from source
|
||||
* checks the health of the deployed workloads
|
||||
* runs `Kustomizations` in a specific order, taking into account the depends-on relationship
|
||||
* reports on Slack or Discord whenever a `Kustomization` status changes
|
||||
* notifies whenever a `Kustomization` status changes
|
||||
|
||||
Specifications:
|
||||
* [API](docs/spec/v1alpha1/README.md)
|
||||
|
@ -262,34 +262,57 @@ set in the Git repository.
|
|||
|
||||
### Configure alerting
|
||||
|
||||
The kustomize controller can post message to Slack or Discord whenever a kustomization status changes.
|
||||
The kustomize-controller emits Kubernetes events whenever a kustomization status changes.
|
||||
|
||||
Alerting can be configured by creating a profile that targets a list of kustomizations:
|
||||
You can use the [notification-controller](https://github.com/fluxcd/notification-controller) to forward these events
|
||||
to Slack, Microsoft Teams, Discord or Rocket chart.
|
||||
|
||||
Create a notification provider for Slack:
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.fluxcd.io/v1alpha1
|
||||
kind: Profile
|
||||
apiVersion: notification.fluxcd.io/v1alpha1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: default
|
||||
name: slack
|
||||
namespace: kustomize-system
|
||||
spec:
|
||||
alert:
|
||||
type: slack
|
||||
verbosity: info
|
||||
address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
|
||||
username: kustomize-controller
|
||||
channel: general
|
||||
kustomizations:
|
||||
- '*'
|
||||
type: slack
|
||||
channel: alerts
|
||||
secretRef:
|
||||
name: slack-url
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: slack-url
|
||||
namespace: kustomize-system
|
||||
data:
|
||||
address: <encoded-url>
|
||||
```
|
||||
|
||||
The alert provider type can be: `slack` or `discord` and the verbosity can be set to `info` or `error`.
|
||||
Create an alert for a list of GitRepositories and Kustomizations:
|
||||
|
||||
The `*` wildcard tells the controller to use this profile for all kustomizations that are present
|
||||
in the same namespace as the profile.
|
||||
Multiple profiles can be used to send alerts to different channels or Slack organizations.
|
||||
```yaml
|
||||
apiVersion: notification.fluxcd.io/v1alpha1
|
||||
kind: Alert
|
||||
metadata:
|
||||
name: on-call
|
||||
namespace: kustomize-system
|
||||
spec:
|
||||
providerRef:
|
||||
name: slack
|
||||
eventSeverity: info
|
||||
eventSources:
|
||||
- kind: GitRepository
|
||||
name: podinfo-releases
|
||||
- kind: Kustomization
|
||||
name: podinfo-production
|
||||
```
|
||||
|
||||
When the verbosity is set to `error`, the controller will alert on any error encountered during the
|
||||
Multiple alerts can be used to send notifications to different channels or Slack organizations.
|
||||
|
||||
The event severity can be set to `info` or `error`.
|
||||
When the severity is set to `error`, the controller will alert on any error encountered during the
|
||||
reconciliation process. This includes kustomize build and validation errors, apply errors and
|
||||
health check failures.
|
||||
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Flux CD contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ProfileSpec defines the desired state of Profile
|
||||
type ProfileSpec struct {
|
||||
// Alerting configuration of the kustomizations targeted by this profile.
|
||||
// +optional
|
||||
Alert *AlertProvider `json:"alert"`
|
||||
|
||||
// The list of kustomizations that this profile applies to.
|
||||
// +required
|
||||
Kustomizations []string `json:"kustomizations"`
|
||||
}
|
||||
|
||||
// Alert is the configuration of alerting for a specific provider
|
||||
type AlertProvider struct {
|
||||
// HTTP(S) webhook address of this provider
|
||||
// +required
|
||||
Address string `json:"address"`
|
||||
|
||||
// Alert channel for this provider
|
||||
// +required
|
||||
Channel string `json:"channel"`
|
||||
|
||||
// Bot username for this provider
|
||||
// +required
|
||||
Username string `json:"username"`
|
||||
|
||||
// Filter alerts based on verbosity level, defaults to ('error').
|
||||
// +kubebuilder:validation:Enum=info;error
|
||||
// +optional
|
||||
Verbosity string `json:"verbosity,omitempty"`
|
||||
|
||||
// Type of provider
|
||||
// +kubebuilder:validation:Enum=slack;discord
|
||||
// +required
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ProfileStatus defines the observed state of Profile
|
||||
type ProfileStatus struct {
|
||||
// +optional
|
||||
Conditions []Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
|
||||
|
||||
// Profile is the Schema for the profiles API
|
||||
type Profile struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ProfileSpec `json:"spec,omitempty"`
|
||||
Status ProfileStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ProfileList contains a list of Profile
|
||||
type ProfileList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Profile `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Profile{}, &ProfileList{})
|
||||
}
|
|
@ -25,21 +25,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AlertProvider) DeepCopyInto(out *AlertProvider) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertProvider.
|
||||
func (in *AlertProvider) DeepCopy() *AlertProvider {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AlertProvider)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Condition) DeepCopyInto(out *Condition) {
|
||||
*out = *in
|
||||
|
@ -179,112 +164,6 @@ func (in *KustomizationStatus) DeepCopy() *KustomizationStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Profile) DeepCopyInto(out *Profile) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Profile.
|
||||
func (in *Profile) DeepCopy() *Profile {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Profile)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Profile) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProfileList) DeepCopyInto(out *ProfileList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Profile, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileList.
|
||||
func (in *ProfileList) DeepCopy() *ProfileList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProfileList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ProfileList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProfileSpec) DeepCopyInto(out *ProfileSpec) {
|
||||
*out = *in
|
||||
if in.Alert != nil {
|
||||
in, out := &in.Alert, &out.Alert
|
||||
*out = new(AlertProvider)
|
||||
**out = **in
|
||||
}
|
||||
if in.Kustomizations != nil {
|
||||
in, out := &in.Kustomizations, &out.Kustomizations
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileSpec.
|
||||
func (in *ProfileSpec) DeepCopy() *ProfileSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProfileSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProfileStatus) DeepCopyInto(out *ProfileStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileStatus.
|
||||
func (in *ProfileStatus) DeepCopy() *ProfileStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProfileStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) {
|
||||
*out = *in
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.5
|
||||
creationTimestamp: null
|
||||
name: profiles.kustomize.fluxcd.io
|
||||
spec:
|
||||
additionalPrinterColumns:
|
||||
- JSONPath: .status.conditions[?(@.type=="Ready")].status
|
||||
name: Ready
|
||||
type: string
|
||||
- JSONPath: .status.conditions[?(@.type=="Ready")].message
|
||||
name: Status
|
||||
type: string
|
||||
- JSONPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
group: kustomize.fluxcd.io
|
||||
names:
|
||||
kind: Profile
|
||||
listKind: ProfileList
|
||||
plural: profiles
|
||||
singular: profile
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Profile is the Schema for the profiles API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ProfileSpec defines the desired state of Profile
|
||||
properties:
|
||||
alert:
|
||||
description: Alerting configuration of the kustomizations targeted by
|
||||
this profile.
|
||||
properties:
|
||||
address:
|
||||
description: HTTP(S) webhook address of this provider
|
||||
type: string
|
||||
channel:
|
||||
description: Alert channel for this provider
|
||||
type: string
|
||||
type:
|
||||
description: Type of provider
|
||||
enum:
|
||||
- slack
|
||||
- discord
|
||||
type: string
|
||||
username:
|
||||
description: Bot username for this provider
|
||||
type: string
|
||||
verbosity:
|
||||
description: Filter alerts based on verbosity level, defaults to
|
||||
('error').
|
||||
enum:
|
||||
- info
|
||||
- error
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- channel
|
||||
- type
|
||||
- username
|
||||
type: object
|
||||
kustomizations:
|
||||
description: The list of kustomizations that this profile applies to.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- kustomizations
|
||||
type: object
|
||||
status:
|
||||
description: ProfileStatus defines the observed state of Profile
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: Condition contains condition information for a kustomization.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the timestamp corresponding
|
||||
to the last status change of this condition.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: Message is a human readable description of the details
|
||||
of the last transition, complementing reason.
|
||||
type: string
|
||||
reason:
|
||||
description: Reason is a brief machine readable explanation for
|
||||
the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Status of the condition, one of ('True', 'False',
|
||||
'Unknown').
|
||||
type: string
|
||||
type:
|
||||
description: Type of the condition, currently ('Ready').
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
|
@ -2,6 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||
kind: Kustomization
|
||||
resources:
|
||||
- bases/kustomize.fluxcd.io_kustomizations.yaml
|
||||
- bases/kustomize.fluxcd.io_profiles.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# permissions for end users to edit profiles.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: profile-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles/status
|
||||
verbs:
|
||||
- get
|
|
@ -1,20 +0,0 @@
|
|||
# permissions for end users to view profiles.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: profile-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles/status
|
||||
verbs:
|
||||
- get
|
|
@ -26,26 +26,6 @@ rules:
|
|||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kustomize.fluxcd.io
|
||||
resources:
|
||||
- profiles/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- source.fluxcd.io
|
||||
resources:
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
apiVersion: kustomize.fluxcd.io/v1alpha1
|
||||
kind: Profile
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
alert:
|
||||
type: slack
|
||||
address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
|
||||
username: kustomize-controller
|
||||
channel: general
|
||||
verbosity: info
|
||||
kustomizations:
|
||||
- '*'
|
|
@ -86,7 +86,7 @@ func (r *GitRepositoryWatcher) Reconcile(req ctrl.Request) (ctrl.Result, error)
|
|||
|
||||
func (r *GitRepositoryWatcher) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// create a kustomization index based on Git repository name
|
||||
err := mgr.GetFieldIndexer().IndexField(&kustomizev1.Kustomization{}, kustomizev1.SourceIndexKey,
|
||||
err := mgr.GetFieldIndexer().IndexField(context.TODO(), &kustomizev1.Kustomization{}, kustomizev1.SourceIndexKey,
|
||||
func(rawObj runtime.Object) []string {
|
||||
k := rawObj.(*kustomizev1.Kustomization)
|
||||
if k.Spec.SourceRef.Kind == "GitRepository" {
|
||||
|
|
|
@ -33,23 +33,27 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/tools/reference"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
kustypes "sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
||||
"github.com/fluxcd/kustomize-controller/internal/alert"
|
||||
"github.com/fluxcd/pkg/lockedfile"
|
||||
"github.com/fluxcd/pkg/recorder"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
)
|
||||
|
||||
// KustomizationReconciler reconciles a Kustomization object
|
||||
type KustomizationReconciler struct {
|
||||
client.Client
|
||||
requeueDependency time.Duration
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
requeueDependency time.Duration
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
EventRecorder kuberecorder.EventRecorder
|
||||
ExternalEventRecorder *recorder.EventRecorder
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=kustomize.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
|
||||
|
@ -130,7 +134,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
|||
// instead we requeue on a fix interval.
|
||||
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
|
||||
log.Error(err, msg)
|
||||
r.alert(kustomization, msg, "info")
|
||||
r.event(kustomization, msg, "info")
|
||||
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
|
||||
}
|
||||
log.Info("All dependencies area ready, proceeding with apply")
|
||||
|
@ -140,7 +144,7 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
|||
syncedKustomization, err := r.sync(*kustomization.DeepCopy(), source)
|
||||
if err != nil {
|
||||
log.Error(err, "Kustomization apply failed", "revision", source.GetArtifact().Revision)
|
||||
r.alert(kustomization, err.Error(), "error")
|
||||
r.event(kustomization, err.Error(), "error")
|
||||
}
|
||||
|
||||
// update status
|
||||
|
@ -488,7 +492,7 @@ func (r *KustomizationReconciler) apply(kustomization kustomizev1.Kustomization,
|
|||
}
|
||||
}
|
||||
if diff {
|
||||
r.alert(kustomization, string(output), "info")
|
||||
r.event(kustomization, string(output), "info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -570,7 +574,7 @@ func (r *KustomizationReconciler) checkHealth(kustomization kustomizev1.Kustomiz
|
|||
}
|
||||
|
||||
if alerts != "" {
|
||||
r.alert(kustomization, alerts, "info")
|
||||
r.event(kustomization, alerts, "info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -628,56 +632,24 @@ func (r *KustomizationReconciler) checkDependencies(kustomization kustomizev1.Ku
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *KustomizationReconciler) getProfiles(kustomization kustomizev1.Kustomization) ([]kustomizev1.Profile, error) {
|
||||
list := make([]kustomizev1.Profile, 0)
|
||||
var profiles kustomizev1.ProfileList
|
||||
err := r.List(context.TODO(), &profiles, client.InNamespace(kustomization.GetNamespace()))
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
// filter profiles that match this kustomization taking into account '*' wildcard
|
||||
for _, profile := range profiles.Items {
|
||||
for _, name := range profile.Spec.Kustomizations {
|
||||
if name == kustomization.GetName() || name == "*" {
|
||||
list = append(list, profile)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *KustomizationReconciler) alert(kustomization kustomizev1.Kustomization, msg string, verbosity string) {
|
||||
profiles, err := r.getProfiles(kustomization)
|
||||
func (r *KustomizationReconciler) event(kustomization kustomizev1.Kustomization, msg string, severity string) {
|
||||
r.EventRecorder.Event(&kustomization, "Normal", severity, msg)
|
||||
objRef, err := reference.GetReference(r.Scheme, &kustomization)
|
||||
if err != nil {
|
||||
r.Log.WithValues(
|
||||
strings.ToLower(kustomization.Kind),
|
||||
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
|
||||
).Error(err, "unable to list profiles")
|
||||
).Error(err, "unable to send event")
|
||||
return
|
||||
}
|
||||
|
||||
for _, profile := range profiles {
|
||||
if settings := profile.Spec.Alert; settings != nil {
|
||||
provider, err := alert.NewProvider(settings.Type, settings.Address, settings.Username, settings.Channel)
|
||||
if err != nil {
|
||||
r.Log.WithValues(
|
||||
strings.ToLower(kustomization.Kind),
|
||||
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
|
||||
).Error(err, "unable to configure alert provider")
|
||||
continue
|
||||
}
|
||||
if settings.Verbosity == verbosity || verbosity == "error" {
|
||||
err = provider.Post(kustomization.GetName(), kustomization.GetNamespace(), msg, verbosity)
|
||||
if err != nil {
|
||||
r.Log.WithValues(
|
||||
strings.ToLower(kustomization.Kind),
|
||||
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
|
||||
).Error(err, "unable to send alert")
|
||||
}
|
||||
}
|
||||
if r.ExternalEventRecorder != nil {
|
||||
if err := r.ExternalEventRecorder.Eventf(*objRef, nil, severity, severity, msg); err != nil {
|
||||
r.Log.WithValues(
|
||||
strings.ToLower(kustomization.Kind),
|
||||
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
|
||||
).Error(err, "unable to send event")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Flux CD contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
||||
)
|
||||
|
||||
// ProfileReconciler reconciles a Profile object
|
||||
type ProfileReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=kustomize.fluxcd.io,resources=profiles,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=kustomize.fluxcd.io,resources=profiles/status,verbs=get;update;patch
|
||||
|
||||
func (r *ProfileReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var profile kustomizev1.Profile
|
||||
if err := r.Get(ctx, req.NamespacedName, &profile); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
log := r.Log.WithValues(strings.ToLower(profile.Kind), req.NamespacedName)
|
||||
|
||||
init := true
|
||||
for _, condition := range profile.Status.Conditions {
|
||||
if condition.Type == kustomizev1.ReadyCondition && condition.Status == corev1.ConditionTrue {
|
||||
init = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if init {
|
||||
profile.Status.Conditions = []kustomizev1.Condition{
|
||||
{
|
||||
Type: kustomizev1.ReadyCondition,
|
||||
Status: corev1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: kustomizev1.InitializedReason,
|
||||
Message: kustomizev1.InitializedReason,
|
||||
},
|
||||
}
|
||||
if err := r.Status().Update(ctx, &profile); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
log.Info("Profile initialised")
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ProfileReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&kustomizev1.Profile{}).
|
||||
Complete(r)
|
||||
}
|
|
@ -10,8 +10,6 @@
|
|||
Resource Types:
|
||||
<ul class="simple"><li>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.Kustomization">Kustomization</a>
|
||||
</li><li>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.Profile">Profile</a>
|
||||
</li></ul>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.Kustomization">Kustomization
|
||||
</h3>
|
||||
|
@ -220,190 +218,11 @@ KustomizationStatus
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.Profile">Profile
|
||||
</h3>
|
||||
<p>Profile is the Schema for the profiles API</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiVersion</code><br>
|
||||
string</td>
|
||||
<td>
|
||||
<code>kustomize.fluxcd.io/v1alpha1</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
string
|
||||
</td>
|
||||
<td>
|
||||
<code>Profile</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>metadata</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
|
||||
Kubernetes meta/v1.ObjectMeta
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
Refer to the Kubernetes API documentation for the fields of the
|
||||
<code>metadata</code> field.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>spec</code><br>
|
||||
<em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.ProfileSpec">
|
||||
ProfileSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<code>alert</code><br>
|
||||
<em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.AlertProvider">
|
||||
AlertProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Alerting configuration of the kustomizations targeted by this profile.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kustomizations</code><br>
|
||||
<em>
|
||||
[]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>The list of kustomizations that this profile applies to.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>status</code><br>
|
||||
<em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.ProfileStatus">
|
||||
ProfileStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.AlertProvider">AlertProvider
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.ProfileSpec">ProfileSpec</a>)
|
||||
</p>
|
||||
<p>Alert is the configuration of alerting for a specific provider</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>address</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>HTTP(S) webhook address of this provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>channel</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Alert channel for this provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>username</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Bot username for this provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>verbosity</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Filter alerts based on verbosity level, defaults to (‘error’).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>type</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Type of provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.Condition">Condition
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.KustomizationStatus">KustomizationStatus</a>,
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.ProfileStatus">ProfileStatus</a>)
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.KustomizationStatus">KustomizationStatus</a>)
|
||||
</p>
|
||||
<p>Condition contains condition information for a kustomization.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
|
@ -696,86 +515,6 @@ Snapshot
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.ProfileSpec">ProfileSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.Profile">Profile</a>)
|
||||
</p>
|
||||
<p>ProfileSpec defines the desired state of Profile</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>alert</code><br>
|
||||
<em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.AlertProvider">
|
||||
AlertProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Alerting configuration of the kustomizations targeted by this profile.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kustomizations</code><br>
|
||||
<em>
|
||||
[]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>The list of kustomizations that this profile applies to.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.ProfileStatus">ProfileStatus
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.Profile">Profile</a>)
|
||||
</p>
|
||||
<p>ProfileStatus defines the observed state of Profile</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>conditions</code><br>
|
||||
<em>
|
||||
<a href="#kustomize.fluxcd.io/v1alpha1.Condition">
|
||||
[]Condition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="kustomize.fluxcd.io/v1alpha1.ServiceAccount">ServiceAccount
|
||||
</h3>
|
||||
<p>
|
||||
|
|
|
@ -14,8 +14,6 @@ of Kubernetes objects generated with Kustomize.
|
|||
+ [Kustomization dependencies](kustomization.md#kustomization-dependencies)
|
||||
+ [Role-based access control](kustomization.md#role-based-access-control)
|
||||
+ [Status](kustomization.md#status)
|
||||
- [Profile CRD](profile.md)
|
||||
+ [Alerting configuration](profile.md#alerting)
|
||||
|
||||
## Implementation
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
# Profile
|
||||
|
||||
The `Profile` API defines a common behavior for a group of [Kustomization](kustomization.md) objects.
|
||||
|
||||
## Specification
|
||||
|
||||
```go
|
||||
type ProfileSpec struct {
|
||||
// Alerting configuration of the kustomizations targeted by this profile.
|
||||
// +optional
|
||||
Alert *AlertProvider `json:"alert"`
|
||||
|
||||
// The list of kustomizations that this profile applies to.
|
||||
// +required
|
||||
Kustomizations []string `json:"kustomizations"`
|
||||
}
|
||||
```
|
||||
|
||||
Alerting configuration:
|
||||
|
||||
```go
|
||||
type AlertProvider struct {
|
||||
// HTTP(S) webhook address of this provider
|
||||
// +required
|
||||
Address string `json:"address"`
|
||||
|
||||
// Alert channel for this provider
|
||||
// +required
|
||||
Channel string `json:"channel"`
|
||||
|
||||
// Bot username for this provider
|
||||
// +required
|
||||
Username string `json:"username"`
|
||||
|
||||
// Filter alerts based on verbosity level, defaults to ('error').
|
||||
// +kubebuilder:validation:Enum=info;error
|
||||
// +optional
|
||||
Verbosity string `json:"verbosity,omitempty"`
|
||||
|
||||
// Type of provider
|
||||
// +kubebuilder:validation:Enum=slack;discord
|
||||
// +required
|
||||
Type string `json:"type"`
|
||||
}
|
||||
```
|
||||
|
||||
Status condition types:
|
||||
|
||||
```go
|
||||
const (
|
||||
// ReadyCondition represents the fact that a given Profile has been
|
||||
// processed by the controller.
|
||||
ReadyCondition string = "Ready"
|
||||
)
|
||||
```
|
||||
|
||||
Status condition reasons:
|
||||
|
||||
```go
|
||||
const (
|
||||
// InitializedReason represents the fact that a given resource has been initialized.
|
||||
InitializedReason string = "Initialized"
|
||||
)
|
||||
```
|
||||
|
||||
## Alerting
|
||||
|
||||
Alerting can be configured by creating a profile that contains an alert definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.fluxcd.io/v1alpha1
|
||||
kind: Profile
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
alert:
|
||||
type: slack
|
||||
verbosity: info
|
||||
address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
|
||||
username: kustomize-controller
|
||||
channel: general
|
||||
kustomizations:
|
||||
- '*'
|
||||
```
|
||||
|
||||
The alert provider type can be: `slack` or `discord` and the verbosity can be set to `info` or `error`.
|
||||
|
||||
The `*` wildcard tells the controller to use this profile for all kustomizations that are present
|
||||
in the same namespace as the profile.
|
||||
Multiple profiles can be used to send alerts to different channels or Slack organizations.
|
||||
|
||||
When the verbosity is set to `error`, the controller will alert on any error encountered during the
|
||||
reconciliation process. This includes kustomize build and validation errors, apply errors and
|
||||
health check failures.
|
||||
|
||||
When the verbosity is set to `info`, the controller will alert whenever a kustomization status changes.
|
||||
|
10
go.mod
10
go.mod
|
@ -3,15 +3,15 @@ module github.com/fluxcd/kustomize-controller
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg v0.0.1
|
||||
github.com/fluxcd/pkg v0.0.2
|
||||
github.com/fluxcd/source-controller v0.0.1
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
github.com/onsi/gomega v1.8.1
|
||||
k8s.io/api v0.17.2
|
||||
k8s.io/apimachinery v0.17.2
|
||||
k8s.io/client-go v0.17.2
|
||||
sigs.k8s.io/controller-runtime v0.5.0
|
||||
k8s.io/api v0.18.4
|
||||
k8s.io/apimachinery v0.18.4
|
||||
k8s.io/client-go v0.18.4
|
||||
sigs.k8s.io/controller-runtime v0.6.0
|
||||
sigs.k8s.io/kustomize/api v0.4.1
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
|
44
go.sum
44
go.sum
|
@ -124,6 +124,7 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
|
|||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
|
@ -135,8 +136,8 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
|
|||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fluxcd/pkg v0.0.1 h1:yECp5SBjX7vUBOjd3KYBoVQwt22A0u1SZJjYV4PduAk=
|
||||
github.com/fluxcd/pkg v0.0.1/go.mod h1:3DgEcVmkVYrA/BDb/fyDIJllxK++c/ovLCMPRlkAp9Y=
|
||||
github.com/fluxcd/pkg v0.0.2 h1:e2ekyxBNZg0phh3adSxCsaGcozGyy/FQwhD2LtA+Y0Q=
|
||||
github.com/fluxcd/pkg v0.0.2/go.mod h1:rtlppQU+9DNikyDZptLdOeTf+wBvQQiQQ/J113FPoeU=
|
||||
github.com/fluxcd/source-controller v0.0.1 h1:17/b/Zcb3OUkUoo03W+L7TGwkCKG23K9HrgL+d5WMXE=
|
||||
github.com/fluxcd/source-controller v0.0.1/go.mod h1:tmscNdCxEt7+Xt2g1+bI38hMPw2leYMFAaCn4UlMGuw=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
|
@ -278,6 +279,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
|||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
|
@ -287,6 +290,7 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
|||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
|
@ -305,10 +309,14 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -453,6 +461,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx
|
|||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
|
@ -582,6 +592,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
|
@ -659,6 +670,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -729,6 +741,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
|
|||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
@ -770,31 +783,51 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
|||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
|
||||
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4=
|
||||
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
|
||||
k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
|
||||
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
|
||||
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
|
||||
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
|
||||
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA=
|
||||
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
|
||||
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
|
||||
k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc=
|
||||
k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
|
||||
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
|
||||
k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc=
|
||||
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
|
||||
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
|
||||
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
|
@ -804,15 +837,22 @@ mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIa
|
|||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
|
||||
sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo=
|
||||
sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8=
|
||||
sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
|
||||
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/kustomize/api v0.4.1 h1:Lwco6Rsxd3TcubJzx9wAV2k7roh0M95FjrS29n76TRo=
|
||||
sigs.k8s.io/kustomize/api v0.4.1/go.mod h1:NqxqT+wbYHrD0P19Uu4dXiMsVwI1IwQs+MJHlLhmPqQ=
|
||||
sigs.k8s.io/kustomize/kyaml v0.1.11/go.mod h1:72/rLkSi+L/pHM1oCjwrf3ClU+tH5kZQvvdLSqIHwWU=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
package alert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Provider holds the information needed to post alerts
|
||||
type Provider struct {
|
||||
URL string
|
||||
Username string
|
||||
Channel string
|
||||
}
|
||||
|
||||
// Payload holds the channel and attachments
|
||||
type Payload struct {
|
||||
Channel string `json:"channel"`
|
||||
Username string `json:"username"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
// Attachment holds the markdown message body
|
||||
type Attachment struct {
|
||||
Color string `json:"color"`
|
||||
AuthorName string `json:"author_name"`
|
||||
Text string `json:"text"`
|
||||
MrkdwnIn []string `json:"mrkdwn_in"`
|
||||
}
|
||||
|
||||
// NewProvider validates the URL and returns a provider object
|
||||
func NewProvider(providerType string, hookURL string, username string, channel string) (*Provider, error) {
|
||||
hook, err := url.ParseRequestURI(hookURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hook URL %s", hookURL)
|
||||
}
|
||||
|
||||
if providerType == "discord" {
|
||||
// https://birdie0.github.io/discord-webhooks-guide/other/slack_formatting.html
|
||||
if !strings.HasSuffix(hookURL, "/slack") {
|
||||
hook.Path = path.Join(hook.Path, "slack")
|
||||
hookURL = hook.String()
|
||||
}
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return nil, errors.New("empty username")
|
||||
}
|
||||
|
||||
if channel == "" {
|
||||
return nil, errors.New("empty channel")
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
Channel: channel,
|
||||
URL: hookURL,
|
||||
Username: username,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Post message to the provider hook URL
|
||||
func (s *Provider) Post(name string, namespace string, message string, severity string) error {
|
||||
payload := Payload{
|
||||
Channel: s.Channel,
|
||||
Username: s.Username,
|
||||
}
|
||||
|
||||
color := "good"
|
||||
if severity == "error" {
|
||||
color = "danger"
|
||||
}
|
||||
|
||||
a := Attachment{
|
||||
Color: color,
|
||||
AuthorName: fmt.Sprintf("%s/%s", namespace, name),
|
||||
Text: message,
|
||||
MrkdwnIn: []string{"text"},
|
||||
}
|
||||
|
||||
payload.Attachments = []Attachment{a}
|
||||
|
||||
err := postMessage(s.URL, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postMessage failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postMessage(address string, payload interface{}) error {
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling notification payload failed: %w", err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
req, err := http.NewRequest("POST", address, b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http.NewRequest failed: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-type", "application/json")
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
res, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sending notification failed: %w", err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
statusCode := res.StatusCode
|
||||
if statusCode != 200 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
return fmt.Errorf("sending notification failed: %s", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
main.go
36
main.go
|
@ -21,15 +21,15 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
||||
"github.com/fluxcd/kustomize-controller/controllers"
|
||||
"github.com/fluxcd/pkg/recorder"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
||||
"github.com/fluxcd/kustomize-controller/controllers"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
|
@ -49,6 +49,7 @@ func init() {
|
|||
func main() {
|
||||
var (
|
||||
metricsAddr string
|
||||
eventsAddr string
|
||||
enableLeaderElection bool
|
||||
concurrent int
|
||||
requeueDependency time.Duration
|
||||
|
@ -56,6 +57,7 @@ func main() {
|
|||
)
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8282", "The address the metric endpoint binds to.")
|
||||
flag.StringVar(&eventsAddr, "events-addr", "", "The address of the events receiver.")
|
||||
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
|
@ -66,6 +68,16 @@ func main() {
|
|||
|
||||
ctrl.SetLogger(zap.New(zap.UseDevMode(!logJSON)))
|
||||
|
||||
var eventRecorder *recorder.EventRecorder
|
||||
if eventsAddr != "" {
|
||||
if er, err := recorder.NewEventRecorder(eventsAddr, "kustomize-controller"); err != nil {
|
||||
setupLog.Error(err, "unable to create event recorder")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
eventRecorder = er
|
||||
}
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
|
@ -88,9 +100,11 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
if err = (&controllers.KustomizationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Kustomization"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Kustomization"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
EventRecorder: mgr.GetEventRecorderFor("kustomize-controller"),
|
||||
ExternalEventRecorder: eventRecorder,
|
||||
}).SetupWithManager(mgr, controllers.KustomizationReconcilerOptions{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
DependencyRequeueInterval: requeueDependency,
|
||||
|
@ -98,14 +112,6 @@ func main() {
|
|||
setupLog.Error(err, "unable to create controller", "controller", "Kustomization")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = (&controllers.ProfileReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Profile"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Profile")
|
||||
os.Exit(1)
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
|
|
Loading…
Reference in New Issue