diff --git a/Makefile b/Makefile index 3053b281e..0a05278a5 100644 --- a/Makefile +++ b/Makefile @@ -163,19 +163,8 @@ docker-push: ## Push Docker image docker push $(IMG):$(TAG) controller-gen: ## Find or download controller-gen -ifeq (, $(shell which controller-gen)) - @{ \ - set -e; \ - CONTROLLER_GEN_TMP_DIR=$$(mktemp -d); \ - cd $$CONTROLLER_GEN_TMP_DIR; \ - go mod init tmp; \ - go get sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION); \ - rm -rf $$CONTROLLER_GEN_TMP_DIR; \ - } + go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION) CONTROLLER_GEN=$(GOBIN)/controller-gen -else -CONTROLLER_GEN=$(shell which controller-gen) -endif gen-crd-api-reference-docs: ## Find or download gen-crd-api-reference-docs ifeq (, $(shell which gen-crd-api-reference-docs)) diff --git a/PROJECT b/PROJECT index a807390b9..76acc1a2c 100644 --- a/PROJECT +++ b/PROJECT @@ -13,4 +13,7 @@ resources: - group: source kind: Bucket version: v1beta1 +- group: source + kind: Omaha + version: v1beta1 version: "2" diff --git a/api/v1beta1/omaha_types.go b/api/v1beta1/omaha_types.go new file mode 100644 index 000000000..5465f52cb --- /dev/null +++ b/api/v1beta1/omaha_types.go @@ -0,0 +1,161 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "github.com/fluxcd/pkg/apis/meta" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OmahaSpec defines the desired state of Omaha +type OmahaSpec struct { + // The Omaha server URL, a valid URL contains at least a protocol and host. + // +required + URL string `json:"url"` + + // +required + AppID string `json:"appid"` + + // +required + Track string `json:"track"` + + // +optional + Arch string `json:"arch"` + + // The interval at which to check for omaha updates. + // +required + Interval metav1.Duration `json:"interval"` + + // This flag tells the controller to suspend the reconciliation of this source. + // +optional + Suspend bool `json:"suspend,omitempty"` +} + +// OmahaStatus defines the observed state of Omaha +type OmahaStatus struct { + // ObservedGeneration is the last observed generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Conditions holds the conditions for Ohama. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // URL is the download link for the artifact output of the last Ohama sync. + // +optional + URL string `json:"url,omitempty"` + + // TODO + AppVersion string `json:"appVersion,omitempty"` + + // Artifact represents the output of the last successful Omaha sync. + // +optional + Artifact *Artifact `json:"artifact,omitempty"` + + meta.ReconcileRequestStatus `json:",inline"` +} + +const ( + // OmahaOperationSucceedReason represents the fact that the omaha listing and + // download operations succeeded. + OmahaOperationSucceedReason string = "OmahaOperationSucceed" + + // OmahaOperationFailedReason represents the fact that the omaha listing or + // download operations failed. + OmahaOperationFailedReason string = "OmahaOperationFailed" +) + +// OmahaProgressing resets the conditions of the Omaha to metav1.Condition of +// type meta.ReadyCondition with status 'Unknown' and meta.ProgressingReason +// reason and message. It returns the modified Omaha. +func OmahaProgressing(omaha Omaha) Omaha { + omaha.Status.ObservedGeneration = omaha.Generation + omaha.Status.URL = "" + omaha.Status.Conditions = []metav1.Condition{} + meta.SetResourceCondition(&omaha, meta.ReadyCondition, metav1.ConditionUnknown, meta.ProgressingReason, "reconciliation in progress") + return omaha +} + +// OmahaReady sets the given Artifact and URL on the Omaha and sets the +// meta.ReadyCondition to 'True', with the given reason and message. It returns +// the modified Omaha. +func OmahaReady(omaha Omaha, artifact *Artifact, url, reason, message string) Omaha { + omaha.Status.Artifact = artifact + omaha.Status.URL = url + meta.SetResourceCondition(&omaha, meta.ReadyCondition, metav1.ConditionTrue, reason, message) + return omaha +} + +// OmahaNotReady sets the meta.ReadyCondition on the Omaha to 'False', with +// the given reason and message. It returns the modified Omaha. +func OmahaNotReady(omaha Omaha, reason, message string) Omaha { + meta.SetResourceCondition(&omaha, meta.ReadyCondition, metav1.ConditionFalse, reason, message) + return omaha +} + +// OmahaReadyMessage returns the message of the metav1.Condition of type +// meta.ReadyCondition with status 'True' if present, or an empty string. +func OmahaReadyMessage(omaha Omaha) string { + if c := apimeta.FindStatusCondition(omaha.Status.Conditions, meta.ReadyCondition); c != nil { + if c.Status == metav1.ConditionTrue { + return c.Message + } + } + return "" +} + +// GetArtifact returns the latest artifact from the source if present in the +// status sub-resource. +func (in *Omaha) GetArtifact() *Artifact { + return in.Status.Artifact +} + +// GetStatusConditions returns a pointer to the Status.Conditions slice +func (in *Omaha) GetStatusConditions() *[]metav1.Condition { + return &in.Status.Conditions +} + +// GetInterval returns the interval at which the source is updated. +func (in *Omaha) GetInterval() metav1.Duration { + return in.Spec.Interval +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Omaha is the Schema for the omahas API +type Omaha struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OmahaSpec `json:"spec,omitempty"` + Status OmahaStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OmahaList contains a list of Omaha +type OmahaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Omaha `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Omaha{}, &OmahaList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 443f17a31..6bde2ad07 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* @@ -608,3 +609,106 @@ 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 *Omaha) DeepCopyInto(out *Omaha) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Omaha. +func (in *Omaha) DeepCopy() *Omaha { + if in == nil { + return nil + } + out := new(Omaha) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Omaha) 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 *OmahaList) DeepCopyInto(out *OmahaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Omaha, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OmahaList. +func (in *OmahaList) DeepCopy() *OmahaList { + if in == nil { + return nil + } + out := new(OmahaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OmahaList) 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 *OmahaSpec) DeepCopyInto(out *OmahaSpec) { + *out = *in + out.Interval = in.Interval +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OmahaSpec. +func (in *OmahaSpec) DeepCopy() *OmahaSpec { + if in == nil { + return nil + } + out := new(OmahaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OmahaStatus) DeepCopyInto(out *OmahaStatus) { + *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 OmahaStatus. +func (in *OmahaStatus) DeepCopy() *OmahaStatus { + if in == nil { + return nil + } + out := new(OmahaStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/source.toolkit.fluxcd.io_omahas.yaml b/config/crd/bases/source.toolkit.fluxcd.io_omahas.yaml new file mode 100644 index 000000000..a4b70f1f0 --- /dev/null +++ b/config/crd/bases/source.toolkit.fluxcd.io_omahas.yaml @@ -0,0 +1,150 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.5.0 + creationTimestamp: null + name: omahas.source.toolkit.fluxcd.io +spec: + group: source.toolkit.fluxcd.io + names: + kind: Omaha + listKind: OmahaList + plural: omahas + singular: omaha + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Omaha is the Schema for the omahas 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: OmahaSpec defines the desired state of Omaha + properties: + appid: + type: string + arch: + type: string + interval: + description: The interval at which to check for omaha updates. + type: string + suspend: + description: This flag tells the controller to suspend the reconciliation of this source. + type: boolean + track: + type: string + url: + description: The Omaha server URL, a valid URL contains at least a protocol and host. + type: string + required: + - appid + - interval + - track + - url + type: object + status: + description: OmahaStatus defines the observed state of Omaha + properties: + appVersion: + description: TODO + type: string + artifact: + description: Artifact represents the output of the last successful Omaha sync. + properties: + checksum: + description: Checksum is the SHA1 checksum of the artifact. + type: string + lastUpdateTime: + description: LastUpdateTime is the timestamp corresponding to the last update of this artifact. + format: date-time + type: string + path: + description: Path is the relative file path of this artifact. + type: string + revision: + description: Revision is a human readable identifier traceable in the origin source system. It can be a Git commit SHA, Git tag, a Helm index timestamp, a Helm chart version, etc. + type: string + url: + description: URL is the HTTP address of this artifact. + type: string + required: + - path + - url + type: object + conditions: + description: Conditions holds the conditions for Ohama. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent reconcile request value, so a change can be detected. + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + url: + description: URL is the download link for the artifact output of the last Ohama 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..47aba692e 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_omahas.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 000000000..6f83d9a94 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,17 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + group: apiextensions.k8s.io + path: spec/conversion/webhookClientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + group: apiextensions.k8s.io + path: spec/conversion/webhookClientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_omahas.yaml b/config/crd/patches/cainjection_in_omahas.yaml new file mode 100644 index 000000000..e407c1721 --- /dev/null +++ b/config/crd/patches/cainjection_in_omahas.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: omahas.source.toolkit.fluxcd.io diff --git a/config/crd/patches/webhook_in_omahas.yaml b/config/crd/patches/webhook_in_omahas.yaml new file mode 100644 index 000000000..38b3a6d62 --- /dev/null +++ b/config/crd/patches/webhook_in_omahas.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: omahas.source.toolkit.fluxcd.io +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 3206171eb..216e53049 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,5 +5,10 @@ resources: - deployment.yaml images: - name: fluxcd/source-controller +<<<<<<< HEAD newName: fluxcd/source-controller newTag: v0.19.2 +======= + newName: docker.io/guilhem/source-controller + newTag: omaha +>>>>>>> c11d601 (feat: Add an omaha source) diff --git a/config/rbac/omaha_editor_role.yaml b/config/rbac/omaha_editor_role.yaml new file mode 100644 index 000000000..8e8c48eee --- /dev/null +++ b/config/rbac/omaha_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit omahas. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: omaha-editor-role +rules: +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas/status + verbs: + - get diff --git a/config/rbac/omaha_viewer_role.yaml b/config/rbac/omaha_viewer_role.yaml new file mode 100644 index 000000000..55f1f5bf7 --- /dev/null +++ b/config/rbac/omaha_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view omahas. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: omaha-viewer-role +rules: +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas + verbs: + - get + - list + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 8cf5c66a0..effc59c7c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -141,3 +141,23 @@ rules: - get - patch - update +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - omahas/status + verbs: + - get + - patch + - update diff --git a/config/samples/source_v1beta1_omaha.yaml b/config/samples/source_v1beta1_omaha.yaml new file mode 100644 index 000000000..2f32b5a63 --- /dev/null +++ b/config/samples/source_v1beta1_omaha.yaml @@ -0,0 +1,10 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: Omaha +metadata: + name: omaha-sample +spec: + url: http://10.152.183.78 + appid: 4246ed1e-5137-412b-8e0f-28964bd70ab8 + track: stable + arch: all + interval: 30m diff --git a/controllers/omaha_controller.go b/controllers/omaha_controller.go new file mode 100644 index 000000000..a07aea4a0 --- /dev/null +++ b/controllers/omaha_controller.go @@ -0,0 +1,398 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "errors" + "fmt" + "net/http" + "path" + "time" + + "github.com/go-logr/logr" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kuberecorder "k8s.io/client-go/tools/record" + "k8s.io/client-go/tools/reference" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/events" + "github.com/fluxcd/pkg/runtime/metrics" + "github.com/fluxcd/pkg/runtime/predicates" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" + sourcev1beta1 "github.com/fluxcd/source-controller/api/v1beta1" + goomaha "github.com/kinvolk/go-omaha/omaha" + oclient "github.com/kinvolk/go-omaha/omaha/client" +) + +//+kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=omahas,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=omahas/status,verbs=get;update;patch + +// OmahaReconciler reconciles a Omaha object +type OmahaReconciler struct { + client.Client + Scheme *runtime.Scheme + Storage *Storage + EventRecorder kuberecorder.EventRecorder + ExternalEventRecorder *events.Recorder + MetricsRecorder *metrics.Recorder +} + +type OmahaReconcilerOptions struct { + MaxConcurrentReconciles int +} + +func (r *OmahaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + start := time.Now() + log := logr.FromContext(ctx) + + var omaha sourcev1beta1.Omaha + if err := r.Get(ctx, req.NamespacedName, &omaha); err != nil { + log.Error(err, "can't get Omaha", "Object", req.NamespacedName) + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Record suspended status metric + defer r.recordSuspension(ctx, omaha) + + // Add our finalizer if it does not exist + if !controllerutil.ContainsFinalizer(&omaha, sourcev1beta1.SourceFinalizer) { + controllerutil.AddFinalizer(&omaha, sourcev1beta1.SourceFinalizer) + if err := r.Update(ctx, &omaha); err != nil { + log.Error(err, "unable to register finalizer") + return ctrl.Result{}, err + } + } + + // Examine if the object is under deletion + if !omaha.ObjectMeta.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, omaha) + } + + // Return early if the object is suspended. + if omaha.Spec.Suspend { + log.Info("Reconciliation is suspended for this object") + return ctrl.Result{}, nil + } + + if r.MetricsRecorder != nil { + objRef, err := reference.GetReference(r.Scheme, &omaha) + if err != nil { + return ctrl.Result{}, err + } + defer r.MetricsRecorder.RecordDuration(*objRef, start) + } + + // set initial status + if resetOmaha, ok := r.resetStatus(omaha); ok { + log.Info("status reset") + omaha = resetOmaha + if err := r.updateStatus(ctx, req, omaha.Status); err != nil { + log.Error(err, "unable to update status") + return ctrl.Result{Requeue: true}, err + } + r.recordReadiness(ctx, omaha) + } + + // record the value of the reconciliation request, if any + // TODO(hidde): would be better to defer this in combination with + // always patching the status sub-resource after a reconciliation. + if v, ok := meta.ReconcileAnnotationValue(omaha.GetAnnotations()); ok { + omaha.Status.SetLastHandledReconcileRequest(v) + } + + // purge old artifacts from storage + if err := r.gc(omaha); err != nil { + log.Error(err, "unable to purge old artifacts") + } + + // reconcile omaha by downloading its content + reconciledOmaha, reconcileErr := r.reconcile(ctx, *omaha.DeepCopy()) + + // update status with the reconciliation result + if err := r.updateStatus(ctx, req, reconciledOmaha.Status); err != nil { + log.Error(err, "unable to update status") + return ctrl.Result{Requeue: true}, err + } + + // if reconciliation failed, record the failure and requeue immediately + if reconcileErr != nil { + r.event(ctx, reconciledOmaha, events.EventSeverityError, reconcileErr.Error()) + r.recordReadiness(ctx, reconciledOmaha) + return ctrl.Result{Requeue: true}, reconcileErr + } + + // emit revision change event + if omaha.Status.Artifact == nil || reconciledOmaha.Status.Artifact.Revision != omaha.Status.Artifact.Revision { + log.Info("event") + r.event(ctx, reconciledOmaha, events.EventSeverityInfo, sourcev1beta1.OmahaReadyMessage(reconciledOmaha)) + } + r.recordReadiness(ctx, reconciledOmaha) + + log.Info(fmt.Sprintf("Reconciliation finished in %s, next run in %s", + time.Now().Sub(start).String(), + omaha.GetInterval().Duration.String(), + )) + + return ctrl.Result{RequeueAfter: omaha.GetInterval().Duration}, nil +} + +func (r *OmahaReconciler) reconcile(ctx context.Context, omaha sourcev1beta1.Omaha) (sourcev1beta1.Omaha, error) { + log := logr.FromContext(ctx) + + // oc, err := oclient.New(omaha.Spec.URL, string(omaha.UID)) + // if err != nil { + // return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.URLInvalidReason, err.Error()), err + // } + + version := omaha.Status.AppVersion + if version == "" { + version = "0.0.0" + } + + appc, err := oclient.NewAppClient(omaha.Spec.URL, string(omaha.UID), omaha.Spec.AppID, version) + if err != nil { + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.URLInvalidReason, err.Error()), err + } + + if omaha.Spec.Arch == "" { + appc.SetArch("all") + } else { + appc.SetArch(omaha.Spec.Arch) + } + + appc.SetMachine(false) + + if err := appc.SetTrack(omaha.Spec.Track); err != nil { + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.OmahaOperationFailedReason, err.Error()), err + } + + upd, err := appc.UpdateCheck() + if err != nil { + log.Error(err, "updatecheck fail", "url", omaha.Spec.URL, "appID", omaha.Spec.AppID, "version", version) + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.OmahaOperationFailedReason, err.Error()), err + } + + switch upd.Status { + case goomaha.NoUpdate: + log.Info("no update") + return omaha, nil + case goomaha.UpdateOK: + break + default: + err := fmt.Errorf("omaha status '%s': %w", upd.Status, err) + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.OmahaOperationFailedReason, err.Error()), err + } + + var url string + + switch l := len(upd.URLs); { + case l == 0: + err := errors.New("can't find URL") + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.OmahaOperationFailedReason, err.Error()), err + case l > 1: + log.Info("answer with more than 1 url, keep only one") + fallthrough + case l == 1: + url = upd.URLs[0].CodeBase + } + + // for _, url := range upd.URLs { + resp, err := http.Get(url) + if err != nil { + log.Error(err, "can't download file", "url", url) + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.StorageOperationFailedReason, err.Error()), err + } + defer resp.Body.Close() + + newVersion := upd.Manifest.Version + + artifact := r.Storage.NewArtifactFor(omaha.Kind, + omaha.GetObjectMeta(), + newVersion, + fmt.Sprintf("%s-%s", newVersion, path.Base(url))) + + // create artifact dir + + if err := r.Storage.MkdirAll(artifact); err != nil { + err = fmt.Errorf("unable to create repository index directory: %w", err) + return sourcev1.OmahaNotReady(omaha, sourcev1.StorageOperationFailedReason, err.Error()), err + } + + // acquire lock + unlock, err := r.Storage.Lock(artifact) + if err != nil { + err = fmt.Errorf("unable to acquire lock: %w", err) + return sourcev1.OmahaNotReady(omaha, sourcev1.StorageOperationFailedReason, err.Error()), err + } + defer unlock() + + if err := r.Storage.AtomicWriteFile(&artifact, resp.Body, 0644); err != nil { + err := fmt.Errorf("can't store file: %w", err) + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.StorageOperationFailedReason, err.Error()), err + } + + storageURL, err := r.Storage.Symlink(artifact, "latest.tar.gz") + if err != nil { + err = fmt.Errorf("storage symlink error: %w", err) + return sourcev1beta1.OmahaNotReady(omaha, sourcev1beta1.StorageOperationFailedReason, err.Error()), err + } + + message := fmt.Sprintf("Fetched revision: %s", artifact.Revision) + return sourcev1beta1.OmahaReady(omaha, &artifact, storageURL, sourcev1beta1.OmahaOperationSucceedReason, message), nil +} + +func (r *OmahaReconciler) reconcileDelete(ctx context.Context, omaha sourcev1beta1.Omaha) (ctrl.Result, error) { + if err := r.gc(omaha); err != nil { + r.event(ctx, omaha, events.EventSeverityError, + fmt.Sprintf("garbage collection for deleted resource failed: %s", err.Error())) + // Return the error so we retry the failed garbage collection + return ctrl.Result{}, err + } + + // Record deleted status + r.recordReadiness(ctx, omaha) + + // Remove our finalizer from the list and update it + controllerutil.RemoveFinalizer(&omaha, sourcev1.SourceFinalizer) + if err := r.Update(ctx, &omaha); err != nil { + return ctrl.Result{}, err + } + + // Stop reconciliation as the object is being deleted + return ctrl.Result{}, nil +} + +// resetStatus returns a modified v1beta1.Omaha and a boolean indicating +// if the status field has been reset. +func (r *OmahaReconciler) resetStatus(omaha sourcev1beta1.Omaha) (sourcev1beta1.Omaha, bool) { + // We do not have an artifact, or it does no longer exist + if omaha.GetArtifact() == nil || !r.Storage.ArtifactExist(*omaha.GetArtifact()) { + omaha = sourcev1beta1.OmahaProgressing(omaha) + omaha.Status.Artifact = nil + return omaha, true + } + if omaha.Generation != omaha.Status.ObservedGeneration { + return sourcev1beta1.OmahaProgressing(omaha), true + } + return omaha, false +} + +// gc performs a garbage collection for the given v1beta1.Omaha. +// It removes all but the current artifact except for when the +// deletion timestamp is set, which will result in the removal of +// all artifacts for the resource. +func (r *OmahaReconciler) gc(omaha sourcev1beta1.Omaha) error { + if !omaha.DeletionTimestamp.IsZero() { + return r.Storage.RemoveAll(r.Storage.NewArtifactFor(omaha.Kind, omaha.GetObjectMeta(), "", "*")) + } + if omaha.GetArtifact() != nil { + return r.Storage.RemoveAllButCurrent(*omaha.GetArtifact()) + } + return nil +} + +// event emits a Kubernetes event and forwards the event to notification controller if configured +func (r *OmahaReconciler) event(ctx context.Context, omaha sourcev1beta1.Omaha, severity, msg string) { + log := logr.FromContext(ctx) + if r.EventRecorder != nil { + r.EventRecorder.Eventf(&omaha, "Normal", severity, msg) + } + if r.ExternalEventRecorder != nil { + objRef, err := reference.GetReference(r.Scheme, &omaha) + if err != nil { + log.Error(err, "unable to send event") + return + } + + if err := r.ExternalEventRecorder.Eventf(*objRef, nil, severity, severity, msg); err != nil { + log.Error(err, "unable to send event") + return + } + } +} + +func (r *OmahaReconciler) recordReadiness(ctx context.Context, omaha sourcev1beta1.Omaha) { + log := logr.FromContext(ctx) + if r.MetricsRecorder == nil { + return + } + objRef, err := reference.GetReference(r.Scheme, &omaha) + if err != nil { + log.Error(err, "unable to record readiness metric") + return + } + if rc := apimeta.FindStatusCondition(omaha.Status.Conditions, meta.ReadyCondition); rc != nil { + r.MetricsRecorder.RecordCondition(*objRef, *rc, !omaha.DeletionTimestamp.IsZero()) + } else { + r.MetricsRecorder.RecordCondition(*objRef, metav1.Condition{ + Type: meta.ReadyCondition, + Status: metav1.ConditionUnknown, + }, !omaha.DeletionTimestamp.IsZero()) + } +} + +func (r *OmahaReconciler) recordSuspension(ctx context.Context, omaha sourcev1beta1.Omaha) { + if r.MetricsRecorder == nil { + return + } + log := logr.FromContext(ctx) + + objRef, err := reference.GetReference(r.Scheme, &omaha) + if err != nil { + log.Error(err, "unable to record suspended metric") + return + } + + if !omaha.DeletionTimestamp.IsZero() { + r.MetricsRecorder.RecordSuspend(*objRef, false) + } else { + r.MetricsRecorder.RecordSuspend(*objRef, omaha.Spec.Suspend) + } +} + +func (r *OmahaReconciler) updateStatus(ctx context.Context, req ctrl.Request, newStatus sourcev1beta1.OmahaStatus) error { + var omaha sourcev1beta1.Omaha + if err := r.Get(ctx, req.NamespacedName, &omaha); err != nil { + return err + } + + patch := client.MergeFrom(omaha.DeepCopy()) + omaha.Status = newStatus + + return r.Status().Patch(ctx, &omaha, patch) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OmahaReconciler) SetupWithManager(mgr ctrl.Manager) error { + return r.SetupWithManagerAndOptions(mgr, OmahaReconcilerOptions{}) +} + +func (r *OmahaReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts OmahaReconcilerOptions) error { + return ctrl.NewControllerManagedBy(mgr). + For(&sourcev1beta1.Omaha{}). + WithEventFilter(predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{})). + WithOptions(controller.Options{MaxConcurrentReconciles: opts.MaxConcurrentReconciles}). + Complete(r) +} diff --git a/controllers/omaha_controller_test.go b/controllers/omaha_controller_test.go new file mode 100644 index 000000000..6b6937778 --- /dev/null +++ b/controllers/omaha_controller_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2020 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + "os" + "path" + "time" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" + "github.com/kinvolk/go-omaha/omaha" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("OmahaReconciler", func() { + + const ( + timeout = time.Second * 30 + interval = time.Second * 1 + indexInterval = time.Second * 2 + repositoryTimeout = time.Second * 5 + ) + + Context("Omaha", func() { + var ( + namespace *corev1.Namespace + omahaServer *omaha.TrivialServer + err error + ) + + BeforeEach(func() { + namespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "omaha-repository-" + randStringRunes(5)}, + } + err = k8sClient.Create(context.Background(), namespace) + Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") + + omahaServer, err = omaha.NewTrivialServer("127.0.0.1:0") + Expect(err).To(Succeed()) + }) + + AfterEach(func() { + Expect(omahaServer.Destroy()).Should(Succeed()) + // os.RemoveAll(omahaServer.Root()) + + Eventually(func() error { + return k8sClient.Delete(context.Background(), namespace) + }, timeout, interval).Should(Succeed(), "failed to delete test namespace") + }) + + It("Creates artifacts for", func() { + omahaServer.SetVersion("1.2.3") + Expect(omahaServer.AddPackage(path.Join("testdata/charts/helmchart-0.1.0.tgz"), "app")).Should(Succeed()) + + go omahaServer.Serve() + + key := types.NamespacedName{ + Name: "omaha-sample-" + randStringRunes(5), + Namespace: namespace.Name, + } + created := &sourcev1.Omaha{ + ObjectMeta: metav1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Spec: sourcev1.OmahaSpec{ + URL: fmt.Sprintf("http://%s", omahaServer.Addr().String()), + Interval: metav1.Duration{Duration: indexInterval}, + AppID: "app", + // Timeout: &metav1.Duration{Duration: repositoryTimeout}, + }, + } + Expect(k8sClient.Create(context.Background(), created)).Should(Succeed()) + + By("Expecting artifact") + got := &sourcev1.Omaha{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), key, got) + return got.Status.Artifact != nil && storage.ArtifactExist(*got.Status.Artifact) + }, timeout, interval).Should(BeTrue()) + + By("Expecting revision change and GC") + omahaServer.SetVersion("1.2.4") + + Eventually(func() bool { + now := &sourcev1.Omaha{} + _ = k8sClient.Get(context.Background(), key, now) + // Test revision change and garbage collection + return now.Status.Artifact != nil && now.Status.Artifact.Revision != got.Status.Artifact.Revision && + !storage.ArtifactExist(*got.Status.Artifact) + }, timeout, interval).Should(BeTrue()) + + updated := &sourcev1.Omaha{} + Expect(k8sClient.Get(context.Background(), key, updated)).Should(Succeed()) + updated.Spec.URL = "invalid#url?" + Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed()) + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), key, updated) + for _, c := range updated.Status.Conditions { + if c.Reason == sourcev1.URLInvalidReason { + return true + } + } + return false + }, timeout, interval).Should(BeTrue()) + Expect(updated.Status.Artifact).ToNot(BeNil()) + + By("Expecting to delete successfully") + got = &sourcev1.Omaha{} + Eventually(func() error { + _ = k8sClient.Get(context.Background(), key, got) + return k8sClient.Delete(context.Background(), got) + }, timeout, interval).Should(Succeed()) + + By("Expecting delete to finish") + Eventually(func() error { + r := &sourcev1.Omaha{} + return k8sClient.Get(context.Background(), key, r) + }, timeout, interval).ShouldNot(Succeed()) + + exists := func(path string) bool { + // wait for tmp sync on macOS + time.Sleep(time.Second) + _, err := os.Stat(path) + return err == nil + } + + By("Expecting GC after delete") + Eventually(exists(got.Status.Artifact.Path), timeout, interval).ShouldNot(BeTrue()) + }) + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 5f5341155..394a9704a 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -86,12 +86,6 @@ var _ = BeforeSuite(func(done Done) { err = sourcev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = sourcev1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - err = sourcev1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - // +kubebuilder:scaffold:scheme Expect(loadExampleKeys()).To(Succeed()) @@ -140,6 +134,13 @@ var _ = BeforeSuite(func(done Done) { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred(), "failed to setup HelmChartReconciler") + err = (&OmahaReconciler{ + Client: k8sManager.GetClient(), + Scheme: scheme.Scheme, + Storage: storage, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred(), "failed to setup OmahaReconciler") + go func() { err = k8sManager.Start(ctrl.SetupSignalHandler()) Expect(err).ToNot(HaveOccurred()) diff --git a/docs/diagrams/source-controller-overview.png b/docs/diagrams/source-controller-overview.png index 554c00540..3620ca960 100644 Binary files a/docs/diagrams/source-controller-overview.png and b/docs/diagrams/source-controller-overview.png differ diff --git a/go.mod b/go.mod index f781aa473..75ed57d9c 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/googleapis/gax-go/v2 v2.1.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/kinvolk/go-omaha v0.0.1 github.com/libgit2/git2go/v31 v31.6.1 github.com/minio/minio-go/v7 v7.0.15 github.com/onsi/ginkgo v1.16.4 @@ -78,3 +79,5 @@ replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.3 // Fix CVE-2021-41190 replace github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.2 + +replace github.com/kinvolk/go-omaha => github.com/guilhem/go-omaha v0.0.2-0.20211019232651-3b6535cd26d0 diff --git a/go.sum b/go.sum index 0acdc7568..310d34ab1 100644 --- a/go.sum +++ b/go.sum @@ -136,7 +136,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 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= @@ -498,8 +501,9 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 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 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= @@ -530,6 +534,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf 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.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/guilhem/go-omaha v0.0.2-0.20211019232651-3b6535cd26d0 h1:hUpYU5t+f+1NsPiwkwz7mK4yHUOxveJtZUZ/UWk8dcE= +github.com/guilhem/go-omaha v0.0.2-0.20211019232651-3b6535cd26d0/go.mod h1:SAEkKv9dpmCAVA3KxGdlHMoO4VaCAfIZuui57PqGQbg= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -624,6 +630,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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= diff --git a/main.go b/main.go index 67f00a920..003e02fff 100644 --- a/main.go +++ b/main.go @@ -223,6 +223,19 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Bucket") os.Exit(1) } + if err = (&controllers.OmahaReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Storage: storage, + EventRecorder: mgr.GetEventRecorderFor(controllerName), + ExternalEventRecorder: eventRecorder, + MetricsRecorder: metricsRecorder, + }).SetupWithManagerAndOptions(mgr, controllers.OmahaReconcilerOptions{ + MaxConcurrentReconciles: concurrent, + }); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Omaha") + os.Exit(1) + } // +kubebuilder:scaffold:builder go func() {