From 1ec271346f157406880fc1031f5af01ef9e5f68b Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Wed, 6 Oct 2021 17:07:48 -0700 Subject: [PATCH] Add support for using an OCI image as source This change defines an OCIRepository CRD that allows a user to specify a given image to use as a source. The contents of the image (including images with multiple layers) are converted into a TAR and exposed to consumers following the same conventions as the other source types. apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: OCIRepository metadata: name: podinfo-deploy namespace: flux-system spec: interval: 10m url: ghcr.io/stefanprodan/podinfo-deploy ref: # one of tag: "latest" digest: "sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2" semver: "1.x" auth: # one of secretRef: name: regcred serviceAccountName: reg In addition to the url, interval, timeout, ignore, and suspend keys (all of which behave consistently with the existing source types) this CRD also defines authentication via both an image pull Secret and a service serviceAccountName which provide ways to contribute registry connection credentials for the specified image. This change also adds a new way to write to the storage archive by streaming data from an incoming TAR without writing it to the filesystem. A couple of code and test functions were extracted to reuse common functionality for both archive strategies. Signed-off-by: Ben Hale --- .github/workflows/e2e.yaml | 2 + PROJECT | 3 + api/v1beta1/ocirepository_types.go | 221 ++++++++ api/v1beta1/zz_generated.deepcopy.go | 159 ++++++ ...rce.toolkit.fluxcd.io_ocirepositories.yaml | 189 +++++++ config/crd/kustomization.yaml | 1 + config/rbac/ocirepository_editor_role.yaml | 24 + config/rbac/ocirepository_viewer_role.yaml | 20 + config/rbac/role.yaml | 38 ++ .../samples/source_v1beta1_ocirepository.yaml | 11 + controllers/ocirepository_controller.go | 484 ++++++++++++++++++ controllers/storage.go | 161 ++++-- controllers/storage_test.go | 226 ++++++-- controllers/suite_test.go | 3 + go.mod | 2 + go.sum | 95 +++- main.go | 14 + 17 files changed, 1547 insertions(+), 106 deletions(-) create mode 100644 api/v1beta1/ocirepository_types.go create mode 100644 config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml create mode 100644 config/rbac/ocirepository_editor_role.yaml create mode 100644 config/rbac/ocirepository_viewer_role.yaml create mode 100644 config/samples/source_v1beta1_ocirepository.yaml create mode 100644 controllers/ocirepository_controller.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 12472092b..fe64e56f3 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -56,6 +56,7 @@ jobs: kubectl -n source-system wait gitrepository/gitrepository-sample --for=condition=ready --timeout=1m kubectl -n source-system wait helmrepository/helmrepository-sample --for=condition=ready --timeout=1m kubectl -n source-system wait helmchart/helmchart-sample --for=condition=ready --timeout=1m + kubectl -n source-system wait ocirepository/ocirepository-sample --for=condition=ready --timeout=1m kubectl -n source-system delete -f ./config/samples - name: Run HelmChart values file tests run: | @@ -113,6 +114,7 @@ jobs: kubectl -n source-system get gitrepositories -oyaml kubectl -n source-system get helmrepositories -oyaml kubectl -n source-system get helmcharts -oyaml + kubectl -n source-system get ocirepositories -oyaml kubectl -n source-system get all kubectl -n source-system logs deploy/source-controller kubectl -n minio get all diff --git a/PROJECT b/PROJECT index a807390b9..aff547871 100644 --- a/PROJECT +++ b/PROJECT @@ -13,4 +13,7 @@ resources: - group: source kind: Bucket version: v1beta1 +- group: source + kind: OCIRepository + version: v1beta1 version: "2" diff --git a/api/v1beta1/ocirepository_types.go b/api/v1beta1/ocirepository_types.go new file mode 100644 index 000000000..da582ea40 --- /dev/null +++ b/api/v1beta1/ocirepository_types.go @@ -0,0 +1,221 @@ +/* +Copyright 2020 The Flux authors + +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 v1beta1 + +import ( + "github.com/fluxcd/pkg/apis/meta" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // OCIRepositoryKind is the string representation of a OCIRepository. + OCIRepositoryKind = "OCIRepository" +) + +// OCIRepositorySpec defines the desired state of OCIRepository +type OCIRepositorySpec struct { + + // URL is a reference to an image in a remote registry + // +required + URL string `json:"url"` + + // The OCI reference to pull and monitor for changes, defaults to + // latest tag. + // +optional + Reference *OCIRepositoryRef `json:"ref,omitempty"` + + // The credentials to use to pull and monitor for changes, defaults + // to anonymous access. + // +optional + Authentication *OCIRepositoryAuth `json:"auth,omitempty"` + + // The interval at which to check for image updates. + // +required + Interval metav1.Duration `json:"interval"` + + // The timeout for remote OCI Repository operations like pulling, defaults to 20s. + // +kubebuilder:default="20s" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Ignore overrides the set of excluded patterns in the .sourceignore format + // (which is the same as .gitignore). If not provided, a default will be used, + // consult the documentation for your version to find out what those are. + // +optional + Ignore *string `json:"ignore,omitempty"` + + // This flag tells the controller to suspend the reconciliation of this source. + // +optional + Suspend bool `json:"suspend,omitempty"` +} + +// OCIRepositoryRef defines the image reference for the OCIRepository's URL +type OCIRepositoryRef struct { + + // Digest is the image digest to pull, takes precedence over SemVer. + // Value should be in the form sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + // +optional + Digest string `json:"digest,omitempty"` + + // SemVer is the range of tags to pull selecting the latest within + // the range, takes precedence over Tag. + // +optional + SemVer string `json:"semver,omitempty"` + + // Tag is the image tag to pull, defaults to latest. + // +kubebuilder:default:=latest + // +optional + Tag string `json:"tag,omitempty"` +} + +// OCIRepositoryAuth defines the desired authentication mechanism of OCIRepository +type OCIRepositoryAuth struct { + + // SecretRef contains the secret name containing the registry login + // credentials to resolve image metadata. + // The secret must be of type kubernetes.io/dockerconfigjson. + // +optional + SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` + + // ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate + // the image pull if the service account has attached pull secrets. For more information: + // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} + +// OCIRepositoryStatus defines the observed state of OCIRepository +type OCIRepositoryStatus struct { + // ObservedGeneration is the last observed generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Conditions holds the conditions for the OCIRepository. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // URL is the download link for the artifact output of the last + //image sync. + // +optional + URL string `json:"url,omitempty"` + + // Artifact represents the output of the last successful image sync. + // +optional + Artifact *Artifact `json:"artifact,omitempty"` + + meta.ReconcileRequestStatus `json:",inline"` +} + +const ( + // OCIRepositoryOperationSucceedReason represents the fact that the + // image pull operation succeeded. + OCIRepositoryOperationSucceedReason string = "OCIRepositoryOperationSucceed" + + // OCIRepositoryOperationFailedReason represents the fact that the + // image pull operation failed. + OCIRepositoryOperationFailedReason string = "OCIRepositoryOperationFailed" +) + +// OCIRepositoryProgressing resets the conditions of the OCIRepository +// to metav1.Condition of type meta.ReadyCondition with status 'Unknown' +// and meta.ProgressingReason reason and message. It returns the +// modified OCCIRepository. +func OCIRepositoryProgressing(repository OCIRepository) OCIRepository { + repository.Status.ObservedGeneration = repository.Generation + repository.Status.URL = "" + repository.Status.Conditions = []metav1.Condition{} + meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") + return repository +} + +// OCIRepositoryReady sets the given Artifact and URL on the +// OCIRepository and sets the meta.ReadyCondition to 'True', with the +// given reason and message. It returns the modified OCIRepository. +func OCIRepositoryReady(repository OCIRepository, artifact Artifact, url, reason, message string) OCIRepository { + repository.Status.Artifact = &artifact + repository.Status.URL = url + meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionTrue, reason, message) + return repository +} + +// OCIRepositoryNotReady sets the meta.ReadyCondition on the given +// OCIRepository to 'False', with the given reason and message. It +// returns the modified OCIRepository. +func OCIRepositoryNotReady(repository OCIRepository, reason, message string) OCIRepository { + meta.SetResourceCondition(&repository, meta.ReadyCondition, metav1.ConditionFalse, reason, message) + return repository +} + +// OCIRepositoryReadyMessage returns the message of the +// metav1.Condition of type meta.ReadyCondition with status 'True' if +// present, or an empty string. +func OCIRepositoryReadyMessage(repository OCIRepository) string { + if c := apimeta.FindStatusCondition(repository.Status.Conditions, meta.ReadyCondition); c != nil { + if c.Status == metav1.ConditionTrue { + return c.Message + } + } + return "" +} + +// GetArtifact returns the latest artifact from the source if present in the +// status sub-resource. +func (in *OCIRepository) GetArtifact() *Artifact { + return in.Status.Artifact +} + +// GetStatusConditions returns a pointer to the Status.Conditions slice +func (in *OCIRepository) GetStatusConditions() *[]metav1.Condition { + return &in.Status.Conditions +} + +// GetInterval returns the interval at which the source is updated. +func (in *OCIRepository) GetInterval() metav1.Duration { + return in.Spec.Interval +} + +// +genclient +// +genclient:Namespaced +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="URL",type=string,JSONPath=`.spec.url` +// +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="" + +// OCIRepository is the Schema for the ocirepositories API +type OCIRepository struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OCIRepositorySpec `json:"spec,omitempty"` + Status OCIRepositoryStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OCIRepositoryList contains a list of OCIRepository +type OCIRepositoryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OCIRepository `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OCIRepository{}, &OCIRepositoryList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index fd20920de..390d1d5b9 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* @@ -587,3 +588,161 @@ func (in *LocalHelmChartSourceReference) DeepCopy() *LocalHelmChartSourceReferen in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCIRepository) DeepCopyInto(out *OCIRepository) { + *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 OCIRepository. +func (in *OCIRepository) DeepCopy() *OCIRepository { + if in == nil { + return nil + } + out := new(OCIRepository) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OCIRepository) 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 *OCIRepositoryAuth) DeepCopyInto(out *OCIRepositoryAuth) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(meta.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryAuth. +func (in *OCIRepositoryAuth) DeepCopy() *OCIRepositoryAuth { + if in == nil { + return nil + } + out := new(OCIRepositoryAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCIRepositoryList) DeepCopyInto(out *OCIRepositoryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OCIRepository, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryList. +func (in *OCIRepositoryList) DeepCopy() *OCIRepositoryList { + if in == nil { + return nil + } + out := new(OCIRepositoryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OCIRepositoryList) 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 *OCIRepositoryRef) DeepCopyInto(out *OCIRepositoryRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryRef. +func (in *OCIRepositoryRef) DeepCopy() *OCIRepositoryRef { + if in == nil { + return nil + } + out := new(OCIRepositoryRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCIRepositorySpec) DeepCopyInto(out *OCIRepositorySpec) { + *out = *in + if in.Reference != nil { + in, out := &in.Reference, &out.Reference + *out = new(OCIRepositoryRef) + **out = **in + } + if in.Authentication != nil { + in, out := &in.Authentication, &out.Authentication + *out = new(OCIRepositoryAuth) + (*in).DeepCopyInto(*out) + } + out.Interval = in.Interval + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Ignore != nil { + in, out := &in.Ignore, &out.Ignore + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositorySpec. +func (in *OCIRepositorySpec) DeepCopy() *OCIRepositorySpec { + if in == nil { + return nil + } + out := new(OCIRepositorySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCIRepositoryStatus) DeepCopyInto(out *OCIRepositoryStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Artifact != nil { + in, out := &in.Artifact, &out.Artifact + *out = new(Artifact) + (*in).DeepCopyInto(*out) + } + out.ReconcileRequestStatus = in.ReconcileRequestStatus +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryStatus. +func (in *OCIRepositoryStatus) DeepCopy() *OCIRepositoryStatus { + if in == nil { + return nil + } + out := new(OCIRepositoryStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml new file mode 100644 index 000000000..93057bc0f --- /dev/null +++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml @@ -0,0 +1,189 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.5.0 + creationTimestamp: null + name: ocirepositories.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: OCIRepository + listKind: OCIRepositoryList + plural: ocirepositories + singular: ocirepository + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.url + name: URL + type: string + - 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 + name: v1beta1 + schema: + openAPIV3Schema: + description: OCIRepository is the Schema for the ocirepositories 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: OCIRepositorySpec defines the desired state of OCIRepository + properties: + auth: + description: The credentials to use to pull and monitor for changes, defaults to anonymous access. + properties: + secretRef: + description: SecretRef contains the secret name containing the registry login credentials to resolve image metadata. The secret must be of type kubernetes.io/dockerconfigjson. + properties: + name: + description: Name of the referent + type: string + required: + - name + type: object + serviceAccountName: + description: 'ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate the image pull if the service account has attached pull secrets. For more information: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account' + type: string + type: object + ignore: + description: Ignore overrides the set of excluded patterns in the .sourceignore format (which is the same as .gitignore). If not provided, a default will be used, consult the documentation for your version to find out what those are. + type: string + interval: + description: The interval at which to check for image updates. + type: string + ref: + description: The OCI reference to pull and monitor for changes, defaults to latest tag. + properties: + digest: + description: Digest is the image digest to pull, takes precedence over SemVer. Value should be in the form sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + type: string + semver: + description: SemVer is the range of tags to pull selecting the latest within the range, takes precedence over Tag. + type: string + tag: + default: latest + description: Tag is the image tag to pull, defaults to latest. + type: string + type: object + suspend: + description: This flag tells the controller to suspend the reconciliation of this source. + type: boolean + timeout: + default: 20s + description: The timeout for remote OCI Repository operations like pulling, defaults to 20s. + type: string + url: + description: URL is a reference to an image in a remote registry + type: string + required: + - interval + - url + type: object + status: + description: OCIRepositoryStatus defines the observed state of OCIRepository + properties: + artifact: + description: Artifact represents the output of the last successful image sync. + properties: + checksum: + description: Checksum is the SHA1 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for the OCIRepository. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the artifact output of the last image sync. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index a666a9259..c00716353 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,4 +5,5 @@ resources: - bases/source.toolkit.fluxcd.io_helmrepositories.yaml - bases/source.toolkit.fluxcd.io_helmcharts.yaml - bases/source.toolkit.fluxcd.io_buckets.yaml +- bases/source.toolkit.fluxcd.io_ocirepositories.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/rbac/ocirepository_editor_role.yaml b/config/rbac/ocirepository_editor_role.yaml new file mode 100644 index 000000000..e4defde09 --- /dev/null +++ b/config/rbac/ocirepository_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit ocirepositories. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ocirepository-editor-role +rules: +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories/status + verbs: + - get diff --git a/config/rbac/ocirepository_viewer_role.yaml b/config/rbac/ocirepository_viewer_role.yaml new file mode 100644 index 000000000..f769ac5a9 --- /dev/null +++ b/config/rbac/ocirepository_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view ocirepositories. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ocirepository-viewer-role +rules: +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories + verbs: + - get + - list + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 8cf5c66a0..c63839537 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,6 +21,14 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch - apiGroups: - source.toolkit.fluxcd.io resources: @@ -141,3 +149,33 @@ rules: - get - patch - update +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories/finalizers + verbs: + - create + - delete + - get + - patch + - update +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories/status + verbs: + - get + - patch + - update diff --git a/config/samples/source_v1beta1_ocirepository.yaml b/config/samples/source_v1beta1_ocirepository.yaml new file mode 100644 index 000000000..41bf739a6 --- /dev/null +++ b/config/samples/source_v1beta1_ocirepository.yaml @@ -0,0 +1,11 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: OCIRepository +metadata: + name: ocirepository-sample +spec: + interval: 1m + url: ghcr.io/stefanprodan/podinfo-deploy + ref: +# digest: sha256:8c82f0d855e06ad223dc9c2f2209eb2c30f2c553eddd55fa0b3dd2fbb52a8a3a + semver: 6.x +# tag: 6.0.2 diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go new file mode 100644 index 000000000..ceb3c7008 --- /dev/null +++ b/controllers/ocirepository_controller.go @@ -0,0 +1,484 @@ +/* +Copyright 2020 The Flux authors + +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 ( + "archive/tar" + "context" + "fmt" + "github.com/Masterminds/semver/v3" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/events" + "github.com/fluxcd/pkg/runtime/metrics" + "github.com/fluxcd/pkg/runtime/predicates" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" + "github.com/fluxcd/source-controller/pkg/sourceignore" + "github.com/go-git/go-git/v5/plumbing/format/gitignore" + "github.com/go-logr/logr" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/authn/k8schain" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + corev1 "k8s.io/api/core/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + kuberecorder "k8s.io/client-go/tools/record" + "k8s.io/client-go/tools/reference" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sort" + "strings" + "time" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/finalizers,verbs=get;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch + +// OCIRepositoryReconciler reconciles a OCIRepository object +type OCIRepositoryReconciler struct { + client.Client + requeueDependency time.Duration + Scheme *runtime.Scheme + Storage *Storage + EventRecorder kuberecorder.EventRecorder + ExternalEventRecorder *events.Recorder + MetricsRecorder *metrics.Recorder +} + +type OCIRepositoryReconcilerOptions struct { + MaxConcurrentReconciles int + DependencyRequeueInterval time.Duration +} + +func (r *OCIRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { + return r.SetupWithManagerAndOptions(mgr, OCIRepositoryReconcilerOptions{}) +} + +func (r *OCIRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts OCIRepositoryReconcilerOptions) error { + r.requeueDependency = opts.DependencyRequeueInterval + + return ctrl.NewControllerManagedBy(mgr). + For(&sourcev1.OCIRepository{}, builder.WithPredicates( + predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}), + )). + WithOptions(controller.Options{MaxConcurrentReconciles: opts.MaxConcurrentReconciles}). + Complete(r) +} + +func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + start := time.Now() + log := logr.FromContext(ctx) + + var repository sourcev1.OCIRepository + if err := r.Get(ctx, req.NamespacedName, &repository); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Record suspended status metric + defer r.recordSuspension(ctx, repository) + + // Add our finalizer if it does not exist + if !controllerutil.ContainsFinalizer(&repository, sourcev1.SourceFinalizer) { + controllerutil.AddFinalizer(&repository, sourcev1.SourceFinalizer) + if err := r.Update(ctx, &repository); err != nil { + log.Error(err, "unable to register finalizer") + return ctrl.Result{}, err + } + } + + // Examine if the object is under deletion + if !repository.ObjectMeta.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, repository) + } + + // Return early if the object is suspended. + if repository.Spec.Suspend { + log.Info("Reconciliation is suspended for this object") + return ctrl.Result{}, nil + } + + // record reconciliation duration + if r.MetricsRecorder != nil { + objRef, err := reference.GetReference(r.Scheme, &repository) + if err != nil { + return ctrl.Result{}, err + } + defer r.MetricsRecorder.RecordDuration(*objRef, start) + } + + // set initial status + if resetRepository, ok := r.resetStatus(repository); ok { + repository = resetRepository + if err := r.updateStatus(ctx, req, repository.Status); err != nil { + log.Error(err, "unable to update status") + return ctrl.Result{Requeue: true}, err + } + r.recordReadiness(ctx, repository) + } + + // record the value of the reconciliation request, if any + // TODO(hidde): would be better to defer this in combination with + // always patching the status sub-resource after a reconciliation. + if v, ok := meta.ReconcileAnnotationValue(repository.GetAnnotations()); ok { + repository.Status.SetLastHandledReconcileRequest(v) + } + + // purge old artifacts from storage + if err := r.gc(repository); err != nil { + log.Error(err, "unable to purge old artifacts") + } + + // reconcile repository by pulling the latest repository + reconciledRepository, reconcileErr := r.reconcile(ctx, *repository.DeepCopy()) + + // update status with the reconciliation result + if err := r.updateStatus(ctx, req, reconciledRepository.Status); err != nil { + log.Error(err, "unable to update status") + return ctrl.Result{Requeue: true}, err + } + + // if reconciliation failed, record the failure and requeue immediately + if reconcileErr != nil { + r.event(ctx, reconciledRepository, events.EventSeverityError, reconcileErr.Error()) + r.recordReadiness(ctx, reconciledRepository) + return ctrl.Result{Requeue: true}, reconcileErr + } + + // emit revision change event + if repository.Status.Artifact == nil || reconciledRepository.Status.Artifact.Revision != repository.Status.Artifact.Revision { + r.event(ctx, reconciledRepository, events.EventSeverityInfo, sourcev1.OCIRepositoryReadyMessage(reconciledRepository)) + } + r.recordReadiness(ctx, reconciledRepository) + + log.Info(fmt.Sprintf("Reconciliation finished in %s, next run in %s", + time.Now().Sub(start).String(), + repository.GetInterval().Duration.String(), + )) + + return ctrl.Result{RequeueAfter: repository.GetInterval().Duration}, nil +} + +// event emits a Kubernetes event and forwards the event to notification controller if configured +func (r *OCIRepositoryReconciler) event(ctx context.Context, repository sourcev1.OCIRepository, severity, msg string) { + log := logr.FromContext(ctx) + + if r.EventRecorder != nil { + r.EventRecorder.Eventf(&repository, "Normal", severity, msg) + } + if r.ExternalEventRecorder != nil { + objRef, err := reference.GetReference(r.Scheme, &repository) + if err != nil { + log.Error(err, "unable to send event") + return + } + + if err := r.ExternalEventRecorder.Eventf(*objRef, nil, severity, severity, msg); err != nil { + log.Error(err, "unable to send event") + return + } + } +} + +// gc performs a garbage collection for the given v1beta1.OCIRepository. +// It removes all but the current artifact except for when the +// deletion timestamp is set, which will result in the removal of +// all artifacts for the resource. +func (r *OCIRepositoryReconciler) gc(repository sourcev1.OCIRepository) error { + if !repository.DeletionTimestamp.IsZero() { + return r.Storage.RemoveAll(r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), "", "*")) + } + if repository.GetArtifact() != nil { + return r.Storage.RemoveAllButCurrent(*repository.GetArtifact()) + } + return nil +} + +// keychain generates the credential keychain based on the resource +// configuration. If no auth is specified a default keychain with +// anonymous access is returned +func (r *OCIRepositoryReconciler) keychain(ctx context.Context, repository sourcev1.OCIRepository) (authn.Keychain, error) { + auth := repository.Spec.Authentication + if auth == nil { + return authn.DefaultKeychain, nil + } + + pullSecretNames := sets.NewString() + if auth.SecretRef != nil { + pullSecretNames.Insert(auth.SecretRef.Name) + } + + // lookup service account + serviceAccountName := auth.ServiceAccountName + if serviceAccountName == "" { + serviceAccountName = "default" + } + serviceAccount := corev1.ServiceAccount{} + err := r.Get(ctx, types.NamespacedName{Namespace: repository.Namespace, Name: serviceAccountName}, &serviceAccount) + if err != nil { + return nil, err + } + for _, ips := range serviceAccount.ImagePullSecrets { + pullSecretNames.Insert(ips.Name) + } + + // lookup image pull secrets + imagePullSecrets := make([]corev1.Secret, len(pullSecretNames)) + for i, imagePullSecretName := range pullSecretNames.List() { + imagePullSecret := corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Namespace: repository.Namespace, Name: imagePullSecretName}, &imagePullSecret) + if err != nil { + return nil, err + } + imagePullSecrets[i] = imagePullSecret + } + + return k8schain.NewFromPullSecrets(ctx, imagePullSecrets) +} + +func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, repository sourcev1.OCIRepository) (sourcev1.OCIRepository, error) { + ociCtx, cancel := context.WithTimeout(ctx, repository.Spec.Timeout.Duration) + defer cancel() + + keychain, err := r.keychain(ociCtx, repository) + if err != nil { + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err + } + + ref, err := r.reference(ociCtx, keychain, repository) + if err != nil { + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.OCIRepositoryOperationFailedReason, err.Error()), err + } + + image, err := remote.Image(ref, remote.WithAuthFromKeychain(keychain)) + if err != nil { + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.OCIRepositoryOperationFailedReason, err.Error()), err + } + + digest, err := image.Digest() + if err != nil { + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.OCIRepositoryOperationFailedReason, err.Error()), err + } + + revision := fmt.Sprintf("%s@%s", ref.Context().Name(), digest) + artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", digest)) + + // return early on unchanged digest + if apimeta.IsStatusConditionTrue(repository.Status.Conditions, meta.ReadyCondition) && repository.GetArtifact().HasRevision(artifact.Revision) { + if artifact.URL != repository.GetArtifact().URL { + r.Storage.SetArtifactURL(repository.GetArtifact()) + repository.Status.URL = r.Storage.SetHostname(repository.Status.URL) + } + return repository, nil + } + + // create artifact dir + err = r.Storage.MkdirAll(artifact) + if err != nil { + err = fmt.Errorf("mkdir dir error: %w", err) + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err + } + + // acquire lock + unlock, err := r.Storage.Lock(artifact) + if err != nil { + err = fmt.Errorf("unable to acquire lock: %w", err) + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err + } + defer unlock() + + // archive artifact and check integrity + var ignoreDomain []string + var ps []gitignore.Pattern + if repository.Spec.Ignore != nil { + ps = append(ps, sourceignore.ReadPatterns(strings.NewReader(*repository.Spec.Ignore), ignoreDomain)...) + } + + in := mutate.Extract(image) + if err := r.Storage.ArchiveTar(&artifact, tar.NewReader(in), SourceIgnoreFilter(ps, ignoreDomain)); err != nil { + in.Close() + err = fmt.Errorf("storage archive error: %w", err) + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err + } + in.Close() + + // update latest symlink + url, err := r.Storage.Symlink(artifact, "latest.tar.gz") + if err != nil { + err = fmt.Errorf("storage symlink error: %w", err) + return sourcev1.OCIRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err + } + + message := fmt.Sprintf("Fetched revision: %s", artifact.Revision) + return sourcev1.OCIRepositoryReady(repository, artifact, url, sourcev1.OCIRepositoryOperationSucceedReason, message), nil +} + +func (r *OCIRepositoryReconciler) reconcileDelete(ctx context.Context, repository sourcev1.OCIRepository) (ctrl.Result, error) { + if err := r.gc(repository); err != nil { + r.event(ctx, repository, events.EventSeverityError, + fmt.Sprintf("garbage collection for deleted resource failed: %s", err.Error())) + // Return the error so we retry the failed garbage collection + return ctrl.Result{}, err + } + + // Record deleted status + r.recordReadiness(ctx, repository) + + // Remove our finalizer from the list and update it + controllerutil.RemoveFinalizer(&repository, sourcev1.SourceFinalizer) + if err := r.Update(ctx, &repository); err != nil { + return ctrl.Result{}, err + } + + // Stop reconciliation as the object is being deleted + return ctrl.Result{}, nil +} + +func (r *OCIRepositoryReconciler) recordReadiness(ctx context.Context, repository sourcev1.OCIRepository) { + log := logr.FromContext(ctx) + if r.MetricsRecorder == nil { + return + } + objRef, err := reference.GetReference(r.Scheme, &repository) + if err != nil { + log.Error(err, "unable to record readiness metric") + return + } + if rc := apimeta.FindStatusCondition(repository.Status.Conditions, meta.ReadyCondition); rc != nil { + r.MetricsRecorder.RecordCondition(*objRef, *rc, !repository.DeletionTimestamp.IsZero()) + } else { + r.MetricsRecorder.RecordCondition(*objRef, metav1.Condition{ + Type: meta.ReadyCondition, + Status: metav1.ConditionUnknown, + }, !repository.DeletionTimestamp.IsZero()) + } +} + +func (r *OCIRepositoryReconciler) recordSuspension(ctx context.Context, repository sourcev1.OCIRepository) { + if r.MetricsRecorder == nil { + return + } + log := logr.FromContext(ctx) + + objRef, err := reference.GetReference(r.Scheme, &repository) + if err != nil { + log.Error(err, "unable to record suspended metric") + return + } + + if !repository.DeletionTimestamp.IsZero() { + r.MetricsRecorder.RecordSuspend(*objRef, false) + } else { + r.MetricsRecorder.RecordSuspend(*objRef, repository.Spec.Suspend) + } +} + +// resetStatus returns a modified v1beta1.OCIRepository and a boolean indicating +// if the status field has been reset. +func (r *OCIRepositoryReconciler) resetStatus(repository sourcev1.OCIRepository) (sourcev1.OCIRepository, bool) { + // We do not have an artifact, or it does no longer exist + if repository.GetArtifact() == nil || !r.Storage.ArtifactExist(*repository.GetArtifact()) { + repository = sourcev1.OCIRepositoryProgressing(repository) + repository.Status.Artifact = nil + return repository, true + } + if repository.Generation != repository.Status.ObservedGeneration { + return sourcev1.OCIRepositoryProgressing(repository), true + } + return repository, false +} + +func (r *OCIRepositoryReconciler) reference(ctx context.Context, keychain authn.Keychain, repository sourcev1.OCIRepository) (name.Reference, error) { + url := repository.Spec.URL + + ref := repository.Spec.Reference + if ref == nil { + return name.ParseReference(fmt.Sprintf("%s:latest", url)) + } + + if ref.Digest != "" { + return name.ParseReference(fmt.Sprintf("%s@%s", url, ref.Digest)) + } + + if ref.SemVer != "" { + repo, err := name.NewRepository(url) + if err != nil { + return nil, err + } + + tags, err := remote.List(repo, remote.WithAuthFromKeychain(keychain), remote.WithContext(ctx)) + if err != nil { + return nil, err + } + + c, err := semver.NewConstraint(ref.SemVer) + if err != nil { + return nil, err + } + + var candidates []*semver.Version + for _, t := range tags { + v, err := semver.NewVersion(t) + if err != nil { + continue + } + + if c != nil && !c.Check(v) { + continue + } + + candidates = append(candidates, v) + } + + if len(candidates) == 0 { + return nil, fmt.Errorf("no matching tags were found") + } + + sort.Sort(sort.Reverse(semver.Collection(candidates))) + return name.ParseReference(fmt.Sprintf("%s:%s", url, candidates[0])) + } + + if ref.Tag != "" { + return name.ParseReference(fmt.Sprintf("%s:%s", url, ref.Tag)) + } + + return name.ParseReference(fmt.Sprintf("%s:latest", url)) +} + +func (r *OCIRepositoryReconciler) updateStatus(ctx context.Context, req ctrl.Request, newStatus sourcev1.OCIRepositoryStatus) error { + var repository sourcev1.OCIRepository + if err := r.Get(ctx, req.NamespacedName, &repository); err != nil { + return err + } + + patch := client.MergeFrom(repository.DeepCopy()) + repository.Status = newStatus + + return r.Status().Patch(ctx, &repository, patch) +} diff --git a/controllers/storage.go b/controllers/storage.go index 139e999cd..adaa3bf34 100644 --- a/controllers/storage.go +++ b/controllers/storage.go @@ -169,6 +169,96 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv return fmt.Errorf("invalid dir path: %s", dir) } + return s.archive(artifact, func(tw *tar.Writer) error { + return filepath.Walk(dir, func(p string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + // Ignore anything that is not a file (directories, symlinks) + if !fi.Mode().IsRegular() { + return nil + } + + // Skip filtered files + if filter != nil && filter(p, fi) { + return nil + } + + header, err := tar.FileInfoHeader(fi, p) + if err != nil { + return err + } + // The name needs to be modified to maintain directory structure + // as tar.FileInfoHeader only has access to the base name of the file. + // Ref: https://golang.org/src/archive/tar/common.go?#L626 + relFilePath := p + if filepath.IsAbs(dir) { + relFilePath, err = filepath.Rel(dir, p) + if err != nil { + return err + } + } + header.Name = relFilePath + + // We want to remove any environment specific data as well, this + // ensures the checksum is purely content based. + header = hashSafe(header) + + if err := tw.WriteHeader(header); err != nil { + return err + } + + f, err := os.Open(p) + if err != nil { + f.Close() + return err + } + if _, err := io.Copy(tw, f); err != nil { + f.Close() + return err + } + return f.Close() + }) + }) +} + +func (s *Storage) ArchiveTar(artifact *sourcev1.Artifact, tr *tar.Reader, filter ArchiveFileFilter) (err error) { + return s.archive(artifact, func(tw *tar.Writer) error { + for { + header, err := tr.Next() + if err == io.EOF { + break // End of archive + } + + // Ignore anything that is not a file (directories, symlinks) + if !header.FileInfo().Mode().IsRegular() { + continue + } + + // Skip filtered files + if filter != nil && filter(header.Name, header.FileInfo()) { + continue + } + + // We want to remove any environment specific data as well, this + // ensures the checksum is purely content based. + header = hashSafe(header) + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if _, err := io.Copy(tw, tr); err != nil { + return err + } + } + + return nil + }) +} + +func (s *Storage) archive(artifact *sourcev1.Artifact, fn func(tw *tar.Writer) error) (err error) { localPath := s.LocalPath(*artifact) tf, err := os.CreateTemp(filepath.Split(localPath)) if err != nil { @@ -186,62 +276,7 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv gw := gzip.NewWriter(mw) tw := tar.NewWriter(gw) - if err := filepath.Walk(dir, func(p string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - - // Ignore anything that is not a file (directories, symlinks) - if !fi.Mode().IsRegular() { - return nil - } - - // Skip filtered files - if filter != nil && filter(p, fi) { - return nil - } - - header, err := tar.FileInfoHeader(fi, p) - if err != nil { - return err - } - // The name needs to be modified to maintain directory structure - // as tar.FileInfoHeader only has access to the base name of the file. - // Ref: https://golang.org/src/archive/tar/common.go?#L626 - relFilePath := p - if filepath.IsAbs(dir) { - relFilePath, err = filepath.Rel(dir, p) - if err != nil { - return err - } - } - header.Name = relFilePath - - // We want to remove any environment specific data as well, this - // ensures the checksum is purely content based. - header.Gid = 0 - header.Uid = 0 - header.Uname = "" - header.Gname = "" - header.ModTime = time.Time{} - header.AccessTime = time.Time{} - header.ChangeTime = time.Time{} - - if err := tw.WriteHeader(header); err != nil { - return err - } - - f, err := os.Open(p) - if err != nil { - f.Close() - return err - } - if _, err := io.Copy(tw, f); err != nil { - f.Close() - return err - } - return f.Close() - }); err != nil { + if err := fn(tw); err != nil { tw.Close() gw.Close() tf.Close() @@ -274,6 +309,20 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv return nil } +func hashSafe(header *tar.Header) *tar.Header { + return &tar.Header{ + Typeflag: header.Typeflag, + Name: header.Name, + Linkname: header.Linkname, + Size: header.Size, + Mode: header.Mode, + Devmajor: header.Devmajor, + Devminor: header.Devminor, + PAXRecords: header.PAXRecords, + Format: header.Format, + } +} + // AtomicWriteFile atomically writes the io.Reader contents to the v1beta1.Artifact path. // If successful, it sets the checksum and last update time on the artifact. func (s *Storage) AtomicWriteFile(artifact *sourcev1.Artifact, reader io.Reader, mode os.FileMode) (err error) { diff --git a/controllers/storage_test.go b/controllers/storage_test.go index 8da8d49df..47a8cf104 100644 --- a/controllers/storage_test.go +++ b/controllers/storage_test.go @@ -21,6 +21,7 @@ import ( "compress/gzip" "fmt" "io" + "io/fs" "os" "path" "path/filepath" @@ -117,55 +118,89 @@ func TestStorage_Archive(t *testing.T) { t.Fatalf("error while bootstrapping storage: %v", err) } - createFiles := func(files map[string][]byte) (dir string, err error) { - defer func() { - if err != nil && dir != "" { - os.RemoveAll(dir) - } - }() - dir, err = os.MkdirTemp("", "archive-test-files-") - if err != nil { - return - } - for name, b := range files { - absPath := filepath.Join(dir, name) - if err = os.MkdirAll(filepath.Dir(absPath), 0755); err != nil { + tests := []struct { + name string + files map[string][]byte + filter ArchiveFileFilter + want map[string][]byte + wantErr bool + }{ + { + name: "no filter", + files: map[string][]byte{ + ".git/config": nil, + "file.jpg": []byte(`contents`), + "manifest.yaml": nil, + }, + filter: nil, + want: map[string][]byte{ + ".git/config": nil, + "file.jpg": []byte(`contents`), + "manifest.yaml": nil, + }, + }, + { + name: "exclude VCS", + files: map[string][]byte{ + ".git/config": nil, + "manifest.yaml": nil, + }, + filter: SourceIgnoreFilter(nil, nil), + want: map[string][]byte{ + "!.git/config": nil, + "manifest.yaml": nil, + }, + }, + { + name: "custom", + files: map[string][]byte{ + ".git/config": nil, + "custom": nil, + "horse.jpg": nil, + }, + filter: SourceIgnoreFilter([]gitignore.Pattern{ + gitignore.ParsePattern("custom", nil), + }, nil), + want: map[string][]byte{ + "!git/config": nil, + "!custom": nil, + "horse.jpg": nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := createFiles(tt.files) + if err != nil { + t.Error(err) return } - f, err := os.Create(absPath) - if err != nil { - return "", fmt.Errorf("could not create file %q: %w", absPath, err) + defer os.RemoveAll(dir) + artifact := sourcev1.Artifact{ + Path: filepath.Join(randStringRunes(10), randStringRunes(10), randStringRunes(10)+".tar.gz"), } - if n, err := f.Write(b); err != nil { - f.Close() - return "", fmt.Errorf("could not write %d bytes to file %q: %w", n, f.Name(), err) + if err := storage.MkdirAll(artifact); err != nil { + t.Fatalf("artifact directory creation failed: %v", err) } - f.Close() - } - return + if err := storage.Archive(&artifact, dir, tt.filter); (err != nil) != tt.wantErr { + t.Errorf("Archive() error = %v, wantErr %v", err, tt.wantErr) + } + matchFiles(t, storage, artifact, tt.want) + }) } +} - matchFiles := func(t *testing.T, storage *Storage, artifact sourcev1.Artifact, files map[string][]byte) { - for name, b := range files { - mustExist := !(name[0:1] == "!") - if !mustExist { - name = name[1:] - } - s, exist, err := walkTar(storage.LocalPath(artifact), name) - if err != nil { - t.Fatalf("failed reading tarball: %v", err) - } - if bs := int64(len(b)); s != bs { - t.Fatalf("%q size %v != %v", name, s, bs) - } - if exist != mustExist { - if mustExist { - t.Errorf("could not find file %q in tarball", name) - } else { - t.Errorf("tarball contained excluded file %q", name) - } - } - } +func TestStorage_ArchiveTar(t *testing.T) { + dir, err := createStoragePath() + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanupStoragePath(dir)) + + storage, err := NewStorage(dir, "hostname", time.Minute) + if err != nil { + t.Fatalf("error while bootstrapping storage: %v", err) } tests := []struct { @@ -227,13 +262,65 @@ func TestStorage_Archive(t *testing.T) { return } defer os.RemoveAll(dir) + + in, out := io.Pipe() + defer in.Close() + + go func() { + tw := tar.NewWriter(out) + defer tw.Close() + + if err := filepath.Walk(dir, func(p string, fi fs.FileInfo, err error) error { + if err != nil { + return err + } + + header, err := tar.FileInfoHeader(fi, p) + if err != nil { + return err + } + relFilePath := p + if filepath.IsAbs(dir) { + relFilePath, err = filepath.Rel(dir, p) + if err != nil { + return err + } + } + header.Name = relFilePath + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if fi.Mode().IsRegular() { + f, err := os.Open(p) + if err != nil { + f.Close() + return err + } + if _, err := io.Copy(tw, f); err != nil { + f.Close() + return err + } + return f.Close() + } + + return nil + }); err != nil { + out.Close() + return + } + + out.Close() + }() + artifact := sourcev1.Artifact{ Path: filepath.Join(randStringRunes(10), randStringRunes(10), randStringRunes(10)+".tar.gz"), } if err := storage.MkdirAll(artifact); err != nil { t.Fatalf("artifact directory creation failed: %v", err) } - if err := storage.Archive(&artifact, dir, tt.filter); (err != nil) != tt.wantErr { + if err := storage.ArchiveTar(&artifact, tar.NewReader(in), tt.filter); (err != nil) != tt.wantErr { t.Errorf("Archive() error = %v, wantErr %v", err, tt.wantErr) } matchFiles(t, storage, artifact, tt.want) @@ -241,6 +328,57 @@ func TestStorage_Archive(t *testing.T) { } } +func createFiles(files map[string][]byte) (dir string, err error) { + defer func() { + if err != nil && dir != "" { + os.RemoveAll(dir) + } + }() + dir, err = os.MkdirTemp("", "archive-test-files-") + if err != nil { + return + } + for name, b := range files { + absPath := filepath.Join(dir, name) + if err = os.MkdirAll(filepath.Dir(absPath), 0755); err != nil { + return + } + f, err := os.Create(absPath) + if err != nil { + return "", fmt.Errorf("could not create file %q: %w", absPath, err) + } + if n, err := f.Write(b); err != nil { + f.Close() + return "", fmt.Errorf("could not write %d bytes to file %q: %w", n, f.Name(), err) + } + f.Close() + } + return +} + +func matchFiles(t *testing.T, storage *Storage, artifact sourcev1.Artifact, files map[string][]byte) { + for name, b := range files { + mustExist := !(name[0:1] == "!") + if !mustExist { + name = name[1:] + } + s, exist, err := walkTar(storage.LocalPath(artifact), name) + if err != nil { + t.Fatalf("failed reading tarball: %v", err) + } + if bs := int64(len(b)); s != bs { + t.Fatalf("%q size %v != %v", name, s, bs) + } + if exist != mustExist { + if mustExist { + t.Errorf("could not find file %q in tarball", name) + } else { + t.Errorf("tarball contained excluded file %q", name) + } + } + } +} + func TestStorageRemoveAllButCurrent(t *testing.T) { t.Run("bad directory in archive", func(t *testing.T) { dir, err := os.MkdirTemp("", "") diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 5f5341155..66d2079ff 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -92,6 +92,9 @@ var _ = BeforeSuite(func(done Done) { err = sourcev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = sourcev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme Expect(loadExampleKeys()).To(Succeed()) diff --git a/go.mod b/go.mod index 374e22c72..7f4d7cc3c 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,8 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/go-logr/logr v0.4.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-containerregistry v0.6.0 + github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211006165628-72ae53c6cd67 github.com/googleapis/gax-go/v2 v2.1.0 // indirect github.com/libgit2/git2go/v31 v31.6.1 github.com/minio/minio-go/v7 v7.0.10 diff --git a/go.sum b/go.sum index 4702f7e64..afc4364db 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -47,21 +48,43 @@ cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2 cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6NN0r5QfmE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v43.0.0+incompatible h1:/wSNCu0e6EsHFR4Qa3vBEBbicaprEHMyyga9g8RTULI= +github.com/Azure/azure-sdk-for-go v43.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0 h1:ISSNzGUh+ZSzizJWOWzs8bwpXIePbGLW4z/AmUFGH5A= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20200415212048-7901bc822317/go.mod h1:DF8FZRxMHMGv/vP2lQP6h+dYzzjpuRn24VeRiYn3qjQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= @@ -77,8 +100,9 @@ github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -125,6 +149,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.35.24 h1:U3GNTg8+7xSM6OAJ8zksiSM4bRqxBWmVwwehvOSNG3A= +github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -136,6 +162,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA= @@ -179,6 +206,9 @@ github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8 github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/stargz-snapshotter/estargz v0.6.4/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= +github.com/containerd/stargz-snapshotter/estargz v0.7.0 h1:1d/rydzTywc76lnjJb6qbPCiTiCwts49AzKps/Ecblw= +github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -192,6 +222,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -215,12 +246,14 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.5+incompatible h1:bjflayQbWg+xOkF2WPEAOi4Y7zWhR7ptoPhV/VqLVDE= github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= +github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -285,6 +318,7 @@ github.com/fluxcd/pkg/untar v0.1.0/go.mod h1:aGswNyzB1mlz/T/kpOS58mITBMxMKc9tlJB github.com/fluxcd/pkg/version v0.1.0 h1:v+SmCanmCB5Tj2Cx9TXlj+kNRfPGbAvirkeqsp7ZEAQ= github.com/fluxcd/pkg/version v0.1.0/go.mod h1:V7Z/w8dxLQzv0FHqa5ox5TeyOd2zOd49EeuWFgnwyj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= @@ -439,6 +473,7 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -457,6 +492,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.5.2-0.20210609162550-f0ce2270b3b4/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q= +github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4= +github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= +github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211006165628-72ae53c6cd67 h1:5la/qEKaPvyV75xs6vq6xE4Xr1Jlaj/2GvnfaqKCx9o= +github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20211006165628-72ae53c6cd67/go.mod h1:j3IqhBG3Ox1NXmmhbWU4UmiHVAf2dUgB7le1Ch7JZQ0= 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= @@ -494,6 +534,7 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -566,6 +607,10 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= @@ -590,12 +635,16 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.0 h1:2T7tUoQrQT+fQWdaY5rjWztFGAFwbGD04iPJg90ZiOs= +github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -623,6 +672,7 @@ github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -671,6 +721,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= @@ -739,7 +790,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.0.0-rc95 h1:RMuWVfY3E1ILlVsC3RhIq38n4sJtlOFwU9gfFZSqrd0= github.com/opencontainers/runc v1.0.0-rc95/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -756,6 +806,7 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -768,6 +819,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -821,9 +873,11 @@ github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -855,6 +909,7 @@ github.com/sosedoff/gitkit v0.2.1-0.20200818155723-72ebbcf5056d/go.mod h1:A+o6Za github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -863,9 +918,11 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -875,6 +932,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -900,9 +958,12 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vdemeester/k8s-pkg-credentialprovider v1.21.0-1 h1:7Ajl3rjeYoB5V47jPknnLbyxYlhMXTTJiQsye5aT7f0= +github.com/vdemeester/k8s-pkg-credentialprovider v1.21.0-1/go.mod h1:l4LxiP0cmEcc5q4BTDE8tZSyIiyXe0T28x37yHpMzoM= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= @@ -934,6 +995,9 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -970,6 +1034,7 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -980,6 +1045,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1082,6 +1148,7 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1095,6 +1162,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -1122,6 +1191,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1151,6 +1221,7 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1184,6 +1255,7 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1303,6 +1375,7 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.1-0.20200106000736-b8fc810ca6b5/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= @@ -1317,6 +1390,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18= @@ -1450,19 +1524,22 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1518,6 +1595,8 @@ k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs= k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= +k8s.io/cloud-provider v0.21.0 h1:NSTS+czpv6LQAaIpY/VUghsT4oj62hYmQPErkDKTzKU= +k8s.io/cloud-provider v0.21.0/go.mod h1:z17TQgu3JgUFjcgby8sj5X86YdVK5Pbt+jm/eYMZU9M= k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= @@ -1526,6 +1605,8 @@ k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkII k8s.io/component-base v0.21.3 h1:4WuuXY3Npa+iFfi2aDRiOz+anhNvRfye0859ZgfC5Og= k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= +k8s.io/controller-manager v0.21.0/go.mod h1:Ohy0GRNRKPVjB8C8G+dV+4aPn26m8HYUI6ejloUBvUA= +k8s.io/csi-translation-lib v0.21.0/go.mod h1:edq+UMpgqEx3roTuGF/03uIuSOsI986jtu65+ytLlkA= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -1536,6 +1617,8 @@ k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7Br k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= +k8s.io/legacy-cloud-providers v0.21.0 h1:iWf5xaX9yvYT5mkz8UB96UtISQ5IkrWeuMPMhRp01ZY= +k8s.io/legacy-cloud-providers v0.21.0/go.mod h1:bNxo7gDg+PGkBmT/MFZswLTWdSWK9kAlS1s8DJca5q4= k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/main.go b/main.go index 55a2d2f97..bdc4c8c6e 100644 --- a/main.go +++ b/main.go @@ -207,6 +207,20 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Bucket") os.Exit(1) } + if err = (&controllers.OCIRepositoryReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Storage: storage, + EventRecorder: mgr.GetEventRecorderFor(controllerName), + ExternalEventRecorder: eventRecorder, + MetricsRecorder: metricsRecorder, + }).SetupWithManagerAndOptions(mgr, controllers.OCIRepositoryReconcilerOptions{ + MaxConcurrentReconciles: concurrent, + DependencyRequeueInterval: requeueDependency, + }); err != nil { + setupLog.Error(err, "unable to create controller", "controller", sourcev1.OCIRepositoryKind) + os.Exit(1) + } // +kubebuilder:scaffold:builder go func() {