diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 475e1aa3d..9462c13b4 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -26,7 +26,7 @@ jobs: with: # this fetches all branches. Needed because we need gh-pages branch for deploy to work fetch-depth: 0 - - uses: ruby/setup-ruby@v1.160.0 + - uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' diff --git a/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml b/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml index 79c24c635..d61c82967 100644 --- a/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml +++ b/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml @@ -78,6 +78,8 @@ spec: type: string playbookContents: type: string + role: + type: string tlsCerts: additionalProperties: properties: diff --git a/apis/dataplane/v1beta1/openstackdataplaneservice_types.go b/apis/dataplane/v1beta1/openstackdataplaneservice_types.go index 475446b37..8c90a9776 100644 --- a/apis/dataplane/v1beta1/openstackdataplaneservice_types.go +++ b/apis/dataplane/v1beta1/openstackdataplaneservice_types.go @@ -72,6 +72,9 @@ type OpenStackDataPlaneServiceSpec struct { // Playbook is a path to the playbook that ansible will run on this execution Playbook string `json:"playbook,omitempty"` + // Role is a path to the role that ansible will run on this execution + Role string `json:"role,omitempty"` + // CACerts - Secret containing the CA certificate chain // +kubebuilder:validation:Optional // +kubebuilder:validation:MaxLength:=253 diff --git a/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go b/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go index 7088a12c3..5aa41daad 100644 --- a/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go +++ b/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go @@ -78,12 +78,31 @@ func (r *OpenStackDataPlaneService) ValidateCreate() (admission.Warnings, error) return nil, nil } -func (r *OpenStackDataPlaneServiceSpec) ValidateCreate() field.ErrorList { - // TODO(user): fill in your validation logic upon object creation. +func (r *OpenStackDataPlaneServiceSpec) ValidateArtifact() field.ErrorList { + if len(r.Playbook) == len(r.PlaybookContents) && len(r.Playbook) == len(r.Role) && len(r.Playbook) == 0 { + return field.ErrorList{ + field.Invalid( + field.NewPath("Playbook"), + r.Playbook, "Playbook, PlaybookContents and Role cannot be empty at the same time", + ), + field.Invalid( + field.NewPath("PlaybookContents"), + r.Playbook, "Playbook, PlaybookContents and Role cannot be empty at the same time", + ), + field.Invalid( + field.NewPath("Role"), + r.Playbook, "Playbook, PlaybookContents and Role cannot be empty at the same time", + ), + } + } return field.ErrorList{} } +func (r *OpenStackDataPlaneServiceSpec) ValidateCreate() field.ErrorList { + return r.ValidateArtifact() +} + func (r *OpenStackDataPlaneService) ValidateUpdate(original runtime.Object) (admission.Warnings, error) { openstackdataplaneservicelog.Info("validate update", "name", r.Name) errors := r.Spec.ValidateUpdate() @@ -100,9 +119,7 @@ func (r *OpenStackDataPlaneService) ValidateUpdate(original runtime.Object) (adm } func (r *OpenStackDataPlaneServiceSpec) ValidateUpdate() field.ErrorList { - // TODO(user): fill in your validation logic upon object creation. - - return field.ErrorList{} + return r.ValidateArtifact() } func (r *OpenStackDataPlaneService) ValidateDelete() (admission.Warnings, error) { diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml index 79c24c635..d61c82967 100644 --- a/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml @@ -78,6 +78,8 @@ spec: type: string playbookContents: type: string + role: + type: string tlsCerts: additionalProperties: properties: diff --git a/pkg/dataplane/util/ansible_execution.go b/pkg/dataplane/util/ansible_execution.go index e86b6d6d7..fb79174c8 100644 --- a/pkg/dataplane/util/ansible_execution.go +++ b/pkg/dataplane/util/ansible_execution.go @@ -218,6 +218,9 @@ func (a *EEJob) BuildAeeJobSpec( if len(service.Spec.Playbook) > 0 { a.Playbook = service.Spec.Playbook } + if len(service.Spec.Role) > 0 { + a.Role = service.Spec.Role + } a.BackoffLimit = deployment.Spec.BackoffLimit a.PreserveJobs = deployment.Spec.PreserveJobs diff --git a/pkg/dataplane/util/ansibleee.go b/pkg/dataplane/util/ansibleee.go index 0dff7b072..b0928c74c 100644 --- a/pkg/dataplane/util/ansibleee.go +++ b/pkg/dataplane/util/ansibleee.go @@ -22,6 +22,8 @@ type EEJob struct { PlaybookContents string `json:"playbookContents,omitempty"` // Playbook is the playbook that ansible will run on this execution, accepts path or FQN from collection Playbook string `json:"playbook,omitempty"` + // Role is the role that ansible will run on this execution, accepts path or FQN from collection + Role string `json:"role,omitempty"` // Image is the container image that will execute the ansible command Image string `json:"image,omitempty"` // Name is the name of the execution job @@ -78,12 +80,20 @@ func (a *EEJob) JobForOpenStackAnsibleEE(h *helper.Helper) (*batchv1.Job, error) args := a.Args - playbook := a.Playbook if len(args) == 0 { - if len(playbook) == 0 { - playbook = CustomPlaybook + artifact := a.Playbook + param := "-p" + if len(artifact) == 0 { + if len(a.PlaybookContents) > 0 { + artifact = CustomPlaybook + } else if len(a.Role) > 0 { + artifact = a.Role + param = "-r" + } else { + return nil, fmt.Errorf("no playbook, playbookContents or role specified") + } } - args = []string{"ansible-runner", "run", "/runner", "-p", playbook} + args = []string{"ansible-runner", "run", "/runner", param, artifact} } // ansible runner identifier @@ -169,12 +179,15 @@ func (a *EEJob) JobForOpenStackAnsibleEE(h *helper.Helper) (*batchv1.Job, error) } } + if len(a.Role) > 0 { + setRunnerEnvVar(h, "RUNNER_ROLE", a.Role, "role", job, hashes) + } if len(a.PlaybookContents) > 0 { setRunnerEnvVar(h, "RUNNER_PLAYBOOK", a.PlaybookContents, "playbookContents", job, hashes) - } else if len(playbook) > 0 { + } else if len(a.Playbook) > 0 { // As we set "playbook.yaml" as default // we need to ensure that PlaybookContents is empty before adding playbook - setRunnerEnvVar(h, "RUNNER_PLAYBOOK", playbook, "playbooks", job, hashes) + setRunnerEnvVar(h, "RUNNER_PLAYBOOK", a.Playbook, "playbooks", job, hashes) } if len(a.CmdLine) > 0 { diff --git a/tests/functional/dataplane/base_test.go b/tests/functional/dataplane/base_test.go index 3f6f098c0..43ad7b6f2 100644 --- a/tests/functional/dataplane/base_test.go +++ b/tests/functional/dataplane/base_test.go @@ -105,6 +105,9 @@ func CreateDataplaneServicesWithSameServiceType(name types.NamespacedName) { // Create an OpenStackDataPlaneService with a given NamespacedName, and a given unstructured spec func CreateDataPlaneServiceFromSpec(name types.NamespacedName, spec map[string]interface{}) *unstructured.Unstructured { + if spec["playbook"] == nil && spec["playbookContents"] == nil && spec["role"] == nil { + spec["playbook"] = "test" + } raw := map[string]interface{}{ "apiVersion": "dataplane.openstack.org/v1beta1", @@ -514,6 +517,9 @@ func DefaultDataplaneService(name types.NamespacedName) map[string]interface{} { "metadata": map[string]interface{}{ "name": name.Name, "namespace": name.Namespace, + }, + "spec": map[string]interface{}{ + "playbook": "test", }} } @@ -531,6 +537,7 @@ func DefaultDataplaneGlobalService(name types.NamespacedName) map[string]interfa }, "spec": map[string]interface{}{ "deployOnAllNodeSets": true, + "playbook": "test", }, } } diff --git a/tests/functional/dataplane/openstackdataplaneservice_controller_test.go b/tests/functional/dataplane/openstackdataplaneservice_controller_test.go index 1a622486e..38eba2223 100644 --- a/tests/functional/dataplane/openstackdataplaneservice_controller_test.go +++ b/tests/functional/dataplane/openstackdataplaneservice_controller_test.go @@ -42,7 +42,8 @@ var _ = Describe("OpenstackDataplaneService Test", func() { It("spec fields are set up", func() { service := GetService(dataplaneServiceName) Expect(service.Spec.DataSources).To(BeEmpty()) - Expect(service.Spec.Playbook).To(BeEmpty()) + Expect(service.Spec.PlaybookContents).To(BeEmpty()) + Expect(service.Spec.Role).To(BeEmpty()) Expect(service.Spec.DeployOnAllNodeSets).To(BeFalse()) }) }) @@ -57,7 +58,8 @@ var _ = Describe("OpenstackDataplaneService Test", func() { It("spec fields are set up", func() { service := GetService(dataplaneServiceName) Expect(service.Spec.DataSources).To(BeEmpty()) - Expect(service.Spec.Playbook).To(BeEmpty()) + Expect(service.Spec.PlaybookContents).To(BeEmpty()) + Expect(service.Spec.Role).To(BeEmpty()) Expect(service.Spec.DeployOnAllNodeSets).To(BeTrue()) }) }) diff --git a/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml b/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml index ebce91149..7c620a3b6 100644 --- a/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml +++ b/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml @@ -95,15 +95,15 @@ spec: - ansible-runner - run - /runner - - -p - - playbook.yaml + - -r + - test role - -i - custom-img-svc-edpm-compute-no-nodes-edpm-no-nodes-custom-svc env: - - name: RUNNER_PLAYBOOK + - name: RUNNER_ROLE value: |2+ - playbook.yaml + test role - name: RUNNER_EXTRA_VARS value: |2+ diff --git a/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml index 358f0c4c9..6b5630fc1 100644 --- a/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml +++ b/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml @@ -4,14 +4,7 @@ metadata: name: custom-img-svc spec: openStackAnsibleEERunnerImage: example.com/repo/runner-image:latest - role: - name: "test role" - hosts: "all" - strategy: "linear" - tasks: - - name: "test task" - import_role: - name: "test role" + role: "test role" --- apiVersion: dataplane.openstack.org/v1beta1 kind: OpenStackDataPlaneNodeSet