diff --git a/Makefile b/Makefile index e988a43..fdcc2b2 100644 --- a/Makefile +++ b/Makefile @@ -251,10 +251,10 @@ configure-nodes-dev-lab: ## Configure the nodes in the development lab cluster gnmic -a clab-3-nodes-leaf2:57400 -u $(TARGET_USERNAME) -p $(TARGET_PASSWORD) --skip-verify set --request-file lab/dev/configs/leaf2.yaml .PHONY: apply-resources-dev-lab -apply-resources-dev-lab: apply-targets-dev-lab apply-subscriptions-dev-lab apply-outputs-dev-lab apply-pipelines-dev-lab apply-clusters-dev-lab ## Apply the resources for the development lab cluster +apply-resources-dev-lab: apply-targets-dev-lab apply-subscriptions-dev-lab apply-outputs-dev-lab apply-pipelines-dev-lab apply-clusters-dev-lab apply-targetsources-dev-lab ## Apply the resources for the development lab cluster .PHONY: delete-resources-dev-lab -delete-resources-dev-lab: delete-clusters-dev-lab delete-targets-dev-lab delete-subscriptions-dev-lab delete-outputs-dev-lab delete-pipelines-dev-lab ## Delete the resources for the development lab cluster +delete-resources-dev-lab: delete-clusters-dev-lab delete-targets-dev-lab delete-subscriptions-dev-lab delete-outputs-dev-lab delete-pipelines-dev-lab delete-targetsources-dev-lab ## Delete the resources for the development lab cluster .PHONY: apply-targets-dev-lab apply-targets-dev-lab: ## Apply the targets for the development lab cluster @@ -288,6 +288,7 @@ apply-pipelines-dev-lab: ## Apply the pipelines for the development lab cluster .PHONY: delete-pipelines-dev-lab delete-pipelines-dev-lab: ## Delete the pipelines for the development lab cluster kubectl delete -f lab/dev/resources/pipelines + .PHONY: apply-clusters-dev-lab apply-clusters-dev-lab: ## Apply the clusters for the development lab cluster kubectl apply -f lab/dev/resources/clusters @@ -296,6 +297,14 @@ apply-clusters-dev-lab: ## Apply the clusters for the development lab cluster delete-clusters-dev-lab: ## Delete the clusters for the development lab cluster kubectl delete -f lab/dev/resources/clusters +.PHONY: apply-targetsources-dev-lab +apply-targetsources-dev-lab: ## Apply the target sources for the development lab cluster + kubectl apply -f lab/dev/resources/targetsources + +.PHONY: delete-targetsources-dev-lab +delete-targetsources-dev-lab: ## Delete the target sources for the development lab cluster + kubectl delete -f lab/dev/resources/targetsources + ##@ Testing Lab .PHONY: run-integration-tests diff --git a/api/v1alpha1/targetsource_types.go b/api/v1alpha1/targetsource_types.go index 99da5d5..feea000 100644 --- a/api/v1alpha1/targetsource_types.go +++ b/api/v1alpha1/targetsource_types.go @@ -21,21 +21,29 @@ import ( ) // TargetSourceSpec defines the desired state of TargetSource +// +kubebuilder:validation:Required type TargetSourceSpec struct { - HTTP *HTTPConfig `json:"http,omitempty"` - Consul *ConsulConfig `json:"consul,omitempty"` - ConfigMap string `json:"configMap,omitempty"` - PodSelector metav1.LabelSelector `json:"podSelector,omitempty"` - ServiceSelector metav1.LabelSelector `json:"serviceSelector,omitempty"` + Provider *ProviderSpec `json:"provider"` // - Labels map[string]string `json:"labels,omitempty"` + TargetLabels map[string]string `json:"targetLabels,omitempty"` + + // +kubebuilder:validation:MinLength=1 + TargetProfile string `json:"targetProfile"` +} + +// +kubebuilder:validation:ExactlyOneOf=http;consul +type ProviderSpec struct { + HTTP *HTTPConfig `json:"http,omitempty"` + Consul *ConsulConfig `json:"consul,omitempty"` } type HTTPConfig struct { - URL string `json:"url,omitempty"` + // +kubebuilder:validation:MinLength=1 + URL string `json:"url"` } type ConsulConfig struct { + // +kubebuilder:validation:MinLength=1 URL string `json:"url,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 07b0239..61e81fd 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -818,6 +818,31 @@ func (in *ProcessorStatus) DeepCopy() *ProcessorStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) { + *out = *in + if in.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(HTTPConfig) + **out = **in + } + if in.Consul != nil { + in, out := &in.Consul, &out.Consul + *out = new(ConsulConfig) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderSpec. +func (in *ProviderSpec) DeepCopy() *ProviderSpec { + if in == nil { + return nil + } + out := new(ProviderSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceConfig) DeepCopyInto(out *ServiceConfig) { *out = *in @@ -1262,20 +1287,13 @@ func (in *TargetSourceList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetSourceSpec) DeepCopyInto(out *TargetSourceSpec) { *out = *in - if in.HTTP != nil { - in, out := &in.HTTP, &out.HTTP - *out = new(HTTPConfig) - **out = **in - } - if in.Consul != nil { - in, out := &in.Consul, &out.Consul - *out = new(ConsulConfig) - **out = **in + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + *out = new(ProviderSpec) + (*in).DeepCopyInto(*out) } - in.PodSelector.DeepCopyInto(&out.PodSelector) - in.ServiceSelector.DeepCopyInto(&out.ServiceSelector) - if in.Labels != nil { - in, out := &in.Labels, &out.Labels + if in.TargetLabels != nil { + in, out := &in.TargetLabels, &out.TargetLabels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val diff --git a/config/crd/bases/operator.gnmic.dev_targetsources.yaml b/config/crd/bases/operator.gnmic.dev_targetsources.yaml index 789ff3f..f373822 100644 --- a/config/crd/bases/operator.gnmic.dev_targetsources.yaml +++ b/config/crd/bases/operator.gnmic.dev_targetsources.yaml @@ -39,120 +39,37 @@ spec: spec: description: TargetSourceSpec defines the desired state of TargetSource properties: - configMap: - type: string - consul: - properties: - url: - type: string - type: object - http: + provider: properties: - url: - type: string + consul: + properties: + url: + minLength: 1 + type: string + type: object + http: + properties: + url: + minLength: 1 + type: string + required: + - url + type: object type: object - labels: + x-kubernetes-validations: + - message: exactly one of the fields in [http consul] must be set + rule: '[has(self.http),has(self.consul)].filter(x,x==true).size() + == 1' + targetLabels: additionalProperties: type: string type: object - podSelector: - description: |- - A label selector is a label query over a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector matches all objects. A null - label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - serviceSelector: - description: |- - A label selector is a label query over a set of resources. The result of matchLabels and - matchExpressions are ANDed. An empty label selector matches all objects. A null - label selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic + targetProfile: + minLength: 1 + type: string + required: + - provider + - targetProfile type: object status: description: TargetSourceStatus defines the observed state of TargetSource diff --git a/internal/controller/discovery/client.go b/internal/controller/discovery/client.go new file mode 100644 index 0000000..3bc7ef7 --- /dev/null +++ b/internal/controller/discovery/client.go @@ -0,0 +1,27 @@ +package discovery + +// File may become obsolete, depends on how the logic to compare desired vs. existing state will get implemented + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + + gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" +) + +func FetchExistingTargets(ctx context.Context, c client.Client, ts gnmicv1alpha1.TargetSource) ([]gnmicv1alpha1.Target, error) { + var targetList gnmicv1alpha1.TargetList + + err := c.List(ctx, &targetList, + client.InNamespace(ts.Namespace), + client.MatchingLabels{ + "gnmic.io/source": ts.Name, + }, + ) + if err != nil { + return nil, err + } + + return targetList.Items, nil +} diff --git a/internal/controller/discovery/core/loader_interface.go b/internal/controller/discovery/core/loader_interface.go new file mode 100644 index 0000000..82036a8 --- /dev/null +++ b/internal/controller/discovery/core/loader_interface.go @@ -0,0 +1,23 @@ +package core + +import ( + "context" + + gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" +) + +// Loader defines a pluggable TargetSource loader interface +// Loaders observe external Sources of Truth and emit target snapshots through a channel +type Loader interface { + // Name returns the unique loader identifier e.g. "pull" + Name() string + + // Start begins discovery and pushes target snapshots into the out channel + // The loader must stop cleanly when ctx is cancelled + Start( + ctx context.Context, + targetsourceName string, + spec gnmicv1alpha1.TargetSourceSpec, + out chan<- []DiscoveryMessage, + ) error +} diff --git a/internal/controller/discovery/core/types.go b/internal/controller/discovery/core/types.go new file mode 100644 index 0000000..406a22b --- /dev/null +++ b/internal/controller/discovery/core/types.go @@ -0,0 +1,22 @@ +package core + +// DiscoveredTarget represents a target discovered from an external source +// before it is materialized as a Kubernetes Target CR +type DiscoveredTarget struct { + Name string + Address string + Labels map[string]string +} + +const ( + DELETE DiscoveryEvent = 0 + CREATE DiscoveryEvent = 1 + UPDATE DiscoveryEvent = 2 +) + +type DiscoveryEvent int + +type DiscoveryMessage struct { + Target DiscoveredTarget + Event DiscoveryEvent +} diff --git a/internal/controller/discovery/loader.go b/internal/controller/discovery/loader.go new file mode 100644 index 0000000..3d45f42 --- /dev/null +++ b/internal/controller/discovery/loader.go @@ -0,0 +1,24 @@ +package discovery + +import ( + "fmt" + + gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" + "github.com/gnmic/operator/internal/controller/discovery/core" + pull "github.com/gnmic/operator/internal/controller/discovery/loaders/pull" +) + +// NewLoader creates a loader by name +func NewLoader(name string, namespace string, spec gnmicv1alpha1.TargetSourceSpec) (core.Loader, error) { + loaderName := namespace + "/" + name + + switch { + case spec.Provider.HTTP != nil: + return pull.New(), nil + case spec.Provider.Consul != nil: + return nil, fmt.Errorf("unknown targetsource loader, check TargetSource CRD for %s", loaderName) + default: + return nil, fmt.Errorf("unknown targetsource loader, check TargetSource CRD for %s", loaderName) + } + +} diff --git a/internal/controller/discovery/loaders/all/all.go b/internal/controller/discovery/loaders/all/all.go new file mode 100644 index 0000000..d05604b --- /dev/null +++ b/internal/controller/discovery/loaders/all/all.go @@ -0,0 +1,6 @@ +package all + +import ( + _ "github.com/gnmic/operator/internal/controller/discovery/loaders/pull" + // _ "github.com/gnmic/operator/internal/controller/targetsource/loaders/push" +) diff --git a/internal/controller/discovery/loaders/pull/loader.go b/internal/controller/discovery/loaders/pull/loader.go new file mode 100644 index 0000000..5540ea2 --- /dev/null +++ b/internal/controller/discovery/loaders/pull/loader.go @@ -0,0 +1,78 @@ +package pull + +import ( + "context" + "time" + + "sigs.k8s.io/controller-runtime/pkg/log" + + gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" + "github.com/gnmic/operator/internal/controller/discovery/core" +) + +type Loader struct{} + +// New instantiates the pull loader +func New() core.Loader { + return &Loader{} +} + +func (l *Loader) Name() string { + return "pull" +} + +func (l *Loader) Start( + ctx context.Context, + targetsourceName string, + spec gnmicv1alpha1.TargetSourceSpec, + out chan<- []core.DiscoveryMessage, +) error { + logger := log.FromContext(ctx).WithValues("loader", l.Name()) + + logger.Info("HTTP pull loader started") + + // Only for debugging: emit a static snapshot every 30 seconds + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + logger.Info("HTTP pull loader stopped") + return nil + + case <-ticker.C: + // Example snapshot (placeholder) + targets := []core.DiscoveryMessage{ + { + Target: core.DiscoveredTarget{ + Name: "ceos1", + Address: "clab-3-nodes-ceos1:6030", + Labels: map[string]string{"TargetSource": targetsourceName}, + }, + Event: core.CREATE, + }, + { + Target: core.DiscoveredTarget{ + Name: "leaf1", + Address: "clab-3-nodes-leaf1:57400", + Labels: map[string]string{"TargetSource": targetsourceName}, + }, + Event: core.CREATE, + }, + } + + // Non-blocking context-aware send + select { + case out <- targets: + logger.V(1).Info( + "emitted target snapshot", + "count", len(targets), + ) + case <-ctx.Done(): + logger.Info("context cancelled while emitting targets") + return nil + } + } + } +} diff --git a/internal/controller/discovery/loaders/pull/loader_test.go b/internal/controller/discovery/loaders/pull/loader_test.go new file mode 100644 index 0000000..0493bec --- /dev/null +++ b/internal/controller/discovery/loaders/pull/loader_test.go @@ -0,0 +1 @@ +package pull diff --git a/internal/controller/discovery/loaders/push/loader.go b/internal/controller/discovery/loaders/push/loader.go new file mode 100644 index 0000000..92f0ccc --- /dev/null +++ b/internal/controller/discovery/loaders/push/loader.go @@ -0,0 +1,4 @@ +package push + +// this file implements the logic receive target updates via HTTP push +// REST API defined internal/apiserver diff --git a/internal/controller/discovery/loaders/push/loader_test.go b/internal/controller/discovery/loaders/push/loader_test.go new file mode 100644 index 0000000..63fdf61 --- /dev/null +++ b/internal/controller/discovery/loaders/push/loader_test.go @@ -0,0 +1 @@ +package push diff --git a/internal/controller/discovery/mapper.go b/internal/controller/discovery/mapper.go new file mode 100644 index 0000000..18470b2 --- /dev/null +++ b/internal/controller/discovery/mapper.go @@ -0,0 +1,4 @@ +package discovery + +// This file makes diff between existing and new targets +// file decides which targets to create/update/delete diff --git a/internal/controller/discovery/mapper_test.go b/internal/controller/discovery/mapper_test.go new file mode 100644 index 0000000..5844159 --- /dev/null +++ b/internal/controller/discovery/mapper_test.go @@ -0,0 +1 @@ +package discovery diff --git a/internal/controller/discovery/target_manager.go b/internal/controller/discovery/target_manager.go new file mode 100644 index 0000000..245942d --- /dev/null +++ b/internal/controller/discovery/target_manager.go @@ -0,0 +1,70 @@ +package discovery + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" + "github.com/gnmic/operator/internal/controller/discovery/core" +) + +// TargetManager consumes discovered targets and applies them to Kubernetes. +type TargetManager struct { + client client.Client + scheme *runtime.Scheme + targetSource *gnmicv1alpha1.TargetSource + in <-chan []core.DiscoveryMessage +} + +// NewTargetManager wires a TargetManager instance. +func NewTargetManager(c client.Client, s *runtime.Scheme, ts *gnmicv1alpha1.TargetSource, in <-chan []core.DiscoveryMessage) *TargetManager { + return &TargetManager{ + client: c, + scheme: s, + targetSource: ts, + in: in, + } +} + +// Run is a long‑running loop that receives target snapshots +// and reconciles Target CRs accordingly +func (m *TargetManager) Run(ctx context.Context) error { + logger := log.FromContext(ctx). + WithValues("targetSource", m.targetSource) + + logger.Info("target manager started") + + for { + select { + case <-ctx.Done(): + logger.Info("target manager stopped") + return nil + + case targets := <-m.in: + logger.Info( + "received discovered targets", + "count", len(targets), + ) + + // List existing Target CRs owned by this TargetSource + // var existing gnmicv1alpha1.TargetList + // if err := m.client.List( + // ctx, + // &existing, + // client.MatchingLabels{ + // "gnmic.dev/targetsource": m.targetsource, + // }, + // ); err != nil { + // return err + // } + + // TODO: Target Lifecycle Management + // 1. Compare and determine which Targets to create/update/delete + // 2. Create/update/delete Target CRs accordingly + // 3. Update TargetSource status with sync results + } + } +} diff --git a/internal/controller/targetsource_controller.go b/internal/controller/targetsource_controller.go index 032b103..8cd6f68 100644 --- a/internal/controller/targetsource_controller.go +++ b/internal/controller/targetsource_controller.go @@ -18,19 +18,33 @@ package controller import ( "context" + "sync" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" gnmicv1alpha1 "github.com/gnmic/operator/api/v1alpha1" + "github.com/gnmic/operator/internal/controller/discovery" + "github.com/gnmic/operator/internal/controller/discovery/core" + _ "github.com/gnmic/operator/internal/controller/discovery/loaders/all" ) +const targetSourceFinalizer = "operator.gnmic.dev/targetsource-finalizer" + +type runningSource struct { + cancel context.CancelFunc +} + // TargetSourceReconciler reconciles a TargetSource object type TargetSourceReconciler struct { client.Client Scheme *runtime.Scheme + + mu sync.Mutex + running map[client.ObjectKey]runningSource } // +kubebuilder:rbac:groups=operator.gnmic.dev,resources=targetsources,verbs=get;list;watch;create;update;patch;delete @@ -45,23 +59,106 @@ func (r *TargetSourceReconciler) Reconcile(ctx context.Context, req ctrl.Request var targetSource gnmicv1alpha1.TargetSource if err := r.Get(ctx, req.NamespacedName, &targetSource); err != nil { + // If the TargetSource no longer exists, ensure runtime cleanup + if client.IgnoreNotFound(err) == nil { + r.stopDiscovery(req.NamespacedName) + } return ctrl.Result{}, client.IgnoreNotFound(err) } logger.Info("reconciling TargetSource", "name", targetSource.Name) - // TODO: Implement target discovery logic based on spec: - // - HTTP: fetch targets from HTTP endpoint - // - Consul: discover from Consul - // - ConfigMap: read from ConfigMap - // - PodSelector: select Kubernetes pods - // - ServiceSelector: select Kubernetes services + // Handle deletion with finalizer + if !targetSource.DeletionTimestamp.IsZero() { + logger.Info("TargetSource is being deleted, stopping pipeline", "name", targetSource.Name) + + r.stopDiscovery(req.NamespacedName) + + // Remove finalizer if exists + if controllerutil.ContainsFinalizer(&targetSource, targetSourceFinalizer) { + controllerutil.RemoveFinalizer(&targetSource, targetSourceFinalizer) + if err := r.Update(ctx, &targetSource); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil + } + + // Ensure finalizer is set + if !controllerutil.ContainsFinalizer(&targetSource, targetSourceFinalizer) { + controllerutil.AddFinalizer(&targetSource, targetSourceFinalizer) + if err := r.Update(ctx, &targetSource); err != nil { + return ctrl.Result{}, err + } + // Requeue to continue with a clean state + return ctrl.Result{}, nil + } + + // TODO: + // 1. Check if a pipeline is already running for this TargetSource + // 2. If not, create and start a new pipeline: + // a. Create a Loader based on TargetSource spec + // b. Start the Loader in a new goroutine, passing a channel for discovered targets + // c. Start a TargetManager in another goroutine to consume discovered targets and manage Target CRs + // 3. If yes, check if the spec has changed and restart the pipeline if needed + + r.mu.Lock() + _, exists := r.running[req.NamespacedName] + r.mu.Unlock() + + // If a targetsource loader exists, return immediately without starting + // any new loader or target manager + if exists { + return ctrl.Result{}, nil + } + + loader, err := discovery.NewLoader(targetSource.ObjectMeta.Name, targetSource.ObjectMeta.Namespace, targetSource.Spec) + if err != nil { + return ctrl.Result{}, err + } + + runtimeCtx, cancel := context.WithCancel(context.Background()) + + targetChannel := make(chan []core.DiscoveryMessage, 10) + + // start loader + go loader.Start(runtimeCtx, targetSource.Name, targetSource.Spec, targetChannel) + + // start target manager + manager := discovery.NewTargetManager( + r.Client, + r.Scheme, + &targetSource, + targetChannel, + ) + go manager.Run(runtimeCtx) + + r.mu.Lock() + r.running[req.NamespacedName] = runningSource{cancel: cancel} + r.mu.Unlock() + + logger.Info("TargetSource pipeline started", "name", targetSource.Name) return ctrl.Result{}, nil } +// stopDiscovery stops and removes a running discovery pipeline +// for the given TargetSource key +func (r *TargetSourceReconciler) stopDiscovery(key client.ObjectKey) { + r.mu.Lock() + defer r.mu.Unlock() + + if running, ok := r.running[key]; ok { + running.cancel() + delete(r.running, key) + } +} + // SetupWithManager sets up the controller with the Manager. func (r *TargetSourceReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.running = make(map[client.ObjectKey]runningSource) + return ctrl.NewControllerManagedBy(mgr). For(&gnmicv1alpha1.TargetSource{}). Named("targetsource"). diff --git a/lab/dev/resources/targetsources/ctest1.yaml b/lab/dev/resources/targetsources/ctest1.yaml new file mode 100644 index 0000000..e0aea43 --- /dev/null +++ b/lab/dev/resources/targetsources/ctest1.yaml @@ -0,0 +1,12 @@ +apiVersion: operator.gnmic.dev/v1alpha1 +kind: TargetSource +metadata: + name: http-discovery +spec: + provider: + http: + url: http://inventory-service:8080/targets + labels: + source: inventory + type: http + profile: eos \ No newline at end of file