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
  • Bucket

    @@ -880,6 +882,231 @@ HelmRepositoryStatus +

    OCIRepository +

    +

    OCIRepository is the Schema for the ocirepositories API

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +apiVersion
    +string
    +source.toolkit.fluxcd.io/v1beta2 +
    +kind
    +string +
    +OCIRepository +
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +OCIRepositorySpec + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +url
    + +string + +
    +

    URL is a reference to an OCI artifact repository hosted +on a remote container registry.

    +
    +ref
    + + +OCIRepositoryRef + + +
    +(Optional) +

    The OCI reference to pull and monitor for changes, +defaults to the latest tag.

    +
    +secretRef
    + + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
    +(Optional) +

    SecretRef contains the secret name containing the registry login +credentials to resolve image metadata. +The secret must be of type kubernetes.io/dockerconfigjson.

    +
    +serviceAccountName
    + +string + +
    +(Optional) +

    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

    +
    +certSecretRef
    + + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
    +(Optional) +

    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.

    +
    +verify
    + + +OCIRepositoryVerification + + +
    +(Optional) +

    Verification specifies the configuration to verify the autheticity +of an OCI Artifact.

    +
    +interval
    + + +Kubernetes meta/v1.Duration + + +
    +

    The interval at which to check for image updates.

    +
    +timeout
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    The timeout for remote OCI Repository operations like pulling, defaults to 60s.

    +
    +ignore
    + +string + +
    +(Optional) +

    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.

    +
    +suspend
    + +bool + +
    +(Optional) +

    This flag tells the controller to suspend the reconciliation of this source.

    +
    +
    +status
    + + +OCIRepositoryStatus + + +
    +
    +
    +

    Artifact

    @@ -887,7 +1114,8 @@ HelmRepositoryStatus BucketStatus, GitRepositoryStatus, HelmChartStatus, -HelmRepositoryStatus) +HelmRepositoryStatus, +OCIRepositoryStatus)

    Artifact represents the output of a Source reconciliation.

    @@ -2291,6 +2519,369 @@ string
    +

    OCIRepositoryRef +

    +

    +(Appears on: +OCIRepositorySpec) +

    +

    OCIRepositoryRef defines the image reference for the OCIRepository’s URL

    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +digest
    + +string + +
    +(Optional) +

    Digest is the image digest to pull, takes precedence over SemVer. +The value should be in the format ‘sha256:’.

    +
    +semver
    + +string + +
    +(Optional) +

    SemVer is the range of tags to pull selecting the latest within +the range, takes precedence over Tag.

    +
    +tag
    + +string + +
    +(Optional) +

    Tag is the image tag to pull, defaults to latest.

    +
    +
    +
    +

    OCIRepositorySpec +

    +

    +(Appears on: +OCIRepository) +

    +

    OCIRepositorySpec defines the desired state of OCIRepository

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +url
    + +string + +
    +

    URL is a reference to an OCI artifact repository hosted +on a remote container registry.

    +
    +ref
    + + +OCIRepositoryRef + + +
    +(Optional) +

    The OCI reference to pull and monitor for changes, +defaults to the latest tag.

    +
    +secretRef
    + + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
    +(Optional) +

    SecretRef contains the secret name containing the registry login +credentials to resolve image metadata. +The secret must be of type kubernetes.io/dockerconfigjson.

    +
    +serviceAccountName
    + +string + +
    +(Optional) +

    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

    +
    +certSecretRef
    + + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
    +(Optional) +

    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.

    +
    +verify
    + + +OCIRepositoryVerification + + +
    +(Optional) +

    Verification specifies the configuration to verify the autheticity +of an OCI Artifact.

    +
    +interval
    + + +Kubernetes meta/v1.Duration + + +
    +

    The interval at which to check for image updates.

    +
    +timeout
    + + +Kubernetes meta/v1.Duration + + +
    +(Optional) +

    The timeout for remote OCI Repository operations like pulling, defaults to 60s.

    +
    +ignore
    + +string + +
    +(Optional) +

    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.

    +
    +suspend
    + +bool + +
    +(Optional) +

    This flag tells the controller to suspend the reconciliation of this source.

    +
    +
    +
    +

    OCIRepositoryStatus +

    +

    +(Appears on: +OCIRepository) +

    +

    OCIRepositoryStatus defines the observed state of OCIRepository

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +observedGeneration
    + +int64 + +
    +(Optional) +

    ObservedGeneration is the last observed generation.

    +
    +conditions
    + + +[]Kubernetes meta/v1.Condition + + +
    +(Optional) +

    Conditions holds the conditions for the OCIRepository.

    +
    +url
    + +string + +
    +(Optional) +

    URL is the download link for the artifact output of the last OCI Repository sync.

    +
    +artifact
    + + +Artifact + + +
    +(Optional) +

    Artifact represents the output of the last successful OCI Repository sync.

    +
    +ReconcileRequestStatus
    + + +github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus + + +
    +

    +(Members of ReconcileRequestStatus are embedded into this type.) +

    +
    +
    +
    +

    OCIRepositoryVerification +

    +

    +(Appears on: +OCIRepositorySpec) +

    +

    OCIRepositoryVerification verifies the authenticity of an OCI Artifact

    +
    +
    + + + + + + + + + + + + + + + + + +
    FieldDescription
    +provider
    + +string + +
    +

    Provider specifies the technology used to sign the OCI Artifact.

    +
    +secretRef
    + + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
    +

    SecretRef specifies the Kubernetes Secret containing the +trusted public keys.

    +
    +
    +

    Source

    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

    OCIRepositoryVerification

    -

    -(Appears on: -OCIRepositorySpec) -

    OCIRepositoryVerification verifies the authenticity of an OCI Artifact

    From 5072091eb54caee0a0cbd75ff02b55256cc38371 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 11 Jul 2022 18:00:05 +0300 Subject: [PATCH 11/25] Remove the default tag value from the CRD Signed-off-by: Stefan Prodan --- api/v1beta2/ocirepository_types.go | 1 - config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go index b35ccca52..39a90c307 100644 --- a/api/v1beta2/ocirepository_types.go +++ b/api/v1beta2/ocirepository_types.go @@ -102,7 +102,6 @@ type OCIRepositoryRef struct { SemVer string `json:"semver,omitempty"` // Tag is the image tag to pull, defaults to latest. - // +kubebuilder:default:=latest // +optional Tag string `json:"tag,omitempty"` } diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml index b7eb96c1d..4980cd2cc 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml @@ -88,7 +88,6 @@ spec: 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 From 05f9c0ee2b521e9d033e19faee30bcca8cf36226 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Tue, 12 Jul 2022 18:21:08 +0300 Subject: [PATCH 12/25] Add the OCI metadata to the internal artifact Signed-off-by: Stefan Prodan --- api/v1beta2/artifact_types.go | 4 ++ api/v1beta2/ocirepository_types.go | 6 ++- api/v1beta2/zz_generated.deepcopy.go | 7 +++ .../source.toolkit.fluxcd.io_buckets.yaml | 5 +++ ...rce.toolkit.fluxcd.io_gitrepositories.yaml | 11 +++++ .../source.toolkit.fluxcd.io_helmcharts.yaml | 5 +++ ...ce.toolkit.fluxcd.io_helmrepositories.yaml | 5 +++ ...rce.toolkit.fluxcd.io_ocirepositories.yaml | 5 +++ controllers/ocirepository_controller.go | 45 ++++++++++++------- controllers/ocirepository_controller_test.go | 33 +++++++++----- controllers/suite_test.go | 2 + docs/api/source.md | 12 +++++ 12 files changed, 112 insertions(+), 28 deletions(-) diff --git a/api/v1beta2/artifact_types.go b/api/v1beta2/artifact_types.go index 9ae05ed94..0832b6ce5 100644 --- a/api/v1beta2/artifact_types.go +++ b/api/v1beta2/artifact_types.go @@ -54,6 +54,10 @@ type Artifact struct { // Size is the number of bytes in the file. // +optional Size *int64 `json:"size,omitempty"` + + // Metadata holds upstream information such as OCI annotations. + // +optional + Metadata map[string]string `json:"metadata,omitempty"` } // HasRevision returns if the given revision matches the current Revision of diff --git a/api/v1beta2/ocirepository_types.go b/api/v1beta2/ocirepository_types.go index 39a90c307..2c6df0911 100644 --- a/api/v1beta2/ocirepository_types.go +++ b/api/v1beta2/ocirepository_types.go @@ -17,9 +17,11 @@ limitations under the License. package v1beta2 import ( - "github.com/fluxcd/pkg/apis/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fluxcd/pkg/apis/meta" ) const ( diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 807799961..fc186d4df 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -37,6 +37,13 @@ func (in *Artifact) DeepCopyInto(out *Artifact) { *out = new(int64) **out = **in } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Artifact. diff --git a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml index 762e67931..d8fc0f533 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml @@ -384,6 +384,11 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object 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 diff --git a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml index 0e798c061..b260fb694 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml @@ -559,6 +559,11 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object 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 @@ -677,6 +682,12 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI + annotations. + type: object 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 diff --git a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml index a45d0370b..6b15e7bfb 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml @@ -432,6 +432,11 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object 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 diff --git a/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml index bde30e786..c19552fdd 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml @@ -362,6 +362,11 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object 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 diff --git a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml index 4980cd2cc..deb7fb454 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml @@ -142,6 +142,11 @@ spec: the last update of the Artifact. format: date-time type: string + metadata: + additionalProperties: + type: string + description: Metadata holds upstream information such as OCI annotations. + type: object 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 diff --git a/controllers/ocirepository_controller.go b/controllers/ocirepository_controller.go index 6cdd4d212..54355c948 100644 --- a/controllers/ocirepository_controller.go +++ b/controllers/ocirepository_controller.go @@ -33,7 +33,6 @@ import ( "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" "github.com/google/go-containerregistry/pkg/v1/remote" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -110,7 +109,7 @@ var ociRepositoryFailConditions = []string{ // 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) +type ociRepositoryReconcileFunc func(ctx context.Context, obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) // OCIRepositoryReconciler reconciles a v1beta2.OCIRepository object type OCIRepositoryReconciler struct { @@ -261,16 +260,15 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.O }() conditions.Delete(obj, sourcev1.StorageOperationFailedCondition) - hs := gcrv1.Hash{} var ( - res sreconcile.Result - resErr error - digest = hs.DeepCopy() + res sreconcile.Result + resErr error + metadata = sourcev1.Artifact{} ) // Run the sub-reconcilers and build the result of reconciliation. for _, rec := range reconcilers { - recResult, err := rec(ctx, obj, digest, tmpDir) + recResult, err := rec(ctx, obj, &metadata, tmpDir) // Exit immediately on ResultRequeue. if recResult == sreconcile.ResultRequeue { return sreconcile.ResultRequeue, nil @@ -286,14 +284,14 @@ func (r *OCIRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.O res = sreconcile.LowestRequeuingResult(res, recResult) } - r.notify(ctx, oldObj, obj, digest, res, resErr) + r.notify(ctx, oldObj, obj, res, resErr) return res, resErr } // 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) { +func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) { ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration) defer cancel() @@ -352,9 +350,25 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour } // Set the internal revision to the remote digest hex - imgDigest.DeepCopyInto(digest) revision := imgDigest.Hex + // Copy the OCI annotations to the internal artifact metadata + manifest, err := img.Manifest() + if err != nil { + e := serror.NewGeneric( + fmt.Errorf("failed to parse artifact manifest: %w", err), + sourcev1.OCIOperationFailedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error()) + return sreconcile.ResultEmpty, e + } + + m := &sourcev1.Artifact{ + Revision: revision, + Metadata: manifest.Annotations, + } + m.DeepCopyInto(metadata) + // Mark observations about the revision on the object defer func() { if !obj.GetArtifact().HasRevision(revision) { @@ -606,7 +620,7 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, // 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) { + obj *sourcev1.OCIRepository, _ *sourcev1.Artifact, _ string) (sreconcile.Result, error) { // Garbage collect previous advertised artifact(s) from storage _ = r.garbageCollect(ctx, obj) @@ -642,9 +656,9 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, // 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) { + obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) { // Calculate revision - revision := digest.Hex + revision := metadata.Revision // Create artifact artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision, fmt.Sprintf("%s.tar.gz", revision)) @@ -712,6 +726,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, // Record it on the object obj.Status.Artifact = artifact.DeepCopy() + obj.Status.Artifact.Metadata = metadata.Metadata // Update symlink on a "best effort" basis url, err := r.Storage.Symlink(artifact, "latest.tar.gz") @@ -798,7 +813,7 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, // 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) { + oldObj, newObj *sourcev1.OCIRepository, 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 { @@ -812,7 +827,7 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldChecksum = oldObj.GetArtifact().Checksum } - message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", digest.String(), newObj.Spec.URL) + message := fmt.Sprintf("stored artifact with digest '%s' from '%s'", newObj.Status.Artifact.Revision, newObj.Spec.URL) // Notify on new artifact and failure recovery. if oldChecksum != newObj.GetArtifact().Checksum { diff --git a/controllers/ocirepository_controller_test.go b/controllers/ocirepository_controller_test.go index fab26b9e4..03b241119 100644 --- a/controllers/ocirepository_controller_test.go +++ b/controllers/ocirepository_controller_test.go @@ -44,7 +44,8 @@ import ( "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" + 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" @@ -163,11 +164,13 @@ func TestOCIRepository_Reconcile(t *testing.T) { obj.Generation == obj.Status.ObservedGeneration }, timeout).Should(BeTrue()) - 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 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)) + // Check if the artifact storage path matches the expected file path localPath := testStorage.LocalPath(*obj.Status.Artifact) t.Logf("artifact local path: %s", localPath) @@ -252,6 +255,7 @@ func TestOCIRepository_SecretRef(t *testing.T) { 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, @@ -265,7 +269,7 @@ func TestOCIRepository_SecretRef(t *testing.T) { tests := []struct { name string url string - digest v1.Hash + digest gcrv1.Hash includeSecretRef bool includeServiceAccount bool }{ @@ -449,6 +453,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) { 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, @@ -462,7 +467,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) { tests := []struct { name string url string - digest v1.Hash + digest gcrv1.Hash repoUsername string repoPassword string includeSecretRef bool @@ -644,7 +649,7 @@ func TestOCIRepository_CertSecret(t *testing.T) { name string url string tag string - digest v1.Hash + digest gcrv1.Hash certSecret *corev1.Secret expectreadyconition bool expectedstatusmessage string @@ -760,7 +765,7 @@ type artifactFixture struct { type podinfoImage struct { url string tag string - digest v1.Hash + digest gcrv1.Hash } func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Server) (*podinfoImage, error) { @@ -770,6 +775,8 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se return nil, err } + image = setPodinfoImageAnnotations(image, tag) + url, err := url.Parse(imageServer.URL) if err != nil { return nil, err @@ -784,7 +791,6 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se // Push image err = crane.Push(image, repositoryURL, crane.WithTransport(imageServer.Client().Transport)) - if err != nil { return nil, err } @@ -802,8 +808,15 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se }, nil } -// These two taken verbatim from https://ericchiang.github.io/post/go-tls/ +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), + } + return mutate.Annotations(img, metadata).(gcrv1.Image) +} +// 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) @@ -842,8 +855,6 @@ func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv return } -// ---- - func createTLSServer() (*httptest.Server, []byte, []byte, []byte, tls.Certificate, error) { var clientTLSCert tls.Certificate var rootCertPEM, clientCertPEM, clientKeyPEM []byte diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 39711a2dc..06e94890a 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -161,6 +161,8 @@ func setupRegistryServer(ctx context.Context) (*registryClientTestServer, error) server.registryHost = fmt.Sprintf("localhost:%d", port) config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second + 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{ diff --git a/docs/api/source.md b/docs/api/source.md index f45c5ca02..c82525e65 100644 --- a/docs/api/source.md +++ b/docs/api/source.md @@ -1190,6 +1190,18 @@ int64

    Size is the number of bytes in the file.

    + + +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=