From 53c379f145984c452010e4419940e4bd3222a1bc Mon Sep 17 00:00:00 2001 From: Dennis Kniep Date: Wed, 10 Jan 2024 00:31:22 +0100 Subject: [PATCH] Implemented TaskSchedule Managed Resource --- README.md | 20 + apis/core/v1alpha1/taskschedule_types.go | 126 +++++ apis/core/v1alpha1/zz_generated.deepcopy.go | 154 ++++++ apis/core/v1alpha1/zz_generated.managed.go | 76 +++ .../core/v1alpha1/zz_generated.managedlist.go | 9 + apis/core/v1alpha1/zz_generated.resolvers.go | 51 ++ go.mod | 2 +- go.sum | 4 +- internal/clients/application_test.go | 4 +- internal/clients/service.go | 4 + internal/clients/service_test.go | 6 + internal/clients/taskdefinition.go | 4 +- internal/clients/taskdefinition_test.go | 4 +- internal/clients/taskschedule.go | 105 +++++ internal/clients/taskschedule_test.go | 106 +++++ internal/controller/springclouddataflow.go | 2 + .../controller/taskschedule/taskschedule.go | 258 ++++++++++ ...ddataflow.crossplane.io_taskschedules.yaml | 439 ++++++++++++++++++ tests/docker-compose.yaml | 1 + 19 files changed, 1364 insertions(+), 11 deletions(-) create mode 100644 apis/core/v1alpha1/taskschedule_types.go create mode 100644 apis/core/v1alpha1/zz_generated.resolvers.go create mode 100644 internal/clients/taskschedule.go create mode 100644 internal/clients/taskschedule_test.go create mode 100644 internal/controller/taskschedule/taskschedule.go create mode 100644 package/crds/core.springclouddataflow.crossplane.io_taskschedules.yaml diff --git a/README.md b/README.md index f2e1fb3..55e5f55 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ spec: Currently covered Managed Resources: - [Application](#application) - [TaskDefinition](#taskdefinition) +- [TaskSchedule](#taskschedule) ## Application @@ -131,6 +132,25 @@ spec: name: provider-spring-cloud-dataflow-config ``` +## TaskSchedule + +[docs](https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#_the_scheduler) + +[rest api](https://docs.spring.io/spring-cloud-dataflow/docs/current/reference/htmlsingle/#api-guide-resources-task-scheduler) + +Example: +``` +apiVersion: core.springclouddataflow.crossplane.io/v1alpha1 +kind: TaskSchedule +metadata: + name: schedule-1 +spec: + forProvider: + name: "MyTaskSchedule01" + providerConfigRef: + name: provider-spring-cloud-dataflow-config +``` + # Contribute ## Developing 1. Add new type by running the following command: diff --git a/apis/core/v1alpha1/taskschedule_types.go b/apis/core/v1alpha1/taskschedule_types.go new file mode 100644 index 0000000..5b0f8c5 --- /dev/null +++ b/apis/core/v1alpha1/taskschedule_types.go @@ -0,0 +1,126 @@ +/* +Copyright 2022 The Crossplane 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 v1alpha1 + +import ( + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +) + +// TaskScheduleParameters are the configurable fields of a TaskSchedule. +type TaskScheduleParameters struct { + + // Name of the task schedule (immutable) + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=52 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name is immutable" + ScheduleName string `json:"scheduleName"` + + // TaskDefinition Name that will be scheduled (immutable) + // At least one of taskDefinitionName, taskDefinitionNameRef or taskDefinitionNameSelector is required. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="TaskDefinitionName is immutable" + // +crossplane:generate:reference:type=github.com/denniskniep/provider-springclouddataflow/apis/core/v1alpha1.TaskDefinition + TaskDefinitionName *string `json:"taskDefinitionName,omitempty"` + + // TaskDefinition reference to retrieve the TaskDefinition Name, that will be scheduled + // At least one of taskDefinitionName, taskDefinitionNameRef or taskDefinitionNameSelector is required. + // +optional + TaskDefinitionNameRef *xpv1.Reference `json:"taskDefinitionNameRef,omitempty"` + + // TaskDefinitionNameSelector selects a reference to a TaskDefinition and retrieves its name + // At least one of temporalNamespaceName, temporalNamespaceNameRef or temporalNamespaceNameSelector is required. + // +optional + TaskDefinitionNameSelector *xpv1.Selector `json:"taskDefinitionNameSelector,omitempty"` + + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Platform is immutable" + // +kubebuilder:validation:Required + CronExpression string `json:"cronExpression,omitempty"` + + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Platform is immutable" + // +kubebuilder:default=default + Platform string `json:"platform,omitempty"` + + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Arguments is immutable" + Arguments *string `json:"arguments,omitempty"` + + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Properties is immutable" + Properties *string `json:"properties,omitempty"` +} + +// TaskScheduleObservation are the observable fields of a TaskSchedule. +type TaskScheduleObservation struct { + ScheduleName string `json:"scheduleName"` + + TaskDefinitionName *string `json:"taskDefinitionName,omitempty"` +} + +// A TaskScheduleSpec defines the desired state of a TaskSchedule. +type TaskScheduleSpec struct { + xpv1.ResourceSpec `json:",inline"` + ForProvider TaskScheduleParameters `json:"forProvider"` +} + +// A TaskScheduleStatus represents the observed state of a TaskSchedule. +type TaskScheduleStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider TaskScheduleObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true + +// A TaskSchedule is an example API type. +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="EXTERNAL-NAME",type="string",JSONPath=".metadata.annotations.crossplane\\.io/external-name" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,springclouddataflow} +type TaskSchedule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TaskScheduleSpec `json:"spec"` + Status TaskScheduleStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TaskScheduleList contains a list of TaskSchedule +type TaskScheduleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TaskSchedule `json:"items"` +} + +// TaskSchedule type metadata. +var ( + TaskScheduleKind = reflect.TypeOf(TaskSchedule{}).Name() + TaskScheduleGroupKind = schema.GroupKind{Group: Group, Kind: TaskScheduleKind}.String() + TaskScheduleKindAPIVersion = TaskScheduleKind + "." + SchemeGroupVersion.String() + TaskScheduleGroupVersionKind = SchemeGroupVersion.WithKind(TaskScheduleKind) +) + +func init() { + SchemeBuilder.Register(&TaskSchedule{}, &TaskScheduleList{}) +} diff --git a/apis/core/v1alpha1/zz_generated.deepcopy.go b/apis/core/v1alpha1/zz_generated.deepcopy.go index 9aa52d9..575dc38 100644 --- a/apis/core/v1alpha1/zz_generated.deepcopy.go +++ b/apis/core/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/crossplane/crossplane-runtime/apis/common/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -270,3 +271,156 @@ func (in *TaskDefinitionStatus) DeepCopy() *TaskDefinitionStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskSchedule) DeepCopyInto(out *TaskSchedule) { + *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 TaskSchedule. +func (in *TaskSchedule) DeepCopy() *TaskSchedule { + if in == nil { + return nil + } + out := new(TaskSchedule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TaskSchedule) 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 *TaskScheduleList) DeepCopyInto(out *TaskScheduleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TaskSchedule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskScheduleList. +func (in *TaskScheduleList) DeepCopy() *TaskScheduleList { + if in == nil { + return nil + } + out := new(TaskScheduleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TaskScheduleList) 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 *TaskScheduleObservation) DeepCopyInto(out *TaskScheduleObservation) { + *out = *in + if in.TaskDefinitionName != nil { + in, out := &in.TaskDefinitionName, &out.TaskDefinitionName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskScheduleObservation. +func (in *TaskScheduleObservation) DeepCopy() *TaskScheduleObservation { + if in == nil { + return nil + } + out := new(TaskScheduleObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskScheduleParameters) DeepCopyInto(out *TaskScheduleParameters) { + *out = *in + if in.TaskDefinitionName != nil { + in, out := &in.TaskDefinitionName, &out.TaskDefinitionName + *out = new(string) + **out = **in + } + if in.TaskDefinitionNameRef != nil { + in, out := &in.TaskDefinitionNameRef, &out.TaskDefinitionNameRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.TaskDefinitionNameSelector != nil { + in, out := &in.TaskDefinitionNameSelector, &out.TaskDefinitionNameSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + if in.Arguments != nil { + in, out := &in.Arguments, &out.Arguments + *out = new(string) + **out = **in + } + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskScheduleParameters. +func (in *TaskScheduleParameters) DeepCopy() *TaskScheduleParameters { + if in == nil { + return nil + } + out := new(TaskScheduleParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskScheduleSpec) DeepCopyInto(out *TaskScheduleSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskScheduleSpec. +func (in *TaskScheduleSpec) DeepCopy() *TaskScheduleSpec { + if in == nil { + return nil + } + out := new(TaskScheduleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskScheduleStatus) DeepCopyInto(out *TaskScheduleStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskScheduleStatus. +func (in *TaskScheduleStatus) DeepCopy() *TaskScheduleStatus { + if in == nil { + return nil + } + out := new(TaskScheduleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/core/v1alpha1/zz_generated.managed.go b/apis/core/v1alpha1/zz_generated.managed.go index 2bc3a59..5825829 100644 --- a/apis/core/v1alpha1/zz_generated.managed.go +++ b/apis/core/v1alpha1/zz_generated.managed.go @@ -170,3 +170,79 @@ func (mg *TaskDefinition) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectio func (mg *TaskDefinition) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r } + +// GetCondition of this TaskSchedule. +func (mg *TaskSchedule) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this TaskSchedule. +func (mg *TaskSchedule) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetManagementPolicies of this TaskSchedule. +func (mg *TaskSchedule) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this TaskSchedule. +func (mg *TaskSchedule) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +/* +GetProviderReference of this TaskSchedule. +Deprecated: Use GetProviderConfigReference. +*/ +func (mg *TaskSchedule) GetProviderReference() *xpv1.Reference { + return mg.Spec.ProviderReference +} + +// GetPublishConnectionDetailsTo of this TaskSchedule. +func (mg *TaskSchedule) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { + return mg.Spec.PublishConnectionDetailsTo +} + +// GetWriteConnectionSecretToReference of this TaskSchedule. +func (mg *TaskSchedule) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this TaskSchedule. +func (mg *TaskSchedule) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this TaskSchedule. +func (mg *TaskSchedule) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetManagementPolicies of this TaskSchedule. +func (mg *TaskSchedule) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this TaskSchedule. +func (mg *TaskSchedule) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +/* +SetProviderReference of this TaskSchedule. +Deprecated: Use SetProviderConfigReference. +*/ +func (mg *TaskSchedule) SetProviderReference(r *xpv1.Reference) { + mg.Spec.ProviderReference = r +} + +// SetPublishConnectionDetailsTo of this TaskSchedule. +func (mg *TaskSchedule) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { + mg.Spec.PublishConnectionDetailsTo = r +} + +// SetWriteConnectionSecretToReference of this TaskSchedule. +func (mg *TaskSchedule) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/core/v1alpha1/zz_generated.managedlist.go b/apis/core/v1alpha1/zz_generated.managedlist.go index 67d2c90..e82bf9c 100644 --- a/apis/core/v1alpha1/zz_generated.managedlist.go +++ b/apis/core/v1alpha1/zz_generated.managedlist.go @@ -36,3 +36,12 @@ func (l *TaskDefinitionList) GetItems() []resource.Managed { } return items } + +// GetItems of this TaskScheduleList. +func (l *TaskScheduleList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/apis/core/v1alpha1/zz_generated.resolvers.go b/apis/core/v1alpha1/zz_generated.resolvers.go new file mode 100644 index 0000000..1bb20f3 --- /dev/null +++ b/apis/core/v1alpha1/zz_generated.resolvers.go @@ -0,0 +1,51 @@ +/* +Copyright 2020 The Crossplane 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. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + reference "github.com/crossplane/crossplane-runtime/pkg/reference" + errors "github.com/pkg/errors" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResolveReferences of this TaskSchedule. +func (mg *TaskSchedule) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + var rsp reference.ResolutionResponse + var err error + + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.TaskDefinitionName), + Extract: reference.ExternalName(), + Reference: mg.Spec.ForProvider.TaskDefinitionNameRef, + Selector: mg.Spec.ForProvider.TaskDefinitionNameSelector, + To: reference.To{ + List: &TaskDefinitionList{}, + Managed: &TaskDefinition{}, + }, + }) + if err != nil { + return errors.Wrap(err, "mg.Spec.ForProvider.TaskDefinitionName") + } + mg.Spec.ForProvider.TaskDefinitionName = reference.ToPtrValue(rsp.ResolvedValue) + mg.Spec.ForProvider.TaskDefinitionNameRef = rsp.ResolvedReference + + return nil +} diff --git a/go.mod b/go.mod index c51e998..2befed8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/crossplane/crossplane-runtime v1.14.0-rc.0.0.20230815060607-4f3cb3d9fd2b github.com/crossplane/crossplane-tools v0.0.0-20230714144037-2684f4bc7638 - github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-prerelease1 + github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-1.2.0 github.com/google/go-cmp v0.6.0 github.com/pkg/errors v0.9.1 gopkg.in/alecthomas/kingpin.v2 v2.2.6 diff --git a/go.sum b/go.sum index 58860bd..2505199 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpT 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/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-prerelease1 h1:k0oM27YHtXKY/DM2wLMwFh3czaerVfFMqM+gkzyUQV8= -github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-prerelease1/go.mod h1:Umr0NPkL2c3rFgYat/09muAkHUme3AklLOy2pQfTF2o= +github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-1.2.0 h1:cY4i8MfIJuYv0huZB4kBQSOXzv1r0ptotVqazlOM7Eo= +github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2 v2.11.2-1.2.0/go.mod h1:Umr0NPkL2c3rFgYat/09muAkHUme3AklLOy2pQfTF2o= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= diff --git a/internal/clients/application_test.go b/internal/clients/application_test.go index 4188bd5..a5cb982 100644 --- a/internal/clients/application_test.go +++ b/internal/clients/application_test.go @@ -9,9 +9,7 @@ import ( ) func CreateApplicationService(t *testing.T) ApplicationService { - jsonConfig := `{ - "Uri": "http://localhost:9393/" - }` + jsonConfig := getJsonConfig() srv, err := NewApplicationService([]byte(jsonConfig)) if err != nil { diff --git a/internal/clients/service.go b/internal/clients/service.go index c36c566..acf40cb 100644 --- a/internal/clients/service.go +++ b/internal/clients/service.go @@ -50,3 +50,7 @@ func NewApplicationService(configData []byte) (ApplicationService, error) { func NewTaskDefinitionService(configData []byte) (TaskDefinitionService, error) { return NewDataFlowService(configData) } + +func NewTaskScheduleService(configData []byte) (TaskScheduleService, error) { + return NewDataFlowService(configData) +} diff --git a/internal/clients/service_test.go b/internal/clients/service_test.go index 5a6cb86..37dd414 100644 --- a/internal/clients/service_test.go +++ b/internal/clients/service_test.go @@ -7,3 +7,9 @@ func skipIfIsShort(t *testing.T) { t.Skip("skipping test in short mode.") } } + +func getJsonConfig() string { + return `{ + "url": "http://localhost:9393" + }` +} diff --git a/internal/clients/taskdefinition.go b/internal/clients/taskdefinition.go index 5e5a751..6c2ea4c 100644 --- a/internal/clients/taskdefinition.go +++ b/internal/clients/taskdefinition.go @@ -35,8 +35,8 @@ type TaskDefinitionDescribeResponse struct { Status string `json:"status"` } -func (s *DataFlowServiceImpl) MapToTaskDefinitionCompare(app interface{}) (*TaskDefinitionCompare, error) { - taskJson, err := json.Marshal(app) +func (s *DataFlowServiceImpl) MapToTaskDefinitionCompare(taskDefinition interface{}) (*TaskDefinitionCompare, error) { + taskJson, err := json.Marshal(taskDefinition) if err != nil { return nil, err } diff --git a/internal/clients/taskdefinition_test.go b/internal/clients/taskdefinition_test.go index 912a23d..bbb571b 100644 --- a/internal/clients/taskdefinition_test.go +++ b/internal/clients/taskdefinition_test.go @@ -9,9 +9,7 @@ import ( ) func CreateTaskDefinitionService(t *testing.T) TaskDefinitionService { - jsonConfig := `{ - "Uri": "http://localhost:9393/" - }` + jsonConfig := getJsonConfig() srv, err := NewTaskDefinitionService([]byte(jsonConfig)) if err != nil { diff --git a/internal/clients/taskschedule.go b/internal/clients/taskschedule.go new file mode 100644 index 0000000..0d2e41a --- /dev/null +++ b/internal/clients/taskschedule.go @@ -0,0 +1,105 @@ +package clients + +import ( + "context" + "encoding/json" + "errors" + + core "github.com/denniskniep/provider-springclouddataflow/apis/core/v1alpha1" + "github.com/denniskniep/spring-cloud-dataflow-sdk-go/v2/client/tasks" + kiota "github.com/microsoft/kiota-abstractions-go" +) + +type TaskScheduleService interface { + DescribeTaskSchedule(ctx context.Context, app *core.TaskScheduleParameters) (*core.TaskScheduleObservation, error) + + CreateTaskSchedule(ctx context.Context, app *core.TaskScheduleParameters) error + UpdateTaskSchedule(ctx context.Context, app *core.TaskScheduleParameters) error + DeleteTaskSchedule(ctx context.Context, app *core.TaskScheduleParameters) error + + MapToTaskScheduleCompare(app interface{}) (*TaskScheduleCompare, error) +} + +type TaskScheduleCompare struct { + ScheduleName string `json:"scheduleName"` + TaskDefinitionName *string `json:"taskDefinitionName,omitempty"` +} + +func (s *DataFlowServiceImpl) MapToTaskScheduleCompare(taskSchedule interface{}) (*TaskScheduleCompare, error) { + taskJson, err := json.Marshal(taskSchedule) + if err != nil { + return nil, err + } + + var taskCompare = TaskScheduleCompare{} + err = json.Unmarshal(taskJson, &taskCompare) + if err != nil { + return nil, err + } + + return &taskCompare, nil +} + +func (s *DataFlowServiceImpl) CreateTaskSchedule(ctx context.Context, task *core.TaskScheduleParameters) error { + + properties := "scheduler.cron.expression=" + task.CronExpression + if task.Properties != nil { + properties = properties + "," + *task.Properties + } + + err := s.client.Tasks().Schedules().Post(ctx, &tasks.SchedulesRequestBuilderPostRequestConfiguration{ + QueryParameters: &tasks.SchedulesRequestBuilderPostQueryParameters{ + ScheduleName: &task.ScheduleName, + TaskDefinitionName: task.TaskDefinitionName, + Platform: &task.Platform, + Arguments: task.Arguments, + Properties: &properties, + }, + }) + + if err != nil { + return err + } + + return nil +} + +func (s *DataFlowServiceImpl) UpdateTaskSchedule(ctx context.Context, task *core.TaskScheduleParameters) error { + return errors.New("Update not implemented - all properties are immutable!") +} + +func (s *DataFlowServiceImpl) DescribeTaskSchedule(ctx context.Context, task *core.TaskScheduleParameters) (*core.TaskScheduleObservation, error) { + result, err := s.client.Tasks().Schedules().BySchedulesId(task.ScheduleName).Get(ctx, nil) + + var apiError *kiota.ApiError + if errors.As(err, &apiError) && apiError.ResponseStatusCode == 404 { + return nil, nil + } + + if err != nil { + return nil, err + } + + var observed = core.TaskScheduleObservation{} + err = json.Unmarshal(result, &observed) + if err != nil { + return nil, err + } + + return &observed, nil +} + +func (s *DataFlowServiceImpl) DeleteTaskSchedule(ctx context.Context, task *core.TaskScheduleParameters) error { + _, err := s.client.Tasks().Schedules().BySchedulesId(task.ScheduleName).Delete(ctx, nil) + + var apiError *kiota.ApiError + if errors.As(err, &apiError) && apiError.ResponseStatusCode == 404 { + return nil + } + + if err != nil { + return err + } + + return nil +} diff --git a/internal/clients/taskschedule_test.go b/internal/clients/taskschedule_test.go new file mode 100644 index 0000000..5d8dbf0 --- /dev/null +++ b/internal/clients/taskschedule_test.go @@ -0,0 +1,106 @@ +package clients + +import ( + "testing" + + core "github.com/denniskniep/provider-springclouddataflow/apis/core/v1alpha1" + "github.com/google/go-cmp/cmp" + "golang.org/x/net/context" +) + +func CreateTaskScheduleService(t *testing.T) TaskScheduleService { + jsonConfig := getJsonConfig() + + srv, err := NewTaskScheduleService([]byte(jsonConfig)) + if err != nil { + t.Fatal(err) + } + + return srv +} + +func CreateDefaultTaskSchedule(scheduleName string, taskDefinitionName string) *core.TaskScheduleParameters { + return &core.TaskScheduleParameters{ + ScheduleName: scheduleName, + TaskDefinitionName: &taskDefinitionName, + CronExpression: "0 * * * *", + Platform: "default", + } +} + +// Local Env is not able to add Schedules +/*func TestCreateTaskSchedule(t *testing.T) { + skipIfIsShort(t) + + srvApp := CreateApplicationService(t) + srvTask := CreateTaskDefinitionService(t) + srvSchedule := CreateTaskScheduleService(t) + + testApp := CreateDefaultApplication("task", "Test040", "v1.0.0") + _ = CreateApplication(t, srvApp, testApp) + + testTask := CreateDefaultTaskDefinition("MyTask30", "MyDesc", "Test040") + _ = CreateTaskDefinition(t, srvTask, testTask) + + testSchedule := CreateDefaultTaskSchedule("schedule-2", "MyTask30") + created := CreateTaskSchedule(t, srvSchedule, testSchedule) + + AssertTaskScheduleAreEqual(t, srvSchedule, created, testSchedule) + + DeleteTaskSchedule(t,srvSchedule,testSchedule) + DeleteTaskDefinition(t, srvTask, testTask) + DeleteApplication(t, srvApp, testApp) +}*/ + +func CreateTaskSchedule(t *testing.T, srv TaskScheduleService, task *core.TaskScheduleParameters) *core.TaskScheduleObservation { + t.Helper() + err := srv.CreateTaskSchedule(context.Background(), task) + if err != nil { + t.Fatal(err) + } + + createdTask, err := srv.DescribeTaskSchedule(context.Background(), task) + if err != nil { + t.Fatal(err) + } + + if createdTask == nil { + t.Fatal("TaskSchedule was not found") + } + return createdTask +} + +func DeleteTaskSchedule(t *testing.T, srv TaskScheduleService, task *core.TaskScheduleParameters) { + t.Helper() + err := srv.DeleteTaskSchedule(context.Background(), task) + if err != nil { + t.Fatal(err) + } + + noApp, err := srv.DescribeTaskSchedule(context.Background(), task) + if err != nil { + t.Fatal(err) + } + + if noApp != nil { + t.Fatal("TaskSchedule was not deleted") + } +} + +func AssertTaskScheduleAreEqual(t *testing.T, srv TaskScheduleService, actual *core.TaskScheduleObservation, expected *core.TaskScheduleParameters) { + t.Helper() + mappedActual, err := srv.MapToTaskScheduleCompare(actual) + if err != nil { + t.Fatal(err) + } + + mappedExpected, err := srv.MapToTaskScheduleCompare(expected) + if err != nil { + t.Fatal(err) + } + + diff := cmp.Diff(mappedActual, mappedExpected) + if diff != "" { + t.Fatal(diff) + } +} diff --git a/internal/controller/springclouddataflow.go b/internal/controller/springclouddataflow.go index fd6c3b5..d1d8d08 100644 --- a/internal/controller/springclouddataflow.go +++ b/internal/controller/springclouddataflow.go @@ -23,6 +23,7 @@ import ( application "github.com/denniskniep/provider-springclouddataflow/internal/controller/application" "github.com/denniskniep/provider-springclouddataflow/internal/controller/config" "github.com/denniskniep/provider-springclouddataflow/internal/controller/taskdefinition" + "github.com/denniskniep/provider-springclouddataflow/internal/controller/taskschedule" ) // Setup creates all SpringCloudDataFlow controllers with the supplied logger and adds them to @@ -32,6 +33,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { config.Setup, application.Setup, taskdefinition.Setup, + taskschedule.Setup, } { if err := setup(mgr, o); err != nil { return err diff --git a/internal/controller/taskschedule/taskschedule.go b/internal/controller/taskschedule/taskschedule.go new file mode 100644 index 0000000..b54b9ca --- /dev/null +++ b/internal/controller/taskschedule/taskschedule.go @@ -0,0 +1,258 @@ +package taskschedule + +import ( + "context" + "strconv" + + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/connection" + "github.com/crossplane/crossplane-runtime/pkg/controller" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + + "github.com/denniskniep/provider-springclouddataflow/apis/core/v1alpha1" + apisv1alpha1 "github.com/denniskniep/provider-springclouddataflow/apis/v1alpha1" + "github.com/denniskniep/provider-springclouddataflow/internal/clients" + "github.com/denniskniep/provider-springclouddataflow/internal/features" +) + +const ( + errNotTaskSchedule = "managed resource is not a TaskSchedule custom resource" + errTrackPCUsage = "cannot track ProviderConfig usage" + errGetPC = "cannot get ProviderConfig" + errGetCreds = "cannot get credentials" + + errNewClient = "cannot create new Service" + errDescribe = "failed to describe TaskSchedule resource" + errCreate = "failed to create TaskSchedule resource" + errUpdate = "failed to update TaskSchedule resource" + errDelete = "failed to delete TaskSchedule resource" + errMapping = "failed to map TaskSchedule resource" +) + +// Setup adds a controller that reconciles TaskSchedule managed resources. +func Setup(mgr ctrl.Manager, o controller.Options) error { + o.Logger.Info("Setup Controller: TaskSchedule") + name := managed.ControllerName(v1alpha1.TaskScheduleGroupKind) + + cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} + if o.Features.Enabled(features.EnableAlphaExternalSecretStores) { + cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), apisv1alpha1.StoreConfigGroupVersionKind)) + } + + r := managed.NewReconciler(mgr, + resource.ManagedKind(v1alpha1.TaskScheduleGroupVersionKind), + managed.WithExternalConnecter(&connector{ + kube: mgr.GetClient(), + usage: resource.NewProviderConfigUsageTracker(mgr.GetClient(), &apisv1alpha1.ProviderConfigUsage{}), + newServiceFn: clients.NewTaskScheduleService, + logger: o.Logger.WithValues("controller", name)}), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithPollInterval(o.PollInterval), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + managed.WithInitializers(), + managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), + managed.WithConnectionPublishers(cps...)) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + WithEventFilter(resource.DesiredStateChanged()). + For(&v1alpha1.TaskSchedule{}). + Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) +} + +// A connector is expected to produce an ExternalClient when its Connect method +// is called. +type connector struct { + kube client.Client + usage resource.Tracker + logger logging.Logger + newServiceFn func(creds []byte) (clients.TaskScheduleService, error) +} + +// Connect typically produces an ExternalClient by: +// 1. Tracking that the managed resource is using a ProviderConfig. +// 2. Getting the managed resource's ProviderConfig. +// 3. Getting the credentials specified by the ProviderConfig. +// 4. Using the credentials to form a client. +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + logger := c.logger.WithValues("method", "connect") + logger.Debug("Start Connect") + cr, ok := mg.(*v1alpha1.TaskSchedule) + if !ok { + return nil, errors.New(errNotTaskSchedule) + } + + if err := c.usage.Track(ctx, mg); err != nil { + return nil, errors.Wrap(err, errTrackPCUsage) + } + + pc := &apisv1alpha1.ProviderConfig{} + if err := c.kube.Get(ctx, types.NamespacedName{Name: cr.GetProviderConfigReference().Name}, pc); err != nil { + return nil, errors.Wrap(err, errGetPC) + } + + cd := pc.Spec.Credentials + data, err := resource.CommonCredentialExtractor(ctx, cd.Source, c.kube, cd.CommonCredentialSelectors) + if err != nil { + return nil, errors.Wrap(err, errGetCreds) + } + + svc, err := c.newServiceFn(data) + if err != nil { + return nil, errors.Wrap(err, errNewClient) + } + logger.Debug("Connected") + return &external{service: svc, logger: c.logger}, nil +} + +// An ExternalClient observes, then either creates, updates, or deletes an +// external resource to ensure it reflects the managed resource's desired state. +type external struct { + // A 'client' used to connect to the external resource API. In practice this + // would be something like an AWS SDK client. + service clients.TaskScheduleService + logger logging.Logger +} + +func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + logger := c.logger.WithValues("method", "observe") + logger.Debug("Start observe") + cr, ok := mg.(*v1alpha1.TaskSchedule) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotTaskSchedule) + } + + c.logger.Debug("ExternalName: '" + meta.GetExternalName(cr) + "'") + + uniqueId := createUniqueIdentifier(&cr.Spec.ForProvider) + + observed, err := c.service.DescribeTaskSchedule(ctx, &cr.Spec.ForProvider) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errDescribe) + } + + if observed == nil { + c.logger.Debug("Managed resource '" + uniqueId + "' does not exist") + return managed.ExternalObservation{ + ResourceExists: false, + ResourceUpToDate: false, + ConnectionDetails: managed.ConnectionDetails{}, + }, nil + } + + c.logger.Debug("Found '" + uniqueId + "'") + + // Update Status + cr.Status.AtProvider = *observed + cr.SetConditions(xpv1.Available().WithMessage("TaskSchedule exists")) + + observedCompareable, err := c.service.MapToTaskScheduleCompare(observed) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errMapping) + } + + specCompareable, err := c.service.MapToTaskScheduleCompare(&cr.Spec.ForProvider) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errMapping) + } + + diff := "" + resourceUpToDate := cmp.Equal(specCompareable, observedCompareable) + + // Compare Spec with observed + if !resourceUpToDate { + diff = cmp.Diff(specCompareable, observedCompareable) + } + c.logger.Debug("Managed resource '" + uniqueId + "' upToDate: " + strconv.FormatBool(resourceUpToDate) + "") + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: resourceUpToDate, + Diff: diff, + ResourceLateInitialized: false, + ConnectionDetails: managed.ConnectionDetails{}, + }, nil +} + +func createUniqueIdentifier(app *v1alpha1.TaskScheduleParameters) string { + return app.ScheduleName +} + +func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + logger := c.logger.WithValues("method", "create") + logger.Debug("Start create") + cr, ok := mg.(*v1alpha1.TaskSchedule) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotTaskSchedule) + } + + err := c.service.CreateTaskSchedule(ctx, &cr.Spec.ForProvider) + + if err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreate) + } + + uniqueId := createUniqueIdentifier(&cr.Spec.ForProvider) + meta.SetExternalName(cr, uniqueId) + c.logger.Debug("Managed resource '" + uniqueId + "' created") + + return managed.ExternalCreation{ + // Optionally return any details that may be required to connect to the + // external resource. These will be stored as the connection secret. + ConnectionDetails: managed.ConnectionDetails{}, + }, nil +} + +func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + logger := c.logger.WithValues("method", "update") + logger.Debug("Start update") + cr, ok := mg.(*v1alpha1.TaskSchedule) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotTaskSchedule) + } + + err := c.service.UpdateTaskSchedule(ctx, &cr.Spec.ForProvider) + + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdate) + } + + uniqueId := createUniqueIdentifier(&cr.Spec.ForProvider) + c.logger.Debug("Managed resource '" + uniqueId + "' updated") + return managed.ExternalUpdate{ + // Optionally return any details that may be required to connect to the + // external resource. These will be stored as the connection secret. + ConnectionDetails: managed.ConnectionDetails{}, + }, nil +} + +func (c *external) Delete(ctx context.Context, mg resource.Managed) error { + logger := c.logger.WithValues("method", "delete") + logger.Debug("Start delete") + cr, ok := mg.(*v1alpha1.TaskSchedule) + if !ok { + return errors.New(errNotTaskSchedule) + } + + err := c.service.DeleteTaskSchedule(ctx, &cr.Spec.ForProvider) + + if err != nil { + return errors.Wrap(err, errDelete) + } + + uniqueId := createUniqueIdentifier(&cr.Spec.ForProvider) + c.logger.Debug("Managed resource '" + uniqueId + "' deleted") + return nil +} diff --git a/package/crds/core.springclouddataflow.crossplane.io_taskschedules.yaml b/package/crds/core.springclouddataflow.crossplane.io_taskschedules.yaml new file mode 100644 index 0000000..cfcc8bd --- /dev/null +++ b/package/crds/core.springclouddataflow.crossplane.io_taskschedules.yaml @@ -0,0 +1,439 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.1 + name: taskschedules.core.springclouddataflow.crossplane.io +spec: + group: core.springclouddataflow.crossplane.io + names: + categories: + - crossplane + - managed + - springclouddataflow + kind: TaskSchedule + listKind: TaskScheduleList + plural: taskschedules + singular: taskschedule + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.annotations.crossplane\.io/external-name + name: EXTERNAL-NAME + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: A TaskSchedule is an example API type. + 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: A TaskScheduleSpec defines the desired state of a TaskSchedule. + properties: + deletionPolicy: + default: Delete + description: 'DeletionPolicy specifies what will happen to the underlying + external when this managed resource is deleted - either "Delete" + or "Orphan" the external resource. This field is planned to be deprecated + in favor of the ManagementPolicies field in a future release. Currently, + both could be set independently and non-default values would be + honored if the feature flag is enabled. See the design doc for more + information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223' + enum: + - Orphan + - Delete + type: string + forProvider: + description: TaskScheduleParameters are the configurable fields of + a TaskSchedule. + properties: + arguments: + type: string + x-kubernetes-validations: + - message: Arguments is immutable + rule: self == oldSelf + cronExpression: + type: string + x-kubernetes-validations: + - message: Platform is immutable + rule: self == oldSelf + platform: + default: default + type: string + x-kubernetes-validations: + - message: Platform is immutable + rule: self == oldSelf + properties: + type: string + x-kubernetes-validations: + - message: Properties is immutable + rule: self == oldSelf + scheduleName: + description: Name of the task schedule (immutable) + maxLength: 52 + type: string + x-kubernetes-validations: + - message: Name is immutable + rule: self == oldSelf + taskDefinitionName: + description: TaskDefinition Name that will be scheduled (immutable) + At least one of taskDefinitionName, taskDefinitionNameRef or + taskDefinitionNameSelector is required. + type: string + x-kubernetes-validations: + - message: TaskDefinitionName is immutable + rule: self == oldSelf + taskDefinitionNameRef: + description: TaskDefinition reference to retrieve the TaskDefinition + Name, that will be scheduled At least one of taskDefinitionName, + taskDefinitionNameRef or taskDefinitionNameSelector is required. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: Resolution specifies whether resolution of + this reference is required. The default is 'Required', + which means the reconcile will fail if the reference + cannot be resolved. 'Optional' means this reference + will be a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: Resolve specifies when this reference should + be resolved. The default is 'IfNotPresent', which will + attempt to resolve the reference only when the corresponding + field is not present. Use 'Always' to resolve the reference + on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + taskDefinitionNameSelector: + description: TaskDefinitionNameSelector selects a reference to + a TaskDefinition and retrieves its name At least one of temporalNamespaceName, + temporalNamespaceNameRef or temporalNamespaceNameSelector is + required. + properties: + matchControllerRef: + description: MatchControllerRef ensures an object with the + same controller reference as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: Resolution specifies whether resolution of + this reference is required. The default is 'Required', + which means the reconcile will fail if the reference + cannot be resolved. 'Optional' means this reference + will be a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: Resolve specifies when this reference should + be resolved. The default is 'IfNotPresent', which will + attempt to resolve the reference only when the corresponding + field is not present. Use 'Always' to resolve the reference + on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object + required: + - scheduleName + type: object + managementPolicies: + default: + - '*' + description: 'THIS IS AN ALPHA FIELD. Do not use it in production. + It is not honored unless the relevant Crossplane feature flag is + enabled, and may be changed or removed without notice. ManagementPolicies + specify the array of actions Crossplane is allowed to take on the + managed and external resources. This field is planned to replace + the DeletionPolicy field in a future release. Currently, both could + be set independently and non-default values would be honored if + the feature flag is enabled. If both are custom, the DeletionPolicy + field will be ignored. See the design doc for more information: + https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md' + items: + description: A ManagementAction represents an action that the Crossplane + controllers can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + name: default + description: ProviderConfigReference specifies how the provider that + will be used to create, observe, update, and delete this managed + resource should be configured. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: Resolution specifies whether resolution of this + reference is required. The default is 'Required', which + means the reconcile will fail if the reference cannot be + resolved. 'Optional' means this reference will be a no-op + if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: Resolve specifies when this reference should + be resolved. The default is 'IfNotPresent', which will attempt + to resolve the reference only when the corresponding field + is not present. Use 'Always' to resolve the reference on + every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + providerRef: + description: 'ProviderReference specifies the provider that will be + used to create, observe, update, and delete this managed resource. + Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`' + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: Resolution specifies whether resolution of this + reference is required. The default is 'Required', which + means the reconcile will fail if the reference cannot be + resolved. 'Optional' means this reference will be a no-op + if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: Resolve specifies when this reference should + be resolved. The default is 'IfNotPresent', which will attempt + to resolve the reference only when the corresponding field + is not present. Use 'Always' to resolve the reference on + every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + publishConnectionDetailsTo: + description: PublishConnectionDetailsTo specifies the connection secret + config which contains a name, metadata and a reference to secret + store config to which any connection details for this managed resource + should be written. Connection details frequently include the endpoint, + username, and password required to connect to the managed resource. + properties: + configRef: + default: + name: default + description: SecretStoreConfigRef specifies which secret store + config should be used for this ConnectionSecret. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: Resolution specifies whether resolution of + this reference is required. The default is 'Required', + which means the reconcile will fail if the reference + cannot be resolved. 'Optional' means this reference + will be a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: Resolve specifies when this reference should + be resolved. The default is 'IfNotPresent', which will + attempt to resolve the reference only when the corresponding + field is not present. Use 'Always' to resolve the reference + on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + metadata: + description: Metadata is the metadata for connection secret. + properties: + annotations: + additionalProperties: + type: string + description: Annotations are the annotations to be added to + connection secret. - For Kubernetes secrets, this will be + used as "metadata.annotations". - It is up to Secret Store + implementation for others store types. + type: object + labels: + additionalProperties: + type: string + description: Labels are the labels/tags to be added to connection + secret. - For Kubernetes secrets, this will be used as "metadata.labels". + - It is up to Secret Store implementation for others store + types. + type: object + type: + description: Type is the SecretType for the connection secret. + - Only valid for Kubernetes Secret Stores. + type: string + type: object + name: + description: Name is the name of the connection secret. + type: string + required: + - name + type: object + writeConnectionSecretToRef: + description: WriteConnectionSecretToReference specifies the namespace + and name of a Secret to which any connection details for this managed + resource should be written. Connection details frequently include + the endpoint, username, and password required to connect to the + managed resource. This field is planned to be replaced in a future + release in favor of PublishConnectionDetailsTo. Currently, both + could be set independently and connection details would be published + to both without affecting each other. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - forProvider + type: object + status: + description: A TaskScheduleStatus represents the observed state of a TaskSchedule. + properties: + atProvider: + description: TaskScheduleObservation are the observable fields of + a TaskSchedule. + properties: + scheduleName: + type: string + taskDefinitionName: + type: string + required: + - scheduleName + type: object + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time this condition + transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's + last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: Type of this condition. At most one of each condition + type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml index b0ea8f4..216fdc9 100644 --- a/tests/docker-compose.yaml +++ b/tests/docker-compose.yaml @@ -37,6 +37,7 @@ services: - SPRING_CLOUD_DATAFLOW_CONTAINER_REGISTRY_CONFIGURATIONS_DEFAULT_SECRET=${METADATA_DEFAULT_DOCKERHUB_PASSWORD} - SPRING_CLOUD_DATAFLOW_CONTAINER_REGISTRYCONFIGURATIONS_DEFAULT_USER=${METADATA_DEFAULT_DOCKERHUB_USER} - SPRING_CLOUD_DATAFLOW_CONTAINER_REGISTRYCONFIGURATIONS_DEFAULT_SECRET=${METADATA_DEFAULT_DOCKERHUB_PASSWORD} + - SPRING_CLOUD_DATAFLOW_FEATURES_SCHEDULES_ENABLED=true - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/dataflow - SPRING_DATASOURCE_USERNAME=root - SPRING_DATASOURCE_PASSWORD=rootpw