From 46fe7a389c0d13908fd03663b2ddb9b7b236584c Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 21 Jun 2022 15:40:03 +0300
Subject: [PATCH 01/25] Add OCIRepository kind to v1beta2 API
Signed-off-by: Stefan Prodan
---
PROJECT | 3 +
api/v1beta2/ocirepository_types.go | 204 ++++++
api/v1beta2/zz_generated.deepcopy.go | 164 +++++
...rce.toolkit.fluxcd.io_ocirepositories.yaml | 287 +++++++++
config/crd/kustomization.yaml | 1 +
config/rbac/ocirepository_editor_role.yaml | 24 +
config/rbac/ocirepository_viewer_role.yaml | 20 +
config/rbac/role.yaml | 30 +
docs/api/source.md | 593 +++++++++++++++++-
9 files changed, 1325 insertions(+), 1 deletion(-)
create mode 100644 api/v1beta2/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
diff --git a/PROJECT b/PROJECT
index 776217e9f..10d980ac1 100644
--- a/PROJECT
+++ b/PROJECT
@@ -25,4 +25,7 @@ resources:
- group: source
kind: Bucket
version: v1beta1
+- group: source
+ kind: OCIRepository
+ version: v1beta2
version: "2"
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
new file mode 100644
index 000000000..f308ae490
--- /dev/null
+++ b/api/v1beta2/ocirepository_types.go
@@ -0,0 +1,204 @@
+/*
+Copyright 2022 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 v1beta2
+
+import (
+ "github.com/fluxcd/pkg/apis/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "time"
+)
+
+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 OCI artifact repository hosted
+ // on a remote container registry.
+ // +required
+ URL string `json:"url"`
+
+ // The OCI reference to pull and monitor for changes,
+ // defaults to the latest tag.
+ // +optional
+ Reference *OCIRepositoryRef `json:"ref,omitempty"`
+
+ // 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"`
+
+ // CertSecretRef can be given the name of a secret containing
+ // either or both of
+ //
+ // - a PEM-encoded client certificate (`certFile`) and private
+ // key (`keyFile`);
+ // - a PEM-encoded CA certificate (`caFile`)
+ //
+ // and whichever are supplied, will be used for connecting to the
+ // registry. The client cert and key are useful if you are
+ // authenticating with a certificate; the CA cert is useful if
+ // you are using a self-signed server certificate.
+ // +optional
+ CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
+
+ // Verification specifies the configuration to verify the autheticity
+ // of an OCI Artifact.
+ // +optional
+ Verification *OCIRepositoryVerification `json:"verify,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 60s.
+ // +kubebuilder:default="60s"
+ // +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.
+ // The value should be in the format 'sha256:'.
+ // +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"`
+}
+
+// OCIRepositoryVerification verifies the authenticity of an OCI Artifact
+type OCIRepositoryVerification struct {
+ // Provider specifies the technology used to sign the OCI Artifact.
+ // +kubebuilder:validation:Enum=cosign
+ Provider string `json:"provider"`
+
+ // SecretRef specifies the Kubernetes Secret containing the
+ // trusted public keys.
+ SecretRef meta.LocalObjectReference `json:"secretRef"`
+}
+
+// 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 OCI Repository sync.
+ // +optional
+ URL string `json:"url,omitempty"`
+
+ // Artifact represents the output of the last successful OCI Repository sync.
+ // +optional
+ Artifact *Artifact `json:"artifact,omitempty"`
+
+ meta.ReconcileRequestStatus `json:",inline"`
+}
+
+const (
+ // OCIOperationSucceedReason signals that a Git operation (e.g. pull) succeeded.
+ OCIOperationSucceedReason string = "OCIOperationSucceeded"
+
+ // OCIOperationFailedReason signals that an OCI operation (e.g. pull) failed.
+ OCIOperationFailedReason string = "OCIOperationFailed"
+)
+
+// GetConditions returns the status conditions of the object.
+func (in OCIRepository) GetConditions() []metav1.Condition {
+ return in.Status.Conditions
+}
+
+// SetConditions sets the status conditions on the object.
+func (in *OCIRepository) SetConditions(conditions []metav1.Condition) {
+ in.Status.Conditions = conditions
+}
+
+// GetRequeueAfter returns the duration after which the GitRepository must be
+// reconciled again.
+func (in OCIRepository) GetRequeueAfter() time.Duration {
+ return in.Spec.Interval.Duration
+}
+
+// GetArtifact returns the latest Artifact from the GitRepository if present in
+// the status sub-resource.
+func (in *OCIRepository) GetArtifact() *Artifact {
+ return in.Status.Artifact
+}
+
+// +genclient
+// +genclient:Namespaced
+// +kubebuilder:storageversion
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:shortName=ocirepo
+// +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"`
+ // +kubebuilder:default={"observedGeneration":-1}
+ Status OCIRepositoryStatus `json:"status,omitempty"`
+}
+
+// OCIRepositoryList contains a list of OCIRepository
+// +kubebuilder:object:root=true
+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/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index b789d81da..d7213100a 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -614,3 +614,167 @@ 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 *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.SecretRef != nil {
+ in, out := &in.SecretRef, &out.SecretRef
+ *out = new(meta.LocalObjectReference)
+ **out = **in
+ }
+ if in.CertSecretRef != nil {
+ in, out := &in.CertSecretRef, &out.CertSecretRef
+ *out = new(meta.LocalObjectReference)
+ **out = **in
+ }
+ if in.Verification != nil {
+ in, out := &in.Verification, &out.Verification
+ *out = new(OCIRepositoryVerification)
+ **out = **in
+ }
+ 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
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OCIRepositoryVerification) DeepCopyInto(out *OCIRepositoryVerification) {
+ *out = *in
+ out.SecretRef = in.SecretRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryVerification.
+func (in *OCIRepositoryVerification) DeepCopy() *OCIRepositoryVerification {
+ if in == nil {
+ return nil
+ }
+ out := new(OCIRepositoryVerification)
+ 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..7a163165a
--- /dev/null
+++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
@@ -0,0 +1,287 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.7.0
+ creationTimestamp: null
+ name: ocirepositories.source.toolkit.fluxcd.io
+spec:
+ group: source.toolkit.fluxcd.io
+ names:
+ kind: OCIRepository
+ listKind: OCIRepositoryList
+ plural: ocirepositories
+ shortNames:
+ - ocirepo
+ 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: v1beta2
+ 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:
+ certSecretRef:
+ description: "CertSecretRef can be given the name of a secret containing
+ either or both of \n - a PEM-encoded client certificate (`certFile`)
+ and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`)
+ \n and whichever are supplied, will be used for connecting to the
+ \ registry. The client cert and key are useful if you are authenticating
+ with a certificate; the CA cert is useful if you are using a self-signed
+ server certificate."
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ 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 the latest tag.
+ properties:
+ digest:
+ description: Digest is the image digest to pull, takes precedence
+ over SemVer. The value should be in the format 'sha256:'.
+ 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
+ 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
+ suspend:
+ description: This flag tells the controller to suspend the reconciliation
+ of this source.
+ type: boolean
+ timeout:
+ default: 60s
+ description: The timeout for remote OCI Repository operations like
+ pulling, defaults to 60s.
+ type: string
+ url:
+ description: URL is a reference to an OCI artifact repository hosted
+ on a remote container registry.
+ type: string
+ verify:
+ description: Verification specifies the configuration to verify the
+ autheticity of an OCI Artifact.
+ properties:
+ provider:
+ description: Provider specifies the technology used to sign the
+ OCI Artifact.
+ enum:
+ - cosign
+ type: string
+ secretRef:
+ description: SecretRef specifies the Kubernetes Secret containing
+ the trusted public keys.
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ type: object
+ required:
+ - provider
+ - secretRef
+ type: object
+ required:
+ - interval
+ - url
+ type: object
+ status:
+ default:
+ observedGeneration: -1
+ description: OCIRepositoryStatus defines the observed state of OCIRepository
+ properties:
+ artifact:
+ description: Artifact represents the output of the last successful
+ OCI Repository sync.
+ properties:
+ checksum:
+ description: Checksum is the SHA256 checksum of the Artifact file.
+ type: string
+ lastUpdateTime:
+ description: LastUpdateTime is the timestamp corresponding to
+ the last update of the Artifact.
+ format: date-time
+ type: string
+ path:
+ description: Path is the relative file path of the Artifact. It
+ can be used to locate the file in the root of the Artifact storage
+ on the local file system of the controller managing the Source.
+ 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 chart version, etc.
+ type: string
+ size:
+ description: Size is the number of bytes in the file.
+ format: int64
+ type: integer
+ url:
+ description: URL is the HTTP address of the Artifact as exposed
+ by the controller managing the Source. It can be used to retrieve
+ the Artifact for consumption, e.g. by another controller applying
+ the Artifact contents.
+ 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 of the annotation value 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 OCI Repository 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..a048672d6 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -141,3 +141,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/docs/api/source.md b/docs/api/source.md
index 521571ead..1ecf0a13a 100644
--- a/docs/api/source.md
+++ b/docs/api/source.md
@@ -16,6 +16,8 @@ Resource Types:
HelmChart
HelmRepository
+
+OCIRepository
@@ -880,6 +882,231 @@ HelmRepositoryStatus
+
+OCIRepository is the Schema for the ocirepositories API
+
@@ -887,7 +1114,8 @@ HelmRepositoryStatus
BucketStatus,
GitRepositoryStatus,
HelmChartStatus,
-HelmRepositoryStatus)
+HelmRepositoryStatus,
+OCIRepositoryStatus)
Artifact represents the output of a Source reconciliation.
@@ -2291,6 +2519,369 @@ string
+
+
+(Appears on:
+OCIRepositorySpec)
+
+OCIRepositoryRef defines the image reference for the OCIRepository’s URL
+
+
+
+(Appears on:
+OCIRepository)
+
+OCIRepositorySpec defines the desired state of OCIRepository
+
+
+
+(Appears on:
+OCIRepository)
+
+OCIRepositoryStatus defines the observed state of OCIRepository
+
+
+
+(Appears on:
+OCIRepositorySpec)
+
+OCIRepositoryVerification verifies the authenticity of an OCI Artifact
+
Source interface must be supported by all API types.
From 07466730c09a11579c143cccaf3b722575c455a1 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 21 Jun 2022 15:42:33 +0300
Subject: [PATCH 02/25] Implement OCIRepository controller for public repos
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 4 +-
.../samples/source_v1beta2_ocirepository.yaml | 9 +
controllers/ocirepository_controller.go | 593 ++++++++++++++++++
controllers/ocirepository_controller_test.go | 122 ++++
controllers/suite_test.go | 9 +
hack/ci/e2e.sh | 2 +
main.go | 13 +
7 files changed, 750 insertions(+), 2 deletions(-)
create mode 100644 config/samples/source_v1beta2_ocirepository.yaml
create mode 100644 controllers/ocirepository_controller.go
create mode 100644 controllers/ocirepository_controller_test.go
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index f308ae490..bc6d830ba 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -158,13 +158,13 @@ func (in *OCIRepository) SetConditions(conditions []metav1.Condition) {
in.Status.Conditions = conditions
}
-// GetRequeueAfter returns the duration after which the GitRepository must be
+// GetRequeueAfter returns the duration after which the OCIRepository must be
// reconciled again.
func (in OCIRepository) GetRequeueAfter() time.Duration {
return in.Spec.Interval.Duration
}
-// GetArtifact returns the latest Artifact from the GitRepository if present in
+// GetArtifact returns the latest Artifact from the OCIRepository if present in
// the status sub-resource.
func (in *OCIRepository) GetArtifact() *Artifact {
return in.Status.Artifact
diff --git a/config/samples/source_v1beta2_ocirepository.yaml b/config/samples/source_v1beta2_ocirepository.yaml
new file mode 100644
index 000000000..2fbdf9969
--- /dev/null
+++ b/config/samples/source_v1beta2_ocirepository.yaml
@@ -0,0 +1,9 @@
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name: ocirepository-sample
+spec:
+ interval: 1m
+ url: ghcr.io/stefanprodan/manifests/podinfo
+ ref:
+ tag: 6.1.6
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
new file mode 100644
index 000000000..da4916f64
--- /dev/null
+++ b/controllers/ocirepository_controller.go
@@ -0,0 +1,593 @@
+/*
+Copyright 2022 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 (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/google/go-containerregistry/pkg/crane"
+ gcrv1 "github.com/google/go-containerregistry/pkg/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/uuid"
+ kuberecorder "k8s.io/client-go/tools/record"
+
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/ratelimiter"
+
+ "github.com/fluxcd/pkg/apis/meta"
+ "github.com/fluxcd/pkg/runtime/conditions"
+ helper "github.com/fluxcd/pkg/runtime/controller"
+ "github.com/fluxcd/pkg/runtime/events"
+ "github.com/fluxcd/pkg/runtime/patch"
+ "github.com/fluxcd/pkg/runtime/predicates"
+ "github.com/fluxcd/pkg/untar"
+ sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ serror "github.com/fluxcd/source-controller/internal/error"
+ sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
+ "github.com/fluxcd/source-controller/internal/reconcile/summarize"
+)
+
+// ociRepositoryReadyCondition contains the information required to summarize a
+// v1beta2.OCIRepository Ready Condition.
+var ociRepositoryReadyCondition = summarize.Conditions{
+ Target: meta.ReadyCondition,
+ Owned: []string{
+ sourcev1.StorageOperationFailedCondition,
+ sourcev1.FetchFailedCondition,
+ sourcev1.ArtifactOutdatedCondition,
+ sourcev1.ArtifactInStorageCondition,
+ meta.ReadyCondition,
+ meta.ReconcilingCondition,
+ meta.StalledCondition,
+ },
+ Summarize: []string{
+ sourcev1.StorageOperationFailedCondition,
+ sourcev1.FetchFailedCondition,
+ sourcev1.ArtifactOutdatedCondition,
+ sourcev1.ArtifactInStorageCondition,
+ meta.StalledCondition,
+ meta.ReconcilingCondition,
+ },
+ NegativePolarity: []string{
+ sourcev1.StorageOperationFailedCondition,
+ sourcev1.FetchFailedCondition,
+ sourcev1.ArtifactOutdatedCondition,
+ meta.StalledCondition,
+ meta.ReconcilingCondition,
+ },
+}
+
+// ociRepositoryFailConditions contains the conditions that represent a failure.
+var ociRepositoryFailConditions = []string{
+ sourcev1.FetchFailedCondition,
+ sourcev1.StorageOperationFailedCondition,
+}
+
+// ociRepositoryReconcileFunc is the function type for all the v1beta2.OCIRepository
+// (sub)reconcile functions. The type implementations are grouped and
+// executed serially to perform the complete reconcile of the object.
+type ociRepositoryReconcileFunc func(ctx context.Context, obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error)
+
+// OCIRepositoryReconciler reconciles a v1beta2.OCIRepository object
+type OCIRepositoryReconciler struct {
+ client.Client
+ helper.Metrics
+ kuberecorder.EventRecorder
+
+ Storage *Storage
+ ControllerName string
+ requeueDependency time.Duration
+}
+
+type OCIRepositoryReconcilerOptions struct {
+ MaxConcurrentReconciles int
+ DependencyRequeueInterval time.Duration
+ RateLimiter ratelimiter.RateLimiter
+}
+
+// SetupWithManager sets up the controller with the Manager.
+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,
+ RateLimiter: opts.RateLimiter,
+ }).
+ Complete(r)
+}
+
+// +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
+
+func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
+ start := time.Now()
+ log := ctrl.LoggerFrom(ctx).
+ // Sets a reconcile ID to correlate logs from all suboperations.
+ WithValues("reconcileID", uuid.NewUUID())
+
+ // logger will be associated to the new context that is
+ // returned from ctrl.LoggerInto.
+ ctx = ctrl.LoggerInto(ctx, log)
+
+ // Fetch the OCIRepository
+ obj := &sourcev1.OCIRepository{}
+ if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
+ return ctrl.Result{}, client.IgnoreNotFound(err)
+ }
+
+ // Record suspended status metric
+ r.RecordSuspend(ctx, obj, obj.Spec.Suspend)
+
+ // Return early if the object is suspended
+ if obj.Spec.Suspend {
+ log.Info("reconciliation is suspended for this object")
+ return ctrl.Result{}, nil
+ }
+
+ // Initialize the patch helper with the current version of the object.
+ patchHelper, err := patch.NewHelper(obj, r.Client)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+
+ // recResult stores the abstracted reconcile result.
+ var recResult sreconcile.Result
+
+ // Always attempt to patch the object and status after each reconciliation
+ // NOTE: The final runtime result and error are set in this block.
+ defer func() {
+ summarizeHelper := summarize.NewHelper(r.EventRecorder, patchHelper)
+ summarizeOpts := []summarize.Option{
+ summarize.WithConditions(ociRepositoryReadyCondition),
+ summarize.WithReconcileResult(recResult),
+ summarize.WithReconcileError(retErr),
+ summarize.WithIgnoreNotFound(),
+ summarize.WithProcessors(
+ summarize.RecordContextualError,
+ summarize.RecordReconcileReq,
+ ),
+ summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetRequeueAfter()}),
+ summarize.WithPatchFieldOwner(r.ControllerName),
+ }
+ result, retErr = summarizeHelper.SummarizeAndPatch(ctx, obj, summarizeOpts...)
+
+ // Always record readiness and duration metrics
+ r.Metrics.RecordReadiness(ctx, obj)
+ r.Metrics.RecordDuration(ctx, obj, start)
+ }()
+
+ // Add finalizer first if not exist to avoid the race condition between init and delete
+ if !controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer) {
+ controllerutil.AddFinalizer(obj, sourcev1.SourceFinalizer)
+ recResult = sreconcile.ResultRequeue
+ return
+ }
+
+ // Examine if the object is under deletion
+ if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
+ recResult, retErr = r.reconcileDelete(ctx, obj)
+ return
+ }
+
+ // Reconcile actual object
+ reconcilers := []ociRepositoryReconcileFunc{
+ r.reconcileStorage,
+ r.reconcileSource,
+ r.reconcileArtifact,
+ }
+ recResult, retErr = r.reconcile(ctx, obj, reconcilers)
+ return
+}
+
+// reconcile iterates through the ociRepositoryReconcileFunc tasks for the
+// object. It returns early on the first call that returns
+// reconcile.ResultRequeue, or produces an error.
+func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.OCIRepository, reconcilers []ociRepositoryReconcileFunc) (sreconcile.Result, error) {
+ oldObj := obj.DeepCopy()
+
+ // Mark as reconciling if generation differs.
+ if obj.Generation != obj.Status.ObservedGeneration {
+ conditions.MarkReconciling(obj, "NewGeneration", "reconciling new object generation (%d)", obj.Generation)
+ }
+
+ // Create temp working dir
+ tmpDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s-%s-", obj.Kind, obj.Namespace, obj.Name))
+ if err != nil {
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to create temporary working directory: %w", err),
+ Reason: sourcev1.DirCreationFailedReason,
+ }
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+ defer func() {
+ if err = os.RemoveAll(tmpDir); err != nil {
+ ctrl.LoggerFrom(ctx).Error(err, "failed to remove temporary working directory")
+ }
+ }()
+ conditions.Delete(obj, sourcev1.StorageOperationFailedCondition)
+
+ hs := gcrv1.Hash{}
+ var (
+ res sreconcile.Result
+ resErr error
+ digest = hs.DeepCopy()
+ )
+
+ // Run the sub-reconcilers and build the result of reconciliation.
+ for _, rec := range reconcilers {
+ recResult, err := rec(ctx, obj, digest, tmpDir)
+ // Exit immediately on ResultRequeue.
+ if recResult == sreconcile.ResultRequeue {
+ return sreconcile.ResultRequeue, nil
+ }
+ // If an error is received, prioritize the returned results because an
+ // error also means immediate requeue.
+ if err != nil {
+ resErr = err
+ res = recResult
+ break
+ }
+ // Prioritize requeue request in the result.
+ res = sreconcile.LowestRequeuingResult(res, recResult)
+ }
+
+ r.notify(ctx, oldObj, obj, digest, res, resErr)
+
+ return res, resErr
+}
+
+// notify emits notification related to the reconciliation.
+func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, digest *gcrv1.Hash, res sreconcile.Result, resErr error) {
+ // Notify successful reconciliation for new artifact and recovery from any
+ // failure.
+ if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
+ annotations := map[string]string{
+ sourcev1.GroupVersion.Group + "/revision": newObj.Status.Artifact.Revision,
+ sourcev1.GroupVersion.Group + "/checksum": newObj.Status.Artifact.Checksum,
+ }
+
+ var oldChecksum string
+ if oldObj.GetArtifact() != nil {
+ oldChecksum = oldObj.GetArtifact().Checksum
+ }
+
+ message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", digest.String(), newObj.Spec.URL)
+
+ // Notify on new artifact and failure recovery.
+ if oldChecksum != newObj.GetArtifact().Checksum {
+ r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
+ "NewArtifact", message)
+ ctrl.LoggerFrom(ctx).Info(message)
+ } else {
+ if sreconcile.FailureRecovery(oldObj, newObj, ociRepositoryFailConditions) {
+ r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
+ meta.SucceededReason, message)
+ ctrl.LoggerFrom(ctx).Info(message)
+ }
+ }
+ }
+}
+
+// reconcileSource fetches the upstream OCI artifact content.
+// If this fails, it records v1beta2.FetchFailedCondition=True on the object and returns early.
+func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error) {
+ ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
+ defer cancel()
+
+ url := obj.Spec.URL
+ if obj.Spec.Reference != nil {
+ if obj.Spec.Reference.Tag != "" {
+ url = fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag)
+ }
+ if obj.Spec.Reference.Digest != "" {
+ url = fmt.Sprintf("%s@%s", obj.Spec.URL, obj.Spec.Reference.Digest)
+ }
+ }
+
+ // Pull OCI artifact
+ img, err := crane.Pull(url, r.craneOptions(ctxTimeout)...)
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ // Fetch digest
+ imgDigest, err := img.Digest()
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ // Set revision from digest hex
+ imgDigest.DeepCopyInto(digest)
+ revision := imgDigest.Hex
+
+ // Mark observations about the revision on the object
+ defer func() {
+ if !obj.GetArtifact().HasRevision(revision) {
+ message := fmt.Sprintf("new upstream revision '%s'", revision)
+ conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", message)
+ conditions.MarkReconciling(obj, "NewRevision", message)
+ }
+ }()
+
+ // Extract the content of the first artifact layer
+ if !obj.GetArtifact().HasRevision(revision) {
+ layers, err := img.Layers()
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ if len(layers) < 1 {
+ err = fmt.Errorf("no layers found in artifact")
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ blob, err := layers[0].Compressed()
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ if _, err = untar.Untar(blob, dir); err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+ }
+
+ conditions.Delete(obj, sourcev1.FetchFailedCondition)
+ return sreconcile.ResultSuccess, nil
+}
+
+// reconcileStorage ensures the current state of the storage matches the
+// desired and previously observed state.
+//
+// All Artifacts for the object except for the current one in the Status are
+// garbage collected from the Storage.
+// If the Artifact in the Status of the object disappeared from the Storage,
+// it is removed from the object.
+// If the object does not have an Artifact in its Status, a Reconciling
+// condition is added.
+// The hostname of any URL in the Status of the object are updated, to ensure
+// they match the Storage server hostname of current runtime.
+func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.OCIRepository, _ *gcrv1.Hash, _ string) (sreconcile.Result, error) {
+ // Garbage collect previous advertised artifact(s) from storage
+ _ = r.garbageCollect(ctx, obj)
+
+ // Determine if the advertised artifact is still in storage
+ if artifact := obj.GetArtifact(); artifact != nil && !r.Storage.ArtifactExist(*artifact) {
+ obj.Status.Artifact = nil
+ obj.Status.URL = ""
+ // Remove the condition as the artifact doesn't exist.
+ conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
+ }
+
+ // Record that we do not have an artifact
+ if obj.GetArtifact() == nil {
+ conditions.MarkReconciling(obj, "NoArtifact", "no artifact for resource in storage")
+ conditions.Delete(obj, sourcev1.ArtifactInStorageCondition)
+ return sreconcile.ResultSuccess, nil
+ }
+
+ // Always update URLs to ensure hostname is up-to-date
+ r.Storage.SetArtifactURL(obj.GetArtifact())
+ obj.Status.URL = r.Storage.SetHostname(obj.Status.URL)
+
+ return sreconcile.ResultSuccess, nil
+}
+
+// reconcileArtifact archives a new Artifact to the Storage, if the current
+// (Status) data on the object does not match the given.
+//
+// The inspection of the given data to the object is differed, ensuring any
+// stale observations like v1beta2.ArtifactOutdatedCondition are removed.
+// If the given Artifact does not differ from the object's current, it returns
+// early.
+// On a successful archive, the Artifact in the Status of the object is set,
+// and the symlink in the Storage is updated to its path.
+func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error) {
+ // Calculate revision
+ revision := digest.Hex
+
+ // Create artifact
+ artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision, fmt.Sprintf("%s.tar.gz", revision))
+
+ // Set the ArtifactInStorageCondition if there's no drift.
+ defer func() {
+ if obj.GetArtifact().HasRevision(artifact.Revision) {
+ conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
+ conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
+ "stored artifact for revision '%s'", artifact.Revision)
+ }
+ }()
+
+ // The artifact is up-to-date
+ if obj.GetArtifact().HasRevision(artifact.Revision) {
+ r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
+ return sreconcile.ResultSuccess, nil
+ }
+
+ // Ensure target path exists and is a directory
+ if f, err := os.Stat(dir); err != nil {
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to stat source path: %w", err),
+ Reason: sourcev1.StatOperationFailedReason,
+ }
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ } else if !f.IsDir() {
+ e := &serror.Event{
+ Err: fmt.Errorf("source path '%s' is not a directory", dir),
+ Reason: sourcev1.InvalidPathReason,
+ }
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ // Ensure artifact directory exists and acquire lock
+ if err := r.Storage.MkdirAll(artifact); err != nil {
+ e := &serror.Event{
+ Err: fmt.Errorf("failed to create artifact directory: %w", err),
+ Reason: sourcev1.DirCreationFailedReason,
+ }
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+ unlock, err := r.Storage.Lock(artifact)
+ if err != nil {
+ return sreconcile.ResultEmpty, &serror.Event{
+ Err: fmt.Errorf("failed to acquire lock for artifact: %w", err),
+ Reason: meta.FailedReason,
+ }
+ }
+ defer unlock()
+
+ // Archive directory to storage
+ if err := r.Storage.Archive(&artifact, dir, nil); err != nil {
+ e := &serror.Event{
+ Err: fmt.Errorf("unable to archive artifact to storage: %s", err),
+ Reason: sourcev1.ArchiveOperationFailedReason,
+ }
+ conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
+ // Record it on the object
+ obj.Status.Artifact = artifact.DeepCopy()
+
+ // Update symlink on a "best effort" basis
+ url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
+ if err != nil {
+ r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason,
+ "failed to update status URL symlink: %s", err)
+ }
+ if url != "" {
+ obj.Status.URL = url
+ }
+ conditions.Delete(obj, sourcev1.StorageOperationFailedCondition)
+ return sreconcile.ResultSuccess, nil
+}
+
+// reconcileDelete handles the deletion of the object.
+// It first garbage collects all Artifacts for the object from the Storage.
+// Removing the finalizer from the object if successful.
+func (r *OCIRepositoryReconciler) reconcileDelete(ctx context.Context, obj *sourcev1.OCIRepository) (sreconcile.Result, error) {
+ // Garbage collect the resource's artifacts
+ if err := r.garbageCollect(ctx, obj); err != nil {
+ // Return the error so we retry the failed garbage collection
+ return sreconcile.ResultEmpty, err
+ }
+
+ // Remove our finalizer from the list
+ controllerutil.RemoveFinalizer(obj, sourcev1.SourceFinalizer)
+
+ // Stop reconciliation as the object is being deleted
+ return sreconcile.ResultEmpty, nil
+}
+
+// garbageCollect performs a garbage collection for the given object.
+//
+// It removes all but the current Artifact from the Storage, unless the
+// deletion timestamp on the object is set. Which will result in the
+// removal of all Artifacts for the objects.
+func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourcev1.OCIRepository) error {
+ if !obj.DeletionTimestamp.IsZero() {
+ if deleted, err := r.Storage.RemoveAll(r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "", "*")); err != nil {
+ return serror.NewGeneric(
+ fmt.Errorf("garbage collection for deleted resource failed: %w", err),
+ "GarbageCollectionFailed",
+ )
+ } else if deleted != "" {
+ r.eventLogf(ctx, obj, events.EventTypeTrace, "GarbageCollectionSucceeded",
+ "garbage collected artifacts for deleted resource")
+ }
+ obj.Status.Artifact = nil
+ return nil
+ }
+ if obj.GetArtifact() != nil {
+ delFiles, err := r.Storage.GarbageCollect(ctx, *obj.GetArtifact(), time.Second*5)
+ if err != nil {
+ return serror.NewGeneric(
+ fmt.Errorf("garbage collection of artifacts failed: %w", err),
+ "GarbageCollectionFailed",
+ )
+ }
+ if len(delFiles) > 0 {
+ r.eventLogf(ctx, obj, events.EventTypeTrace, "GarbageCollectionSucceeded",
+ fmt.Sprintf("garbage collected %d artifacts", len(delFiles)))
+ return nil
+ }
+ }
+ return nil
+}
+
+// eventLogf records events, and logs at the same time.
+//
+// This log is different from the debug log in the EventRecorder, in the sense
+// that this is a simple log. While the debug log contains complete details
+// about the event.
+func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
+ msg := fmt.Sprintf(messageFmt, args...)
+ // Log and emit event.
+ if eventType == corev1.EventTypeWarning {
+ ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg)
+ } else {
+ ctrl.LoggerFrom(ctx).Info(msg)
+ }
+ r.Eventf(obj, eventType, reason, msg)
+}
+
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Option {
+ return []crane.Option{
+ crane.WithContext(ctx),
+ crane.WithUserAgent("flux/v2"),
+ crane.WithPlatform(&gcrv1.Platform{
+ Architecture: "flux",
+ OS: "flux",
+ OSVersion: "v2",
+ }),
+ }
+}
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
new file mode 100644
index 000000000..561ef42ed
--- /dev/null
+++ b/controllers/ocirepository_controller_test.go
@@ -0,0 +1,122 @@
+package controllers
+
+import (
+ "testing"
+ "time"
+
+ "github.com/darkowlzz/controller-check/status"
+ "github.com/fluxcd/pkg/apis/meta"
+ "github.com/fluxcd/pkg/runtime/conditions"
+ "github.com/fluxcd/pkg/runtime/patch"
+ sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ . "github.com/onsi/gomega"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func TestOCIRepository_Reconcile(t *testing.T) {
+
+ tests := []struct {
+ name string
+ url string
+ tag string
+ digest string
+ }{
+ {
+ name: "public latest",
+ url: "ghcr.io/stefanprodan/manifests/podinfo",
+ tag: "6.1.6",
+ digest: "3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-reconcile-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-reconcile",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{
+ Tag: tt.tag,
+ },
+ },
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ // Wait for the finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return len(obj.Finalizers) > 0
+ }, timeout).Should(BeFalse())
+
+ // Wait for the object to be Ready
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ if !conditions.IsReady(obj) {
+ return false
+ }
+ readyCondition := conditions.Get(obj, meta.ReadyCondition)
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ obj.Generation == obj.Status.ObservedGeneration
+ }, timeout).Should(BeTrue())
+
+ // Check if the revision is set to the digest format
+ g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest))
+
+ // Check if the object status is valid
+ condns := &status.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
+ checker := status.NewChecker(testEnv.Client, condns)
+ checker.CheckErr(ctx, obj)
+
+ // kstatus client conformance check
+ u, err := patch.ToUnstructured(obj)
+ g.Expect(err).ToNot(HaveOccurred())
+ res, err := kstatus.Compute(u)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(res.Status).To(Equal(kstatus.CurrentStatus))
+
+ // Patch the object with reconcile request annotation.
+ patchHelper, err := patch.NewHelper(obj, testEnv.Client)
+ g.Expect(err).ToNot(HaveOccurred())
+ annotations := map[string]string{
+ meta.ReconcileRequestAnnotation: "now",
+ }
+ obj.SetAnnotations(annotations)
+ g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return obj.Status.LastHandledReconcileAt == "now"
+ }, timeout).Should(BeTrue())
+
+ // Wait for the object to be deleted
+ g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+ })
+ }
+}
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 011b5de7b..39711a2dc 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -235,6 +235,15 @@ func TestMain(m *testing.M) {
testCache = cache.New(5, 1*time.Second)
cacheRecorder := cache.MustMakeMetrics()
+ if err := (&OCIRepositoryReconciler{
+ Client: testEnv,
+ EventRecorder: record.NewFakeRecorder(32),
+ Metrics: testMetricsH,
+ Storage: testStorage,
+ }).SetupWithManager(testEnv); err != nil {
+ panic(fmt.Sprintf("Failed to start OCIRepositoryReconciler: %v", err))
+ }
+
if err := (&HelmRepositoryReconciler{
Client: testEnv,
EventRecorder: record.NewFakeRecorder(32),
diff --git a/hack/ci/e2e.sh b/hack/ci/e2e.sh
index cbeac1d82..ccb2540f3 100755
--- a/hack/ci/e2e.sh
+++ b/hack/ci/e2e.sh
@@ -36,6 +36,7 @@ function cleanup(){
kubectl -n kube-system describe pods
kubectl -n source-system describe pods
kubectl -n source-system get gitrepositories -oyaml
+ kubectl -n source-system get ocirepositories -oyaml
kubectl -n source-system get helmrepositories -oyaml
kubectl -n source-system get helmcharts -oyaml
kubectl -n source-system get all
@@ -72,6 +73,7 @@ echo "Run smoke tests"
kubectl -n source-system apply -f "${ROOT_DIR}/config/samples"
kubectl -n source-system rollout status deploy/source-controller --timeout=1m
kubectl -n source-system wait gitrepository/gitrepository-sample --for=condition=ready --timeout=1m
+kubectl -n source-system wait ocirepository/ocirepository-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 delete -f "${ROOT_DIR}/config/samples"
diff --git a/main.go b/main.go
index 0121fd62a..621cea36c 100644
--- a/main.go
+++ b/main.go
@@ -309,6 +309,19 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "Bucket")
os.Exit(1)
}
+ if err = (&controllers.OCIRepositoryReconciler{
+ Client: mgr.GetClient(),
+ Storage: storage,
+ EventRecorder: eventRecorder,
+ ControllerName: controllerName,
+ Metrics: metricsH,
+ }).SetupWithManagerAndOptions(mgr, controllers.OCIRepositoryReconcilerOptions{
+ MaxConcurrentReconciles: concurrent,
+ RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
+ }); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "OCIRepository")
+ os.Exit(1)
+ }
// +kubebuilder:scaffold:builder
go func() {
From 768adc2dd9bfa0d6461a7558b53f56f7aea28347 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Wed, 22 Jun 2022 18:35:00 +0300
Subject: [PATCH 03/25] Implement OCIRepository ref.semver
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 162 ++++++++++++-------
controllers/ocirepository_controller_test.go | 29 +++-
2 files changed, 129 insertions(+), 62 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index da4916f64..fb7ad29c4 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -21,8 +21,10 @@ import (
"errors"
"fmt"
"os"
+ "sort"
"time"
+ "github.com/Masterminds/semver/v3"
"github.com/google/go-containerregistry/pkg/crane"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
corev1 "k8s.io/api/core/v1"
@@ -45,6 +47,7 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
"github.com/fluxcd/pkg/untar"
+ "github.com/fluxcd/pkg/version"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
serror "github.com/fluxcd/source-controller/internal/error"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
@@ -271,55 +274,21 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.O
return res, resErr
}
-// notify emits notification related to the reconciliation.
-func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, digest *gcrv1.Hash, res sreconcile.Result, resErr error) {
- // Notify successful reconciliation for new artifact and recovery from any
- // failure.
- if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
- annotations := map[string]string{
- sourcev1.GroupVersion.Group + "/revision": newObj.Status.Artifact.Revision,
- sourcev1.GroupVersion.Group + "/checksum": newObj.Status.Artifact.Checksum,
- }
-
- var oldChecksum string
- if oldObj.GetArtifact() != nil {
- oldChecksum = oldObj.GetArtifact().Checksum
- }
-
- message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", digest.String(), newObj.Spec.URL)
-
- // Notify on new artifact and failure recovery.
- if oldChecksum != newObj.GetArtifact().Checksum {
- r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
- "NewArtifact", message)
- ctrl.LoggerFrom(ctx).Info(message)
- } else {
- if sreconcile.FailureRecovery(oldObj, newObj, ociRepositoryFailConditions) {
- r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
- meta.SucceededReason, message)
- ctrl.LoggerFrom(ctx).Info(message)
- }
- }
- }
-}
-
-// reconcileSource fetches the upstream OCI artifact content.
+// reconcileSource fetches the upstream OCI artifact metadata and content.
// If this fails, it records v1beta2.FetchFailedCondition=True on the object and returns early.
func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error) {
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
defer cancel()
- url := obj.Spec.URL
- if obj.Spec.Reference != nil {
- if obj.Spec.Reference.Tag != "" {
- url = fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag)
- }
- if obj.Spec.Reference.Digest != "" {
- url = fmt.Sprintf("%s@%s", obj.Spec.URL, obj.Spec.Reference.Digest)
- }
+ // Determine which artifact revision to pull
+ url, err := r.getArtifactURL(ctxTimeout, obj)
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
}
- // Pull OCI artifact
+ // Pull artifact from the remote container registry
img, err := crane.Pull(url, r.craneOptions(ctxTimeout)...)
if err != nil {
e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
@@ -327,7 +296,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultEmpty, e
}
- // Fetch digest
+ // Determine the artifact SHA256 digest
imgDigest, err := img.Digest()
if err != nil {
e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
@@ -335,14 +304,14 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultEmpty, e
}
- // Set revision from digest hex
+ // Set the internal revision to the remote digest hex
imgDigest.DeepCopyInto(digest)
revision := imgDigest.Hex
// Mark observations about the revision on the object
defer func() {
if !obj.GetArtifact().HasRevision(revision) {
- message := fmt.Sprintf("new upstream revision '%s'", revision)
+ message := fmt.Sprintf("new upstream revision '%s' for '%s'", revision, url)
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", message)
conditions.MarkReconciling(obj, "NewRevision", message)
}
@@ -382,6 +351,71 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultSuccess, nil
}
+// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
+func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository) (string, error) {
+ url := obj.Spec.URL
+ if obj.Spec.Reference != nil {
+ if obj.Spec.Reference.Digest != "" {
+ return fmt.Sprintf("%s@%s", obj.Spec.URL, obj.Spec.Reference.Digest), nil
+ }
+
+ if obj.Spec.Reference.SemVer != "" {
+ tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s:%s", obj.Spec.URL, tag), nil
+ }
+
+ if obj.Spec.Reference.Tag != "" {
+ return fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag), nil
+ }
+ }
+
+ return url, nil
+}
+
+// getTagBySemver call the remote container registry, fetches all the tags from the repository,
+// and returns the latest tag according to the semver expression.
+func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string) (string, error) {
+ tags, err := crane.ListTags(url, r.craneOptions(ctx)...)
+ if err != nil {
+ return "", err
+ }
+
+ constraint, err := semver.NewConstraint(exp)
+ if err != nil {
+ return "", fmt.Errorf("semver '%s' parse error: %w", exp, err)
+ }
+
+ var matchingVersions []*semver.Version
+ for _, t := range tags {
+ v, err := version.ParseVersion(t)
+ if err != nil {
+ continue
+ }
+
+ if constraint.Check(v) {
+ matchingVersions = append(matchingVersions, v)
+ }
+ }
+
+ if len(matchingVersions) == 0 {
+ return "", fmt.Errorf("no match found for semver: %s", exp)
+ }
+
+ sort.Sort(sort.Reverse(semver.Collection(matchingVersions)))
+ return matchingVersions[0].Original(), nil
+}
+
+// craneOptions sets the timeout and user agent for all operations against remote container registries.
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Option {
+ return []crane.Option{
+ crane.WithContext(ctx),
+ crane.WithUserAgent("flux/v2"),
+ }
+}
+
// reconcileStorage ensures the current state of the storage matches the
// desired and previously observed state.
//
@@ -580,14 +614,34 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Obj
r.Eventf(obj, eventType, reason, msg)
}
-func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Option {
- return []crane.Option{
- crane.WithContext(ctx),
- crane.WithUserAgent("flux/v2"),
- crane.WithPlatform(&gcrv1.Platform{
- Architecture: "flux",
- OS: "flux",
- OSVersion: "v2",
- }),
+// notify emits notification related to the reconciliation.
+func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, digest *gcrv1.Hash, res sreconcile.Result, resErr error) {
+ // Notify successful reconciliation for new artifact and recovery from any
+ // failure.
+ if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
+ annotations := map[string]string{
+ sourcev1.GroupVersion.Group + "/revision": newObj.Status.Artifact.Revision,
+ sourcev1.GroupVersion.Group + "/checksum": newObj.Status.Artifact.Checksum,
+ }
+
+ var oldChecksum string
+ if oldObj.GetArtifact() != nil {
+ oldChecksum = oldObj.GetArtifact().Checksum
+ }
+
+ message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", digest.String(), newObj.Spec.URL)
+
+ // Notify on new artifact and failure recovery.
+ if oldChecksum != newObj.GetArtifact().Checksum {
+ r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
+ "NewArtifact", message)
+ ctrl.LoggerFrom(ctx).Info(message)
+ } else {
+ if sreconcile.FailureRecovery(oldObj, newObj, ociRepositoryFailConditions) {
+ r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
+ meta.SucceededReason, message)
+ ctrl.LoggerFrom(ctx).Info(message)
+ }
+ }
}
}
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 561ef42ed..044d8666f 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -17,19 +17,25 @@ import (
)
func TestOCIRepository_Reconcile(t *testing.T) {
-
tests := []struct {
name string
url string
tag string
+ semver string
digest string
}{
{
- name: "public latest",
+ name: "public tag",
url: "ghcr.io/stefanprodan/manifests/podinfo",
tag: "6.1.6",
digest: "3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de",
},
+ {
+ name: "public semver",
+ url: "ghcr.io/stefanprodan/manifests/podinfo",
+ semver: ">= 6.1 <= 6.1.5",
+ digest: "1d1bf6980fc86f69481bd8c875c531aa23d761ac890ce2594d4df2b39ecd8713",
+ },
}
for _, tt := range tests {
@@ -46,14 +52,19 @@ func TestOCIRepository_Reconcile(t *testing.T) {
Namespace: ns.Name,
},
Spec: sourcev1.OCIRepositorySpec{
- URL: tt.url,
- Interval: metav1.Duration{Duration: 60 * time.Minute},
- Reference: &sourcev1.OCIRepositoryRef{
- Tag: tt.tag,
- },
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{},
},
}
+ if tt.tag != "" {
+ obj.Spec.Reference.Tag = tt.tag
+ }
+ if tt.semver != "" {
+ obj.Spec.Reference.SemVer = tt.semver
+ }
+
g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
@@ -79,7 +90,9 @@ func TestOCIRepository_Reconcile(t *testing.T) {
obj.Generation == obj.Status.ObservedGeneration
}, timeout).Should(BeTrue())
- // Check if the revision is set to the digest format
+ t.Log(obj.Spec.Reference)
+
+ // Check if the revision matches the expected digest
g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest))
// Check if the object status is valid
From c9f5af7ddcf6c14aae46860d36d56ec80c122e27 Mon Sep 17 00:00:00 2001
From: rashedkvm
Date: Tue, 5 Jul 2022 13:52:05 +0300
Subject: [PATCH 04/25] Implements basic auth with static credentials
OCIRepository
Signed-off-by: rashedkvm
---
controllers/ocirepository_controller.go | 71 ++-
controllers/ocirepository_controller_test.go | 515 +++++++++++++++++-
.../testdata/podinfo/podinfo-6.1.4.tar | Bin 0 -> 14848 bytes
.../testdata/podinfo/podinfo-6.1.5.tar | Bin 0 -> 14848 bytes
.../testdata/podinfo/podinfo-6.1.6.tar | Bin 0 -> 14848 bytes
5 files changed, 569 insertions(+), 17 deletions(-)
create mode 100644 controllers/testdata/podinfo/podinfo-6.1.4.tar
create mode 100644 controllers/testdata/podinfo/podinfo-6.1.5.tar
create mode 100644 controllers/testdata/podinfo/podinfo-6.1.6.tar
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index fb7ad29c4..0e441f8a5 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -25,10 +25,14 @@ import (
"time"
"github.com/Masterminds/semver/v3"
+ "github.com/google/go-containerregistry/pkg/authn"
+ "github.com/google/go-containerregistry/pkg/authn/k8schain"
"github.com/google/go-containerregistry/pkg/crane"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/uuid"
kuberecorder "k8s.io/client-go/tools/record"
@@ -280,8 +284,16 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
defer cancel()
+ // Generates registry credential keychain
+ keychain, err := r.keychain(ctx, obj)
+ if err != nil {
+ e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
// Determine which artifact revision to pull
- url, err := r.getArtifactURL(ctxTimeout, obj)
+ url, err := r.getArtifactURL(ctxTimeout, obj, keychain)
if err != nil {
e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -289,7 +301,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}
// Pull artifact from the remote container registry
- img, err := crane.Pull(url, r.craneOptions(ctxTimeout)...)
+ img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain)...)
if err != nil {
e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -352,7 +364,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
-func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository) (string, error) {
+func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain) (string, error) {
url := obj.Spec.URL
if obj.Spec.Reference != nil {
if obj.Spec.Reference.Digest != "" {
@@ -360,7 +372,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
}
if obj.Spec.Reference.SemVer != "" {
- tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer)
+ tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer, keychain)
if err != nil {
return "", err
}
@@ -377,8 +389,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
// and returns the latest tag according to the semver expression.
-func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string) (string, error) {
- tags, err := crane.ListTags(url, r.craneOptions(ctx)...)
+func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string, keychain authn.Keychain) (string, error) {
+ tags, err := crane.ListTags(url, r.craneOptions(ctx, keychain)...)
if err != nil {
return "", err
}
@@ -408,11 +420,56 @@ func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp s
return matchingVersions[0].Original(), 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, obj *sourcev1.OCIRepository) (authn.Keychain, error) {
+ pullSecretNames := sets.NewString()
+
+ // lookup auth secret
+ if obj.Spec.SecretRef != nil {
+ pullSecretNames.Insert(obj.Spec.SecretRef.Name)
+ }
+
+ // lookup service account
+ if obj.Spec.ServiceAccountName != "" {
+ serviceAccountName := obj.Spec.ServiceAccountName
+ serviceAccount := corev1.ServiceAccount{}
+ err := r.Get(ctx, types.NamespacedName{Namespace: obj.Namespace, Name: serviceAccountName}, &serviceAccount)
+ if err != nil {
+ return nil, err
+ }
+ for _, ips := range serviceAccount.ImagePullSecrets {
+ pullSecretNames.Insert(ips.Name)
+ }
+ }
+
+ // if no pullsecrets available return DefaultKeyChain
+ if len(pullSecretNames) == 0 {
+ return authn.DefaultKeychain, nil
+ }
+
+ // 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: obj.Namespace, Name: imagePullSecretName}, &imagePullSecret)
+ if err != nil {
+ r.eventLogf(ctx, obj, events.EventSeverityTrace, "secret %q not found", imagePullSecretName)
+ return nil, err
+ }
+ imagePullSecrets[i] = imagePullSecret
+ }
+
+ return k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
+}
+
// craneOptions sets the timeout and user agent for all operations against remote container registries.
-func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Option {
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain authn.Keychain) []crane.Option {
return []crane.Option{
crane.WithContext(ctx),
crane.WithUserAgent("flux/v2"),
+ crane.WithAuthFromKeychain(keychain),
}
}
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 044d8666f..bcae3ad1d 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -1,6 +1,27 @@
+/*
+Copyright 2022 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 (
+ "fmt"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
"testing"
"time"
@@ -8,8 +29,14 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
+ "github.com/fluxcd/pkg/untar"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ "github.com/google/go-containerregistry/pkg/authn"
+ "github.com/google/go-containerregistry/pkg/crane"
+ "github.com/google/go-containerregistry/pkg/registry"
+ v1 "github.com/google/go-containerregistry/pkg/v1"
. "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
@@ -17,24 +44,60 @@ import (
)
func TestOCIRepository_Reconcile(t *testing.T) {
+ g := NewWithT(t)
+
+ // Registry server with public images
+ regServer := httptest.NewServer(registry.New())
+ versions := []string{"6.1.4", "6.1.5", "6.1.6"}
+ podinfoVersions := make(map[string]podinfoImage)
+
+ for i := 0; i < len(versions); i++ {
+ pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], regServer)
+ g.Expect(err).ToNot(HaveOccurred())
+
+ podinfoVersions[versions[i]] = *pi
+
+ }
+
tests := []struct {
- name string
- url string
- tag string
- semver string
- digest string
+ name string
+ url string
+ tag string
+ semver string
+ digest string
+ assertArtifact []artifactFixture
}{
{
name: "public tag",
- url: "ghcr.io/stefanprodan/manifests/podinfo",
- tag: "6.1.6",
- digest: "3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de",
+ url: podinfoVersions["6.1.6"].url,
+ tag: podinfoVersions["6.1.6"].tag,
+ digest: podinfoVersions["6.1.6"].digest.Hex,
+ assertArtifact: []artifactFixture{
+ {
+ expectedPath: "kustomize/deployment.yaml",
+ expectedChecksum: "6fd625effe6bb805b6a78943ee082a4412e763edb7fcaed6e8fe644d06cbf423",
+ },
+ {
+ expectedPath: "kustomize/hpa.yaml",
+ expectedChecksum: "d20e92e3b2926ebfee1644be0f4d0abadebfa95a8005c12f71bfd534a4be4ff9",
+ },
+ },
},
{
name: "public semver",
- url: "ghcr.io/stefanprodan/manifests/podinfo",
+ url: podinfoVersions["6.1.5"].url,
semver: ">= 6.1 <= 6.1.5",
- digest: "1d1bf6980fc86f69481bd8c875c531aa23d761ac890ce2594d4df2b39ecd8713",
+ digest: podinfoVersions["6.1.5"].digest.Hex,
+ assertArtifact: []artifactFixture{
+ {
+ expectedPath: "kustomize/deployment.yaml",
+ expectedChecksum: "dce4f5f780a8e8994b06031e5b567bf488ceaaaabd9bd3fc278b4f3bfc8c577b",
+ },
+ {
+ expectedPath: "kustomize/hpa.yaml",
+ expectedChecksum: "d20e92e3b2926ebfee1644be0f4d0abadebfa95a8005c12f71bfd534a4be4ff9",
+ },
+ },
},
}
@@ -95,6 +158,36 @@ func TestOCIRepository_Reconcile(t *testing.T) {
// Check if the revision matches the expected digest
g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest))
+ // Check if the artifact storage path matches the expected file path
+ localPath := testStorage.LocalPath(*obj.Status.Artifact)
+ t.Logf("artifact local path: %s", localPath)
+
+ f, err := os.Open(localPath)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer f.Close()
+
+ // create a tmp directory to extract artifact
+ tmp, err := os.MkdirTemp("", "ocirepository-test-")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer os.RemoveAll(tmp)
+
+ ep, err := untar.Untar(f, tmp)
+ g.Expect(err).ToNot(HaveOccurred())
+ t.Logf("extracted summary: %s", ep)
+
+ for _, af := range tt.assertArtifact {
+ expectedFile := filepath.Join(tmp, af.expectedPath)
+ g.Expect(expectedFile).To(BeAnExistingFile())
+
+ f2, err := os.Open(expectedFile)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer f2.Close()
+
+ h := testStorage.Checksum(f2)
+ t.Logf("file %q hash: %q", expectedFile, h)
+ g.Expect(h).To(Equal(af.expectedChecksum))
+ }
+
// Check if the object status is valid
condns := &status.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
checker := status.NewChecker(testEnv.Client, condns)
@@ -133,3 +226,405 @@ func TestOCIRepository_Reconcile(t *testing.T) {
})
}
}
+
+func TestOCIRepository_SecretRef(t *testing.T) {
+ g := NewWithT(t)
+
+ // Instantiate Authenticated Registry Server
+ regServer, err := setupRegistryServer(ctx)
+ g.Expect(err).ToNot(HaveOccurred())
+
+ // Create Test Image
+ image, err := crane.Load(path.Join("testdata", "podinfo", "podinfo-6.1.6.tar"))
+ g.Expect(err).ToNot(HaveOccurred())
+
+ repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
+
+ // Push Test Image
+ err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }))
+ g.Expect(err).ToNot(HaveOccurred())
+
+ // Test Image digest
+ podinfoImageDigest, err := image.Digest()
+ g.Expect(err).ToNot(HaveOccurred())
+
+ tests := []struct {
+ name string
+ url string
+ digest v1.Hash
+ includeSecretRef bool
+ includeServiceAccount bool
+ }{
+ {
+ name: "private-registry-access-via-secretref",
+ url: repositoryURL,
+ digest: podinfoImageDigest,
+ includeSecretRef: true,
+ includeServiceAccount: false,
+ },
+ {
+ name: "private-registry-access-via-serviceaccount",
+ url: repositoryURL,
+ digest: podinfoImageDigest,
+ includeSecretRef: false,
+ includeServiceAccount: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "auth-secretref",
+ Namespace: ns.Name,
+ },
+ Type: corev1.SecretTypeDockerConfigJson,
+ StringData: map[string]string{
+ ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, testRegistryUsername, testRegistryPassword),
+ },
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, secret)).To(Succeed()) }()
+
+ serviceAccount := &corev1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "sa-ocitest",
+ Namespace: ns.Name,
+ },
+ ImagePullSecrets: []corev1.LocalObjectReference{{Name: secret.Name}},
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, serviceAccount)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, serviceAccount)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-test-resource",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ },
+ }
+
+ if tt.includeSecretRef {
+ obj.Spec.SecretRef = &meta.LocalObjectReference{Name: secret.Name}
+ }
+
+ if tt.includeServiceAccount {
+ obj.Spec.ServiceAccountName = serviceAccount.Name
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ // Wait for the finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return len(obj.Finalizers) > 0
+ }, timeout).Should(BeFalse())
+
+ // Wait for the object to be Ready
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ if !conditions.IsReady(obj) {
+ return false
+ }
+ readyCondition := conditions.Get(obj, meta.ReadyCondition)
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ obj.Generation == obj.Status.ObservedGeneration
+ }, timeout).Should(BeTrue())
+
+ t.Log(obj.Status.Artifact.Revision)
+
+ // Check if the revision matches the expected digest
+ g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest.Hex))
+
+ // Check if the artifact storage path matches the expected file path
+ localPath := testStorage.LocalPath(*obj.Status.Artifact)
+ t.Logf("artifact local path: %s", localPath)
+
+ f, err := os.Open(localPath)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer f.Close()
+
+ // create a tmp directory to extract artifact
+ tmp, err := os.MkdirTemp("", "ocirepository-test-")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer os.RemoveAll(tmp)
+
+ ep, err := untar.Untar(f, tmp)
+ g.Expect(err).ToNot(HaveOccurred())
+ t.Logf("extracted summary: %s", ep)
+
+ expectedFile := filepath.Join(tmp, `kustomize/deployment.yaml`)
+ g.Expect(expectedFile).To(BeAnExistingFile())
+
+ f2, err := os.Open(expectedFile)
+ g.Expect(err).ToNot(HaveOccurred())
+ defer f2.Close()
+
+ h := testStorage.Checksum(f2)
+ t.Logf("hash: %q", h)
+ g.Expect(h).To(Equal("6fd625effe6bb805b6a78943ee082a4412e763edb7fcaed6e8fe644d06cbf423"))
+
+ // Check if the object status is valid
+ condns := &status.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
+ checker := status.NewChecker(testEnv.Client, condns)
+ checker.CheckErr(ctx, obj)
+
+ // kstatus client conformance check
+ u, err := patch.ToUnstructured(obj)
+ g.Expect(err).ToNot(HaveOccurred())
+ res, err := kstatus.Compute(u)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(res.Status).To(Equal(kstatus.CurrentStatus))
+
+ // Patch the object with reconcile request annotation.
+ patchHelper, err := patch.NewHelper(obj, testEnv.Client)
+ g.Expect(err).ToNot(HaveOccurred())
+ annotations := map[string]string{
+ meta.ReconcileRequestAnnotation: "now",
+ }
+ obj.SetAnnotations(annotations)
+ g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return false
+ }
+ return obj.Status.LastHandledReconcileAt == "now"
+ }, timeout).Should(BeTrue())
+
+ // Wait for the object to be deleted
+ g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, obj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+
+ })
+ }
+}
+
+func TestOCIRepository_FailedAuth(t *testing.T) {
+ g := NewWithT(t)
+
+ // Instantiate Authenticated Registry Server
+ regServer, err := setupRegistryServer(ctx)
+ g.Expect(err).ToNot(HaveOccurred())
+
+ // Create Test Image
+ image, err := crane.Load(path.Join("testdata", "podinfo", "podinfo-6.1.6.tar"))
+ g.Expect(err).ToNot(HaveOccurred())
+
+ repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
+
+ // Push Test Image
+ err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }))
+ g.Expect(err).ToNot(HaveOccurred())
+
+ // Test Image digest
+ podinfoImageDigest, err := image.Digest()
+ g.Expect(err).ToNot(HaveOccurred())
+
+ tests := []struct {
+ name string
+ url string
+ digest v1.Hash
+ repoUsername string
+ repoPassword string
+ includeSecretRef bool
+ includeServiceAccount bool
+ }{
+ {
+ name: "missing-auth",
+ url: repositoryURL,
+ repoUsername: "",
+ repoPassword: "",
+ digest: podinfoImageDigest,
+ includeSecretRef: false,
+ includeServiceAccount: false,
+ },
+ {
+ name: "invalid-auth-via-secret",
+ url: repositoryURL,
+ repoUsername: "InvalidUser",
+ repoPassword: "InvalidPassword",
+ digest: podinfoImageDigest,
+ includeSecretRef: true,
+ includeServiceAccount: false,
+ },
+ {
+ name: "invalid-auth-via-service-account",
+ url: repositoryURL,
+ repoUsername: "InvalidUser",
+ repoPassword: "InvalidPassword",
+ digest: podinfoImageDigest,
+ includeSecretRef: false,
+ includeServiceAccount: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "auth-secretref",
+ Namespace: ns.Name,
+ },
+ Type: corev1.SecretTypeDockerConfigJson,
+ StringData: map[string]string{
+ ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, tt.repoUsername, tt.repoPassword),
+ },
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, secret)).To(Succeed()) }()
+
+ serviceAccount := &corev1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "sa-ocitest",
+ Namespace: ns.Name,
+ },
+ ImagePullSecrets: []corev1.LocalObjectReference{{Name: secret.Name}},
+ }
+ g.Expect(testEnv.CreateAndWait(ctx, serviceAccount)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, serviceAccount)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-test-resource",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ },
+ }
+
+ if tt.includeSecretRef {
+ obj.Spec.SecretRef = &meta.LocalObjectReference{Name: secret.Name}
+ }
+
+ if tt.includeServiceAccount {
+ obj.Spec.ServiceAccountName = serviceAccount.Name
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ failedObj := sourcev1.OCIRepository{}
+
+ // Wait for the finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &failedObj); err != nil {
+ return false
+ }
+ return len(failedObj.Finalizers) > 0
+ }, timeout).Should(BeTrue())
+
+ // Wait for the object to fail
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &failedObj); err != nil {
+ return false
+ }
+ readyCondition := conditions.Get(&failedObj, meta.ReadyCondition)
+ if readyCondition == nil {
+ return false
+ }
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ !conditions.IsReady(&failedObj)
+ }, timeout).Should(BeTrue())
+
+ g.Expect(testEnv.Get(ctx, key, &failedObj)).To(Succeed())
+ readyCondition := conditions.Get(&failedObj, meta.ReadyCondition)
+ g.Expect(readyCondition.Status).To(Equal(metav1.ConditionFalse))
+ g.Expect(readyCondition.Message).Should(ContainSubstring("UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:podinfo Type:repository]]"))
+
+ // Wait for the object to be deleted
+ g.Expect(testEnv.Delete(ctx, &failedObj)).To(Succeed())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &failedObj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+ })
+ }
+}
+
+type artifactFixture struct {
+ expectedPath string
+ expectedChecksum string
+}
+type podinfoImage struct {
+ url string
+ tag string
+ digest v1.Hash
+}
+
+func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Server) (*podinfoImage, error) {
+
+ // Create Image
+ image, err := crane.Load(path.Join("testdata", "podinfo", tarFileName))
+ if err != nil {
+ return nil, err
+ }
+
+ url, err := url.Parse(imageServer.URL)
+ if err != nil {
+ return nil, err
+ }
+ repositoryURL := fmt.Sprintf("%s/podinfo", url.Host)
+
+ // Image digest
+ podinfoImageDigest, err := image.Digest()
+ if err != nil {
+ return nil, err
+ }
+
+ // Push image
+ err = crane.Push(image, repositoryURL)
+ if err != nil {
+ return nil, err
+ }
+
+ // Tag the image
+ err = crane.Tag(repositoryURL, tag)
+ if err != nil {
+ return nil, err
+ }
+
+ return &podinfoImage{
+ url: repositoryURL,
+ tag: tag,
+ digest: podinfoImageDigest,
+ }, nil
+}
diff --git a/controllers/testdata/podinfo/podinfo-6.1.4.tar b/controllers/testdata/podinfo/podinfo-6.1.4.tar
new file mode 100644
index 0000000000000000000000000000000000000000..dbc58051dbcb9bd5d231b40d8df6e69b123986be
GIT binary patch
literal 14848
zcmeHOZ*SW+6VK~>3PQtx4XABWmSkB#UTjH+2J4z2P5J=OP!u#p*<2-2qiADpknjE+
z^)IsPI8N;L*;WYwY?H_1@s4-=-HE~WaO8~`afn0xiBEh&Bi~^Zdv?TF#IT2bpNu9E
zr5?A3_6XbX(~n#?a;W3m{XcS<9&LXx!Ps-0BV0b~pLpn!y3J4=VH-QHeT49KwukAR
z=b9>*eqV0ui`$pqR~mn7QN?CLbEfBtTY+W0u$bah^ALW(qDZg!yT&*u{{9ODGm&el
zmR9il?PJ7u?_XFlFZn7dbp+pUExpXlTrpj8l>@_2M9~KkLf`LJKBKnl1tT0`MuuJ(
z`nF?FScDypeb@CdCr-p|n>#+4OkfKRy~yUSi#;+KO)zF|Lf%?^*ne~gFq
zo+$T%O$cn4F8*&*>Vf)5fA5p;K$3K9{vR6H+yD2+f5f&MY*_n0#t{F%%Fg`86*Yku8t7XrBr1~h*>6xgceVJ;t%%n&`u9D&+{dq3phSh62;c+#*4O2UP#Bm4Jzvelx
zuj3Dj9IkJ2=m($e_>Xu6<~NbQd;tHG(XhjR59;%f9sR*aI00oMKJi1cUEyWS`lS4i
zkRbv4clqCn8o$Vo#`=ShXR%x+Jk|XrP2vO2X6(PE{x`I}wfY}%hPdOZp(j&vs(7BC!I&5zVKdCb(3@)o^~dSbFa|C%29oJb
z1`_bH3Ff1J-K2CuMNB6#$YX{fWXg&Gx@r$F?05{-O{vPU3Ds;?;;)(+towdNz=bDS
z+|1QpyP1Qgsnk@%vhsR-u^ynC@p;}Ca*#7cjUbj*b8Z!qnUS#Q7EvY@D8m|$#-o}9
z)&i*Tp6i-iu#!sKhFgQCn*qI5+l0-;!T{F*O=nCM;%Oey6r>4hI;ad8czx1$R!3X{
z0KSy5V9NlVM{lIQ1d~B{XsFB*n%NPm=bvevH8d?jFN+nA{juNb1u?W_G7ix7>1EU2
zX4M0wDZ%ol^K+Jn4x_P>2FSQH+mltQ}Z+^M>aC83Q^y2l!Rl^LS1&!xs_kTBZ
z+|h{DyBnccEO-iDf2ri8@nj<-|Er85wYZz8M&c3}&3hdiJR`PNYH$8lM>rH37*)tNPI*;gD-?=z|v~5D)(@OEc0e54N&g
z&K2V=KR1SiNWRhn7R%-Vvf-ppS{j2u%@cT21W}*ZzqJjg_@8s0Z)5`RAU5;x9ANSA
zQP%Z5Lj(8tZX
zu;44H#3xg}13(b|RFQGD128oF_%&5iuCI7ho+B%BR+!Vo*w1jMX!`l%LW5sD!gFk>eY~tmr<+;O@LyGkX8y)&j9hO=mq>&NL%;TsPaaOSbQpg
zUqy$|cc@q%nrBEvXr2SiLLx_WA{V?xG#-DCWP|`80wgZN9uLv!^|?)|_1RxI(Lk>D}^C*bUcrh1Mrak10Y|DR&UXVbQQvEBi^Ul!>5
zAN?YqSr8Ko>b&sRs$NvOLhVci8t9?=5P?12Lu(af9vo
ztJRjZ5=N_H&`$BI6VUX+iFaQh*pz;kr*oODH(8#^42!Y%DRY~ymGf_6Swf70=m9Nt
z&7B514Rjj#_h|qMnIeKh%r5u1pS$|)?|tp^8-oA$aiRX6yzrW&l;Bag0=Aq`lHi~zIhqx?wch|Y&4z}q?It_Fh
K=rr&pHSj+Qtdi;g
literal 0
HcmV?d00001
diff --git a/controllers/testdata/podinfo/podinfo-6.1.5.tar b/controllers/testdata/podinfo/podinfo-6.1.5.tar
new file mode 100644
index 0000000000000000000000000000000000000000..335d6a5ad4fb20c1a81bf57d507be7c60dbeeae4
GIT binary patch
literal 14848
zcmeHNZExE+63*xR3POtmZh_h+^=b*ohrOhS2G?tXH0c6c6a__5Hm{PXrDS8>Apd=b
zdW-BjaZaleL*;n27l6Urv|GSr0Z_h8@JZ3K@7`wLJ#(47=JZH7ECjJC__7U1UCXX4_w*BPs
zC*$wp<<{H7?+@mIz4QNnh0J!0X-EDO)9h+PTmL;1>i>6{nLl~{-x?w^0%PB4V7xGx
z;X3S(F(~IYL9iVWC%h(v-W?4Si~9je``+sMt{2KPa>Wta+UI|9It4a9I!tf|3~(Jmg$o9{3o`B
zA^rpYJ75qVP(^d`j;lh*EI>5Ri^-B41D1&bbk@y>)q9qNE5z^=^K)D1Y7R%!z
z=S)*mkzH{bt=@9Lc2N-^3y6v@h1UE^K!s7O*CG{VfE;Mam7FPF6lWll03__hTnw#6
zsQ^jNR$4J=kqNNOZgQ}I*CgnV{&kblB^3z`6R<~%Aylq%c)scjXmvc{3=F7cQ(Cfl
zjlWuDFz)*iffk
z_n302z!`?``CiKcV*ylnuXV#N7)j%8t*t}Toxo^4Hc{8H(7-i7vpG|hc_oi&2G&G0
zn>2w8+%XyJ$kd-o;t~MxrA!1{1?W6}Bg;z&8I*^POAn#z9ig`Wd0FNiPtVZHYQ+B3
zpY~e8j8Zb01nBzovZMD^jlgM2u%c`HQYHAX*ZR21naWv@xpQM-Er+B9tArnwTn
zdA95iwJrdc7iZ^h{(bTB=KSO7#p{c!
zP8mW=nk@9}|HZN$&tvWEMkoH#6b6xU*lTWSgRL*r
z!yjd6+M3nDMpnp$V!W551itEE_kt3
z37i&h*Wucr;(>K+Ab*_)?(yF|r-uf9jY&K3A7I0c^M7Kx-f;f+bpd^*)EfRDAR;m15oe^plVI9HKE1}y01|S33*+`s?r1~rYdQ*F>Mb}zp7R!^Od!2
zYl|vxN|A`q74U2L5c&mG!$XT4i5M*kfLTZsh=y{>drZFn4V
z?&o4V1NgW;pdWvlR{NPH(Y2u67yhR?FRD|aO{NMBj8MCXz?>eTjT426*1*?lejR_?
zsr_{h`N^cZm{0nVIjj?OUO#gFgKgq%F1=Ovu;@uw*Y}Xn
zl{Lhum>%%bKps3Wcwq3r|DOk#J+-)R5{F`JhB$_MpfT}dY)6cS6f^i@?pdbgnV#j+
zID$8GoME^nYVs+Mr{n$a|6t46c>iM(><;+)S9b(p+1@uZjk~)c9iI@Jfo1T(;DNyd-_iqr1CU#khyVZp
literal 0
HcmV?d00001
diff --git a/controllers/testdata/podinfo/podinfo-6.1.6.tar b/controllers/testdata/podinfo/podinfo-6.1.6.tar
new file mode 100644
index 0000000000000000000000000000000000000000..09616c2dfabc3ca74b251763d2de655d892e2629
GIT binary patch
literal 14848
zcmeHNZExE)5YFfP3PQzz4XADE{RPN}E$Pr;SskQF2dpRpTB2;OlBiL%Q8&nc-%)Ro
zT_;XzHz-yK0U{soj>kLR@gYSo@;vIhB-F9%IuUkE98Mw&`_#r5$2z7_>>8o1o4V&<
z2UFK`V$U>VOfBM?=ICJigRzBO+dcq=>RJEarB&~`YwN}VvbU?=onu}|BEY0wH7U#C
zX$QBfzGss6c8&kLR~K(j$8R1o7wZ^1mesnZ@>J_8_-Qt6l?x4Q+xP(O9FvEPYTADA
z`0K-G@p9ws{{MI8ft~aJpMqyw#b;D?DLzDj&hWh_3GxG<}{~L`(TA=MZ4YZe9
zC@7IsDT5&DgYxeZQU-2J{PsHj?g
zm6C+Bw=!ax7Q`ee=u54o@wxnxNCk-2oR@OhzK?U7t?DU%jk6GpsQF)I3oXdzN%B%V
zPGLmV>uE}+wW05LGEZsIwEV@zN0v=bSZ(dAOp0aBSq64hvg7QxIZIkqXJkT?TKu;3
zOz)4@AGce|N#DFj5A2%wql7G}7%B{X@AGi|*Bz)AYxqw$%^v^V>(6~Q3`ZYf1>`CF
zO!ujFMVDICpSJ&l-KO2||5lv%jefMzA4N1z_%fxL94<+k?6E%;{|EMers3$T`LA0h
zhWPjRZ;wH^M-|E0J1PpsvjCAiFGdUf5U@-Xpp$k!9Hvx~h)5C~BBan+fN~BY191N|6
z6o4eBOQjgJ@B~<cWN*zsT2nJNLDUso<
z!e5Ot828CTcro3b+PnItxWC?};B%g^a2cS}_zjom5HctaEteWX+dD!{|1&A`mZxLrc{yU=^Sw?h
zm?3${lK@>FpSSd`ssT7nu&`(wzf=i6?6f|taw_t$!`!;Dw$AtcPU~h>Ax~2RDbg|y
z(6Di2hc*}}2OX^pcz`NuGpHjK{}mZ!UoF_RMG2Nouq9fskPcKH9yMvGCWLwvS3(yw
zIV!WWB35Jp7V~rvQ2=a5
z(eUFMZEX%JwuFZynZbTDP!So;C`sh(b4QM4Ofr(3C=ay-NmZ-nku0UK*WAzsTVJS$
z-^C=wKpit8kN7$oKOsTw}akY-D^jTw0Rb#)vor<`M
zH*2Js11ceaG7YFT00l1qs#XL`5h}c(`WiHlkXBW!EKPu7%92(Y)ARuKt84}QS6bV&
zHmLGSvV?sufnUvs&jjn951jvCOLy1u-?W|n{Aa&GxJO$z|J$RVx78l=5zBjZ;&Plo
zO`zgbZQxf=Th@*+IwuC*Eq=WMs#aL>?hXhxZNID4x!SF_yF9fsEM@Pu%w4|Do_~|d
z3SyK@_jsu%_a5jy(0kzj=K<=5wxMG`_NWQBE?tLso)<^9t4ATGcw)f?P>*;x@=dH8
z*t1-V7{tfU1QR0)!{P4tf3RtEF&xA`CXY93*iL=;{s-LtUC)1AcX5CIx81RO;D8K1u5aUMdsz!0o8dT)Uy^B=
z%^N>DHj}PszM55^A2p~IXX-jpDY?I!Nl*$t^hXVwCyRhs6%4+PGg@rs*qyp9{~_Dg
y_W!zL_xZoO;~$~nMtfX_s#UwZBLK_xu9>Ob-uCJEh}iTjy$5;^^d9(@9{3LiHH=>X
literal 0
HcmV?d00001
From ded0c2d78b6d72f4a6a8a43fc18edce4a2a023d2 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 5 Jul 2022 15:27:45 +0300
Subject: [PATCH 05/25] Add `oci://` prefix
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 4 +++
...rce.toolkit.fluxcd.io_ocirepositories.yaml | 1 +
.../samples/source_v1beta2_ocirepository.yaml | 2 +-
controllers/ocirepository_controller.go | 29 ++++++++++++++++---
controllers/ocirepository_controller_test.go | 18 +++++++-----
5 files changed, 41 insertions(+), 13 deletions(-)
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index bc6d830ba..eac3f1c40 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -25,12 +25,16 @@ import (
const (
// OCIRepositoryKind is the string representation of a OCIRepository.
OCIRepositoryKind = "OCIRepository"
+
+ // OCIRepositoryPrefix is the prefix used for OCIRepository URLs.
+ OCIRepositoryPrefix = "oci://"
)
// OCIRepositorySpec defines the desired state of OCIRepository
type OCIRepositorySpec struct {
// URL is a reference to an OCI artifact repository hosted
// on a remote container registry.
+ // +kubebuilder:validation:Pattern="^oci://"
// +required
URL string `json:"url"`
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
index 7a163165a..7ce115037 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
@@ -120,6 +120,7 @@ spec:
url:
description: URL is a reference to an OCI artifact repository hosted
on a remote container registry.
+ pattern: ^oci://
type: string
verify:
description: Verification specifies the configuration to verify the
diff --git a/config/samples/source_v1beta2_ocirepository.yaml b/config/samples/source_v1beta2_ocirepository.yaml
index 2fbdf9969..e06241b97 100644
--- a/config/samples/source_v1beta2_ocirepository.yaml
+++ b/config/samples/source_v1beta2_ocirepository.yaml
@@ -4,6 +4,6 @@ metadata:
name: ocirepository-sample
spec:
interval: 1m
- url: ghcr.io/stefanprodan/manifests/podinfo
+ url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
tag: 6.1.6
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 0e441f8a5..16e40a90a 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -20,8 +20,10 @@ import (
"context"
"errors"
"fmt"
+ "github.com/google/go-containerregistry/pkg/name"
"os"
"sort"
+ "strings"
"time"
"github.com/Masterminds/semver/v3"
@@ -363,12 +365,31 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultSuccess, nil
}
+// parseRepositoryURL extracts the repository URL.
+func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository) (string, error) {
+ if !strings.HasPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix) {
+ return "", fmt.Errorf("URL must be in format 'oci:////'")
+ }
+
+ url := strings.TrimPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix)
+ ref, err := name.ParseReference(url)
+ if err != nil {
+ return "", fmt.Errorf("'%s' invalid URL: %w", obj.Spec.URL, err)
+ }
+
+ return ref.Context().Name(), nil
+}
+
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain) (string, error) {
- url := obj.Spec.URL
+ url, err := r.parseRepositoryURL(obj)
+ if err != nil {
+ return "", err
+ }
+
if obj.Spec.Reference != nil {
if obj.Spec.Reference.Digest != "" {
- return fmt.Sprintf("%s@%s", obj.Spec.URL, obj.Spec.Reference.Digest), nil
+ return fmt.Sprintf("%s@%s", url, obj.Spec.Reference.Digest), nil
}
if obj.Spec.Reference.SemVer != "" {
@@ -376,11 +397,11 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
if err != nil {
return "", err
}
- return fmt.Sprintf("%s:%s", obj.Spec.URL, tag), nil
+ return fmt.Sprintf("%s:%s", url, tag), nil
}
if obj.Spec.Reference.Tag != "" {
- return fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag), nil
+ return fmt.Sprintf("%s:%s", url, obj.Spec.Reference.Tag), nil
}
}
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index bcae3ad1d..5963702f0 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -239,6 +239,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
+ ociURL := fmt.Sprintf("oci://%s", repositoryURL)
// Push Test Image
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
@@ -260,14 +261,14 @@ func TestOCIRepository_SecretRef(t *testing.T) {
}{
{
name: "private-registry-access-via-secretref",
- url: repositoryURL,
+ url: ociURL,
digest: podinfoImageDigest,
includeSecretRef: true,
includeServiceAccount: false,
},
{
name: "private-registry-access-via-serviceaccount",
- url: repositoryURL,
+ url: ociURL,
digest: podinfoImageDigest,
includeSecretRef: false,
includeServiceAccount: true,
@@ -289,7 +290,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
},
Type: corev1.SecretTypeDockerConfigJson,
StringData: map[string]string{
- ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, testRegistryUsername, testRegistryPassword),
+ ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, testRegistryUsername, testRegistryPassword),
},
}
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
@@ -435,6 +436,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
+ ociURL := fmt.Sprintf("oci://%s", repositoryURL)
// Push Test Image
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
@@ -458,7 +460,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
}{
{
name: "missing-auth",
- url: repositoryURL,
+ url: ociURL,
repoUsername: "",
repoPassword: "",
digest: podinfoImageDigest,
@@ -467,7 +469,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
{
name: "invalid-auth-via-secret",
- url: repositoryURL,
+ url: ociURL,
repoUsername: "InvalidUser",
repoPassword: "InvalidPassword",
digest: podinfoImageDigest,
@@ -476,7 +478,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
{
name: "invalid-auth-via-service-account",
- url: repositoryURL,
+ url: ociURL,
repoUsername: "InvalidUser",
repoPassword: "InvalidPassword",
digest: podinfoImageDigest,
@@ -500,7 +502,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
Type: corev1.SecretTypeDockerConfigJson,
StringData: map[string]string{
- ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, tt.repoUsername, tt.repoPassword),
+ ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, tt.repoUsername, tt.repoPassword),
},
}
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
@@ -623,7 +625,7 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
}
return &podinfoImage{
- url: repositoryURL,
+ url: "oci://" + repositoryURL,
tag: tag,
digest: podinfoImageDigest,
}, nil
From 4506acb9d6ff3f57f1b60145652fd596cf8019c0 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Wed, 6 Jul 2022 17:24:12 +0300
Subject: [PATCH 06/25] Use the internal pkg to handle errors
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 77 +++++++++++++------------
1 file changed, 40 insertions(+), 37 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 16e40a90a..4c6cc170d 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -20,7 +20,6 @@ import (
"context"
"errors"
"fmt"
- "github.com/google/go-containerregistry/pkg/name"
"os"
"sort"
"strings"
@@ -30,6 +29,7 @@ import (
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
"github.com/google/go-containerregistry/pkg/crane"
+ "github.com/google/go-containerregistry/pkg/name"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -58,6 +58,7 @@ import (
serror "github.com/fluxcd/source-controller/internal/error"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
+ "github.com/fluxcd/source-controller/internal/util"
)
// ociRepositoryReadyCondition contains the information required to summarize a
@@ -234,12 +235,12 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.O
}
// Create temp working dir
- tmpDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s-%s-", obj.Kind, obj.Namespace, obj.Name))
+ tmpDir, err := util.TempDirForObj("", obj)
if err != nil {
- e := &serror.Event{
- Err: fmt.Errorf("failed to create temporary working directory: %w", err),
- Reason: sourcev1.DirCreationFailedReason,
- }
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to create temporary working directory: %w", err),
+ sourcev1.DirCreationFailedReason,
+ )
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
@@ -289,7 +290,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Generates registry credential keychain
keychain, err := r.keychain(ctx, obj)
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
@@ -297,7 +298,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Determine which artifact revision to pull
url, err := r.getArtifactURL(ctxTimeout, obj, keychain)
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
@@ -305,7 +306,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Pull artifact from the remote container registry
img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain)...)
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
@@ -313,7 +314,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Determine the artifact SHA256 digest
imgDigest, err := img.Digest()
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
@@ -335,27 +336,27 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if !obj.GetArtifact().HasRevision(revision) {
layers, err := img.Layers()
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
if len(layers) < 1 {
err = fmt.Errorf("no layers found in artifact")
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
blob, err := layers[0].Compressed()
if err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
if _, err = untar.Untar(blob, dir); err != nil {
- e := &serror.Event{Err: err, Reason: sourcev1.OCIOperationFailedReason}
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
return sreconcile.ResultEmpty, e
}
@@ -497,8 +498,9 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain aut
// reconcileStorage ensures the current state of the storage matches the
// desired and previously observed state.
//
-// All Artifacts for the object except for the current one in the Status are
-// garbage collected from the Storage.
+// The garbage collection is executed based on the flag configured settings and
+// may remove files that are beyond their TTL or the maximum number of files
+// to survive a collection cycle.
// If the Artifact in the Status of the object disappeared from the Storage,
// it is removed from the object.
// If the object does not have an Artifact in its Status, a Reconciling
@@ -558,51 +560,52 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
// The artifact is up-to-date
if obj.GetArtifact().HasRevision(artifact.Revision) {
- r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
+ r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason,
+ "artifact up-to-date with remote revision: '%s'", artifact.Revision)
return sreconcile.ResultSuccess, nil
}
// Ensure target path exists and is a directory
if f, err := os.Stat(dir); err != nil {
- e := &serror.Event{
- Err: fmt.Errorf("failed to stat source path: %w", err),
- Reason: sourcev1.StatOperationFailedReason,
- }
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to stat source path: %w", err),
+ sourcev1.StatOperationFailedReason,
+ )
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
} else if !f.IsDir() {
- e := &serror.Event{
- Err: fmt.Errorf("source path '%s' is not a directory", dir),
- Reason: sourcev1.InvalidPathReason,
- }
+ e := serror.NewGeneric(
+ fmt.Errorf("source path '%s' is not a directory", dir),
+ sourcev1.InvalidPathReason,
+ )
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
// Ensure artifact directory exists and acquire lock
if err := r.Storage.MkdirAll(artifact); err != nil {
- e := &serror.Event{
- Err: fmt.Errorf("failed to create artifact directory: %w", err),
- Reason: sourcev1.DirCreationFailedReason,
- }
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to create artifact directory: %w", err),
+ sourcev1.DirCreationFailedReason,
+ )
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
unlock, err := r.Storage.Lock(artifact)
if err != nil {
- return sreconcile.ResultEmpty, &serror.Event{
- Err: fmt.Errorf("failed to acquire lock for artifact: %w", err),
- Reason: meta.FailedReason,
- }
+ return sreconcile.ResultEmpty, serror.NewGeneric(
+ fmt.Errorf("failed to acquire lock for artifact: %w", err),
+ meta.FailedReason,
+ )
}
defer unlock()
// Archive directory to storage
if err := r.Storage.Archive(&artifact, dir, nil); err != nil {
- e := &serror.Event{
- Err: fmt.Errorf("unable to archive artifact to storage: %s", err),
- Reason: sourcev1.ArchiveOperationFailedReason,
- }
+ e := serror.NewGeneric(
+ fmt.Errorf("unable to archive artifact to storage: %s", err),
+ sourcev1.ArchiveOperationFailedReason,
+ )
conditions.MarkTrue(obj, sourcev1.StorageOperationFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
From 942d92834b7b925a4ad285e6914ca81bdb1d37b1 Mon Sep 17 00:00:00 2001
From: Rashed Kamal
Date: Thu, 7 Jul 2022 17:33:40 -0400
Subject: [PATCH 07/25] OCIRepository client cert auth
Signed-off-by: Rashed Kamal
---
controllers/ocirepository_controller.go | 83 +++++-
controllers/ocirepository_controller_test.go | 292 ++++++++++++++++++-
2 files changed, 362 insertions(+), 13 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 4c6cc170d..63fac7964 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -18,8 +18,11 @@ package controllers
import (
"context"
+ "crypto/tls"
+ "crypto/x509"
"errors"
"fmt"
+ "net/http"
"os"
"sort"
"strings"
@@ -31,6 +34,7 @@ import (
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
+ "github.com/google/go-containerregistry/pkg/v1/remote"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@@ -61,6 +65,12 @@ import (
"github.com/fluxcd/source-controller/internal/util"
)
+const (
+ ClientCert = "certFile"
+ ClientKey = "keyFile"
+ CACert = "caFile"
+)
+
// ociRepositoryReadyCondition contains the information required to summarize a
// v1beta2.OCIRepository Ready Condition.
var ociRepositoryReadyCondition = summarize.Conditions{
@@ -295,8 +305,16 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultEmpty, e
}
+ // Generates transport for remote operations
+ transport, err := r.transport(ctx, obj)
+ if err != nil {
+ e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
// Determine which artifact revision to pull
- url, err := r.getArtifactURL(ctxTimeout, obj, keychain)
+ url, err := r.getArtifactURL(ctxTimeout, obj, keychain, transport)
if err != nil {
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -304,7 +322,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}
// Pull artifact from the remote container registry
- img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain)...)
+ img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain, transport)...)
if err != nil {
e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
@@ -382,7 +400,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
}
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
-func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain) (string, error) {
+func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
url, err := r.parseRepositoryURL(obj)
if err != nil {
return "", err
@@ -394,7 +412,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
}
if obj.Spec.Reference.SemVer != "" {
- tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer, keychain)
+ tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer, keychain, transport)
if err != nil {
return "", err
}
@@ -411,8 +429,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
// and returns the latest tag according to the semver expression.
-func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string, keychain authn.Keychain) (string, error) {
- tags, err := crane.ListTags(url, r.craneOptions(ctx, keychain)...)
+func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
+ tags, err := crane.ListTags(url, r.craneOptions(ctx, keychain, transport)...)
if err != nil {
return "", err
}
@@ -486,13 +504,62 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
return k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
}
+// transport clones the default transport from remote.
+// If certSecretRef is configured in the resource configuration,
+// returned transport will iclude client and/or CA certifactes
+func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.OCIRepository) (http.RoundTripper, error) {
+ if obj.Spec.CertSecretRef != nil {
+ var certSecret corev1.Secret
+ err := r.Get(ctx,
+ types.NamespacedName{Namespace: obj.Namespace, Name: obj.Spec.CertSecretRef.Name},
+ &certSecret)
+
+ if err != nil {
+ r.eventLogf(ctx, obj, events.EventSeverityTrace, "secret %q not found", obj.Spec.CertSecretRef.Name)
+ return nil, err
+ }
+
+ transport := remote.DefaultTransport.Clone()
+ tlsConfig := transport.TLSClientConfig
+
+ if clientCert, ok := certSecret.Data[ClientCert]; ok {
+ // parse and set client cert and secret
+ if clientKey, ok := certSecret.Data[ClientKey]; ok {
+ cert, err := tls.X509KeyPair(clientCert, clientKey)
+ if err != nil {
+ return nil, err
+ }
+ tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
+ } else {
+ return nil, fmt.Errorf("client certificate found, but no key")
+ }
+ }
+ if caCert, ok := certSecret.Data[CACert]; ok {
+ syscerts, err := x509.SystemCertPool()
+ if err != nil {
+ return nil, err
+ }
+ syscerts.AppendCertsFromPEM(caCert)
+ tlsConfig.RootCAs = syscerts
+ }
+ return transport, nil
+ }
+ return nil, nil
+}
+
// craneOptions sets the timeout and user agent for all operations against remote container registries.
-func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain authn.Keychain) []crane.Option {
- return []crane.Option{
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain authn.Keychain, transport http.RoundTripper) []crane.Option {
+ options := []crane.Option{
crane.WithContext(ctx),
crane.WithUserAgent("flux/v2"),
crane.WithAuthFromKeychain(keychain),
}
+
+ if transport != nil {
+ options = append(options, crane.WithTransport(transport))
+ }
+
+ return options
}
// reconcileStorage ensures the current state of the storage matches the
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 5963702f0..1563b1739 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -16,7 +16,17 @@ limitations under the License.
package controllers
import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "errors"
"fmt"
+ "math/big"
+ "net"
+ "net/http"
"net/http/httptest"
"net/url"
"os"
@@ -138,7 +148,7 @@ func TestOCIRepository_Reconcile(t *testing.T) {
return false
}
return len(obj.Finalizers) > 0
- }, timeout).Should(BeFalse())
+ }, timeout).Should(BeTrue())
// Wait for the object to be Ready
g.Eventually(func() bool {
@@ -336,7 +346,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
return false
}
return len(obj.Finalizers) > 0
- }, timeout).Should(BeFalse())
+ }, timeout).Should(BeTrue())
// Wait for the object to be Ready
g.Eventually(func() bool {
@@ -582,6 +592,167 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
}
}
+func TestOCIRepository_CertSecret(t *testing.T) {
+ g := NewWithT(t)
+
+ registryServer, err := registry.TLS("localhost")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer registryServer.Close()
+
+ pi, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", registryServer)
+ g.Expect(err).ToNot(HaveOccurred())
+
+ ca_cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: registryServer.Certificate().Raw})
+ t.Logf("certdata: %v", string(ca_cert))
+
+ tlsSecretCACert := corev1.Secret{
+ StringData: map[string]string{
+ CACert: string(ca_cert),
+ },
+ }
+
+ srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err := createTLSServer()
+ g.Expect(err).ToNot(HaveOccurred())
+
+ srv.StartTLS()
+ defer srv.Close()
+
+ transport := &http.Transport{
+ TLSClientConfig: &tls.Config{},
+ }
+ // Use the server cert as a CA cert, so the client trusts the
+ // server cert. (Only works because the server uses the same
+ // cert in both roles).
+ pool := x509.NewCertPool()
+ pool.AddCert(srv.Certificate())
+ transport.TLSClientConfig.RootCAs = pool
+ transport.TLSClientConfig.Certificates = []tls.Certificate{clientTLSCert}
+
+ srv.Client().Transport = transport
+ pi2, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv)
+ g.Expect(err).NotTo(HaveOccurred())
+
+ tlsSecretClientCert := corev1.Secret{
+ StringData: map[string]string{
+ CACert: string(rootCertPEM),
+ ClientCert: string(clientCertPEM),
+ ClientKey: string(clientKeyPEM),
+ },
+ }
+
+ tests := []struct {
+ name string
+ url string
+ tag string
+ digest v1.Hash
+ certSecret *corev1.Secret
+ expectreadyconition bool
+ expectedstatusmessage string
+ }{
+ {
+ name: "test connection without CACert",
+ url: pi.url,
+ tag: pi.tag,
+ digest: pi.digest,
+ certSecret: nil,
+ expectreadyconition: false,
+ expectedstatusmessage: "unexpected status code 400 Bad Request: Client sent an HTTP request to an HTTPS server.",
+ },
+ {
+ name: "test connection with CACert",
+ url: pi.url,
+ tag: pi.tag,
+ digest: pi.digest,
+ certSecret: &tlsSecretCACert,
+ expectreadyconition: true,
+ expectedstatusmessage: fmt.Sprintf("stored artifact for revision '%s'", pi.digest.Hex),
+ },
+ {
+ name: "test connection with CACert, Client Cert and Private Key",
+ url: pi2.url,
+ tag: pi2.tag,
+ digest: pi2.digest,
+ certSecret: &tlsSecretClientCert,
+ expectreadyconition: true,
+ expectedstatusmessage: fmt.Sprintf("stored artifact for revision '%s'", pi2.digest.Hex),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-test-resource",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ },
+ }
+
+ if tt.certSecret != nil {
+ tt.certSecret.ObjectMeta = metav1.ObjectMeta{
+ GenerateName: "cert-secretref",
+ Namespace: ns.Name,
+ }
+
+ g.Expect(testEnv.CreateAndWait(ctx, tt.certSecret)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, tt.certSecret)).To(Succeed()) }()
+
+ obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: tt.certSecret.Name}
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ resultobj := sourcev1.OCIRepository{}
+
+ // Wait for the finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return false
+ }
+ return len(resultobj.Finalizers) > 0
+ }, timeout).Should(BeTrue())
+
+ // Wait for the object to fail
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return false
+ }
+ readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
+ if readyCondition == nil {
+ return false
+ }
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ conditions.IsReady(&resultobj) == tt.expectreadyconition
+ }, timeout).Should(BeTrue())
+
+ readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
+ g.Expect(readyCondition.Message).Should(ContainSubstring(tt.expectedstatusmessage))
+
+ // Wait for the object to be deleted
+ g.Expect(testEnv.Delete(ctx, &resultobj)).To(Succeed())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+ })
+ }
+
+}
+
type artifactFixture struct {
expectedPath string
expectedChecksum string
@@ -593,7 +764,6 @@ type podinfoImage struct {
}
func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Server) (*podinfoImage, error) {
-
// Create Image
image, err := crane.Load(path.Join("testdata", "podinfo", tarFileName))
if err != nil {
@@ -613,13 +783,14 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
}
// Push image
- err = crane.Push(image, repositoryURL)
+ err = crane.Push(image, repositoryURL, crane.WithTransport(imageServer.Client().Transport))
+
if err != nil {
return nil, err
}
// Tag the image
- err = crane.Tag(repositoryURL, tag)
+ err = crane.Tag(repositoryURL, tag, crane.WithTransport(imageServer.Client().Transport))
if err != nil {
return nil, err
}
@@ -630,3 +801,114 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
digest: podinfoImageDigest,
}, nil
}
+
+// These two taken verbatim from https://ericchiang.github.io/post/go-tls/
+
+func certTemplate() (*x509.Certificate, error) {
+ // generate a random serial number (a real cert authority would
+ // have some logic behind this)
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ return nil, errors.New("failed to generate serial number: " + err.Error())
+ }
+
+ tmpl := x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{Organization: []string{"Flux project"}},
+ SignatureAlgorithm: x509.SHA256WithRSA,
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(time.Hour), // valid for an hour
+ BasicConstraintsValid: true,
+ }
+ return &tmpl, nil
+}
+
+func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (
+ cert *x509.Certificate, certPEM []byte, err error) {
+
+ certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
+ if err != nil {
+ return
+ }
+ // parse the resulting certificate so we can use it again
+ cert, err = x509.ParseCertificate(certDER)
+ if err != nil {
+ return
+ }
+ // PEM encode the certificate (this is a standard TLS encoding)
+ b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
+ certPEM = pem.EncodeToMemory(&b)
+ return
+}
+
+// ----
+
+func createTLSServer() (*httptest.Server, []byte, []byte, []byte, tls.Certificate, error) {
+ var clientTLSCert tls.Certificate
+ var rootCertPEM, clientCertPEM, clientKeyPEM []byte
+
+ srv := httptest.NewUnstartedServer(registry.New())
+
+ // Create a self-signed cert to use as the CA and server cert.
+ rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+ rootCertTmpl, err := certTemplate()
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+ rootCertTmpl.IsCA = true
+ rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
+ rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
+ rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
+ var rootCert *x509.Certificate
+ rootCert, rootCertPEM, err = createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+
+ rootKeyPEM := pem.EncodeToMemory(&pem.Block{
+ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey),
+ })
+
+ // Create a TLS cert using the private key and certificate.
+ rootTLSCert, err := tls.X509KeyPair(rootCertPEM, rootKeyPEM)
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+
+ // To trust a client certificate, the server must be given a
+ // CA cert pool.
+ pool := x509.NewCertPool()
+ pool.AddCert(rootCert)
+
+ srv.TLS = &tls.Config{
+ ClientAuth: tls.RequireAndVerifyClientCert,
+ Certificates: []tls.Certificate{rootTLSCert},
+ ClientCAs: pool,
+ }
+
+ // Create a client cert, signed by the "CA".
+ clientKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+ clientCertTmpl, err := certTemplate()
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+ clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
+ clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
+ _, clientCertPEM, err = createCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
+ if err != nil {
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+ }
+ // Encode and load the cert and private key for the client.
+ clientKeyPEM = pem.EncodeToMemory(&pem.Block{
+ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
+ })
+ clientTLSCert, err = tls.X509KeyPair(clientCertPEM, clientKeyPEM)
+ return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
+}
From 9a6ff19487afdde154238cfba830cefae13fa3b2 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Fri, 8 Jul 2022 14:06:50 +0300
Subject: [PATCH 08/25] Normalise error messages
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 169 ++++++++++++++----------
1 file changed, 102 insertions(+), 67 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 63fac7964..c500a3da0 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -297,43 +297,57 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
defer cancel()
- // Generates registry credential keychain
+ // Generate the registry credential keychain
keychain, err := r.keychain(ctx, obj)
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to get credential: %w", err),
+ sourcev1.AuthenticationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
- // Generates transport for remote operations
+ // Generate the transport for remote operations
transport, err := r.transport(ctx, obj)
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to generate transport for '%s': %w", obj.Spec.URL, err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
// Determine which artifact revision to pull
url, err := r.getArtifactURL(ctxTimeout, obj, keychain, transport)
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err),
+ sourcev1.URLInvalidReason)
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
// Pull artifact from the remote container registry
img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain, transport)...)
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
// Determine the artifact SHA256 digest
imgDigest, err := img.Digest()
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to determine artifact digest: %w", err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
@@ -344,7 +358,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Mark observations about the revision on the object
defer func() {
if !obj.GetArtifact().HasRevision(revision) {
- message := fmt.Sprintf("new upstream revision '%s' for '%s'", revision, url)
+ message := fmt.Sprintf("new digest '%s' for '%s'", revision, url)
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", message)
conditions.MarkReconciling(obj, "NewRevision", message)
}
@@ -354,28 +368,39 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if !obj.GetArtifact().HasRevision(revision) {
layers, err := img.Layers()
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to parse artifact layers: %w", err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
if len(layers) < 1 {
- err = fmt.Errorf("no layers found in artifact")
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("no layers found in artifact"),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
blob, err := layers[0].Compressed()
if err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to extract the first layer from artifact: %w", err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
if _, err = untar.Untar(blob, dir); err != nil {
- e := serror.NewGeneric(err, sourcev1.OCIOperationFailedReason)
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Error())
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to untar the first layer from artifact: %w", err),
+ sourcev1.OCIOperationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
}
@@ -384,7 +409,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultSuccess, nil
}
-// parseRepositoryURL extracts the repository URL.
+// parseRepositoryURL validates and extracts the repository URL.
func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository) (string, error) {
if !strings.HasPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix) {
return "", fmt.Errorf("URL must be in format 'oci:////'")
@@ -393,14 +418,15 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
url := strings.TrimPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix)
ref, err := name.ParseReference(url)
if err != nil {
- return "", fmt.Errorf("'%s' invalid URL: %w", obj.Spec.URL, err)
+ return "", err
}
return ref.Context().Name(), nil
}
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
-func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
+func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
+ obj *sourcev1.OCIRepository, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
url, err := r.parseRepositoryURL(obj)
if err != nil {
return "", err
@@ -429,7 +455,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
// and returns the latest tag according to the semver expression.
-func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context, url, exp string, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
+func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context,
+ url, exp string, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
tags, err := crane.ListTags(url, r.craneOptions(ctx, keychain, transport)...)
if err != nil {
return "", err
@@ -495,7 +522,8 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
imagePullSecret := corev1.Secret{}
err := r.Get(ctx, types.NamespacedName{Namespace: obj.Namespace, Name: imagePullSecretName}, &imagePullSecret)
if err != nil {
- r.eventLogf(ctx, obj, events.EventSeverityTrace, "secret %q not found", imagePullSecretName)
+ r.eventLogf(ctx, obj, events.EventSeverityTrace, sourcev1.AuthenticationFailedReason,
+ "auth secret '%s' not found", imagePullSecretName)
return nil, err
}
imagePullSecrets[i] = imagePullSecret
@@ -504,51 +532,54 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC
return k8schain.NewFromPullSecrets(ctx, imagePullSecrets)
}
-// transport clones the default transport from remote.
-// If certSecretRef is configured in the resource configuration,
-// returned transport will iclude client and/or CA certifactes
+// transport clones the default transport from remote and when a certSecretRef is specified,
+// the returned transport will include the TLS client and/or CA certificates.
func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.OCIRepository) (http.RoundTripper, error) {
- if obj.Spec.CertSecretRef != nil {
- var certSecret corev1.Secret
- err := r.Get(ctx,
- types.NamespacedName{Namespace: obj.Namespace, Name: obj.Spec.CertSecretRef.Name},
- &certSecret)
+ if obj.Spec.CertSecretRef == nil || obj.Spec.CertSecretRef.Name == "" {
+ return nil, nil
+ }
- if err != nil {
- r.eventLogf(ctx, obj, events.EventSeverityTrace, "secret %q not found", obj.Spec.CertSecretRef.Name)
- return nil, err
- }
+ certSecretName := types.NamespacedName{
+ Namespace: obj.Namespace,
+ Name: obj.Spec.CertSecretRef.Name,
+ }
+ var certSecret corev1.Secret
+ if err := r.Get(ctx, certSecretName, &certSecret); err != nil {
+ return nil, err
+ }
- transport := remote.DefaultTransport.Clone()
- tlsConfig := transport.TLSClientConfig
-
- if clientCert, ok := certSecret.Data[ClientCert]; ok {
- // parse and set client cert and secret
- if clientKey, ok := certSecret.Data[ClientKey]; ok {
- cert, err := tls.X509KeyPair(clientCert, clientKey)
- if err != nil {
- return nil, err
- }
- tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
- } else {
- return nil, fmt.Errorf("client certificate found, but no key")
- }
- }
- if caCert, ok := certSecret.Data[CACert]; ok {
- syscerts, err := x509.SystemCertPool()
+ transport := remote.DefaultTransport.Clone()
+ tlsConfig := transport.TLSClientConfig
+
+ if clientCert, ok := certSecret.Data[ClientCert]; ok {
+ // parse and set client cert and secret
+ if clientKey, ok := certSecret.Data[ClientKey]; ok {
+ cert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
- syscerts.AppendCertsFromPEM(caCert)
- tlsConfig.RootCAs = syscerts
+ tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
+ } else {
+ return nil, fmt.Errorf("'%s' found in secret, but no %s", ClientCert, ClientKey)
}
- return transport, nil
}
- return nil, nil
+
+ if caCert, ok := certSecret.Data[CACert]; ok {
+ syscerts, err := x509.SystemCertPool()
+ if err != nil {
+ return nil, err
+ }
+ syscerts.AppendCertsFromPEM(caCert)
+ tlsConfig.RootCAs = syscerts
+ }
+ return transport, nil
+
}
-// craneOptions sets the timeout and user agent for all operations against remote container registries.
-func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain authn.Keychain, transport http.RoundTripper) []crane.Option {
+// craneOptions sets the auth headers, timeout and user agent
+// for all operations against remote container registries.
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context,
+ keychain authn.Keychain, transport http.RoundTripper) []crane.Option {
options := []crane.Option{
crane.WithContext(ctx),
crane.WithUserAgent("flux/v2"),
@@ -574,7 +605,8 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, keychain aut
// condition is added.
// The hostname of any URL in the Status of the object are updated, to ensure
// they match the Storage server hostname of current runtime.
-func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.OCIRepository, _ *gcrv1.Hash, _ string) (sreconcile.Result, error) {
+func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
+ obj *sourcev1.OCIRepository, _ *gcrv1.Hash, _ string) (sreconcile.Result, error) {
// Garbage collect previous advertised artifact(s) from storage
_ = r.garbageCollect(ctx, obj)
@@ -609,7 +641,8 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sou
// early.
// On a successful archive, the Artifact in the Status of the object is set,
// and the symlink in the Storage is updated to its path.
-func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error) {
+func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context,
+ obj *sourcev1.OCIRepository, digest *gcrv1.Hash, dir string) (sreconcile.Result, error) {
// Calculate revision
revision := digest.Hex
@@ -628,7 +661,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
// The artifact is up-to-date
if obj.GetArtifact().HasRevision(artifact.Revision) {
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason,
- "artifact up-to-date with remote revision: '%s'", artifact.Revision)
+ "artifact up-to-date with remote digest: '%s'", artifact.Revision)
return sreconcile.ResultSuccess, nil
}
@@ -751,7 +784,8 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
// This log is different from the debug log in the EventRecorder, in the sense
// that this is a simple log. While the debug log contains complete details
// about the event.
-func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
+func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
+ obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
msg := fmt.Sprintf(messageFmt, args...)
// Log and emit event.
if eventType == corev1.EventTypeWarning {
@@ -763,7 +797,8 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Obj
}
// notify emits notification related to the reconciliation.
-func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, digest *gcrv1.Hash, res sreconcile.Result, resErr error) {
+func (r *OCIRepositoryReconciler) notify(ctx context.Context,
+ oldObj, newObj *sourcev1.OCIRepository, digest *gcrv1.Hash, res sreconcile.Result, resErr error) {
// Notify successful reconciliation for new artifact and recovery from any
// failure.
if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
From 4b0729203bff60d6b681fa04ff971828eb061b72 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Fri, 8 Jul 2022 15:44:48 +0300
Subject: [PATCH 09/25] Add OCIRepository API spec to docs
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 2 +-
controllers/ocirepository_controller_test.go | 4 +-
docs/spec/v1beta2/README.md | 1 +
docs/spec/v1beta2/ocirepositories.md | 633 +++++++++++++++++++
4 files changed, 637 insertions(+), 3 deletions(-)
create mode 100644 docs/spec/v1beta2/ocirepositories.md
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index c500a3da0..6cdd4d212 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -654,7 +654,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context,
if obj.GetArtifact().HasRevision(artifact.Revision) {
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
- "stored artifact for revision '%s'", artifact.Revision)
+ "stored artifact for digest '%s'", artifact.Revision)
}
}()
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 1563b1739..fab26b9e4 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -665,7 +665,7 @@ func TestOCIRepository_CertSecret(t *testing.T) {
digest: pi.digest,
certSecret: &tlsSecretCACert,
expectreadyconition: true,
- expectedstatusmessage: fmt.Sprintf("stored artifact for revision '%s'", pi.digest.Hex),
+ expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi.digest.Hex),
},
{
name: "test connection with CACert, Client Cert and Private Key",
@@ -674,7 +674,7 @@ func TestOCIRepository_CertSecret(t *testing.T) {
digest: pi2.digest,
certSecret: &tlsSecretClientCert,
expectreadyconition: true,
- expectedstatusmessage: fmt.Sprintf("stored artifact for revision '%s'", pi2.digest.Hex),
+ expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi2.digest.Hex),
},
}
diff --git a/docs/spec/v1beta2/README.md b/docs/spec/v1beta2/README.md
index 917848055..371015871 100644
--- a/docs/spec/v1beta2/README.md
+++ b/docs/spec/v1beta2/README.md
@@ -6,6 +6,7 @@ This is the v1beta2 API specification for defining the desired state sources of
* Source kinds:
+ [GitRepository](gitrepositories.md)
+ + [OCIRepository](ocirepositories.md)
+ [HelmRepository](helmrepositories.md)
+ [HelmChart](helmcharts.md)
+ [Bucket](buckets.md)
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
new file mode 100644
index 000000000..3b394dfa8
--- /dev/null
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -0,0 +1,633 @@
+# OCI Repositories
+
+The `OCIRepository` API defines a Source to produce an Artifact for an OCI
+repository.
+
+## Example
+
+The following is an example of a OCIRepository. It creates a tarball
+(`.tar.gz`) Artifact with the fetched data from an OCI repository for the
+resolved digest.
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name: podinfo
+ namespace: default
+spec:
+ interval: 5m0s
+ url: oci://ghcr.io/stefanprodan/manifests/podinfo
+ ref:
+ tag: latest
+```
+
+In the above example:
+
+- A OCIRepository named `podinfo` is created, indicated by the
+ `.metadata.name` field.
+- The source-controller checks the OCI repository every five minutes, indicated
+ by the `.spec.interval` field.
+- It pulls the `latest` tag of the `ghcr.io/stefanprodan/manifests/podinfo`
+ repository, indicated by the `.spec.ref.tag` and `.spec.url` fields.
+- The specified tag and resolved digest are used as the Artifact
+ revision, reported in-cluster in the `.status.artifact.revision` field.
+- When the current OCIRepository digest differs from the latest fetched
+ digest, a new Artifact is archived.
+- The new Artifact is reported in the `.status.artifact` field.
+
+You can run this example by saving the manifest into `ocirepository.yaml`.
+
+1. Apply the resource on the cluster:
+
+ ```sh
+ kubectl apply -f ocirepository.yaml
+ ```
+
+2. Run `kubectl get ocirepository` to see the OCIRepository:
+
+ ```console
+ NAME URL AGE READY STATUS
+ podinfo oci://ghcr.io/stefanprodan/manifests/podinfo 5s True stored artifact for revision '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+ ```
+
+3. Run `kubectl describe ocirepository podinfo` to see the [Artifact](#artifact)
+ and [Conditions](#conditions) in the OCIRepository's Status:
+
+ ```console
+ ...
+ Status:
+ Artifact:
+ Checksum: d7e924b4882e55b97627355c7b3d2e711e9b54303afa2f50c25377f4df66a83b
+ Last Update Time: 2022-06-14T11:23:36Z
+ Path: ocirepository/default/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.gz
+ Revision: 3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de
+ URL: http://source-controller.flux-system.svc.cluster.local./ocirepository/oci/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.g
+ Conditions:
+ Last Transition Time: 2022-06-14T11:23:36Z
+ Message: stored artifact for digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+ Observed Generation: 1
+ Reason: Succeeded
+ Status: True
+ Type: Ready
+ Last Transition Time: 2022-06-14T11:23:36Z
+ Message: stored artifact for digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+ Observed Generation: 1
+ Reason: Succeeded
+ Status: True
+ Type: ArtifactInStorage
+ Observed Generation: 1
+ URL: http://source-controller.source-system.svc.cluster.local./gitrepository/default/podinfo/latest.tar.gz
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal NewArtifact 62s source-controller stored artifact with digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de' from 'oci://ghcr.io/stefanprodan/manifests/podinfo'
+ ```
+
+## Writing an OCIRepository spec
+
+As with all other Kubernetes config, a OCIRepository needs `apiVersion`,
+`kind`, and `metadata` fields. The name of a OCIRepository object must be a
+valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names#dns-subdomain-names).
+
+A OCIRepository also needs a
+[`.spec` section](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status).
+
+### URL
+
+`.spec.url` is a required field that specifies the address of the
+container image repository in the format `oci://://`.
+
+**Note:** that specifying a tag or digest is not in accepted for this field.
+
+### Secret reference
+
+`.spec.secretRef.name` is an optional field to specify a name reference to a
+Secret in the same namespace as the OCIRepository, containing authentication
+credentials for the OCI repository.
+
+This secret is expected to be in the same format as for[`imagePullSecrets`][image-pull-secrets].
+The usual way to create such a secret is with:
+
+```sh
+kubectl create secret docker-registry ...
+```
+
+### Service Account reference
+
+`.spec.serviceAccountName` is an optional field to specify a name reference to a
+Service Account in the same namespace as the OCIRepository. The controller will
+fetch the image pull secrets attached to the service account and use them for authentication.
+
+**Note:** that for a publicly accessible image repository, you don't need to provide a `secretRef`
+nor `serviceAccountName`.
+
+### TLS Certificates
+
+`.spec.certSecretRef` field names a secret with TLS certificate data. This is for two separate
+purposes:
+
+- to provide a client certificate and private key, if you use a certificate to authenticate with
+ the container registry; and,
+- to provide a CA certificate, if the registry uses a self-signed certificate.
+
+These will often go together, if you are hosting a container registry yourself. All the files in the
+secret are expected to be [PEM-encoded][pem-encoding]. This is an ASCII format for certificates and
+keys; `openssl` and such tools will typically give you an option of PEM output.
+
+Assuming you have obtained a certificate file and private key and put them in the files `client.crt`
+and `client.key` respectively, you can create a secret with `kubectl` like this:
+
+```bash
+kubectl create secret generic tls-certs \
+ --from-file=certFile=client.crt \
+ --from-file=keyFile=client.key
+```
+
+You could also [prepare a secret and encrypt it][sops-guide]; the important bit is that the data
+keys in the secret are `certFile` and `keyFile`.
+
+If you have a CA certificate for the client to use, the data key for that is `caFile`. Adapting the
+previous example, if you have the certificate in the file `ca.crt`, and the client certificate and
+key as before, the whole command would be:
+
+```bash
+kubectl create secret generic tls-certs \
+ --from-file=certFile=client.crt \
+ --from-file=keyFile=client.key \
+ --from-file=caFile=ca.crt
+```
+
+### Interval
+
+`.spec.interval` is a required field that specifies the interval at which the
+OCI repository must be fetched.
+
+After successfully reconciling the object, the source-controller requeues it
+for inspection after the specified interval. The value must be in a
+[Go recognized duration string format](https://pkg.go.dev/time#ParseDuration),
+e.g. `10m0s` to reconcile the object every 10 minutes.
+
+If the `.metadata.generation` of a resource changes (due to e.g. a change to
+the spec), this is handled instantly outside the interval window.
+
+### Timeout
+
+`.spec.timeout` is an optional field to specify a timeout for OCI operations
+like pulling. The value must be in a
+[Go recognized duration string format](https://pkg.go.dev/time#ParseDuration),
+e.g. `1m30s` for a timeout of one minute and thirty seconds. The default value
+is `60s`.
+
+### Reference
+
+`.spec.ref` is an optional field to specify the OCI reference to resolve and
+watch for changes. References are specified in one or more subfields
+(`.tag`, `.semver`, `.digest`), with latter listed fields taking
+precedence over earlier ones. If not specified, it defaults to the `latest`
+tag.
+
+#### Tag example
+
+To pull a specific tag, use `.spec.ref.tag`:
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ ref:
+ tag: ""
+```
+
+#### SemVer example
+
+To pull a tag based on a
+[SemVer range](https://github.com/Masterminds/semver#checking-version-constraints),
+use `.spec.ref.semver`:
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ ref:
+ # SemVer range reference: https://github.com/Masterminds/semver#checking-version-constraints
+ semver: ""
+```
+
+This field takes precedence over [`.tag`](#tag-example).
+
+#### Digest example
+
+To pull a specific digest, use `.spec.ref.digest`:
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ ref:
+ digest: "sha256:"
+```
+
+This field takes precedence over all other fields.
+
+### Ignore
+
+`.spec.ignore` is an optional field to specify rules in [the `.gitignore`
+pattern format](https://git-scm.com/docs/gitignore#_pattern_format). Paths
+matching the defined rules are excluded while archiving.
+
+When specified, `.spec.ignore` overrides the [default exclusion
+list](#default-exclusions), and may overrule the [`.sourceignore` file
+exclusions](#sourceignore-file). See [excluding files](#excluding-files)
+for more information.
+
+### Suspend
+
+`.spec.suspend` is an optional field to suspend the reconciliation of a
+OCIRepository. When set to `true`, the controller will stop reconciling the
+OCIRepository, and changes to the resource or in the OCI repository will not
+result in a new Artifact. When the field is set to `false` or removed, it will
+resume.
+
+## Working with OCIRepositories
+
+### Excluding files
+
+By default, files which match the [default exclusion rules](#default-exclusions)
+are excluded while archiving the OCI repository contents as an Artifact.
+It is possible to overwrite and/or overrule the default exclusions using
+the [`.spec.ignore` field](#ignore).
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ ignore: |
+ # exclude all
+ /*
+ # include deploy dir
+ !/deploy
+ # exclude file extensions from deploy dir
+ /deploy/**/*.md
+ /deploy/**/*.txt
+```
+
+### Triggering a reconcile
+
+To manually tell the source-controller to reconcile a OCIRepository outside the
+[specified interval window](#interval), a OCIRepository can be annotated with
+`reconcile.fluxcd.io/requestedAt: `. Annotating the resource
+queues the OCIRepository for reconciliation if the `` differs
+from the last value the controller acted on, as reported in
+[`.status.lastHandledReconcileAt`](#last-handled-reconcile-at).
+
+Using `kubectl`:
+
+```sh
+kubectl annotate --field-manager=flux-client-side-apply --overwrite ocirepository/ reconcile.fluxcd.io/requestedAt="$(date +%s)"
+```
+
+Using `flux`:
+
+```sh
+flux reconcile source oci
+```
+
+### Waiting for `Ready`
+
+When a change is applied, it is possible to wait for the OCIRepository to reach
+a [ready state](#ready-gitrepository) using `kubectl`:
+
+```sh
+kubectl wait gitrepository/ --for=condition=ready --timeout=1m
+```
+
+### Suspending and resuming
+
+When you find yourself in a situation where you temporarily want to pause the
+reconciliation of a OCIRepository, you can suspend it using the
+[`.spec.suspend` field](#suspend).
+
+#### Suspend an OCIRepository
+
+In your YAML declaration:
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ suspend: true
+```
+
+Using `kubectl`:
+
+```sh
+kubectl patch ocirepository --field-manager=flux-client-side-apply -p '{\"spec\": {\"suspend\" : true }}'
+```
+
+Using `flux`:
+
+```sh
+flux suspend source oci
+```
+
+**Note:** When a OCIRepository has an Artifact and is suspended, and this
+Artifact later disappears from the storage due to e.g. the source-controller
+Pod being evicted from a Node, this will not be reflected in the
+OCIRepository's Status until it is resumed.
+
+#### Resume an OCIRepository
+
+In your YAML declaration, comment out (or remove) the field:
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+spec:
+ # suspend: true
+```
+
+**Note:** Setting the field value to `false` has the same effect as removing
+it, but does not allow for "hot patching" using e.g. `kubectl` while practicing
+GitOps; as the manually applied patch would be overwritten by the declared
+state in Git.
+
+Using `kubectl`:
+
+```sh
+kubectl patch ocirepository --field-manager=flux-client-side-apply -p '{\"spec\" : {\"suspend\" : false }}'
+```
+
+Using `flux`:
+
+```sh
+flux resume source oci
+```
+
+### Debugging an OCIRepository
+
+There are several ways to gather information about a OCIRepository for
+debugging purposes.
+
+#### Describe the OCIRepository
+
+Describing an OCIRepository using
+`kubectl describe ocirepository `
+displays the latest recorded information for the resource in the `Status` and
+`Events` sections:
+
+```console
+...
+Status:
+...
+ Conditions:
+ Last Transition Time: 2022-02-14T09:40:27Z
+ Message: reconciling new object generation (2)
+ Observed Generation: 2
+ Reason: NewGeneration
+ Status: True
+ Type: Reconciling
+ Last Transition Time: 2022-02-14T09:40:27Z
+ Message: failed to pull artifact from 'oci://ghcr.io/stefanprodan/manifests/podinfo': couldn't find tag "0.0.1"
+ Observed Generation: 2
+ Reason: OCIOperationFailed
+ Status: False
+ Type: Ready
+ Last Transition Time: 2022-02-14T09:40:27Z
+ Message: failed to pull artifact from 'oci://ghcr.io/stefanprodan/manifests/podinfo': couldn't find tag "0.0.1"
+ Observed Generation: 2
+ Reason: OCIOperationFailed
+ Status: True
+ Type: FetchFailed
+ Observed Generation: 1
+ URL: http://source-controller.source-system.svc.cluster.local./ocirepository/default/podinfo/latest.tar.gz
+Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Warning OCIOperationFailed 2s (x9 over 4s) source-controller failed to pull artifact from 'oci://ghcr.io/stefanprodan/manifests/podinfo': couldn't find tag "0.0.1"
+```
+
+#### Trace emitted Events
+
+To view events for specific OCIRepository(s), `kubectl get events` can be used
+in combination with `--field-sector` to list the Events for specific objects.
+For example, running
+
+```sh
+kubectl get events --field-selector involvedObject.kind=OCIRepository,involvedObject.name=
+```
+
+lists
+
+```console
+LAST SEEN TYPE REASON OBJECT MESSAGE
+2m14s Normal NewArtifact ocirepository/ stored artifact for digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+36s Normal ArtifactUpToDate ocirepository/ artifact up-to-date with remote digest: '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+94s Warning OCIOperationFailed ocirepository/ failed to pull artifact from 'oci://ghcr.io/stefanprodan/manifests/podinfo': couldn't find tag "0.0.1"
+```
+
+Besides being reported in Events, the reconciliation errors are also logged by
+the controller. The Flux CLI offer commands for filtering the logs for a
+specific OCIRepository, e.g.
+`flux logs --level=error --kind=OCIRepository --name=`.
+
+## OCIRepository Status
+
+### Artifact
+
+The OCIRepository reports the latest synchronized state from the OCI repository
+as an Artifact object in the `.status.artifact` of the resource.
+
+The Artifact file is a gzip compressed TAR archive (`.tar.gz`), and
+can be retrieved in-cluster from the `.status.artifact.url` HTTP address.
+
+#### Artifact example
+
+```yaml
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: OCIRepository
+metadata:
+ name:
+status:
+ artifact:
+ checksum: e750c7a46724acaef8f8aa926259af30bbd9face2ae065ae8896ba5ee5ab832b
+ lastUpdateTime: "2022-06-29T06:59:23Z"
+ path: ocirepository///.tar.gz
+ revision: master/363a6a8fe6a7f13e05d34c163b0ef02a777da20a
+ url: http://source-controller..svc.cluster.local./ocirepository///.tar.gz
+```
+
+#### Default exclusions
+
+The following files and extensions are excluded from the Artifact by
+default:
+
+- Git files (`.git/, .gitignore, .gitmodules, .gitattributes`)
+- File extensions (`.jpg, .jpeg, .gif, .png, .wmv, .flv, .tar.gz, .zip`)
+- CI configs (`.github/, .circleci/, .travis.yml, .gitlab-ci.yml, appveyor.yml, .drone.yml, cloudbuild.yaml, codeship-services.yml, codeship-steps.yml`)
+- CLI configs (`.goreleaser.yml, .sops.yaml`)
+- Flux v1 config (`.flux.yaml`)
+
+To define your own exclusion rules, see [excluding files](#excluding-files).
+
+### Conditions
+
+A OCIRepository enters various states during its lifecycle, reflected as
+[Kubernetes Conditions][typical-status-properties].
+It can be [reconciling](#reconciling-ocirepository) while fetching the remote
+state, it can be [ready](#ready-ocirepository), or it can [fail during
+reconciliation](#failed-ocirepository).
+
+The OCIRepository API is compatible with the [kstatus specification][kstatus-spec],
+and reports `Reconciling` and `Stalled` conditions where applicable to
+provide better (timeout) support to solutions polling the OCIRepository to
+become `Ready`.
+
+#### Reconciling OCIRepository
+
+The source-controller marks a OCIRepository as _reconciling_ when one of the
+following is true:
+
+- There is no current Artifact for the OCIRepository, or the reported Artifact
+ is determined to have disappeared from the storage.
+- The generation of the OCIRepository is newer than the [Observed
+ Generation](#observed-generation).
+- The newly resolved Artifact digest differs from the current Artifact.
+
+When the OCIRepository is "reconciling", the `Ready` Condition status becomes
+`False`, and the controller adds a Condition with the following attributes to
+the OCIRepository's `.status.conditions`:
+
+- `type: Reconciling`
+- `status: "True"`
+- `reason: NewGeneration` | `reason: NoArtifact` | `reason: NewRevision`
+
+If the reconciling state is due to a new revision, an additional Condition is
+added with the following attributes:
+
+- `type: ArtifactOutdated`
+- `status: "True"`
+- `reason: NewRevision`
+
+Both Conditions have a ["negative polarity"][typical-status-properties],
+and are only present on the OCIRepository while their status value is `"True"`.
+
+#### Ready OCIRepository
+
+The source-controller marks a OCIRepository as _ready_ when it has the
+following characteristics:
+
+- The OCIRepository reports an [Artifact](#artifact).
+- The reported Artifact exists in the controller's Artifact storage.
+- The controller was able to communicate with the remote OCI repository using
+ the current spec.
+- The digest of the reported Artifact is up-to-date with the latest
+ resolved digest of the remote OCI repository.
+
+When the OCIRepository is "ready", the controller sets a Condition with the
+following attributes in the OCIRepository's `.status.conditions`:
+
+- `type: Ready`
+- `status: "True"`
+- `reason: Succeeded`
+
+This `Ready` Condition will retain a status value of `"True"` until the
+OCIRepository is marked as [reconciling](#reconciling-gitrepository), or e.g. a
+[transient error](#failed-gitrepository) occurs due to a temporary network issue.
+
+When the OCIRepository Artifact is archived in the controller's Artifact
+storage, the controller sets a Condition with the following attributes in the
+OCIRepository's `.status.conditions`:
+
+- `type: ArtifactInStorage`
+- `status: "True"`
+- `reason: Succeeded`
+
+This `ArtifactInStorage` Condition will retain a status value of `"True"` until
+the Artifact in the storage no longer exists.
+
+#### Failed OCIRepository
+
+The source-controller may get stuck trying to produce an Artifact for a
+OCIRepository without completing. This can occur due to some of the following
+factors:
+
+- The remote OCI repository [URL](#url) is temporarily unavailable.
+- The OCI repository does not exist.
+- The [Secret reference](#secret-reference) contains a reference to a
+ non-existing Secret.
+- The credentials in the referenced Secret are invalid.
+- The OCIRepository spec contains a generic misconfiguration.
+- A storage related failure when storing the artifact.
+
+When this happens, the controller sets the `Ready` Condition status to `False`,
+and adds a Condition with the following attributes to the OCIRepository's
+`.status.conditions`:
+
+- `type: FetchFailed` | `type: IncludeUnavailable` | `type: StorageOperationFailed`
+- `status: "True"`
+- `reason: AuthenticationFailed` | `reason: OCIOperationFailed`
+
+This condition has a ["negative polarity"][typical-status-properties],
+and is only present on the OCIRepository while the status value is `"True"`.
+There may be more arbitrary values for the `reason` field to provide accurate
+reason for a condition.
+
+While the OCIRepository has one or more of these Conditions, the controller
+will continue to attempt to produce an Artifact for the resource with an
+exponential backoff, until it succeeds and the OCIRepository is marked as
+[ready](#ready-ocirepository).
+
+Note that a OCIRepository can be [reconciling](#reconciling-ocirepository)
+while failing at the same time, for example due to a newly introduced
+configuration issue in the OCIRepository spec.
+
+### Content Configuration Checksum
+
+The source-controller calculates the SHA256 checksum of the various
+configurations of the OCIRepository that indicate a change in source and
+records it in `.status.contentConfigChecksum`. This field is used to determine
+if the source artifact needs to be rebuilt.
+
+### Observed Generation
+
+The source-controller reports an [observed generation][typical-status-properties]
+in the OCIRepository's `.status.observedGeneration`. The observed generation is
+the latest `.metadata.generation` which resulted in either a [ready state](#ready-ocirepository),
+or stalled due to error it can not recover from without human
+intervention.
+
+### Last Handled Reconcile At
+
+The source-controller reports the last `reconcile.fluxcd.io/requestedAt`
+annotation value it acted on in the `.status.lastHandledReconcileAt` field.
+
+For practical information about this field, see [triggering a
+reconcile](#triggering-a-reconcile).
+
+[typical-status-properties]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
+[kstatus-spec]: https://github.com/kubernetes-sigs/cli-utils/tree/master/pkg/kstatus
+[image-pull-secrets]: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod
+[image-auto-provider-secrets]: https://fluxcd.io/docs/guides/image-update/#imagerepository-cloud-providers-authentication
+[pem-encoding]: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
+[sops-guide]: https://fluxcd.io/docs/guides/mozilla-sops/
From ada42eeaa71f4749d4c7a5ece4f8f20c9c064138 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Fri, 8 Jul 2022 16:03:44 +0300
Subject: [PATCH 10/25] Remove `spec.verify` from the API
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 5 ---
api/v1beta2/zz_generated.deepcopy.go | 5 ---
...rce.toolkit.fluxcd.io_ocirepositories.yaml | 24 -------------
docs/api/source.md | 34 -------------------
4 files changed, 68 deletions(-)
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index eac3f1c40..b35ccca52 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -69,11 +69,6 @@ type OCIRepositorySpec struct {
// +optional
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
- // Verification specifies the configuration to verify the autheticity
- // of an OCI Artifact.
- // +optional
- Verification *OCIRepositoryVerification `json:"verify,omitempty"`
-
// The interval at which to check for image updates.
// +required
Interval metav1.Duration `json:"interval"`
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index d7213100a..807799961 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -707,11 +707,6 @@ func (in *OCIRepositorySpec) DeepCopyInto(out *OCIRepositorySpec) {
*out = new(meta.LocalObjectReference)
**out = **in
}
- if in.Verification != nil {
- in, out := &in.Verification, &out.Verification
- *out = new(OCIRepositoryVerification)
- **out = **in
- }
out.Interval = in.Interval
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
index 7ce115037..b7eb96c1d 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
@@ -122,30 +122,6 @@ spec:
on a remote container registry.
pattern: ^oci://
type: string
- verify:
- description: Verification specifies the configuration to verify the
- autheticity of an OCI Artifact.
- properties:
- provider:
- description: Provider specifies the technology used to sign the
- OCI Artifact.
- enum:
- - cosign
- type: string
- secretRef:
- description: SecretRef specifies the Kubernetes Secret containing
- the trusted public keys.
- properties:
- name:
- description: Name of the referent.
- type: string
- required:
- - name
- type: object
- required:
- - provider
- - secretRef
- type: object
required:
- interval
- url
diff --git a/docs/api/source.md b/docs/api/source.md
index 1ecf0a13a..f45c5ca02 100644
--- a/docs/api/source.md
+++ b/docs/api/source.md
@@ -1022,21 +1022,6 @@ you are using a self-signed server certificate.
-verify
-
-
-OCIRepositoryVerification
-
-
- |
-
-(Optional)
- Verification specifies the configuration to verify the autheticity
-of an OCI Artifact.
- |
-
-
-
interval
@@ -2678,21 +2663,6 @@ you are using a self-signed server certificate.
|
-verify
-
-
-OCIRepositoryVerification
-
-
- |
-
-(Optional)
- Verification specifies the configuration to verify the autheticity
-of an OCI Artifact.
- |
-
-
-
interval
@@ -2838,10 +2808,6 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
-
-(Appears on:
-OCIRepositorySpec)
-
OCIRepositoryVerification verifies the authenticity of an OCI Artifact
|
+
+
+metadata
+
+map[string]string
+
+ |
+
+(Optional)
+ Metadata holds upstream information such as OCI annotations.
+ |
+
From b072d78874e9f1055fe3121f3cc2fb0866073218 Mon Sep 17 00:00:00 2001
From: Somtochi Onyekwere
Date: Thu, 21 Jul 2022 11:05:39 +0100
Subject: [PATCH 13/25] Add tests for oci controller
Signed-off-by: Somtochi Onyekwere
---
controllers/ocirepository_controller_test.go | 732 +++++++++++--------
controllers/suite_test.go | 56 +-
2 files changed, 444 insertions(+), 344 deletions(-)
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 03b241119..95d2bd4d2 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -16,6 +16,7 @@ limitations under the License.
package controllers
import (
+ "context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
@@ -32,38 +33,47 @@ import (
"os"
"path"
"path/filepath"
+ "strings"
"testing"
"time"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/client-go/tools/record"
+
"github.com/darkowlzz/controller-check/status"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/untar"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/registry"
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
. "github.com/onsi/gomega"
- corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
+ fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestOCIRepository_Reconcile(t *testing.T) {
g := NewWithT(t)
// Registry server with public images
- regServer := httptest.NewServer(registry.New())
+ regServer, err := setupRegistryServer(context.Background(), registryOptions{})
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+
versions := []string{"6.1.4", "6.1.5", "6.1.6"}
podinfoVersions := make(map[string]podinfoImage)
for i := 0; i < len(versions); i++ {
- pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], regServer)
+ pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], fmt.Sprintf("http://%s", regServer.registryHost))
g.Expect(err).ToNot(HaveOccurred())
podinfoVersions[versions[i]] = *pi
@@ -240,52 +250,202 @@ func TestOCIRepository_Reconcile(t *testing.T) {
}
}
-func TestOCIRepository_SecretRef(t *testing.T) {
- g := NewWithT(t)
-
- // Instantiate Authenticated Registry Server
- regServer, err := setupRegistryServer(ctx)
- g.Expect(err).ToNot(HaveOccurred())
-
- // Create Test Image
- image, err := crane.Load(path.Join("testdata", "podinfo", "podinfo-6.1.6.tar"))
- g.Expect(err).ToNot(HaveOccurred())
-
- repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
- ociURL := fmt.Sprintf("oci://%s", repositoryURL)
-
- // Push Test Image
- image = setPodinfoImageAnnotations(image, "6.1.6")
- err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
- Username: testRegistryUsername,
- Password: testRegistryPassword,
- }))
- g.Expect(err).ToNot(HaveOccurred())
+func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
+ type secretOptions struct {
+ username string
+ password string
+ includeSA bool
+ includeSecret bool
+ }
- // Test Image digest
- podinfoImageDigest, err := image.Digest()
- g.Expect(err).ToNot(HaveOccurred())
+ pool := x509.NewCertPool()
+ pool.AppendCertsFromPEM(tlsCA)
tests := []struct {
- name string
- url string
- digest gcrv1.Hash
- includeSecretRef bool
- includeServiceAccount bool
+ name string
+ url string
+ registryOpts registryOptions
+ craneOpts []crane.Option
+ secretOpts secretOptions
+ tlsCertSecret *corev1.Secret
+ want sreconcile.Result
+ wantErr bool
+ assertConditions []metav1.Condition
}{
{
- name: "private-registry-access-via-secretref",
- url: ociURL,
- digest: podinfoImageDigest,
- includeSecretRef: true,
- includeServiceAccount: false,
+ name: "HTTP without basic auth",
+ want: sreconcile.ResultSuccess,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '' for ''"),
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '' for ''"),
+ },
+ },
+ {
+ name: "HTTP with basic auth secret",
+ want: sreconcile.ResultSuccess,
+ registryOpts: registryOptions{
+ withBasicAuth: true,
+ },
+ craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }),
+ },
+ secretOpts: secretOptions{
+ username: testRegistryUsername,
+ password: testRegistryPassword,
+ includeSecret: true,
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '' for ''"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '' for ''"),
+ },
+ },
+ {
+ name: "HTTP with serviceaccount",
+ want: sreconcile.ResultSuccess,
+ registryOpts: registryOptions{
+ withBasicAuth: true,
+ },
+ craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }),
+ },
+ secretOpts: secretOptions{
+ username: testRegistryUsername,
+ password: testRegistryPassword,
+ includeSA: true,
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '' for ''"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '' for ''"),
+ },
+ },
+ {
+ name: "HTTP registry - basic auth with missing secret",
+ want: sreconcile.ResultEmpty,
+ registryOpts: registryOptions{
+ withBasicAuth: true,
+ },
+ wantErr: true,
+ craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }),
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ },
+ },
+ {
+ name: "HTTP registry - basic auth with invalid secret",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ registryOpts: registryOptions{
+ withBasicAuth: true,
+ },
+ craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }),
+ },
+ secretOpts: secretOptions{
+ username: "wrong-pass",
+ password: "wrong-pass",
+ includeSecret: true,
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ },
+ },
+ {
+ name: "HTTP registry - basic auth with invalid serviceaccount",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ registryOpts: registryOptions{
+ withBasicAuth: true,
+ },
+ craneOpts: []crane.Option{crane.WithAuth(&authn.Basic{
+ Username: testRegistryUsername,
+ Password: testRegistryPassword,
+ }),
+ },
+ secretOpts: secretOptions{
+ username: "wrong-pass",
+ password: "wrong-pass",
+ includeSA: true,
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ },
+ },
+ {
+ name: "HTTPS with valid certfile",
+ want: sreconcile.ResultSuccess,
+ registryOpts: registryOptions{
+ withTlS: true,
+ },
+ craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: pool,
+ },
+ }),
+ },
+ tlsCertSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ca-file",
+ },
+ Data: map[string][]byte{
+ "caFile": tlsCA,
+ },
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest '' for ''"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest '' for ''"),
+ },
},
{
- name: "private-registry-access-via-serviceaccount",
- url: ociURL,
- digest: podinfoImageDigest,
- includeSecretRef: false,
- includeServiceAccount: true,
+ name: "HTTPS without certfile",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ registryOpts: registryOptions{
+ withTlS: true,
+ },
+ craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: pool,
+ },
+ }),
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ },
+ },
+ {
+ name: "HTTPS with invalid certfile",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ registryOpts: registryOptions{
+ withTlS: true,
+ },
+ craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: pool,
+ },
+ }),
+ },
+ tlsCertSecret: &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ca-file",
+ },
+ Data: map[string][]byte{
+ "caFile": []byte("invalid"),
+ },
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ },
},
}
@@ -293,306 +453,239 @@ func TestOCIRepository_SecretRef(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
- ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
- g.Expect(err).ToNot(HaveOccurred())
- defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
-
- secret := &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- GenerateName: "auth-secretref",
- Namespace: ns.Name,
- },
- Type: corev1.SecretTypeDockerConfigJson,
- StringData: map[string]string{
- ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, testRegistryUsername, testRegistryPassword),
- },
- }
- g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
- defer func() { g.Expect(testEnv.Delete(ctx, secret)).To(Succeed()) }()
-
- serviceAccount := &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- GenerateName: "sa-ocitest",
- Namespace: ns.Name,
- },
- ImagePullSecrets: []corev1.LocalObjectReference{{Name: secret.Name}},
- }
- g.Expect(testEnv.CreateAndWait(ctx, serviceAccount)).To(Succeed())
- defer func() { g.Expect(testEnv.Delete(ctx, serviceAccount)).To(Succeed()) }()
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
obj := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
- GenerateName: "ocirepository-test-resource",
- Namespace: ns.Name,
+ GenerateName: "auth-strategy-",
},
Spec: sourcev1.OCIRepositorySpec{
- URL: tt.url,
- Interval: metav1.Duration{Duration: 60 * time.Minute},
- Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ Interval: metav1.Duration{Duration: interval},
+ Timeout: &metav1.Duration{Duration: timeout},
},
}
- if tt.includeSecretRef {
- obj.Spec.SecretRef = &meta.LocalObjectReference{Name: secret.Name}
- }
+ server, err := setupRegistryServer(context.Background(), tt.registryOpts)
+ g.Expect(err).NotTo(HaveOccurred())
- if tt.includeServiceAccount {
- obj.Spec.ServiceAccountName = serviceAccount.Name
+ img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", fmt.Sprintf("http://%s", server.registryHost), tt.craneOpts...)
+ g.Expect(err).ToNot(HaveOccurred())
+ obj.Spec.URL = img.url
+ obj.Spec.Reference = &sourcev1.OCIRepositoryRef{
+ Tag: img.tag,
}
- g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
-
- key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
-
- // Wait for the finalizer to be set
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, obj); err != nil {
- return false
+ if tt.secretOpts.username != "" && tt.secretOpts.password != "" {
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "auth-secretref",
+ },
+ Type: corev1.SecretTypeDockerConfigJson,
+ Data: map[string][]byte{
+ ".dockerconfigjson": []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`,
+ server.registryHost, tt.secretOpts.username, tt.secretOpts.password)),
+ },
}
- return len(obj.Finalizers) > 0
- }, timeout).Should(BeTrue())
- // Wait for the object to be Ready
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, obj); err != nil {
- return false
+ builder.WithObjects(secret)
+
+ if tt.secretOpts.includeSA {
+ serviceAccount := &corev1.ServiceAccount{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sa-ocitest",
+ },
+ ImagePullSecrets: []corev1.LocalObjectReference{{Name: secret.Name}},
+ }
+ builder.WithObjects(serviceAccount)
+ obj.Spec.ServiceAccountName = serviceAccount.Name
}
- if !conditions.IsReady(obj) {
- return false
- }
- readyCondition := conditions.Get(obj, meta.ReadyCondition)
- return obj.Generation == readyCondition.ObservedGeneration &&
- obj.Generation == obj.Status.ObservedGeneration
- }, timeout).Should(BeTrue())
-
- t.Log(obj.Status.Artifact.Revision)
- // Check if the revision matches the expected digest
- g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest.Hex))
-
- // Check if the artifact storage path matches the expected file path
- localPath := testStorage.LocalPath(*obj.Status.Artifact)
- t.Logf("artifact local path: %s", localPath)
-
- f, err := os.Open(localPath)
- g.Expect(err).ToNot(HaveOccurred())
- defer f.Close()
-
- // create a tmp directory to extract artifact
- tmp, err := os.MkdirTemp("", "ocirepository-test-")
- g.Expect(err).ToNot(HaveOccurred())
- defer os.RemoveAll(tmp)
-
- ep, err := untar.Untar(f, tmp)
- g.Expect(err).ToNot(HaveOccurred())
- t.Logf("extracted summary: %s", ep)
+ if tt.secretOpts.includeSecret {
+ obj.Spec.SecretRef = &meta.LocalObjectReference{
+ Name: secret.Name,
+ }
+ }
+ }
- expectedFile := filepath.Join(tmp, `kustomize/deployment.yaml`)
- g.Expect(expectedFile).To(BeAnExistingFile())
+ if tt.tlsCertSecret != nil {
+ builder.WithObjects(tt.tlsCertSecret)
+ obj.Spec.CertSecretRef = &meta.LocalObjectReference{
+ Name: tt.tlsCertSecret.Name,
+ }
+ }
- f2, err := os.Open(expectedFile)
- g.Expect(err).ToNot(HaveOccurred())
- defer f2.Close()
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
- h := testStorage.Checksum(f2)
- t.Logf("hash: %q", h)
- g.Expect(h).To(Equal("6fd625effe6bb805b6a78943ee082a4412e763edb7fcaed6e8fe644d06cbf423"))
+ repoURL, err := r.getArtifactURL(context.Background(), obj, nil, nil)
+ g.Expect(err).To(BeNil())
- // Check if the object status is valid
- condns := &status.Conditions{NegativePolarity: ociRepositoryReadyCondition.NegativePolarity}
- checker := status.NewChecker(testEnv.Client, condns)
- checker.CheckErr(ctx, obj)
+ assertConditions := tt.assertConditions
+ for k := range assertConditions {
+ assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "", img.digest.Hex)
+ assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "", repoURL)
+ }
- // kstatus client conformance check
- u, err := patch.ToUnstructured(obj)
- g.Expect(err).ToNot(HaveOccurred())
- res, err := kstatus.Compute(u)
- g.Expect(err).ToNot(HaveOccurred())
- g.Expect(res.Status).To(Equal(kstatus.CurrentStatus))
+ tmpDir := t.TempDir()
+ got, err := r.reconcileSource(context.Background(), obj, &sourcev1.Artifact{}, tmpDir)
- // Patch the object with reconcile request annotation.
- patchHelper, err := patch.NewHelper(obj, testEnv.Client)
- g.Expect(err).ToNot(HaveOccurred())
- annotations := map[string]string{
- meta.ReconcileRequestAnnotation: "now",
+ if tt.wantErr {
+ g.Expect(err).ToNot(BeNil())
+ } else {
+ g.Expect(err).To(BeNil())
}
- obj.SetAnnotations(annotations)
- g.Expect(patchHelper.Patch(ctx, obj)).ToNot(HaveOccurred())
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, obj); err != nil {
- return false
- }
- return obj.Status.LastHandledReconcileAt == "now"
- }, timeout).Should(BeTrue())
-
- // Wait for the object to be deleted
- g.Expect(testEnv.Delete(ctx, obj)).To(Succeed())
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, obj); err != nil {
- return apierrors.IsNotFound(err)
- }
- return false
- }, timeout).Should(BeTrue())
+ g.Expect(got).To(Equal(tt.want))
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
})
}
}
-func TestOCIRepository_FailedAuth(t *testing.T) {
+func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
g := NewWithT(t)
- // Instantiate Authenticated Registry Server
- regServer, err := setupRegistryServer(ctx)
- g.Expect(err).ToNot(HaveOccurred())
-
- // Create Test Image
- image, err := crane.Load(path.Join("testdata", "podinfo", "podinfo-6.1.6.tar"))
+ server, err := setupRegistryServer(context.Background(), registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
- repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
- ociURL := fmt.Sprintf("oci://%s", repositoryURL)
-
- // Push Test Image
- image = setPodinfoImageAnnotations(image, "6.1.6")
- err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
- Username: testRegistryUsername,
- Password: testRegistryPassword,
- }))
+ img5, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", fmt.Sprintf("http://%s", server.registryHost))
g.Expect(err).ToNot(HaveOccurred())
- // Test Image digest
- podinfoImageDigest, err := image.Digest()
+ img6, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", fmt.Sprintf("http://%s", server.registryHost))
g.Expect(err).ToNot(HaveOccurred())
tests := []struct {
- name string
- url string
- digest gcrv1.Hash
- repoUsername string
- repoPassword string
- includeSecretRef bool
- includeServiceAccount bool
+ name string
+ reference *sourcev1.OCIRepositoryRef
+ want sreconcile.Result
+ wantErr bool
+ wantRevision string
+ assertConditions []metav1.Condition
}{
{
- name: "missing-auth",
- url: ociURL,
- repoUsername: "",
- repoPassword: "",
- digest: podinfoImageDigest,
- includeSecretRef: false,
- includeServiceAccount: false,
+ name: "no reference (latest tag)",
+ want: sreconcile.ResultSuccess,
+ wantRevision: img6.digest.Hex,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
+ },
+ {
+ name: "tag reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ Tag: "6.1.6",
+ },
+ want: sreconcile.ResultSuccess,
+ wantRevision: img6.digest.Hex,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
+ },
+ {
+ name: "semver reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ SemVer: ">= 6.1.5",
+ },
+ want: sreconcile.ResultSuccess,
+ wantRevision: img6.digest.Hex,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
+ },
+ {
+ name: "digest reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ Digest: img6.digest.String(),
+ },
+ wantRevision: img6.digest.Hex,
+ want: sreconcile.ResultSuccess,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
+ },
+ {
+ name: "invalid tag reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ Tag: "6.1.0",
+ },
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact"),
+ },
},
{
- name: "invalid-auth-via-secret",
- url: ociURL,
- repoUsername: "InvalidUser",
- repoPassword: "InvalidPassword",
- digest: podinfoImageDigest,
- includeSecretRef: true,
- includeServiceAccount: false,
+ name: "semver should take precedence over tag",
+ reference: &sourcev1.OCIRepositoryRef{
+ SemVer: ">= 6.1.5",
+ Tag: "6.1.5",
+ },
+ want: sreconcile.ResultSuccess,
+ wantRevision: img6.digest.Hex,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
},
{
- name: "invalid-auth-via-service-account",
- url: ociURL,
- repoUsername: "InvalidUser",
- repoPassword: "InvalidPassword",
- digest: podinfoImageDigest,
- includeSecretRef: false,
- includeServiceAccount: true,
+ name: "digest should take precedence over semver",
+ reference: &sourcev1.OCIRepositoryRef{
+ Tag: "6.1.6",
+ SemVer: ">= 6.1.6",
+ Digest: img5.digest.String(),
+ },
+ want: sreconcile.ResultSuccess,
+ wantRevision: img5.digest.Hex,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
+ *conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
+ },
},
}
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- g := NewWithT(t)
-
- ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
- g.Expect(err).ToNot(HaveOccurred())
- defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
- secret := &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- GenerateName: "auth-secretref",
- Namespace: ns.Name,
- },
- Type: corev1.SecretTypeDockerConfigJson,
- StringData: map[string]string{
- ".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, tt.repoUsername, tt.repoPassword),
- },
- }
- g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
- defer func() { g.Expect(testEnv.Delete(ctx, secret)).To(Succeed()) }()
-
- serviceAccount := &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- GenerateName: "sa-ocitest",
- Namespace: ns.Name,
- },
- ImagePullSecrets: []corev1.LocalObjectReference{{Name: secret.Name}},
- }
- g.Expect(testEnv.CreateAndWait(ctx, serviceAccount)).To(Succeed())
- defer func() { g.Expect(testEnv.Delete(ctx, serviceAccount)).To(Succeed()) }()
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
obj := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
- GenerateName: "ocirepository-test-resource",
- Namespace: ns.Name,
+ GenerateName: "checkout-strategy-",
},
Spec: sourcev1.OCIRepositorySpec{
- URL: tt.url,
- Interval: metav1.Duration{Duration: 60 * time.Minute},
- Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ URL: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
+ Interval: metav1.Duration{Duration: interval},
+ Timeout: &metav1.Duration{Duration: timeout},
},
}
- if tt.includeSecretRef {
- obj.Spec.SecretRef = &meta.LocalObjectReference{Name: secret.Name}
+ if tt.reference != nil {
+ obj.Spec.Reference = tt.reference
}
- if tt.includeServiceAccount {
- obj.Spec.ServiceAccountName = serviceAccount.Name
+ artifact := &sourcev1.Artifact{}
+ tmpDir := t.TempDir()
+ got, err := r.reconcileSource(context.TODO(), obj, artifact, tmpDir)
+ if tt.wantErr {
+ g.Expect(err).To(HaveOccurred())
+ } else {
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(artifact.Revision).To(Equal(tt.wantRevision))
}
- g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
-
- key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
-
- failedObj := sourcev1.OCIRepository{}
-
- // Wait for the finalizer to be set
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &failedObj); err != nil {
- return false
- }
- return len(failedObj.Finalizers) > 0
- }, timeout).Should(BeTrue())
-
- // Wait for the object to fail
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &failedObj); err != nil {
- return false
- }
- readyCondition := conditions.Get(&failedObj, meta.ReadyCondition)
- if readyCondition == nil {
- return false
- }
- return obj.Generation == readyCondition.ObservedGeneration &&
- !conditions.IsReady(&failedObj)
- }, timeout).Should(BeTrue())
-
- g.Expect(testEnv.Get(ctx, key, &failedObj)).To(Succeed())
- readyCondition := conditions.Get(&failedObj, meta.ReadyCondition)
- g.Expect(readyCondition.Status).To(Equal(metav1.ConditionFalse))
- g.Expect(readyCondition.Message).Should(ContainSubstring("UNAUTHORIZED: authentication required; [map[Action:pull Class: Name:podinfo Type:repository]]"))
-
- // Wait for the object to be deleted
- g.Expect(testEnv.Delete(ctx, &failedObj)).To(Succeed())
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &failedObj); err != nil {
- return apierrors.IsNotFound(err)
- }
- return false
- }, timeout).Should(BeTrue())
+ g.Expect(got).To(Equal(tt.want))
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
})
}
}
@@ -600,22 +693,6 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
func TestOCIRepository_CertSecret(t *testing.T) {
g := NewWithT(t)
- registryServer, err := registry.TLS("localhost")
- g.Expect(err).ToNot(HaveOccurred())
- defer registryServer.Close()
-
- pi, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", registryServer)
- g.Expect(err).ToNot(HaveOccurred())
-
- ca_cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: registryServer.Certificate().Raw})
- t.Logf("certdata: %v", string(ca_cert))
-
- tlsSecretCACert := corev1.Secret{
- StringData: map[string]string{
- CACert: string(ca_cert),
- },
- }
-
srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err := createTLSServer()
g.Expect(err).ToNot(HaveOccurred())
@@ -634,7 +711,9 @@ func TestOCIRepository_CertSecret(t *testing.T) {
transport.TLSClientConfig.Certificates = []tls.Certificate{clientTLSCert}
srv.Client().Transport = transport
- pi2, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv)
+ pi2, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv.URL, []crane.Option{
+ crane.WithTransport(srv.Client().Transport),
+ }...)
g.Expect(err).NotTo(HaveOccurred())
tlsSecretClientCert := corev1.Secret{
@@ -654,24 +733,6 @@ func TestOCIRepository_CertSecret(t *testing.T) {
expectreadyconition bool
expectedstatusmessage string
}{
- {
- name: "test connection without CACert",
- url: pi.url,
- tag: pi.tag,
- digest: pi.digest,
- certSecret: nil,
- expectreadyconition: false,
- expectedstatusmessage: "unexpected status code 400 Bad Request: Client sent an HTTP request to an HTTPS server.",
- },
- {
- name: "test connection with CACert",
- url: pi.url,
- tag: pi.tag,
- digest: pi.digest,
- certSecret: &tlsSecretCACert,
- expectreadyconition: true,
- expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi.digest.Hex),
- },
{
name: "test connection with CACert, Client Cert and Private Key",
url: pi2.url,
@@ -681,6 +742,29 @@ func TestOCIRepository_CertSecret(t *testing.T) {
expectreadyconition: true,
expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi2.digest.Hex),
},
+ {
+ name: "test connection with with no secret",
+ url: pi2.url,
+ tag: pi2.tag,
+ digest: pi2.digest,
+ expectreadyconition: false,
+ expectedstatusmessage: "failed to pull artifact",
+ },
+ {
+ name: "test connection with with incorrect private key",
+ url: pi2.url,
+ tag: pi2.tag,
+ digest: pi2.digest,
+ certSecret: &corev1.Secret{
+ StringData: map[string]string{
+ CACert: string(rootCertPEM),
+ ClientCert: string(clientCertPEM),
+ ClientKey: string("invalid-key"),
+ },
+ },
+ expectreadyconition: false,
+ expectedstatusmessage: "failed to generate transport",
+ },
}
for _, tt := range tests {
@@ -768,7 +852,7 @@ type podinfoImage struct {
digest gcrv1.Hash
}
-func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Server) (*podinfoImage, error) {
+func createPodinfoImageFromTar(tarFileName, tag, registryURL string, opts ...crane.Option) (*podinfoImage, error) {
// Create Image
image, err := crane.Load(path.Join("testdata", "podinfo", tarFileName))
if err != nil {
@@ -777,11 +861,11 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
image = setPodinfoImageAnnotations(image, tag)
- url, err := url.Parse(imageServer.URL)
+ myURL, err := url.Parse(registryURL)
if err != nil {
return nil, err
}
- repositoryURL := fmt.Sprintf("%s/podinfo", url.Host)
+ repositoryURL := fmt.Sprintf("%s/podinfo", myURL.Host)
// Image digest
podinfoImageDigest, err := image.Digest()
@@ -790,13 +874,13 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
}
// Push image
- err = crane.Push(image, repositoryURL, crane.WithTransport(imageServer.Client().Transport))
+ err = crane.Push(image, repositoryURL, opts...)
if err != nil {
return nil, err
}
// Tag the image
- err = crane.Tag(repositoryURL, tag, crane.WithTransport(imageServer.Client().Transport))
+ err = crane.Tag(repositoryURL, tag, opts...)
if err != nil {
return nil, err
}
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 06e94890a..6ee2402d0 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -117,7 +117,12 @@ type registryClientTestServer struct {
registryClient *helmreg.Client
}
-func setupRegistryServer(ctx context.Context) (*registryClientTestServer, error) {
+type registryOptions struct {
+ withBasicAuth bool
+ withTlS bool
+}
+
+func setupRegistryServer(ctx context.Context, opts registryOptions) (*registryClientTestServer, error) {
server := ®istryClientTestServer{}
// Create a temporary workspace directory for the registry
@@ -139,19 +144,6 @@ func setupRegistryServer(ctx context.Context) (*registryClientTestServer, error)
return nil, fmt.Errorf("failed to create registry client: %s", err)
}
- // create htpasswd file (w BCrypt, which is required)
- pwBytes, err := bcrypt.GenerateFromPassword([]byte(testRegistryPassword), bcrypt.DefaultCost)
- if err != nil {
- return nil, fmt.Errorf("failed to generate password: %s", err)
- }
-
- htpasswdPath := filepath.Join(workspaceDir, testRegistryHtpasswdFileBasename)
- err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testRegistryUsername, string(pwBytes))), 0644)
- if err != nil {
- return nil, fmt.Errorf("failed to create htpasswd file: %s", err)
- }
-
- // Registry config
config := &configuration.Configuration{}
port, err := freeport.GetFreePort()
if err != nil {
@@ -164,12 +156,34 @@ func setupRegistryServer(ctx context.Context) (*registryClientTestServer, error)
config.Log.AccessLog.Disabled = true
config.Log.Level = "error"
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
- config.Auth = configuration.Auth{
- "htpasswd": configuration.Parameters{
- "realm": "localhost",
- "path": htpasswdPath,
- },
+
+ if opts.withBasicAuth {
+ // create htpasswd file (w BCrypt, which is required)
+ pwBytes, err := bcrypt.GenerateFromPassword([]byte(testRegistryPassword), bcrypt.DefaultCost)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate password: %s", err)
+ }
+
+ htpasswdPath := filepath.Join(workspaceDir, testRegistryHtpasswdFileBasename)
+ err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testRegistryUsername, string(pwBytes))), 0644)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create htpasswd file: %s", err)
+ }
+
+ // Registry config
+ config.Auth = configuration.Auth{
+ "htpasswd": configuration.Parameters{
+ "realm": "localhost",
+ "path": htpasswdPath,
+ },
+ }
+ }
+
+ if opts.withTlS {
+ config.HTTP.TLS.Certificate = "testdata/certs/server.pem"
+ config.HTTP.TLS.Key = "testdata/certs/server-key.pem"
}
+
dockerRegistry, err := dockerRegistry.NewRegistry(ctx, config)
if err != nil {
return nil, fmt.Errorf("failed to create docker registry: %w", err)
@@ -205,7 +219,9 @@ func TestMain(m *testing.M) {
testMetricsH = controller.MustMakeMetrics(testEnv)
- testRegistryServer, err = setupRegistryServer(ctx)
+ testRegistryServer, err = setupRegistryServer(ctx, registryOptions{
+ withBasicAuth: true,
+ })
if err != nil {
panic(fmt.Sprintf("Failed to create a test registry server: %v", err))
}
From e42e9d086ce222c6bcc31fd6ca428285f63a85e1 Mon Sep 17 00:00:00 2001
From: Somtochi Onyekwere
Date: Fri, 22 Jul 2022 15:27:39 +0100
Subject: [PATCH 14/25] Add tests for getArtifactURL
Signed-off-by: Somtochi Onyekwere
---
controllers/ocirepository_controller_test.go | 145 +++++++++++++++----
1 file changed, 120 insertions(+), 25 deletions(-)
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 95d2bd4d2..d26b0dcb9 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -16,7 +16,6 @@ limitations under the License.
package controllers
import (
- "context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
@@ -64,22 +63,13 @@ func TestOCIRepository_Reconcile(t *testing.T) {
g := NewWithT(t)
// Registry server with public images
- regServer, err := setupRegistryServer(context.Background(), registryOptions{})
+ regServer, err := setupRegistryServer(ctx, registryOptions{})
if err != nil {
- t.Fatalf(err.Error())
- }
-
- versions := []string{"6.1.4", "6.1.5", "6.1.6"}
- podinfoVersions := make(map[string]podinfoImage)
-
- for i := 0; i < len(versions); i++ {
- pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], fmt.Sprintf("http://%s", regServer.registryHost))
g.Expect(err).ToNot(HaveOccurred())
-
- podinfoVersions[versions[i]] = *pi
-
}
+ podinfoVersions, err := pushMultiplePodinfoImage(regServer.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+
tests := []struct {
name string
url string
@@ -465,10 +455,10 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
},
}
- server, err := setupRegistryServer(context.Background(), tt.registryOpts)
+ server, err := setupRegistryServer(ctx, tt.registryOpts)
g.Expect(err).NotTo(HaveOccurred())
- img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", fmt.Sprintf("http://%s", server.registryHost), tt.craneOpts...)
+ img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", server.registryHost, tt.craneOpts...)
g.Expect(err).ToNot(HaveOccurred())
obj.Spec.URL = img.url
obj.Spec.Reference = &sourcev1.OCIRepositoryRef{
@@ -520,7 +510,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
Storage: testStorage,
}
- repoURL, err := r.getArtifactURL(context.Background(), obj, nil, nil)
+ repoURL, err := r.getArtifactURL(ctx, obj, nil, nil)
g.Expect(err).To(BeNil())
assertConditions := tt.assertConditions
@@ -530,7 +520,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}
tmpDir := t.TempDir()
- got, err := r.reconcileSource(context.Background(), obj, &sourcev1.Artifact{}, tmpDir)
+ got, err := r.reconcileSource(ctx, obj, &sourcev1.Artifact{}, tmpDir)
if tt.wantErr {
g.Expect(err).ToNot(BeNil())
@@ -547,14 +537,12 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
g := NewWithT(t)
- server, err := setupRegistryServer(context.Background(), registryOptions{})
- g.Expect(err).ToNot(HaveOccurred())
-
- img5, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", fmt.Sprintf("http://%s", server.registryHost))
+ server, err := setupRegistryServer(ctx, registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
- img6, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", fmt.Sprintf("http://%s", server.registryHost))
- g.Expect(err).ToNot(HaveOccurred())
+ podinfoVersions, err := pushMultiplePodinfoImage(server.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+ img6 := podinfoVersions["6.1.6"]
+ img5 := podinfoVersions["6.1.5"]
tests := []struct {
name string
@@ -676,7 +664,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
artifact := &sourcev1.Artifact{}
tmpDir := t.TempDir()
- got, err := r.reconcileSource(context.TODO(), obj, artifact, tmpDir)
+ got, err := r.reconcileSource(ctx, obj, artifact, tmpDir)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
@@ -690,6 +678,93 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
}
}
+func TestOCIRepository_getArtifactURL(t *testing.T) {
+ g := NewWithT(t)
+
+ server, err := setupRegistryServer(ctx, registryOptions{})
+ g.Expect(err).ToNot(HaveOccurred())
+
+ imgs, err := pushMultiplePodinfoImage(server.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+ g.Expect(err).ToNot(HaveOccurred())
+
+ tests := []struct {
+ name string
+ url string
+ reference *sourcev1.OCIRepositoryRef
+ wantErr bool
+ want string
+ }{
+ {
+ name: "valid url with no reference",
+ url: "oci://ghcr.io/stefanprodan/charts",
+ want: "ghcr.io/stefanprodan/charts",
+ },
+ {
+ name: "valid url with tag reference",
+ url: "oci://ghcr.io/stefanprodan/charts",
+ reference: &sourcev1.OCIRepositoryRef{
+ Tag: "6.1.6",
+ },
+ want: "ghcr.io/stefanprodan/charts:6.1.6",
+ },
+ {
+ name: "valid url with digest reference",
+ url: "oci://ghcr.io/stefanprodan/charts",
+ reference: &sourcev1.OCIRepositoryRef{
+ Digest: imgs["6.1.6"].digest.Hex,
+ },
+ want: "ghcr.io/stefanprodan/charts@" + imgs["6.1.6"].digest.Hex,
+ },
+ {
+ name: "valid url with semver reference",
+ url: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
+ reference: &sourcev1.OCIRepositoryRef{
+ SemVer: ">= 6.1.6",
+ },
+ want: server.registryHost + "/podinfo:6.1.6",
+ },
+ {
+ name: "invalid url without oci prefix",
+ url: "ghcr.io/stefanprodan/charts",
+ wantErr: true,
+ },
+ }
+
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "artifact-url-",
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: interval},
+ Timeout: &metav1.Duration{Duration: timeout},
+ },
+ }
+
+ if tt.reference != nil {
+ obj.Spec.Reference = tt.reference
+ }
+
+ got, err := r.getArtifactURL(ctx, obj, authn.DefaultKeychain, nil)
+ if tt.wantErr {
+ g.Expect(err).To(HaveOccurred())
+ return
+ }
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(got).To(Equal(tt.want))
+ })
+ }
+}
+
func TestOCIRepository_CertSecret(t *testing.T) {
g := NewWithT(t)
@@ -839,7 +914,6 @@ func TestOCIRepository_CertSecret(t *testing.T) {
}, timeout).Should(BeTrue())
})
}
-
}
type artifactFixture struct {
@@ -861,6 +935,11 @@ func createPodinfoImageFromTar(tarFileName, tag, registryURL string, opts ...cra
image = setPodinfoImageAnnotations(image, tag)
+ // url.Parse doesn't handle urls with no scheme well e.g localhost:
+ if !(strings.HasPrefix(registryURL, "http://") || strings.HasPrefix(registryURL, "https://")) {
+ registryURL = fmt.Sprintf("http://%s", registryURL)
+ }
+
myURL, err := url.Parse(registryURL)
if err != nil {
return nil, err
@@ -892,6 +971,22 @@ func createPodinfoImageFromTar(tarFileName, tag, registryURL string, opts ...cra
}, nil
}
+func pushMultiplePodinfoImage(serverURL string, versions []string) (map[string]podinfoImage, error) {
+ podinfoVersions := make(map[string]podinfoImage)
+
+ for i := 0; i < len(versions); i++ {
+ pi, err := createPodinfoImageFromTar(fmt.Sprintf("podinfo-%s.tar", versions[i]), versions[i], serverURL)
+ if err != nil {
+ return nil, err
+ }
+
+ podinfoVersions[versions[i]] = *pi
+
+ }
+
+ return podinfoVersions, nil
+}
+
func setPodinfoImageAnnotations(img gcrv1.Image, tag string) gcrv1.Image {
metadata := map[string]string{
"org.opencontainers.image.source": "https://github.com/stefanprodan/podinfo",
From 648beef0638d2f7ccbc741378426aaa797ebb93f Mon Sep 17 00:00:00 2001
From: Somtochi Onyekwere
Date: Fri, 22 Jul 2022 22:01:14 +0100
Subject: [PATCH 15/25] Add test for reconcileArtifact
Signed-off-by: Somtochi Onyekwere
---
controllers/ocirepository_controller_test.go | 146 +++++++++++++++++++
d.txt | 1 +
main.go | 1 +
3 files changed, 148 insertions(+)
create mode 100644 d.txt
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index d26b0dcb9..8e40f6413 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -765,6 +765,152 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
}
}
+func TestOCIRepository_reconcileStorage(t *testing.T) {
+ g := NewWithT(t)
+
+ tests := []struct {
+ name string
+ beforeFunc func(obj *sourcev1.OCIRepository) error
+ want sreconcile.Result
+ wantErr bool
+ assertConditions []metav1.Condition
+ assertArtifact *sourcev1.Artifact
+ assertPaths []string
+ }{
+ {
+ name: "garbage collects",
+ beforeFunc: func(obj *sourcev1.OCIRepository) error {
+ revisions := []string{"a", "b", "c", "d"}
+
+ for n := range revisions {
+ v := revisions[n]
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Path: fmt.Sprintf("/oci-reconcile-storage/%s.txt", v),
+ Revision: v,
+ }
+ if err := testStorage.MkdirAll(*obj.Status.Artifact); err != nil {
+ return err
+ }
+
+ if err := testStorage.AtomicWriteFile(obj.Status.Artifact, strings.NewReader(v), 0o640); err != nil {
+ return err
+ }
+
+ if n != len(revisions)-1 {
+ time.Sleep(time.Second)
+ }
+ }
+
+ testStorage.SetArtifactURL(obj.Status.Artifact)
+ return nil
+ },
+ assertArtifact: &sourcev1.Artifact{
+ Path: "/oci-reconcile-storage/d.txt",
+ Revision: "d",
+ Checksum: "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4",
+ URL: testStorage.Hostname + "/oci-reconcile-storage/d.txt",
+ Size: int64p(int64(len("d"))),
+ },
+ assertPaths: []string{
+ "/oci-reconcile-storage/d.txt",
+ "/oci-reconcile-storage/c.txt",
+ "!/oci-reconcile-storage/b.txt",
+ "!/oci-reconcile-storage/a.txt",
+ },
+ want: sreconcile.ResultSuccess,
+ },
+ {
+ name: "notices missing artifact in storage",
+ beforeFunc: func(obj *sourcev1.OCIRepository) error {
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Path: "/oci-reconcile-storage/invalid.txt",
+ Revision: "e",
+ }
+ testStorage.SetArtifactURL(obj.Status.Artifact)
+ return nil
+ },
+ want: sreconcile.ResultSuccess,
+ assertPaths: []string{
+ "!/oci-reconcile-storage/invalid.txt",
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(meta.ReconcilingCondition, "NoArtifact", "no artifact for resource in storage"),
+ },
+ },
+ {
+ name: "updates hostname on diff from current",
+ beforeFunc: func(obj *sourcev1.OCIRepository) error {
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Path: "/oci-reconcile-storage/hostname.txt",
+ Revision: "f",
+ Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80",
+ URL: "http://outdated.com/oci-reconcile-storage/hostname.txt",
+ }
+ if err := testStorage.MkdirAll(*obj.Status.Artifact); err != nil {
+ return err
+ }
+ if err := testStorage.AtomicWriteFile(obj.Status.Artifact, strings.NewReader("file"), 0o640); err != nil {
+ return err
+ }
+ return nil
+ },
+ want: sreconcile.ResultSuccess,
+ assertPaths: []string{
+ "/oci-reconcile-storage/hostname.txt",
+ },
+ assertArtifact: &sourcev1.Artifact{
+ Path: "/oci-reconcile-storage/hostname.txt",
+ Revision: "f",
+ Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80",
+ URL: testStorage.Hostname + "/oci-reconcile-storage/hostname.txt",
+ Size: int64p(int64(len("file"))),
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "test-",
+ },
+ }
+
+ g.Expect(tt.beforeFunc(obj)).To(Succeed())
+ got, err := r.reconcileStorage(ctx, obj, &sourcev1.Artifact{}, "")
+ if tt.wantErr {
+ g.Expect(err).To(HaveOccurred())
+ } else {
+ g.Expect(err).ToNot(HaveOccurred())
+ }
+
+ g.Expect(got).To(Equal(tt.want))
+ g.Expect(obj.Status.Artifact).To(MatchArtifact(tt.assertArtifact))
+ if tt.assertArtifact != nil && tt.assertArtifact.URL != "" {
+ g.Expect(obj.Status.Artifact.URL).To(Equal(tt.assertArtifact.URL))
+ }
+
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
+
+ for _, p := range tt.assertPaths {
+ absoluteP := filepath.Join(testStorage.BasePath, p)
+ if !strings.HasPrefix(p, "!") {
+ g.Expect(absoluteP).To(BeAnExistingFile())
+ continue
+ }
+
+ g.Expect(absoluteP).ToNot(BeAnExistingFile())
+ }
+ })
+ }
+}
+
func TestOCIRepository_CertSecret(t *testing.T) {
g := NewWithT(t)
diff --git a/d.txt b/d.txt
new file mode 100644
index 000000000..4bcfe98e6
--- /dev/null
+++ b/d.txt
@@ -0,0 +1 @@
+d
diff --git a/main.go b/main.go
index 621cea36c..677b30314 100644
--- a/main.go
+++ b/main.go
@@ -357,6 +357,7 @@ func mustInitStorage(path string, storageAdvAddr string, artifactRetentionTTL ti
os.MkdirAll(path, 0o700)
}
+ fmt.Println("PARHHHH", path)
storage, err := controllers.NewStorage(path, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords)
if err != nil {
l.Error(err, "unable to initialise storage")
From eb40efea1c69ec4801f33c85cd9499f3cf292f6f Mon Sep 17 00:00:00 2001
From: Somtochi Onyekwere
Date: Fri, 22 Jul 2022 22:28:11 +0100
Subject: [PATCH 16/25] reconcile artifact
Signed-off-by: Somtochi Onyekwere
---
controllers/ocirepository_controller_test.go | 22 ++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 8e40f6413..85d20e4da 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -608,6 +608,28 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact"),
},
},
+ {
+ name: "invalid semver reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ SemVer: "<= 6.1.0",
+ },
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.URLInvalidReason, "no match found for semver:"),
+ },
+ },
+ {
+ name: "invalid digest reference",
+ reference: &sourcev1.OCIRepositoryRef{
+ Digest: "invalid",
+ },
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact"),
+ },
+ },
{
name: "semver should take precedence over tag",
reference: &sourcev1.OCIRepositoryRef{
From 25b88256efc86404cdd997a8ce95fbf9631253da Mon Sep 17 00:00:00 2001
From: Somtochi Onyekwere
Date: Mon, 25 Jul 2022 10:29:26 +0100
Subject: [PATCH 17/25] Add tests for reconcile delete
Signed-off-by: Somtochi Onyekwere
---
controllers/ocirepository_controller_test.go | 545 ++++++++++++++-----
controllers/suite_test.go | 24 +-
controllers/testdata/oci/repository/foo.txt | 0
d.txt | 1 -
go.mod | 63 ++-
go.sum | 512 ++++++++++++++++-
main.go | 1 -
7 files changed, 964 insertions(+), 182 deletions(-)
create mode 100644 controllers/testdata/oci/repository/foo.txt
delete mode 100644 d.txt
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 85d20e4da..eb1ce3fee 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -45,7 +45,9 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/untar"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+ serror "github.com/fluxcd/source-controller/internal/error"
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
+ "github.com/fluxcd/source-controller/pkg/git"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/registry"
@@ -57,18 +59,20 @@ import (
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func TestOCIRepository_Reconcile(t *testing.T) {
g := NewWithT(t)
// Registry server with public images
- regServer, err := setupRegistryServer(ctx, registryOptions{})
+ tmpDir := t.TempDir()
+ regServer, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
- podinfoVersions, err := pushMultiplePodinfoImage(regServer.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+ podinfoVersions, err := pushMultiplePodinfoImages(regServer.registryHost, "6.1.4", "6.1.5", "6.1.6")
tests := []struct {
name string
@@ -374,7 +378,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
name: "HTTPS with valid certfile",
want: sreconcile.ResultSuccess,
registryOpts: registryOptions{
- withTlS: true,
+ withTLS: true,
},
craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
TLSClientConfig: &tls.Config{
@@ -400,7 +404,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
registryOpts: registryOptions{
- withTlS: true,
+ withTLS: true,
},
craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
TLSClientConfig: &tls.Config{
@@ -417,7 +421,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
registryOpts: registryOptions{
- withTlS: true,
+ withTLS: true,
},
craneOpts: []crane.Option{crane.WithTransport(&http.Transport{
TLSClientConfig: &tls.Config{
@@ -455,7 +459,9 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
},
}
- server, err := setupRegistryServer(ctx, tt.registryOpts)
+ workspaceDir := t.TempDir()
+ server, err := setupRegistryServer(ctx, workspaceDir, tt.registryOpts)
+
g.Expect(err).NotTo(HaveOccurred())
img, err := createPodinfoImageFromTar("podinfo-6.1.6.tar", "6.1.6", server.registryHost, tt.craneOpts...)
@@ -521,7 +527,6 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
tmpDir := t.TempDir()
got, err := r.reconcileSource(ctx, obj, &sourcev1.Artifact{}, tmpDir)
-
if tt.wantErr {
g.Expect(err).ToNot(BeNil())
} else {
@@ -534,13 +539,163 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}
}
+func TestOCIRepository_CertSecret(t *testing.T) {
+ g := NewWithT(t)
+
+ srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err := createTLSServer()
+ g.Expect(err).ToNot(HaveOccurred())
+
+ srv.StartTLS()
+ defer srv.Close()
+
+ transport := &http.Transport{
+ TLSClientConfig: &tls.Config{},
+ }
+ // Use the server cert as a CA cert, so the client trusts the
+ // server cert. (Only works because the server uses the same
+ // cert in both roles).
+ pool := x509.NewCertPool()
+ pool.AddCert(srv.Certificate())
+ transport.TLSClientConfig.RootCAs = pool
+ transport.TLSClientConfig.Certificates = []tls.Certificate{clientTLSCert}
+
+ srv.Client().Transport = transport
+ pi, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv.URL, []crane.Option{
+ crane.WithTransport(srv.Client().Transport),
+ }...)
+ g.Expect(err).NotTo(HaveOccurred())
+
+ tlsSecretClientCert := corev1.Secret{
+ StringData: map[string]string{
+ CACert: string(rootCertPEM),
+ ClientCert: string(clientCertPEM),
+ ClientKey: string(clientKeyPEM),
+ },
+ }
+
+ tests := []struct {
+ name string
+ url string
+ digest gcrv1.Hash
+ certSecret *corev1.Secret
+ expectreadyconition bool
+ expectedstatusmessage string
+ }{
+ {
+ name: "test connection with CACert, Client Cert and Private Key",
+ url: pi.url,
+ digest: pi.digest,
+ certSecret: &tlsSecretClientCert,
+ expectreadyconition: true,
+ expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi.digest.Hex),
+ },
+ {
+ name: "test connection with no secret",
+ url: pi.url,
+ digest: pi.digest,
+ expectreadyconition: false,
+ expectedstatusmessage: "unexpected status code 400 Bad Request: Client sent an HTTP request to an HTTPS server",
+ },
+ {
+ name: "test connection with with incorrect private key",
+ url: pi.url,
+ digest: pi.digest,
+ certSecret: &corev1.Secret{
+ StringData: map[string]string{
+ CACert: string(rootCertPEM),
+ ClientCert: string(clientCertPEM),
+ ClientKey: string("invalid-key"),
+ },
+ },
+ expectreadyconition: false,
+ expectedstatusmessage: "failed to generate transport for '': tls: failed to find any PEM data in key input",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-test-resource",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: tt.url,
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
+ },
+ }
+
+ if tt.certSecret != nil {
+ tt.certSecret.ObjectMeta = metav1.ObjectMeta{
+ GenerateName: "cert-secretref",
+ Namespace: ns.Name,
+ }
+
+ g.Expect(testEnv.CreateAndWait(ctx, tt.certSecret)).To(Succeed())
+ defer func() { g.Expect(testEnv.Delete(ctx, tt.certSecret)).To(Succeed()) }()
+
+ obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: tt.certSecret.Name}
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+
+ resultobj := sourcev1.OCIRepository{}
+
+ // Wait for the finalizer to be set
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return false
+ }
+ return len(resultobj.Finalizers) > 0
+ }, timeout).Should(BeTrue())
+
+ // Wait for the object to fail
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return false
+ }
+ readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
+ if readyCondition == nil {
+ return false
+ }
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ conditions.IsReady(&resultobj) == tt.expectreadyconition
+ }, timeout).Should(BeTrue())
+
+ tt.expectedstatusmessage = strings.ReplaceAll(tt.expectedstatusmessage, "", pi.url)
+
+ readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
+ g.Expect(readyCondition.Message).Should(ContainSubstring(tt.expectedstatusmessage))
+
+ // Wait for the object to be deleted
+ g.Expect(testEnv.Delete(ctx, &resultobj)).To(Succeed())
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return apierrors.IsNotFound(err)
+ }
+ return false
+ }, timeout).Should(BeTrue())
+ })
+ }
+}
+
func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
g := NewWithT(t)
- server, err := setupRegistryServer(ctx, registryOptions{})
+ tmpDir := t.TempDir()
+ server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
- podinfoVersions, err := pushMultiplePodinfoImage(server.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+ podinfoVersions, err := pushMultiplePodinfoImages(server.registryHost, "6.1.4", "6.1.5", "6.1.6")
img6 := podinfoVersions["6.1.6"]
img5 := podinfoVersions["6.1.5"]
@@ -700,13 +855,132 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
}
}
+func TestOCIRepository_reconcileArtifact(t *testing.T) {
+ g := NewWithT(t)
+
+ tests := []struct {
+ name string
+ targetPath string
+ artifact *sourcev1.Artifact
+ beforeFunc func(obj *sourcev1.OCIRepository)
+ want sreconcile.Result
+ wantErr bool
+ assertArtifact *sourcev1.Artifact
+ assertPaths []string
+ assertConditions []metav1.Condition
+ }{
+ {
+ name: "Archiving Artifact creates correct files and condition",
+ targetPath: "testdata/oci/repository",
+ artifact: &sourcev1.Artifact{
+ Revision: "revision",
+ },
+ beforeFunc: func(obj *sourcev1.OCIRepository) {
+ conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest")
+ },
+ want: sreconcile.ResultSuccess,
+ assertPaths: []string{
+ "latest.tar.gz",
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
+ },
+ },
+ {
+ name: "No status changes if artifact is already present",
+ artifact: &sourcev1.Artifact{
+ Revision: "revision",
+ },
+ targetPath: "testdata/oci/repository",
+ want: sreconcile.ResultSuccess,
+ beforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Revision: "revision",
+ }
+ },
+ assertArtifact: &sourcev1.Artifact{
+ Revision: "revision",
+ },
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
+ },
+ },
+ {
+ name: "target path doesn't exist",
+ targetPath: "testdata/oci/non-existent",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.StorageOperationFailedCondition, sourcev1.StatOperationFailedReason, "failed to stat source path: "),
+ },
+ },
+ {
+ name: "target path is a file",
+ targetPath: "testdata/oci/repository/foo.txt",
+ want: sreconcile.ResultEmpty,
+ wantErr: true,
+ assertConditions: []metav1.Condition{
+ *conditions.TrueCondition(sourcev1.StorageOperationFailedCondition, sourcev1.InvalidPathReason, "source path 'testdata/oci/repository/foo.txt' is not a directory"),
+ },
+ },
+ }
+
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
+
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "reconcile-artifact-",
+ },
+ }
+ if tt.beforeFunc != nil {
+ tt.beforeFunc(obj)
+ }
+
+ artifact := &sourcev1.Artifact{}
+ if tt.artifact != nil {
+ artifact = tt.artifact
+ }
+ got, err := r.reconcileArtifact(ctx, obj, artifact, tt.targetPath)
+ if tt.wantErr {
+ g.Expect(err).To(HaveOccurred())
+ } else {
+ g.Expect(err).ToNot(HaveOccurred())
+ }
+
+ g.Expect(got).To(Equal(tt.want))
+ g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
+
+ if tt.assertArtifact != nil {
+ g.Expect(obj.Status.Artifact).To(MatchArtifact(tt.artifact))
+ }
+
+ for _, path := range tt.assertPaths {
+ localPath := testStorage.LocalPath(*obj.GetArtifact())
+ path = filepath.Join(filepath.Dir(localPath), path)
+ _, err := os.Lstat(path)
+ g.Expect(err).ToNot(HaveOccurred())
+ }
+ })
+ }
+}
+
func TestOCIRepository_getArtifactURL(t *testing.T) {
g := NewWithT(t)
- server, err := setupRegistryServer(ctx, registryOptions{})
+ tmpDir := t.TempDir()
+ server, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
- imgs, err := pushMultiplePodinfoImage(server.registryHost, []string{"6.1.4", "6.1.5", "6.1.6"})
+ imgs, err := pushMultiplePodinfoImages(server.registryHost, "6.1.4", "6.1.5", "6.1.6")
g.Expect(err).ToNot(HaveOccurred())
tests := []struct {
@@ -890,14 +1164,16 @@ func TestOCIRepository_reconcileStorage(t *testing.T) {
},
}
+ builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
+ r := &OCIRepositoryReconciler{
+ Client: builder.Build(),
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
- r := &OCIRepositoryReconciler{
- Client: builder.Build(),
- EventRecorder: record.NewFakeRecorder(32),
- Storage: testStorage,
- }
+
obj := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
@@ -933,153 +1209,151 @@ func TestOCIRepository_reconcileStorage(t *testing.T) {
}
}
-func TestOCIRepository_CertSecret(t *testing.T) {
+func TestOCIRepository_ReconcileDelete(t *testing.T) {
g := NewWithT(t)
- srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err := createTLSServer()
- g.Expect(err).ToNot(HaveOccurred())
-
- srv.StartTLS()
- defer srv.Close()
+ r := &OCIRepositoryReconciler{
+ EventRecorder: record.NewFakeRecorder(32),
+ Storage: testStorage,
+ }
- transport := &http.Transport{
- TLSClientConfig: &tls.Config{},
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "reconcile-delete-",
+ DeletionTimestamp: &metav1.Time{Time: time.Now()},
+ Finalizers: []string{
+ sourcev1.SourceFinalizer,
+ },
+ },
+ Status: sourcev1.OCIRepositoryStatus{},
}
- // Use the server cert as a CA cert, so the client trusts the
- // server cert. (Only works because the server uses the same
- // cert in both roles).
- pool := x509.NewCertPool()
- pool.AddCert(srv.Certificate())
- transport.TLSClientConfig.RootCAs = pool
- transport.TLSClientConfig.Certificates = []tls.Certificate{clientTLSCert}
- srv.Client().Transport = transport
- pi2, err := createPodinfoImageFromTar("podinfo-6.1.5.tar", "6.1.5", srv.URL, []crane.Option{
- crane.WithTransport(srv.Client().Transport),
- }...)
+ artifact := testStorage.NewArtifactFor(sourcev1.OCIRepositoryKind, obj.GetObjectMeta(), "revision", "foo.txt")
+ obj.Status.Artifact = &artifact
+
+ got, err := r.reconcileDelete(ctx, obj)
g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(got).To(Equal(sreconcile.ResultEmpty))
+ g.Expect(controllerutil.ContainsFinalizer(obj, sourcev1.SourceFinalizer)).To(BeFalse())
+ g.Expect(obj.Status.Artifact).To(BeNil())
+}
- tlsSecretClientCert := corev1.Secret{
- StringData: map[string]string{
- CACert: string(rootCertPEM),
- ClientCert: string(clientCertPEM),
- ClientKey: string(clientKeyPEM),
- },
- }
+func TestOCIRepositoryReconciler_notify(t *testing.T) {
+
+ noopErr := serror.NewGeneric(fmt.Errorf("some no-op error"), "NoOpReason")
+ noopErr.Ignore = true
tests := []struct {
- name string
- url string
- tag string
- digest gcrv1.Hash
- certSecret *corev1.Secret
- expectreadyconition bool
- expectedstatusmessage string
+ name string
+ res sreconcile.Result
+ resErr error
+ oldObjBeforeFunc func(obj *sourcev1.OCIRepository)
+ newObjBeforeFunc func(obj *sourcev1.OCIRepository)
+ commit git.Commit
+ wantEvent string
}{
{
- name: "test connection with CACert, Client Cert and Private Key",
- url: pi2.url,
- tag: pi2.tag,
- digest: pi2.digest,
- certSecret: &tlsSecretClientCert,
- expectreadyconition: true,
- expectedstatusmessage: fmt.Sprintf("stored artifact for digest '%s'", pi2.digest.Hex),
+ name: "error - no event",
+ res: sreconcile.ResultEmpty,
+ resErr: errors.New("some error"),
},
{
- name: "test connection with with no secret",
- url: pi2.url,
- tag: pi2.tag,
- digest: pi2.digest,
- expectreadyconition: false,
- expectedstatusmessage: "failed to pull artifact",
+ name: "new artifact",
+ res: sreconcile.ResultSuccess,
+ resErr: nil,
+ newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Spec.URL = "oci://newurl.io"
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ },
+ wantEvent: "Normal NewArtifact stored artifact with digest 'xxx' from 'oci://newurl.io'",
},
{
- name: "test connection with with incorrect private key",
- url: pi2.url,
- tag: pi2.tag,
- digest: pi2.digest,
- certSecret: &corev1.Secret{
- StringData: map[string]string{
- CACert: string(rootCertPEM),
- ClientCert: string(clientCertPEM),
- ClientKey: string("invalid-key"),
- },
+ name: "recovery from failure",
+ res: sreconcile.ResultSuccess,
+ resErr: nil,
+ oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "fail")
+ conditions.MarkFalse(obj, meta.ReadyCondition, meta.FailedReason, "foo")
+ },
+ newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Spec.URL = "oci://newurl.io"
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready")
+ },
+ wantEvent: "Normal Succeeded stored artifact with digest 'xxx' from 'oci://newurl.io'",
+ },
+ {
+ name: "recovery and new artifact",
+ res: sreconcile.ResultSuccess,
+ resErr: nil,
+ oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "fail")
+ conditions.MarkFalse(obj, meta.ReadyCondition, meta.FailedReason, "foo")
+ },
+ newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Spec.URL = "oci://newurl.io"
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "aaa", Checksum: "bbb"}
+ conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready")
+ },
+ wantEvent: "Normal NewArtifact stored artifact with digest 'aaa' from 'oci://newurl.io'",
+ },
+ {
+ name: "no updates",
+ res: sreconcile.ResultSuccess,
+ resErr: nil,
+ oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready")
+ },
+ newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready")
+ },
+ },
+ {
+ name: "no updates on requeue",
+ res: sreconcile.ResultRequeue,
+ resErr: nil,
+ oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
+ obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.URLInvalidReason, "ready")
},
- expectreadyconition: false,
- expectedstatusmessage: "failed to generate transport",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
+ recorder := record.NewFakeRecorder(32)
- ns, err := testEnv.CreateNamespace(ctx, "ocirepository-test")
- g.Expect(err).ToNot(HaveOccurred())
- defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+ oldObj := &sourcev1.OCIRepository{}
+ newObj := oldObj.DeepCopy()
- obj := &sourcev1.OCIRepository{
- ObjectMeta: metav1.ObjectMeta{
- GenerateName: "ocirepository-test-resource",
- Namespace: ns.Name,
- },
- Spec: sourcev1.OCIRepositorySpec{
- URL: tt.url,
- Interval: metav1.Duration{Duration: 60 * time.Minute},
- Reference: &sourcev1.OCIRepositoryRef{Digest: tt.digest.String()},
- },
+ if tt.oldObjBeforeFunc != nil {
+ tt.oldObjBeforeFunc(oldObj)
}
-
- if tt.certSecret != nil {
- tt.certSecret.ObjectMeta = metav1.ObjectMeta{
- GenerateName: "cert-secretref",
- Namespace: ns.Name,
- }
-
- g.Expect(testEnv.CreateAndWait(ctx, tt.certSecret)).To(Succeed())
- defer func() { g.Expect(testEnv.Delete(ctx, tt.certSecret)).To(Succeed()) }()
-
- obj.Spec.CertSecretRef = &meta.LocalObjectReference{Name: tt.certSecret.Name}
+ if tt.newObjBeforeFunc != nil {
+ tt.newObjBeforeFunc(newObj)
}
- g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
-
- key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
-
- resultobj := sourcev1.OCIRepository{}
-
- // Wait for the finalizer to be set
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &resultobj); err != nil {
- return false
- }
- return len(resultobj.Finalizers) > 0
- }, timeout).Should(BeTrue())
+ reconciler := &OCIRepositoryReconciler{
+ EventRecorder: recorder,
+ }
+ reconciler.notify(ctx, oldObj, newObj, tt.res, tt.resErr)
- // Wait for the object to fail
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &resultobj); err != nil {
- return false
- }
- readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
- if readyCondition == nil {
- return false
+ select {
+ case x, ok := <-recorder.Events:
+ g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received")
+ if tt.wantEvent != "" {
+ g.Expect(x).To(ContainSubstring(tt.wantEvent))
}
- return obj.Generation == readyCondition.ObservedGeneration &&
- conditions.IsReady(&resultobj) == tt.expectreadyconition
- }, timeout).Should(BeTrue())
-
- readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
- g.Expect(readyCondition.Message).Should(ContainSubstring(tt.expectedstatusmessage))
-
- // Wait for the object to be deleted
- g.Expect(testEnv.Delete(ctx, &resultobj)).To(Succeed())
- g.Eventually(func() bool {
- if err := testEnv.Get(ctx, key, &resultobj); err != nil {
- return apierrors.IsNotFound(err)
+ default:
+ if tt.wantEvent != "" {
+ t.Errorf("expected some event to be emitted")
}
- return false
- }, timeout).Should(BeTrue())
+ }
})
}
}
@@ -1088,6 +1362,7 @@ type artifactFixture struct {
expectedPath string
expectedChecksum string
}
+
type podinfoImage struct {
url string
tag string
@@ -1139,7 +1414,7 @@ func createPodinfoImageFromTar(tarFileName, tag, registryURL string, opts ...cra
}, nil
}
-func pushMultiplePodinfoImage(serverURL string, versions []string) (map[string]podinfoImage, error) {
+func pushMultiplePodinfoImages(serverURL string, versions ...string) (map[string]podinfoImage, error) {
podinfoVersions := make(map[string]podinfoImage)
for i := 0; i < len(versions); i++ {
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 6ee2402d0..b2956b58c 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -119,30 +119,30 @@ type registryClientTestServer struct {
type registryOptions struct {
withBasicAuth bool
- withTlS bool
+ withTLS bool
}
-func setupRegistryServer(ctx context.Context, opts registryOptions) (*registryClientTestServer, error) {
+func setupRegistryServer(ctx context.Context, workspaceDir string, opts registryOptions) (*registryClientTestServer, error) {
server := ®istryClientTestServer{}
- // Create a temporary workspace directory for the registry
- workspaceDir, err := os.MkdirTemp("", "registry-test-")
- if err != nil {
- return nil, fmt.Errorf("failed to create workspace directory: %w", err)
+ if workspaceDir == "" {
+ return nil, fmt.Errorf("workspace directory cannot be an empty string")
}
+
server.workspaceDir = workspaceDir
var out bytes.Buffer
server.out = &out
// init test client
- server.registryClient, err = helmreg.NewClient(
+ client, err := helmreg.NewClient(
helmreg.ClientOptDebug(true),
helmreg.ClientOptWriter(server.out),
)
if err != nil {
return nil, fmt.Errorf("failed to create registry client: %s", err)
}
+ server.registryClient = client
config := &configuration.Configuration{}
port, err := freeport.GetFreePort()
@@ -179,7 +179,7 @@ func setupRegistryServer(ctx context.Context, opts registryOptions) (*registryCl
}
}
- if opts.withTlS {
+ if opts.withTLS {
config.HTTP.TLS.Certificate = "testdata/certs/server.pem"
config.HTTP.TLS.Key = "testdata/certs/server-key.pem"
}
@@ -219,7 +219,11 @@ func TestMain(m *testing.M) {
testMetricsH = controller.MustMakeMetrics(testEnv)
- testRegistryServer, err = setupRegistryServer(ctx, registryOptions{
+ testWorkspaceDir, err := os.MkdirTemp("", "registry-test-")
+ if err != nil {
+ panic(fmt.Sprintf("failed to create workspace directory: %v", err))
+ }
+ testRegistryServer, err = setupRegistryServer(ctx, testWorkspaceDir, registryOptions{
withBasicAuth: true,
})
if err != nil {
@@ -319,7 +323,7 @@ func TestMain(m *testing.M) {
panic(fmt.Sprintf("Failed to remove storage server dir: %v", err))
}
- if err := os.RemoveAll(testRegistryServer.workspaceDir); err != nil {
+ if err := os.RemoveAll(testWorkspaceDir); err != nil {
panic(fmt.Sprintf("Failed to remove registry workspace dir: %v", err))
}
diff --git a/controllers/testdata/oci/repository/foo.txt b/controllers/testdata/oci/repository/foo.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/d.txt b/d.txt
deleted file mode 100644
index 4bcfe98e6..000000000
--- a/d.txt
+++ /dev/null
@@ -1 +0,0 @@
-d
diff --git a/go.mod b/go.mod
index 71717db3f..282612800 100644
--- a/go.mod
+++ b/go.mod
@@ -69,6 +69,11 @@ require (
sigs.k8s.io/yaml v1.3.0
)
+require (
+ github.com/google/go-containerregistry v0.10.0
+ github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20220712174516-ddd39fb9c385
+)
+
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
@@ -82,8 +87,17 @@ require (
cloud.google.com/go v0.102.1 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
+ github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+ github.com/Azure/go-autorest v14.2.0+incompatible // indirect
+ github.com/Azure/go-autorest/autorest v0.11.27 // indirect
+ github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
+ github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
+ github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
+ github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
+ github.com/Azure/go-autorest/logger v0.2.1 // indirect
+ github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
@@ -91,19 +105,34 @@ require (
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
- github.com/PuerkitoBio/purell v1.1.1 // indirect
- github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
+ github.com/aws/aws-sdk-go-v2 v1.16.4 // indirect
+ github.com/aws/aws-sdk-go-v2/config v1.15.8 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.12.3 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 // indirect
+ github.com/aws/smithy-go v1.11.2 // indirect
+ github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.2 // indirect
github.com/bugsnag/bugsnag-go v2.1.2+incompatible // indirect
github.com/bugsnag/panicwrap v1.3.4 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
+ github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 // indirect
github.com/containerd/containerd v1.6.6 // indirect
+ github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
@@ -112,7 +141,7 @@ require (
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
- github.com/emicklei/go-restful v2.9.5+incompatible // indirect
+ github.com/emicklei/go-restful v2.15.0+incompatible // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
@@ -125,18 +154,20 @@ require (
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/zapr v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
- github.com/go-openapi/jsonreference v0.19.5 // indirect
- github.com/go-openapi/swag v0.19.14 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/swag v0.21.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
+ github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect
- github.com/google/gnostic v0.5.7-v3refs // indirect
+ github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.8 // indirect
+ github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20220523143934-b17c48b086b7 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
@@ -145,26 +176,27 @@ require (
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
- github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
+ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
- github.com/klauspost/compress v1.13.6 // indirect
+ github.com/klauspost/compress v1.15.4 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
- github.com/mailru/easyjson v0.7.6 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
@@ -184,7 +216,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
+ github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -194,13 +226,14 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rs/xid v1.2.1 // indirect
github.com/rubenv/sql-migrate v1.1.2 // indirect
- github.com/russross/blackfriday v1.5.2 // indirect
+ github.com/russross/blackfriday v1.6.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.5.0 // indirect
github.com/stretchr/testify v1.7.4 // indirect
+ github.com/vbatts/tar-split v0.11.2 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
@@ -216,9 +249,9 @@ require (
go.uber.org/zap v1.21.0 // indirect
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
- golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
+ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/text v0.3.7 // indirect
- golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
+ golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
@@ -235,10 +268,10 @@ require (
k8s.io/cli-runtime v0.24.2 // indirect
k8s.io/component-base v0.24.2 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
- k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
+ k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 // indirect
k8s.io/kubectl v0.24.2 // indirect
oras.land/oras-go v1.2.0 // indirect
- sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
+ sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect
sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
diff --git a/go.sum b/go.sum
index ecf50b4f3..e42cad64b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
+bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -11,6 +13,7 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
@@ -46,12 +49,15 @@ cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQH
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w=
+cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
@@ -60,7 +66,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.23.0 h1:wWRIaDURQA8xxHguFCshYepGlrWIrbBnAmc7wfg07qY=
cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo=
+github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo=
+github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw=
+github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8=
@@ -71,29 +82,49 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 h1:QSdcrd/UFJv6Bp/Cf
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1/go.mod h1:eZ4g6GUvXiGulfIbbhh1Xr4XwUYaYaWMqzGD/284wCA=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+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.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
+github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
+github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
+github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
+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.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+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/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE=
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
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/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
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=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
@@ -108,15 +139,15 @@ github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NEC
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135 h1:xDc/cFH/hwyr9KyWc0sm26lpsscqtfZBvU8NpRLHwJ0=
github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -124,18 +155,69 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
+github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
+github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI=
+github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU=
+github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250=
+github.com/aws/aws-sdk-go-v2 v1.16.4 h1:swQTEQUyJF/UkEA94/Ga55miiKFoXmm/Zd67XHgmjSg=
+github.com/aws/aws-sdk-go-v2 v1.16.4/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
+github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA=
+github.com/aws/aws-sdk-go-v2/config v1.15.8 h1:Mk9aPT1JiPkhZO9PIP1w2ramuRw95d9w5YNOM3poTKk=
+github.com/aws/aws-sdk-go-v2/config v1.15.8/go.mod h1:Z/guryqWzLw1T3pJbFA0/V3aVXw0sX5oH4lXXiD67aY=
+github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.3 h1:1kPx2lGjvopx7IMqKFmqmhqcuDZQ7pvq9xNXPP5c6qo=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.3/go.mod h1:p6/NGiaGKKM3ihOt/W08Ikz7/F95WhvgjA4x6MWKdS8=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 h1:YPxclBeE07HsLQE8vtjC8T2emcTjM9nzqsnDi2fv5UM=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5/go.mod h1:WAPnuhG5IQ/i6DETFl5NmX3kKqCzw7aau9NHAGcm4QE=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 h1:gsqHplNh1DaQunEKZISK56wlpbCg0yKxNVvGWCFuF1k=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11/go.mod h1:tmUB6jakq5DFNcXsXOA/ZQ7/C8VnSKYkx58OI7Fh79g=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 h1:PLFj+M2PgIDHG//hw3T0O0KLI4itVtAjtxrZx4AHPLg=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5/go.mod h1:fV1AaS2gFc1tM0RCb015FJ0pvWVUfJZANzjwoO4YakM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 h1:j0VqrjtgsY1Bx27tD0ysay36/K4kFMWRp9K3ieO9nLU=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12/go.mod h1:00c7+ALdPh4YeEUPXJzyU0Yy01nPGOq2+9rUaz05z9g=
+github.com/aws/aws-sdk-go-v2/service/ecr v1.4.1/go.mod h1:FglZcyeiBqcbvyinl+n14aT/EWC7S1MIH+Gan2iizt0=
+github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5 h1:W9vzPbvX7rOa/FacbQIDfnNrwxHkn5O+DdfmiIS4cHc=
+github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5/go.mod h1:vk2+DbeZQFXznxJZSMnYrfnCHYxg4oT4Mdh59wSCkw4=
+github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.4.1/go.mod h1:eD5Eo4drVP2FLTw0G+SMIPWNWvQRGGTtIZR2XeAagoA=
+github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.5 h1:Y8dpvUxU4JecYktR5oNFEW+HmUWlA1Oh7mboTVyQWLg=
+github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.5/go.mod h1:gW979HGZOrhGvwjAS6VRgav6M9AYH9Kbey6y3GfF/EA=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 h1:gRW1ZisKc93EWEORNJRvy/ZydF3o6xLSveJHdi1Oa0U=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5/go.mod h1:ZbkttHXaVn3bBo/wpJbQGiiIWR90eTBUVBrEHUEQlho=
+github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 h1:AnTIdD439WgYNyVldYlpccGWY2EIXoUNmVzTDbFqCsg=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.6/go.mod h1:TFVe6Rr2joVLsYQ1ABACXgOC6lXip/qpX2x5jWg/A9w=
+github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 h1:aYToU0/iazkMY67/BYLt3r6/LT/mUtarLAF5mGof1Kg=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.6/go.mod h1:rP1rEOKAGZoXp4iGDxSXFvODAtXpm34Egf0lL0eshaQ=
+github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
+github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
+github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
+github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 h1:p2I85zYI9z5/c/3Q0LiO3RtNXcmXHTtJfml/hV16zNg=
+github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04/go.mod h1:Z+bXnIbhKJYSvxNwsNnwde7pDKxuqlEZCbUBoTwAqf0=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -147,14 +229,20 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
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/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec=
+github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
+github.com/breml/bidichk v0.1.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bshuster-repo/logrus-logstash-hook v1.0.2 h1:JYRWo+QGnQdedgshosug9hxpPYTB9oJ1ZZD3fY31alU=
github.com/bshuster-repo/logrus-logstash-hook v1.0.2/go.mod h1:HgYntJprnHSPaF9VPPPLP1L5S1vMWxRfa1J+vzDrDTw=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bugsnag/bugsnag-go v2.1.2+incompatible h1:E7dor84qzwUO8KdCM68CZwq9QOSR7HXlLx3Wj5vui2s=
github.com/bugsnag/bugsnag-go v2.1.2+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.3.4 h1:A6sXFtDGsgU/4BLf5JT0o5uYg3EeKgGx3Sfs+/uk3pU=
github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
+github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
@@ -164,6 +252,10 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
+github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg=
+github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU=
+github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 h1:9Qh4lJ/KMr5iS1zfZ8I97+3MDpiKjl+0lZVUNBhdvRs=
+github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08/go.mod h1:MAuu1uDJNOS3T3ui0qmKdPUwm59+bO19BbTph2wZafE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -177,37 +269,54 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4=
github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0=
github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0=
+github.com/containerd/stargz-snapshotter/estargz v0.11.4 h1:LjrYUZpyOhiSaU7hHrdR82/RBoxfGWSaC0VeSSMXqnk=
+github.com/containerd/stargz-snapshotter/estargz v0.11.4/go.mod h1:7vRJIcImfY8bpifnMjt+HTJoQxASq7T28MYbP15/Nf0=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
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=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
+github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/darkowlzz/controller-check v0.0.0-20220325122359-11f5827b7981 h1:4GBOSRDmbX+zPT0vV67ay6036Eqz1rh6kZGydsfyh3o=
github.com/darkowlzz/controller-check v0.0.0-20220325122359-11f5827b7981/go.mod h1:haYO9UW76kUUKpIBbv3ydaU5wZ/7r0yqp61PGzVRSYU=
+github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
+github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
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/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/distribution/v3 v3.0.0-20220702071910-8857a1948739 h1:fOBqIwS8s+ircSm/N6VQcIZPaFoomoAWgxwG2Ssp15I=
github.com/distribution/distribution/v3 v3.0.0-20220702071910-8857a1948739/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
@@ -217,6 +326,7 @@ github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -230,6 +340,7 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -251,7 +362,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
+github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -261,10 +375,14 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/fluxcd/gitkit v0.5.1 h1:kmpXs0g+eNuoq9CUzGppGadVF+c7j4n2kPYE/bvkMD0=
github.com/fluxcd/gitkit v0.5.1/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
github.com/fluxcd/pkg/apis/acl v0.0.3 h1:Lw0ZHdpnO4G7Zy9KjrzwwBmDZQuy4qEjaU/RvA6k1lc=
@@ -296,12 +414,15 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
+github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@@ -331,20 +452,39 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro=
+github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
+github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
+github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
+github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw=
+github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
+github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
+github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
+github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
+github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
+github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
+github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
+github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
@@ -355,19 +495,25 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE=
+github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -383,6 +529,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -402,6 +549,16 @@ 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.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
+github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
+github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
+github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
+github.com/golangci/golangci-lint v1.43.0/go.mod h1:VIFlUqidx5ggxDfQagdvd9E67UjMXtTHBkBQ7sHoC5Q=
+github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
+github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
+github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
+github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY=
+github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
@@ -411,8 +568,11 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w=
github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=
-github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
+github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
+github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs=
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
+github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -428,6 +588,12 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-containerregistry v0.10.0 h1:qd/fv2nQajGZJenaNcdaghlwSPjQ0NphN9hzArr2WWg=
+github.com/google/go-containerregistry v0.10.0/go.mod h1:C7uwbB1QUAtvnknyd3ethxJRd4gtEjU/9WLXzckfI1Y=
+github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20220712174516-ddd39fb9c385 h1:5YpLgrjMUhTXx6aQOHs7CmuleIwp0mLB8UcWH0IsSD8=
+github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20220712174516-ddd39fb9c385/go.mod h1:FUBeAeOrhHeM8/cPyFCp8WvdekKo05mh6GKvE60SC8I=
+github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20220523143934-b17c48b086b7 h1:b3NHmEfe3oGfuPaW8H5r92NWSK8bL50UVnxRWS+YQOE=
+github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20220523143934-b17c48b086b7/go.mod h1:hCxWNnETMVVnSa7iue+awKrZS87UPoqgKF8RNOQomPA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -444,6 +610,7 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -455,6 +622,9 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
+github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@@ -473,54 +643,92 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
+github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw=
+github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
+github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
+github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw=
+github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0=
+github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
+github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI=
+github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
+github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
+github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak=
+github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
+github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
+github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
+github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
+github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
@@ -530,12 +738,24 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
+github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
+github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
+github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
+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.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -546,8 +766,11 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0=
+github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
@@ -558,13 +781,18 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
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/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
-github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ=
+github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
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/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -577,53 +805,90 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
+github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
+github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0=
+github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88=
+github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
-github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
-github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
+github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
+github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
+github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy3EhK+0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
+github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.31 h1:zsJ3qPDeU3bC5UMVi9HJ4ED0lyEzrNd3iQguglZS5FE=
@@ -631,6 +896,7 @@ github.com/minio/minio-go/v7 v7.0.31/go.mod h1:/sjRKkKIA75CKh1iu8E3qBy7ktBmCCDGI
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
@@ -638,6 +904,7 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
@@ -646,7 +913,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
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/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
@@ -663,51 +932,74 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
+github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8=
+github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
+github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
+github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc=
+github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ=
+github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
-github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 h1:+czc/J8SlhPKLOtVLMQc+xDCFBT73ZStMsRhSsUhsSg=
+github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198/go.mod h1:j4h1pJW6ZcJTgMZWP3+7RlG3zTaP02aDZ/Qw0sppK7Q=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
+github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
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/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
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=
+github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pjbgf/git2go/v33 v33.0.9-nothread-check h1:gSK7FaLECIM3VSuBOAsVZQtWd+51iTB5lv9RyxhOYMk=
@@ -720,9 +1012,12 @@ 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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg=
github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
@@ -757,26 +1052,48 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA=
+github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
+github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
+github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30=
+github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ=
+github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
+github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
+github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc=
+github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
+github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
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 v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ=
github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
-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 v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
+github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
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/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg=
+github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
+github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=
+github.com/shirou/gopsutil/v3 v3.21.10/go.mod h1:t75NhzCZ/dYyPQjyQmrAYP6c8+LCdFANeBMdLPCNnew=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@@ -785,10 +1102,13 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
+github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
+github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
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=
@@ -797,6 +1117,8 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
@@ -805,17 +1127,23 @@ github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJ
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/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
+github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -826,8 +1154,35 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
+github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
+github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
+github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
+github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8=
+github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
+github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
+github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY=
+github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
+github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
+github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
+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.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
+github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
+github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
@@ -840,6 +1195,12 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
+github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yeya24/promlinter v0.1.0/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc=
+github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -856,7 +1217,10 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
+go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@@ -867,6 +1231,7 @@ go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46O
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
+go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -889,38 +1254,51 @@ go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16g
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-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=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -934,6 +1312,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -959,6 +1338,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -978,7 +1359,10 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -986,6 +1370,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1004,7 +1389,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
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-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1047,6 +1434,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1063,7 +1451,9 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1073,11 +1463,15 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1086,12 +1480,14 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
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-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1107,6 +1503,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1127,10 +1524,14 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1150,8 +1551,9 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJ
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1162,20 +1564,29 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -1185,8 +1596,14 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1194,9 +1611,11 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -1206,30 +1625,59 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
+golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1244,6 +1692,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
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=
@@ -1287,11 +1736,14 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1299,6 +1751,7 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@@ -1319,6 +1772,8 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -1376,15 +1831,19 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
@@ -1431,12 +1890,16 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/cheggaaa/pb.v1 v1.0.28/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.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
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.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
@@ -1452,6 +1915,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -1461,8 +1925,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
-gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
helm.sh/helm/v3 v3.9.1 h1:i1ChBu5ZB01kMaN2Y4KaC7J6viT58L2pHXWrXJ0Ny58=
helm.sh/helm/v3 v3.9.1/go.mod h1:y/dJc/0Lzcn40jgd85KQXnufhFF7sr4v6L/vYMLRaRM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1472,6 +1936,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI=
k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k=
@@ -1496,14 +1961,19 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
-k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU=
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk=
+k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6 h1:nBQrWPlrNIiw0BsX6a6MKr1itkm0ZS0Nl97kNLitFfI=
+k8s.io/kube-openapi v0.0.0-20220413171646-5e7f5fdc6da6/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M=
k8s.io/kubectl v0.24.2 h1:+RfQVhth8akUmIc2Ge8krMl/pt66V7210ka3RE/p0J4=
k8s.io/kubectl v0.24.2/go.mod h1:+HIFJc0bA6Tzu5O/YcuUt45APAxnNL8LeMuXwoiGsPg=
k8s.io/metrics v0.24.2/go.mod h1:5NWURxZ6Lz5gj8TFU83+vdWIVASx7W8lwPpHYCqopMo=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
+mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
+mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
+mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE=
oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4=
oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
@@ -1514,8 +1984,9 @@ sigs.k8s.io/cli-utils v0.31.2 h1:0yX0GPyvbc+yAEWwWlhgHlPF7JtvlLco6HjolSWewt4=
sigs.k8s.io/cli-utils v0.31.2/go.mod h1:g/zB9hJ5eUN7zIEBIxrO0CwhXU4YISJ+BkLJzvWwlEs=
sigs.k8s.io/controller-runtime v0.11.2 h1:H5GTxQl0Mc9UjRJhORusqfJCIjBO8UtUxGggCwL1rLA=
sigs.k8s.io/controller-runtime v0.11.2/go.mod h1:P6QCzrEjLaZGqHsfd+os7JQ+WFZhvB8MRFsn4dWF7O4=
-sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
+sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ=
+sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
sigs.k8s.io/kustomize/api v0.11.4 h1:/0Mr3kfBBNcNPOW5Qwk/3eb8zkswCwnqQxxKtmrTkRo=
sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI=
sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco=
@@ -1525,6 +1996,7 @@ sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2EC
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/main.go b/main.go
index 677b30314..621cea36c 100644
--- a/main.go
+++ b/main.go
@@ -357,7 +357,6 @@ func mustInitStorage(path string, storageAdvAddr string, artifactRetentionTTL ti
os.MkdirAll(path, 0o700)
}
- fmt.Println("PARHHHH", path)
storage, err := controllers.NewStorage(path, storageAdvAddr, artifactRetentionTTL, artifactRetentionRecords)
if err != nil {
l.Error(err, "unable to initialise storage")
From 799d7df4572da094d104b4fb44f87e50cf331802 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Thu, 28 Jul 2022 14:20:28 +0300
Subject: [PATCH 18/25] Add filter option when running tests Allow running
specific controller tests by specifying a prefix e.g. GO_TEST_PREFIX=TestOCI
make test-ctrl
Signed-off-by: Stefan Prodan
---
Makefile | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index ed634cb81..47b44a0a4 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,9 @@ LIBGIT2_TAG ?= v0.2.0
# Allows for defining additional Go test args, e.g. '-tags integration'.
GO_TEST_ARGS ?= -race
+# Allows for filtering tests based on the specified prefix
+GO_TEST_PREFIX ?=
+
# Allows for defining additional Docker buildx arguments,
# e.g. '--push'.
BUILD_ARGS ?=
@@ -69,7 +72,7 @@ build: check-deps $(LIBGIT2) ## Build manager binary
go build $(GO_STATIC_FLAGS) -o $(BUILD_DIR)/bin/manager main.go
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
-test: $(LIBGIT2) install-envtest test-api check-deps ## Run tests
+test: $(LIBGIT2) install-envtest test-api check-deps ## Run all tests
HTTPS_PROXY="" HTTP_PROXY="" \
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
GIT_CONFIG_GLOBAL=/dev/null \
@@ -78,6 +81,15 @@ test: $(LIBGIT2) install-envtest test-api check-deps ## Run tests
$(GO_TEST_ARGS) \
-coverprofile cover.out
+test-ctrl: $(LIBGIT2) install-envtest test-api check-deps ## Run controller tests
+ HTTPS_PROXY="" HTTP_PROXY="" \
+ KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
+ GIT_CONFIG_GLOBAL=/dev/null \
+ go test $(GO_STATIC_FLAGS) \
+ -run "^$(GO_TEST_PREFIX).*" \
+ -v ./controllers \
+ -coverprofile cover.out
+
check-deps:
ifeq ($(shell uname -s),Darwin)
if ! command -v pkg-config &> /dev/null; then echo "pkg-config is required"; exit 1; fi
From acc95d8c50cbc39ff51fe3d30a7114d632035ba2 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Thu, 28 Jul 2022 15:04:52 +0300
Subject: [PATCH 19/25] Add upstream source and revision to logs and events
Enrich the successful reconciliation event message with the upstream
opencontainers annotations
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 3 ---
controllers/ocirepository_controller.go | 22 +++++++++++++++++---
controllers/ocirepository_controller_test.go | 20 +++++++++++-------
docs/spec/v1beta2/ocirepositories.md | 6 +++---
4 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index 2c6df0911..e788d44b4 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -141,9 +141,6 @@ type OCIRepositoryStatus struct {
}
const (
- // OCIOperationSucceedReason signals that a Git operation (e.g. pull) succeeded.
- OCIOperationSucceedReason string = "OCIOperationSucceeded"
-
// OCIOperationFailedReason signals that an OCI operation (e.g. pull) failed.
OCIOperationFailedReason string = "OCIOperationFailed"
)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 54355c948..4170254e4 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -65,9 +65,11 @@ import (
)
const (
- ClientCert = "certFile"
- ClientKey = "keyFile"
- CACert = "caFile"
+ ClientCert = "certFile"
+ ClientKey = "keyFile"
+ CACert = "caFile"
+ OCISourceKey = "org.opencontainers.image.source"
+ OCIRevisionKey = "org.opencontainers.image.revision"
)
// ociRepositoryReadyCondition contains the information required to summarize a
@@ -829,6 +831,20 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", newObj.Status.Artifact.Revision, newObj.Spec.URL)
+ // enrich message with upstream annotations if found
+ if info := newObj.GetArtifact().Metadata; info != nil {
+ var source, revision string
+ if val, ok := info[OCISourceKey]; ok {
+ source = val
+ }
+ if val, ok := info[OCIRevisionKey]; ok {
+ revision = val
+ }
+ if source != "" && revision != "" {
+ message = fmt.Sprintf("%s, origin source '%s', origin revision '%s'", message, source, revision)
+ }
+ }
+
// Notify on new artifact and failure recovery.
if oldChecksum != newObj.GetArtifact().Checksum {
r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal,
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index eb1ce3fee..311f8b20f 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -172,8 +172,8 @@ func TestOCIRepository_Reconcile(t *testing.T) {
g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest))
// Check if the metadata matches the expected annotations
- g.Expect(obj.Status.Artifact.Metadata["org.opencontainers.image.source"]).To(ContainSubstring("podinfo"))
- g.Expect(obj.Status.Artifact.Metadata["org.opencontainers.image.revision"]).To(ContainSubstring(tt.tag))
+ g.Expect(obj.Status.Artifact.Metadata[OCISourceKey]).To(ContainSubstring("podinfo"))
+ g.Expect(obj.Status.Artifact.Metadata[OCIRevisionKey]).To(ContainSubstring(tt.tag))
// Check if the artifact storage path matches the expected file path
localPath := testStorage.LocalPath(*obj.Status.Artifact)
@@ -534,7 +534,6 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}
g.Expect(got).To(Equal(tt.want))
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
-
})
}
}
@@ -1263,9 +1262,16 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) {
resErr: nil,
newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
obj.Spec.URL = "oci://newurl.io"
- obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
+ obj.Status.Artifact = &sourcev1.Artifact{
+ Revision: "xxx",
+ Checksum: "yyy",
+ Metadata: map[string]string{
+ OCISourceKey: "https://github.com/stefanprodan/podinfo",
+ OCIRevisionKey: "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872",
+ },
+ }
},
- wantEvent: "Normal NewArtifact stored artifact with digest 'xxx' from 'oci://newurl.io'",
+ wantEvent: "Normal NewArtifact stored artifact with digest 'xxx' from 'oci://newurl.io', origin source 'https://github.com/stefanprodan/podinfo', origin revision '6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872'",
},
{
name: "recovery from failure",
@@ -1432,8 +1438,8 @@ func pushMultiplePodinfoImages(serverURL string, versions ...string) (map[string
func setPodinfoImageAnnotations(img gcrv1.Image, tag string) gcrv1.Image {
metadata := map[string]string{
- "org.opencontainers.image.source": "https://github.com/stefanprodan/podinfo",
- "org.opencontainers.image.revision": fmt.Sprintf("%s/SHA", tag),
+ OCISourceKey: "https://github.com/stefanprodan/podinfo",
+ OCIRevisionKey: fmt.Sprintf("%s/SHA", tag),
}
return mutate.Annotations(img, metadata).(gcrv1.Image)
}
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index 3b394dfa8..18e129ff6 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -31,7 +31,7 @@ In the above example:
by the `.spec.interval` field.
- It pulls the `latest` tag of the `ghcr.io/stefanprodan/manifests/podinfo`
repository, indicated by the `.spec.ref.tag` and `.spec.url` fields.
-- The specified tag and resolved digest are used as the Artifact
+- The resolved SHA256 digest is used as the Artifact
revision, reported in-cluster in the `.status.artifact.revision` field.
- When the current OCIRepository digest differs from the latest fetched
digest, a new Artifact is archived.
@@ -49,7 +49,7 @@ You can run this example by saving the manifest into `ocirepository.yaml`.
```console
NAME URL AGE READY STATUS
- podinfo oci://ghcr.io/stefanprodan/manifests/podinfo 5s True stored artifact for revision '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
+ podinfo oci://ghcr.io/stefanprodan/manifests/podinfo 5s True stored artifact with digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
```
3. Run `kubectl describe ocirepository podinfo` to see the [Artifact](#artifact)
@@ -63,7 +63,7 @@ You can run this example by saving the manifest into `ocirepository.yaml`.
Last Update Time: 2022-06-14T11:23:36Z
Path: ocirepository/default/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.gz
Revision: 3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de
- URL: http://source-controller.flux-system.svc.cluster.local./ocirepository/oci/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.g
+ URL: http://source-controller.flux-system.svc.cluster.local./ocirepository/oci/podinfo/3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de.tar.gz
Conditions:
Last Transition Time: 2022-06-14T11:23:36Z
Message: stored artifact for digest '3b6cdcc7adcc9a84d3214ee1c029543789d90b5ae69debe9efa3f66e982875de'
From 8cc8798e6e88b742e3ae074921000c54b28befaa Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 2 Aug 2022 13:28:50 +0300
Subject: [PATCH 20/25] Add the provider field to the OCIRepository API
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 24 ++++++++++++++++-
...rce.toolkit.fluxcd.io_ocirepositories.yaml | 12 ++++++++-
docs/api/source.md | 26 +++++++++++++++++++
3 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index e788d44b4..af94b41c6 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -30,13 +30,28 @@ const (
// OCIRepositoryPrefix is the prefix used for OCIRepository URLs.
OCIRepositoryPrefix = "oci://"
+
+ // GenericOCIProvider provides support for authentication using static credentials
+ // for any OCI compatible API such as Docker Registry, GitHub Container Registry,
+ // Docker Hub, Quay, etc.
+ GenericOCIProvider string = "generic"
+
+ // AmazonOCIProvider provides support for OCI authentication using AWS IRSA.
+ AmazonOCIProvider string = "aws"
+
+ // GoogleOCIProvider provides support for OCI authentication using GCP workload identity.
+ GoogleOCIProvider string = "gcp"
+
+ // AzureOCIProvider provides support for OCI authentication using a Azure Service Principal,
+ // Managed Identity or Shared Key.
+ AzureOCIProvider string = "azure"
)
// OCIRepositorySpec defines the desired state of OCIRepository
type OCIRepositorySpec struct {
// URL is a reference to an OCI artifact repository hosted
// on a remote container registry.
- // +kubebuilder:validation:Pattern="^oci://"
+ // +kubebuilder:validation:Pattern="^oci://.*$"
// +required
URL string `json:"url"`
@@ -45,6 +60,13 @@ type OCIRepositorySpec struct {
// +optional
Reference *OCIRepositoryRef `json:"ref,omitempty"`
+ // The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.
+ // When not specified, defaults to 'generic'.
+ // +kubebuilder:validation:Enum=generic;aws;azure;gcp
+ // +kubebuilder:default:=generic
+ // +optional
+ Provider string `json:"provider,omitempty"`
+
// SecretRef contains the secret name containing the registry login
// credentials to resolve image metadata.
// The secret must be of type kubernetes.io/dockerconfigjson.
diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
index deb7fb454..5e214ccd8 100644
--- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
+++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
@@ -75,6 +75,16 @@ spec:
interval:
description: The interval at which to check for image updates.
type: string
+ provider:
+ default: generic
+ description: The provider used for authentication, can be 'aws', 'azure',
+ 'gcp' or 'generic'. When not specified, defaults to 'generic'.
+ enum:
+ - generic
+ - aws
+ - azure
+ - gcp
+ type: string
ref:
description: The OCI reference to pull and monitor for changes, defaults
to the latest tag.
@@ -119,7 +129,7 @@ spec:
url:
description: URL is a reference to an OCI artifact repository hosted
on a remote container registry.
- pattern: ^oci://
+ pattern: ^oci://.*$
type: string
required:
- interval
diff --git a/docs/api/source.md b/docs/api/source.md
index c82525e65..09f072743 100644
--- a/docs/api/source.md
+++ b/docs/api/source.md
@@ -968,6 +968,19 @@ defaults to the latest tag.
+provider
+
+string
+
+ |
+
+(Optional)
+ The provider used for authentication, can be ‘aws’, ‘azure’, ‘gcp’ or ‘generic’.
+When not specified, defaults to ‘generic’.
+ |
+
+
+
secretRef
@@ -2621,6 +2634,19 @@ defaults to the latest tag.
|
+provider
+
+string
+
+ |
+
+(Optional)
+ The provider used for authentication, can be ‘aws’, ‘azure’, ‘gcp’ or ‘generic’.
+When not specified, defaults to ‘generic’.
+ |
+
+
+
secretRef
From 63c94397f7d756518d259f4703ac92900d2dd07e Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Tue, 2 Aug 2022 16:23:59 +0300
Subject: [PATCH 21/25] Implement OCI auth for cloud providers
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 96 +++++++++++++-------
controllers/ocirepository_controller_test.go | 38 ++++----
docs/spec/v1beta2/ocirepositories.md | 27 ++++++
go.mod | 14 +--
go.sum | 25 +++--
5 files changed, 137 insertions(+), 63 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 4170254e4..377dc2111 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -50,6 +50,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
"github.com/fluxcd/pkg/apis/meta"
+ "github.com/fluxcd/pkg/oci"
+ "github.com/fluxcd/pkg/oci/auth/login"
"github.com/fluxcd/pkg/runtime/conditions"
helper "github.com/fluxcd/pkg/runtime/controller"
"github.com/fluxcd/pkg/runtime/events"
@@ -64,14 +66,6 @@ import (
"github.com/fluxcd/source-controller/internal/util"
)
-const (
- ClientCert = "certFile"
- ClientKey = "keyFile"
- CACert = "caFile"
- OCISourceKey = "org.opencontainers.image.source"
- OCIRevisionKey = "org.opencontainers.image.revision"
-)
-
// ociRepositoryReadyCondition contains the information required to summarize a
// v1beta2.OCIRepository Ready Condition.
var ociRepositoryReadyCondition = summarize.Conditions{
@@ -297,7 +291,9 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
defer cancel()
- // Generate the registry credential keychain
+ options := r.craneOptions(ctxTimeout)
+
+ // Generate the registry credential keychain either from static credentials or using cloud OIDC
keychain, err := r.keychain(ctx, obj)
if err != nil {
e := serror.NewGeneric(
@@ -307,6 +303,22 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
+ options = append(options, crane.WithAuthFromKeychain(keychain))
+
+ if obj.Spec.Provider != sourcev1.GenericOCIProvider {
+ auth, authErr := r.oidcAuth(ctxTimeout, obj)
+ if authErr != nil && !errors.Is(authErr, oci.ErrUnconfiguredProvider) {
+ e := serror.NewGeneric(
+ fmt.Errorf("failed to get credential from %s: %w", obj.Spec.Provider, authErr),
+ sourcev1.AuthenticationFailedReason,
+ )
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+ if auth != nil {
+ options = append(options, crane.WithAuth(auth))
+ }
+ }
// Generate the transport for remote operations
transport, err := r.transport(ctx, obj)
@@ -318,9 +330,12 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
+ if transport != nil {
+ options = append(options, crane.WithTransport(transport))
+ }
// Determine which artifact revision to pull
- url, err := r.getArtifactURL(ctxTimeout, obj, keychain, transport)
+ url, err := r.getArtifactURL(obj, options)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err),
@@ -330,7 +345,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
}
// Pull artifact from the remote container registry
- img, err := crane.Pull(url, r.craneOptions(ctxTimeout, keychain, transport)...)
+ img, err := crane.Pull(url, options...)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
@@ -437,12 +452,16 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
return "", err
}
+ imageName := strings.TrimPrefix(url, ref.Context().RegistryStr())
+ if s := strings.Split(imageName, ":"); len(s) > 1 {
+ return "", fmt.Errorf("URL must not contain a tag; remove ':%s'", s[1])
+ }
+
return ref.Context().Name(), nil
}
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
-func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
- obj *sourcev1.OCIRepository, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
+func (r *OCIRepositoryReconciler) getArtifactURL(obj *sourcev1.OCIRepository, options []crane.Option) (string, error) {
url, err := r.parseRepositoryURL(obj)
if err != nil {
return "", err
@@ -454,7 +473,7 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
}
if obj.Spec.Reference.SemVer != "" {
- tag, err := r.getTagBySemver(ctx, url, obj.Spec.Reference.SemVer, keychain, transport)
+ tag, err := r.getTagBySemver(url, obj.Spec.Reference.SemVer, options)
if err != nil {
return "", err
}
@@ -471,9 +490,8 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context,
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
// and returns the latest tag according to the semver expression.
-func (r *OCIRepositoryReconciler) getTagBySemver(ctx context.Context,
- url, exp string, keychain authn.Keychain, transport http.RoundTripper) (string, error) {
- tags, err := crane.ListTags(url, r.craneOptions(ctx, keychain, transport)...)
+func (r *OCIRepositoryReconciler) getTagBySemver(url, exp string, options []crane.Option) (string, error) {
+ tags, err := crane.ListTags(url, options...)
if err != nil {
return "", err
}
@@ -567,20 +585,20 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
transport := remote.DefaultTransport.Clone()
tlsConfig := transport.TLSClientConfig
- if clientCert, ok := certSecret.Data[ClientCert]; ok {
+ if clientCert, ok := certSecret.Data[oci.ClientCert]; ok {
// parse and set client cert and secret
- if clientKey, ok := certSecret.Data[ClientKey]; ok {
+ if clientKey, ok := certSecret.Data[oci.ClientKey]; ok {
cert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
} else {
- return nil, fmt.Errorf("'%s' found in secret, but no %s", ClientCert, ClientKey)
+ return nil, fmt.Errorf("'%s' found in secret, but no %s", oci.ClientCert, oci.ClientKey)
}
}
- if caCert, ok := certSecret.Data[CACert]; ok {
+ if caCert, ok := certSecret.Data[oci.CACert]; ok {
syscerts, err := x509.SystemCertPool()
if err != nil {
return nil, err
@@ -592,20 +610,34 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
}
+// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
+func (r *OCIRepositoryReconciler) oidcAuth(ctx context.Context, obj *sourcev1.OCIRepository) (authn.Authenticator, error) {
+ url := strings.TrimPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix)
+ ref, err := name.ParseReference(url)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse URL '%s': %w", obj.Spec.URL, err)
+ }
+
+ opts := login.ProviderOptions{}
+ switch obj.Spec.Provider {
+ case sourcev1.AmazonOCIProvider:
+ opts.AwsAutoLogin = true
+ case sourcev1.AzureOCIProvider:
+ opts.AzureAutoLogin = true
+ case sourcev1.GoogleOCIProvider:
+ opts.GcpAutoLogin = true
+ }
+
+ return login.NewManager().Login(ctx, url, ref, opts)
+}
+
// craneOptions sets the auth headers, timeout and user agent
// for all operations against remote container registries.
-func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context,
- keychain authn.Keychain, transport http.RoundTripper) []crane.Option {
+func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context) []crane.Option {
options := []crane.Option{
crane.WithContext(ctx),
- crane.WithUserAgent("flux/v2"),
- crane.WithAuthFromKeychain(keychain),
+ crane.WithUserAgent(oci.UserAgent),
}
-
- if transport != nil {
- options = append(options, crane.WithTransport(transport))
- }
-
return options
}
@@ -834,10 +866,10 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context,
// enrich message with upstream annotations if found
if info := newObj.GetArtifact().Metadata; info != nil {
var source, revision string
- if val, ok := info[OCISourceKey]; ok {
+ if val, ok := info[oci.SourceAnnotation]; ok {
source = val
}
- if val, ok := info[OCIRevisionKey]; ok {
+ if val, ok := info[oci.RevisionAnnotation]; ok {
revision = val
}
if source != "" && revision != "" {
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 311f8b20f..b312fe8b9 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -36,11 +36,9 @@ import (
"testing"
"time"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/client-go/tools/record"
-
"github.com/darkowlzz/controller-check/status"
"github.com/fluxcd/pkg/apis/meta"
+ "github.com/fluxcd/pkg/oci"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/untar"
@@ -54,8 +52,10 @@ import (
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
. "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/tools/record"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/controller-runtime/pkg/client"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -172,8 +172,8 @@ func TestOCIRepository_Reconcile(t *testing.T) {
g.Expect(obj.Status.Artifact.Revision).To(Equal(tt.digest))
// Check if the metadata matches the expected annotations
- g.Expect(obj.Status.Artifact.Metadata[OCISourceKey]).To(ContainSubstring("podinfo"))
- g.Expect(obj.Status.Artifact.Metadata[OCIRevisionKey]).To(ContainSubstring(tt.tag))
+ g.Expect(obj.Status.Artifact.Metadata[oci.SourceAnnotation]).To(ContainSubstring("podinfo"))
+ g.Expect(obj.Status.Artifact.Metadata[oci.RevisionAnnotation]).To(ContainSubstring(tt.tag))
// Check if the artifact storage path matches the expected file path
localPath := testStorage.LocalPath(*obj.Status.Artifact)
@@ -516,7 +516,9 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
Storage: testStorage,
}
- repoURL, err := r.getArtifactURL(ctx, obj, nil, nil)
+ opts := r.craneOptions(ctx)
+ opts = append(opts, crane.WithAuthFromKeychain(authn.DefaultKeychain))
+ repoURL, err := r.getArtifactURL(obj, opts)
g.Expect(err).To(BeNil())
assertConditions := tt.assertConditions
@@ -566,9 +568,9 @@ func TestOCIRepository_CertSecret(t *testing.T) {
tlsSecretClientCert := corev1.Secret{
StringData: map[string]string{
- CACert: string(rootCertPEM),
- ClientCert: string(clientCertPEM),
- ClientKey: string(clientKeyPEM),
+ oci.CACert: string(rootCertPEM),
+ oci.ClientCert: string(clientCertPEM),
+ oci.ClientKey: string(clientKeyPEM),
},
}
@@ -601,9 +603,9 @@ func TestOCIRepository_CertSecret(t *testing.T) {
digest: pi.digest,
certSecret: &corev1.Secret{
StringData: map[string]string{
- CACert: string(rootCertPEM),
- ClientCert: string(clientCertPEM),
- ClientKey: string("invalid-key"),
+ oci.CACert: string(rootCertPEM),
+ oci.ClientCert: string(clientCertPEM),
+ oci.ClientKey: string("invalid-key"),
},
},
expectreadyconition: false,
@@ -1049,7 +1051,9 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
obj.Spec.Reference = tt.reference
}
- got, err := r.getArtifactURL(ctx, obj, authn.DefaultKeychain, nil)
+ opts := r.craneOptions(ctx)
+ opts = append(opts, crane.WithAuthFromKeychain(authn.DefaultKeychain))
+ got, err := r.getArtifactURL(obj, opts)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
@@ -1266,8 +1270,8 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) {
Revision: "xxx",
Checksum: "yyy",
Metadata: map[string]string{
- OCISourceKey: "https://github.com/stefanprodan/podinfo",
- OCIRevisionKey: "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872",
+ oci.SourceAnnotation: "https://github.com/stefanprodan/podinfo",
+ oci.RevisionAnnotation: "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872",
},
}
},
@@ -1438,8 +1442,8 @@ func pushMultiplePodinfoImages(serverURL string, versions ...string) (map[string
func setPodinfoImageAnnotations(img gcrv1.Image, tag string) gcrv1.Image {
metadata := map[string]string{
- OCISourceKey: "https://github.com/stefanprodan/podinfo",
- OCIRevisionKey: fmt.Sprintf("%s/SHA", tag),
+ oci.SourceAnnotation: "https://github.com/stefanprodan/podinfo",
+ oci.RevisionAnnotation: fmt.Sprintf("%s/SHA", tag),
}
return mutate.Annotations(img, metadata).(gcrv1.Image)
}
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index 18e129ff6..76832288a 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -101,6 +101,33 @@ container image repository in the format `oci://://
Date: Thu, 4 Aug 2022 15:59:46 +0300
Subject: [PATCH 22/25] Mark resource as stalled on invalid URL
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 22 ++++++++--
controllers/ocirepository_controller_test.go | 44 +++++++++++++++++++-
2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index 377dc2111..df717f29d 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -102,6 +102,14 @@ var ociRepositoryFailConditions = []string{
sourcev1.StorageOperationFailedCondition,
}
+type invalidOCIURLError struct {
+ err error
+}
+
+func (e invalidOCIURLError) Error() string {
+ return e.err.Error()
+}
+
// ociRepositoryReconcileFunc is the function type for all the v1beta2.OCIRepository
// (sub)reconcile functions. The type implementations are grouped and
// executed serially to perform the complete reconcile of the object.
@@ -337,9 +345,17 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Determine which artifact revision to pull
url, err := r.getArtifactURL(obj, options)
if err != nil {
+ if _, ok := err.(invalidOCIURLError); ok {
+ e := serror.NewStalling(
+ fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err),
+ sourcev1.URLInvalidReason)
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
+ return sreconcile.ResultEmpty, e
+ }
+
e := serror.NewGeneric(
- fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err),
- sourcev1.URLInvalidReason)
+ fmt.Errorf("failed to determine the artifact tag for '%s': %w", obj.Spec.URL, err),
+ sourcev1.OCIOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
@@ -464,7 +480,7 @@ func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository
func (r *OCIRepositoryReconciler) getArtifactURL(obj *sourcev1.OCIRepository, options []crane.Option) (string, error) {
url, err := r.parseRepositoryURL(obj)
if err != nil {
- return "", err
+ return "", invalidOCIURLError{err}
}
if obj.Spec.Reference != nil {
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index b312fe8b9..9e54e3260 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -772,7 +772,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.URLInvalidReason, "no match found for semver:"),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to determine the artifact tag for 'oci://%s/podinfo': no match found for semver: <= 6.1.0", server.registryHost),
},
},
{
@@ -1064,6 +1064,48 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
}
}
+func TestOCIRepository_stalled(t *testing.T) {
+ g := NewWithT(t)
+
+ ns, err := testEnv.CreateNamespace(ctx, "ocirepository-stalled-test")
+ g.Expect(err).ToNot(HaveOccurred())
+ defer func() { g.Expect(testEnv.Delete(ctx, ns)).To(Succeed()) }()
+
+ obj := &sourcev1.OCIRepository{
+ ObjectMeta: metav1.ObjectMeta{
+ GenerateName: "ocirepository-reconcile",
+ Namespace: ns.Name,
+ },
+ Spec: sourcev1.OCIRepositorySpec{
+ URL: "oci://ghcr.io/test/test:v1",
+ Interval: metav1.Duration{Duration: 60 * time.Minute},
+ },
+ }
+
+ g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
+
+ key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
+ resultobj := sourcev1.OCIRepository{}
+
+ // Wait for the object to fail
+ g.Eventually(func() bool {
+ if err := testEnv.Get(ctx, key, &resultobj); err != nil {
+ return false
+ }
+ readyCondition := conditions.Get(&resultobj, meta.ReadyCondition)
+ if readyCondition == nil {
+ return false
+ }
+ return obj.Generation == readyCondition.ObservedGeneration &&
+ !conditions.IsReady(&resultobj)
+ }, timeout).Should(BeTrue())
+
+ // Verify that stalled condition is present in status
+ stalledCondition := conditions.Get(&resultobj, meta.StalledCondition)
+ g.Expect(stalledCondition).ToNot(BeNil())
+ g.Expect(stalledCondition.Reason).Should(Equal(sourcev1.URLInvalidReason))
+}
+
func TestOCIRepository_reconcileStorage(t *testing.T) {
g := NewWithT(t)
From 196641147eac285e5569e43823b3fe994b8a27e2 Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Fri, 5 Aug 2022 12:21:47 +0300
Subject: [PATCH 23/25] API docs improvements
Co-authored-by: Paulo Gomes
Signed-off-by: Stefan Prodan
---
docs/spec/v1beta2/ocirepositories.md | 32 ++++++++++++++--------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index 76832288a..1b5e8bb03 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -5,7 +5,7 @@ repository.
## Example
-The following is an example of a OCIRepository. It creates a tarball
+The following is an example of an OCIRepository. It creates a tarball
(`.tar.gz`) Artifact with the fetched data from an OCI repository for the
resolved digest.
@@ -25,7 +25,7 @@ spec:
In the above example:
-- A OCIRepository named `podinfo` is created, indicated by the
+- An OCIRepository named `podinfo` is created, indicated by the
`.metadata.name` field.
- The source-controller checks the OCI repository every five minutes, indicated
by the `.spec.interval` field.
@@ -87,11 +87,11 @@ You can run this example by saving the manifest into `ocirepository.yaml`.
## Writing an OCIRepository spec
-As with all other Kubernetes config, a OCIRepository needs `apiVersion`,
-`kind`, and `metadata` fields. The name of a OCIRepository object must be a
+As with all other Kubernetes config, an OCIRepository needs `apiVersion`,
+`kind`, and `metadata` fields. The name of an OCIRepository object must be a
valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names#dns-subdomain-names).
-A OCIRepository also needs a
+An OCIRepository also needs a
[`.spec` section](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status).
### URL
@@ -99,7 +99,7 @@ A OCIRepository also needs a
`.spec.url` is a required field that specifies the address of the
container image repository in the format `oci://://`.
-**Note:** that specifying a tag or digest is not in accepted for this field.
+**Note:** that specifying a tag or digest is not acceptable for this field.
### Provider
@@ -119,13 +119,13 @@ static credentials are used for authentication, either with
If you do not specify `.spec.provider`, it defaults to `generic`.
The `aws` provider can be used when the source-controller service account
-is associate with an AWS IAM Role using IRSA that grants read-only access to ECR.
+is associated with an AWS IAM Role using IRSA that grants read-only access to ECR.
-The `azure` provider can be used when the source-controller pods are associate
+The `azure` provider can be used when the source-controller pods are associated
with an Azure AAD Pod Identity that grants read-only access to ACR.
The `gcp` provider can be used when the source-controller service account
-is associate with a GCP IAM Role using Workload Identity that grants
+is associated with a GCP IAM Role using Workload Identity that grants
read-only access to Artifact Registry.
### Secret reference
@@ -134,7 +134,7 @@ read-only access to Artifact Registry.
Secret in the same namespace as the OCIRepository, containing authentication
credentials for the OCI repository.
-This secret is expected to be in the same format as for[`imagePullSecrets`][image-pull-secrets].
+This secret is expected to be in the same format as [`imagePullSecrets`][image-pull-secrets].
The usual way to create such a secret is with:
```sh
@@ -315,7 +315,7 @@ spec:
### Triggering a reconcile
To manually tell the source-controller to reconcile a OCIRepository outside the
-[specified interval window](#interval), a OCIRepository can be annotated with
+[specified interval window](#interval), an OCIRepository can be annotated with
`reconcile.fluxcd.io/requestedAt: `. Annotating the resource
queues the OCIRepository for reconciliation if the `` differs
from the last value the controller acted on, as reported in
@@ -345,7 +345,7 @@ kubectl wait gitrepository/ --for=condition=ready --timeout=1m
### Suspending and resuming
When you find yourself in a situation where you temporarily want to pause the
-reconciliation of a OCIRepository, you can suspend it using the
+reconciliation of an OCIRepository, you can suspend it using the
[`.spec.suspend` field](#suspend).
#### Suspend an OCIRepository
@@ -374,7 +374,7 @@ Using `flux`:
flux suspend source oci
```
-**Note:** When a OCIRepository has an Artifact and is suspended, and this
+**Note:** When an OCIRepository has an Artifact and it is suspended, and this
Artifact later disappears from the storage due to e.g. the source-controller
Pod being evicted from a Node, this will not be reflected in the
OCIRepository's Status until it is resumed.
@@ -519,7 +519,7 @@ To define your own exclusion rules, see [excluding files](#excluding-files).
### Conditions
-A OCIRepository enters various states during its lifecycle, reflected as
+OCIRepository has various states during its lifecycle, reflected as
[Kubernetes Conditions][typical-status-properties].
It can be [reconciling](#reconciling-ocirepository) while fetching the remote
state, it can be [ready](#ready-ocirepository), or it can [fail during
@@ -532,7 +532,7 @@ become `Ready`.
#### Reconciling OCIRepository
-The source-controller marks a OCIRepository as _reconciling_ when one of the
+The source-controller marks an OCIRepository as _reconciling_ when one of the
following is true:
- There is no current Artifact for the OCIRepository, or the reported Artifact
@@ -561,7 +561,7 @@ and are only present on the OCIRepository while their status value is `"True"`.
#### Ready OCIRepository
-The source-controller marks a OCIRepository as _ready_ when it has the
+The source-controller marks an OCIRepository as _ready_ when it has the
following characteristics:
- The OCIRepository reports an [Artifact](#artifact).
From 1a59935858907e9418ef63cd33d85c50e81d8f2d Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Fri, 5 Aug 2022 12:42:17 +0300
Subject: [PATCH 24/25] Add OCI failure reasons to API
Signed-off-by: Stefan Prodan
---
api/v1beta2/ocirepository_types.go | 7 +++++--
controllers/ocirepository_controller.go | 18 +++++++++---------
controllers/ocirepository_controller_test.go | 20 ++++++++++----------
docs/spec/v1beta2/ocirepositories.md | 2 +-
4 files changed, 25 insertions(+), 22 deletions(-)
diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go
index af94b41c6..83ff7f3ff 100644
--- a/api/v1beta2/ocirepository_types.go
+++ b/api/v1beta2/ocirepository_types.go
@@ -163,8 +163,11 @@ type OCIRepositoryStatus struct {
}
const (
- // OCIOperationFailedReason signals that an OCI operation (e.g. pull) failed.
- OCIOperationFailedReason string = "OCIOperationFailed"
+ // OCIPullFailedReason signals that a pull operation failed.
+ OCIPullFailedReason string = "OCIArtifactPullFailed"
+
+ // OCILayerOperationFailedReason signals that an OCI layer operation failed.
+ OCILayerOperationFailedReason string = "OCIArtifactLayerOperationFailed"
)
// GetConditions returns the status conditions of the object.
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index df717f29d..e47c743c9 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -333,7 +333,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to generate transport for '%s': %w", obj.Spec.URL, err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.AuthenticationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -355,7 +355,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
e := serror.NewGeneric(
fmt.Errorf("failed to determine the artifact tag for '%s': %w", obj.Spec.URL, err),
- sourcev1.OCIOperationFailedReason)
+ sourcev1.ReadOperationFailedReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
@@ -365,7 +365,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCIPullFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -376,7 +376,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to determine artifact digest: %w", err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -390,7 +390,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to parse artifact manifest: %w", err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -417,7 +417,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to parse artifact layers: %w", err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -426,7 +426,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if len(layers) < 1 {
e := serror.NewGeneric(
fmt.Errorf("no layers found in artifact"),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -436,7 +436,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to extract the first layer from artifact: %w", err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
@@ -445,7 +445,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if _, err = untar.Untar(blob, dir); err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to untar the first layer from artifact: %w", err),
- sourcev1.OCIOperationFailedReason,
+ sourcev1.OCILayerOperationFailedReason,
)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go
index 9e54e3260..b72413b1f 100644
--- a/controllers/ocirepository_controller_test.go
+++ b/controllers/ocirepository_controller_test.go
@@ -329,7 +329,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}),
},
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
},
},
{
@@ -350,7 +350,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
includeSecret: true,
},
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
},
},
{
@@ -371,7 +371,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
includeSA: true,
},
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
},
},
{
@@ -413,7 +413,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
}),
},
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
},
},
{
@@ -438,7 +438,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
},
},
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact from "),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
},
},
}
@@ -761,7 +761,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact"),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact"),
},
},
{
@@ -772,7 +772,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to determine the artifact tag for 'oci://%s/podinfo': no match found for semver: <= 6.1.0", server.registryHost),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.ReadOperationFailedReason, "failed to determine the artifact tag for 'oci://%s/podinfo': no match found for semver: <= 6.1.0", server.registryHost),
},
},
{
@@ -783,7 +783,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
want: sreconcile.ResultEmpty,
wantErr: true,
assertConditions: []metav1.Condition{
- *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "failed to pull artifact"),
+ *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact"),
},
},
{
@@ -1325,7 +1325,7 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) {
resErr: nil,
oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "fail")
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.ReadOperationFailedReason, "fail")
conditions.MarkFalse(obj, meta.ReadyCondition, meta.FailedReason, "foo")
},
newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
@@ -1341,7 +1341,7 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) {
resErr: nil,
oldObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
obj.Status.Artifact = &sourcev1.Artifact{Revision: "xxx", Checksum: "yyy"}
- conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.OCIOperationFailedReason, "fail")
+ conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.ReadOperationFailedReason, "fail")
conditions.MarkFalse(obj, meta.ReadyCondition, meta.FailedReason, "foo")
},
newObjBeforeFunc: func(obj *sourcev1.OCIRepository) {
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index 1b5e8bb03..ac1193032 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -613,7 +613,7 @@ and adds a Condition with the following attributes to the OCIRepository's
- `type: FetchFailed` | `type: IncludeUnavailable` | `type: StorageOperationFailed`
- `status: "True"`
-- `reason: AuthenticationFailed` | `reason: OCIOperationFailed`
+- `reason: AuthenticationFailed` | `reason: OCIArtifactPullFailed` | `reason: OCIArtifactLayerOperationFailed`
This condition has a ["negative polarity"][typical-status-properties],
and is only present on the OCIRepository while the status value is `"True"`.
From 94e98ee5cad52426730d2af606edda9d56bb498f Mon Sep 17 00:00:00 2001
From: Stefan Prodan
Date: Mon, 8 Aug 2022 12:58:04 +0300
Subject: [PATCH 25/25] Add the opencontainers annotations to API docs
Signed-off-by: Stefan Prodan
---
controllers/ocirepository_controller.go | 4 ++--
docs/spec/v1beta2/ocirepositories.md | 21 +++++++++++++++++----
go.mod | 2 +-
go.sum | 4 ++--
4 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go
index e47c743c9..f9e408ed3 100644
--- a/controllers/ocirepository_controller.go
+++ b/controllers/ocirepository_controller.go
@@ -200,7 +200,7 @@ func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques
summarize.WithReconcileError(retErr),
summarize.WithIgnoreNotFound(),
summarize.WithProcessors(
- summarize.RecordContextualError,
+ summarize.ErrorActionHandler,
summarize.RecordReconcileReq,
),
summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetRequeueAfter()}),
@@ -347,7 +347,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
if err != nil {
if _, ok := err.(invalidOCIURLError); ok {
e := serror.NewStalling(
- fmt.Errorf("failed to determine the artifact address for '%s': %w", obj.Spec.URL, err),
+ fmt.Errorf("URL validation failed for '%s': %w", obj.Spec.URL, err),
sourcev1.URLInvalidReason)
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
diff --git a/docs/spec/v1beta2/ocirepositories.md b/docs/spec/v1beta2/ocirepositories.md
index ac1193032..9f3842a57 100644
--- a/docs/spec/v1beta2/ocirepositories.md
+++ b/docs/spec/v1beta2/ocirepositories.md
@@ -484,23 +484,36 @@ specific OCIRepository, e.g.
The OCIRepository reports the latest synchronized state from the OCI repository
as an Artifact object in the `.status.artifact` of the resource.
+The `.status.artifact.revision` holds the SHA256 digest of the upstream OCI artifact.
+
+The `.status.artifact.metadata` holds the upstream OCI artifact metadata such as the
+[OpenContainers standard annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md).
+If the OCI artifact was created with `flux push artifact`, then the `metadata` will contain the following
+annotations:
+- `org.opencontainers.image.created` the date and time on which the artifact was built
+- `org.opencontainers.image.source` the URL of the Git repository containing the source files
+- `org.opencontainers.image.revision` the Git branch and commit SHA1 of the source files
+
The Artifact file is a gzip compressed TAR archive (`.tar.gz`), and
can be retrieved in-cluster from the `.status.artifact.url` HTTP address.
#### Artifact example
```yaml
----
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name:
status:
artifact:
- checksum: e750c7a46724acaef8f8aa926259af30bbd9face2ae065ae8896ba5ee5ab832b
- lastUpdateTime: "2022-06-29T06:59:23Z"
+ checksum: 9f3bc0f341d4ecf2bab460cc59320a2a9ea292f01d7b96e32740a9abfd341088
+ lastUpdateTime: "2022-08-08T09:35:45Z"
+ metadata:
+ org.opencontainers.image.created: "2022-08-08T12:31:41+03:00"
+ org.opencontainers.image.revision: 6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872
+ org.opencontainers.image.source: https://github.com/stefanprodan/podinfo.git
path: ocirepository///.tar.gz
- revision: master/363a6a8fe6a7f13e05d34c163b0ef02a777da20a
+ revision:
url: http://source-controller..svc.cluster.local./ocirepository///.tar.gz
```
diff --git a/go.mod b/go.mod
index 854fb2ea1..bc797cf44 100644
--- a/go.mod
+++ b/go.mod
@@ -37,7 +37,7 @@ require (
github.com/fluxcd/pkg/gitutil v0.1.0
github.com/fluxcd/pkg/helmtestserver v0.7.4
github.com/fluxcd/pkg/lockedfile v0.1.0
- github.com/fluxcd/pkg/oci v0.2.0
+ github.com/fluxcd/pkg/oci v0.3.0
github.com/fluxcd/pkg/runtime v0.16.2
github.com/fluxcd/pkg/ssh v0.5.0
github.com/fluxcd/pkg/testserver v0.2.0
diff --git a/go.sum b/go.sum
index ee588c467..9f48523b7 100644
--- a/go.sum
+++ b/go.sum
@@ -399,8 +399,8 @@ github.com/fluxcd/pkg/helmtestserver v0.7.4 h1:/Xj2+XLz7wr38MI3uPYvVAsZB9wQOq6rp
github.com/fluxcd/pkg/helmtestserver v0.7.4/go.mod h1:aL5V4o8wUOMqeHMfjbVHS057E3ejzHMRVMqEbsK9FUQ=
github.com/fluxcd/pkg/lockedfile v0.1.0 h1:YsYFAkd6wawMCcD74ikadAKXA4s2sukdxrn7w8RB5eo=
github.com/fluxcd/pkg/lockedfile v0.1.0/go.mod h1:EJLan8t9MiOcgTs8+puDjbE6I/KAfHbdvIy9VUgIjm8=
-github.com/fluxcd/pkg/oci v0.2.0 h1:pvLF6iKmSj9u48Da7qlBDVIiH2NLOrbFUFE4Yr431Lc=
-github.com/fluxcd/pkg/oci v0.2.0/go.mod h1:c1pj9E/G5927gSa6ooACAyZe+HwjgmPk9johL7oXDHw=
+github.com/fluxcd/pkg/oci v0.3.0 h1:GFn6JZeg5fV2K4vsQ0s5lJFid6qrpA4RybLXL+7qUbQ=
+github.com/fluxcd/pkg/oci v0.3.0/go.mod h1:c1pj9E/G5927gSa6ooACAyZe+HwjgmPk9johL7oXDHw=
github.com/fluxcd/pkg/runtime v0.16.2 h1:CexfMmJK+r12sHTvKWyAax0pcPomjd6VnaHXcxjUrRY=
github.com/fluxcd/pkg/runtime v0.16.2/go.mod h1:OHSKsrO+T+Ym8WZRS2oidrnauWRARuE2nfm8ewevm7M=
github.com/fluxcd/pkg/ssh v0.5.0 h1:jE9F2XvUXC2mgseeXMATvO014fLqdB30/VzlPLKsk20=
|