├── .ci-operator.yaml ├── .dockerignore ├── .github └── workflows │ ├── build-ironic-operator.yaml │ ├── force-bump-pr-manual.yaml │ ├── force-bump-pr-scheduled.yaml │ ├── lints.yaml │ ├── release-branch-sync.yaml │ └── release-ironic-operator.yaml ├── .gitignore ├── .golangci.yaml ├── .pre-commit-config.yaml ├── .prow_ci.env ├── Dockerfile ├── LICENSE.txt ├── Makefile ├── OWNERS ├── OWNERS_ALIASES ├── PROJECT ├── README.md ├── api ├── bases │ ├── ironic.openstack.org_ironicapis.yaml │ ├── ironic.openstack.org_ironicconductors.yaml │ ├── ironic.openstack.org_ironicinspectors.yaml │ ├── ironic.openstack.org_ironicneutronagents.yaml │ └── ironic.openstack.org_ironics.yaml ├── go.mod ├── go.sum └── v1beta1 │ ├── common_types.go │ ├── conditions.go │ ├── funcs.go │ ├── groupversion_info.go │ ├── ironic_types.go │ ├── ironic_webhook.go │ ├── ironicapi.go │ ├── ironicapi_types.go │ ├── ironicconductor_types.go │ ├── ironicinspector_types.go │ ├── ironicneutronagent_types.go │ ├── webhook_suite_test.go │ └── zz_generated.deepcopy.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── ironic.openstack.org_ironicapis.yaml │ │ ├── ironic.openstack.org_ironicconductors.yaml │ │ ├── ironic.openstack.org_ironicinspectors.yaml │ │ ├── ironic.openstack.org_ironicneutronagents.yaml │ │ └── ironic.openstack.org_ironics.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_ironicapis.yaml │ │ ├── cainjection_in_ironicconductors.yaml │ │ ├── cainjection_in_ironicinspectors.yaml │ │ ├── cainjection_in_ironicneutronagents.yaml │ │ ├── cainjection_in_ironics.yaml │ │ ├── webhook_in_ironicapis.yaml │ │ ├── webhook_in_ironicconductors.yaml │ │ ├── webhook_in_ironicinspectors.yaml │ │ ├── webhook_in_ironicneutronagents.yaml │ │ └── webhook_in_ironics.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_default_images.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── ironic-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── ironic_editor_role.yaml │ ├── ironic_viewer_role.yaml │ ├── ironicapi_editor_role.yaml │ ├── ironicapi_viewer_role.yaml │ ├── ironicconductor_editor_role.yaml │ ├── ironicconductor_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── ironic_v1beta1_ironic.yaml │ ├── ironic_v1beta1_ironic_conductor_groups.yaml │ ├── ironic_v1beta1_ironic_standalone.yaml │ ├── ironic_v1beta1_ironic_tls.yaml │ ├── ironic_v1beta1_ironicapi.yaml │ ├── ironic_v1beta1_ironicconductor.yaml │ ├── ironic_v1beta1_ironicinspector.yaml │ ├── ironic_v1beta1_ironicneutronagent.yaml │ └── kustomization.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── funcs.go ├── ironic_controller.go ├── ironicapi_controller.go ├── ironicconductor_controller.go ├── ironicinspector_controller.go └── ironicneutronagent_controller.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── build-crd-schema-checker.sh ├── clean_local_webhook.sh ├── crd-schema-checker.sh └── run_with_local_webhook.sh ├── kuttl-test.yaml ├── main.go ├── pkg ├── ironic │ ├── const.go │ ├── dbsync.go │ ├── funcs.go │ ├── initcontainer.go │ └── volumes.go ├── ironicapi │ ├── deployment.go │ └── volumes.go ├── ironicconductor │ ├── const.go │ ├── pod.go │ ├── service.go │ ├── statefulset.go │ └── volumes.go ├── ironicinspector │ ├── const.go │ ├── dbsync.go │ ├── initcontainer.go │ ├── pod.go │ ├── service.go │ ├── statefulset.go │ └── volumes.go └── ironicneutronagent │ ├── const.go │ ├── deployment.go │ └── volumes.go ├── renovate.json ├── templates ├── common │ ├── bin │ │ ├── common.sh │ │ ├── get_net_ip │ │ ├── ironic-init.sh │ │ ├── pxe-init.sh │ │ └── runlogwatch.sh │ └── config │ │ ├── dnsmasq.conf │ │ └── ironic.conf ├── ironic │ ├── bin │ │ └── dbsync.sh │ └── config │ │ └── db-sync-config.json ├── ironicapi │ ├── bin │ │ └── api-prep.sh │ └── config │ │ ├── ironic-api-config.json │ │ ├── ironic-api-httpd.conf │ │ └── ssl.conf ├── ironicconductor │ ├── bin │ │ └── init.sh │ └── config │ │ ├── dnsmasq-config.json │ │ ├── httpboot-config.json │ │ ├── httpboot-httpd.conf │ │ ├── ironic-conductor-config.json │ │ └── ramdisk-logs-config.json ├── ironicinspector │ ├── bin │ │ └── inspector-pxe-init.sh │ └── config │ │ ├── 01-inspector.conf │ │ ├── db-sync-config.json │ │ ├── dnsmasq-config.json │ │ ├── dnsmasq.conf │ │ ├── httpboot-config.json │ │ ├── httpboot-httpd.conf │ │ ├── httpd-config.json │ │ ├── httpd.conf │ │ ├── inspector.ipxe │ │ ├── ironic-inspector-config.json │ │ ├── ramdisk-logs-config.json │ │ └── ssl.conf └── ironicneutronagent │ └── config │ ├── 01-ironic_neutron_agent.conf │ └── ironic-neutron-agent-config.json ├── tests ├── functional │ ├── base_test.go │ ├── ironic_controller_test.go │ ├── ironicapi_controller_test.go │ ├── ironicconductor_controller_test.go │ ├── ironicinspector_controller_test.go │ ├── ironicneutronagent_controller_test.go │ ├── sample_test.go │ └── suite_test.go └── kuttl │ ├── common │ ├── assert-endpoints.yaml │ ├── assert_tls_cert.yaml │ ├── cleanup-ironic.yaml │ ├── errors-cleanup-ironic.yaml │ ├── tls_ca_bundle.yaml │ ├── tls_cert_ironic-inspector-internal-svc.yaml │ ├── tls_cert_ironic-inspector-public-svc.yaml │ ├── tls_cert_ironic-internal-svc.yaml │ └── tls_cert_ironic-public-svc.yaml │ └── tests │ ├── deploy │ ├── 10-assert-deploy-ironic.yaml │ ├── 10-deploy-ironic.yaml │ ├── 15-assert-endpoints.yaml │ ├── 20-assert-deploy-ironic-conductor-groups.yaml │ ├── 20-deploy-ironic-conductor-groups.yaml │ ├── 99-cleanup-ironic.yaml │ └── 99-errors-cleanup-ironic.yaml │ ├── deploy_tls │ ├── 00-assert.yaml │ ├── 00-tls_ca_bundle.yaml │ ├── 00-tls_cert_ironic-inspector-internal-svc.yaml │ ├── 00-tls_cert_ironic-inspector-public-svc.yaml │ ├── 00-tls_cert_ironic-internal-svc.yaml │ ├── 00-tls_cert_ironic-public-svc.yaml │ ├── 10-assert-deploy-ironic.yaml │ ├── 10-deploy-ironic.yaml │ ├── 99-cleanup-ironic.yaml │ └── 99-errors-cleanup-ironic.yaml │ └── standalone │ ├── 10-assert-deploy-ironic-standalone.yaml │ ├── 10-deploy-ironic-standalone.yaml │ ├── 15-assert-endpoints.yaml │ ├── 99-cleanup-ironic.yaml │ └── 99-errors-cleanup-ironic.yaml └── zuul.d └── projects.yaml /.ci-operator.yaml: -------------------------------------------------------------------------------- 1 | build_root_image: 2 | name: tools 3 | namespace: openstack-k8s-operators 4 | tag: ci-build-root-golang-1.21-sdk-1.31 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/workflows/build-ironic-operator.yaml: -------------------------------------------------------------------------------- 1 | name: ironic operator image builder 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | env: 9 | imageregistry: 'quay.io' 10 | imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} 11 | latesttag: latest 12 | 13 | jobs: 14 | call-build-workflow: 15 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/reusable-build-operator.yaml@main 16 | with: 17 | operator_name: ironic 18 | go_version: 1.21.x 19 | operator_sdk_version: 1.31.0 20 | secrets: 21 | IMAGENAMESPACE: ${{ secrets.IMAGENAMESPACE }} 22 | QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} 23 | QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} 24 | REDHATIO_USERNAME: ${{ secrets.REDHATIO_USERNAME }} 25 | REDHATIO_PASSWORD: ${{ secrets.REDHATIO_PASSWORD }} 26 | -------------------------------------------------------------------------------- /.github/workflows/force-bump-pr-manual.yaml: -------------------------------------------------------------------------------- 1 | name: Manually Trigger a Force Bump PR 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | call-build-workflow: 8 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-pull-request.yaml@main 9 | with: 10 | operator_name: ironic 11 | branch_name: ${{ github.ref_name }} 12 | secrets: 13 | FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} 14 | -------------------------------------------------------------------------------- /.github/workflows/force-bump-pr-scheduled.yaml: -------------------------------------------------------------------------------- 1 | name: Scheduled Force Bump PR 2 | 3 | on: 4 | schedule: 5 | - cron: '0 3 * * 6' # 3AM UTC Saturday 6 | 7 | jobs: 8 | call-build-workflow: 9 | if: github.ref == 'refs/heads/main' && github.repository_owner == 'openstack-k8s-operators' 10 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-branches.yaml@main 11 | with: 12 | operator_name: ironic 13 | secrets: 14 | FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} 15 | -------------------------------------------------------------------------------- /.github/workflows/lints.yaml: -------------------------------------------------------------------------------- 1 | name: Lints 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | check-go-mod-replace-lines: 7 | name: check for replace lines in go.mod files 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout project code 11 | uses: actions/checkout@v3 12 | - name: check for replace lines in go.mod files 13 | run: | 14 | ! egrep --invert-match -e '^replace.*/api => \./api|^replace.*//allow-merging$' `find . -name 'go.mod'` | egrep -e 'go.mod:replace' 15 | -------------------------------------------------------------------------------- /.github/workflows/release-branch-sync.yaml: -------------------------------------------------------------------------------- 1 | name: Release Branch sync 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | schedule: 8 | - cron: '0 * * * *' 9 | 10 | jobs: 11 | call-build-workflow: 12 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/release-branch-sync.yaml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/release-ironic-operator.yaml: -------------------------------------------------------------------------------- 1 | name: Release Ironic Operator 2 | 3 | on: 4 | release: 5 | types: 6 | - released 7 | - prereleased 8 | 9 | env: 10 | imageregistry: 'quay.io' 11 | imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Tag image 21 | uses: tinact/docker.image-retag@1.0.2 22 | with: 23 | image_name: ${{ env.imagenamespace }}/ 24 | image_old_tag: ${{ github.sha }} 25 | image_new_tag: ${{ github.event.release.tag_name }} 26 | registry: ${{ env.imageregistry }} 27 | registry_username: ${{ secrets.QUAY_USERNAME }} 28 | registry_password: ${{ secrets.QUAY_PASSWORD }} 29 | 30 | - name: Tag -bundle image 31 | uses: tinact/docker.image-retag@1.0.2 32 | with: 33 | image_name: ${{ env.imagenamespace }}/-bundle 34 | image_old_tag: ${{ github.sha }} 35 | image_new_tag: ${{ github.event.release.tag_name }} 36 | registry: ${{ env.imageregistry }} 37 | registry_username: ${{ secrets.QUAY_USERNAME }} 38 | registry_password: ${{ secrets.QUAY_PASSWORD }} 39 | 40 | - name: Tag -index image 41 | uses: tinact/docker.image-retag@1.0.2 42 | with: 43 | image_name: ${{ env.imagenamespace }}/-index 44 | image_old_tag: ${{ github.sha }} 45 | image_new_tag: ${{ github.event.release.tag_name }} 46 | registry: ${{ env.imageregistry }} 47 | registry_username: ${{ secrets.QUAY_USERNAME }} 48 | registry_password: ${{ secrets.QUAY_PASSWORD }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | /bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | #Operator SDK generated files 21 | /bundle/ 22 | bundle.Dockerfile 23 | config/manager/kustomization.yaml 24 | 25 | # editor and IDE paraphernalia 26 | .idea 27 | *.swp 28 | *.swo 29 | *~ 30 | 31 | # Common CI tools repository 32 | CI_TOOLS_REPO 33 | 34 | # generated workspace file 35 | go.work 36 | go.work.sum 37 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | # Enable specific linter 3 | # https://golangci-lint.run/usage/linters/#enabled-by-default 4 | enable: 5 | - errorlint 6 | - revive 7 | - ginkgolinter 8 | - gofmt 9 | - govet 10 | run: 11 | timeout: 5m 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: gotidy 5 | name: gotidy 6 | language: system 7 | entry: make 8 | args: ["tidy"] 9 | pass_filenames: false 10 | - id: make-manifests 11 | name: make-manifests 12 | language: system 13 | entry: make 14 | args: ['manifests'] 15 | pass_filenames: false 16 | - id: make-generate 17 | name: make-generate 18 | language: system 19 | entry: make 20 | args: ['generate'] 21 | pass_filenames: false 22 | - id: make-operator-lint 23 | name: make-operator-lint 24 | language: system 25 | entry: make 26 | args: ['operator-lint'] 27 | pass_filenames: false 28 | - id: make-crd-schema-check 29 | name: make-crd-schema-check 30 | language: system 31 | entry: make 32 | args: ['crd-schema-check'] 33 | pass_filenames: false 34 | 35 | - repo: https://github.com/pre-commit/pre-commit-hooks 36 | rev: v4.4.0 37 | hooks: 38 | - id: check-added-large-files 39 | - id: fix-byte-order-marker 40 | - id: check-case-conflict 41 | - id: check-executables-have-shebangs 42 | exclude: ^vendor 43 | - id: check-shebang-scripts-are-executable 44 | exclude: ^vendor 45 | - id: check-merge-conflict 46 | - id: check-symlinks 47 | - id: destroyed-symlinks 48 | - id: check-yaml 49 | args: [-m] 50 | - id: check-json 51 | - id: detect-private-key 52 | - id: end-of-file-fixer 53 | exclude: ^vendor 54 | - id: no-commit-to-branch 55 | - id: trailing-whitespace 56 | exclude: ^vendor 57 | 58 | - repo: https://github.com/openstack/bashate.git 59 | rev: 2.1.1 60 | hooks: 61 | - id: bashate 62 | entry: bashate --error . --ignore=E006,E040,E011,E020,E012 63 | 64 | - repo: https://github.com/golangci/golangci-lint 65 | rev: v1.59.1 66 | hooks: 67 | - id: golangci-lint-full 68 | args: ["-v"] 69 | 70 | - repo: https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci 71 | # NOTE(gibi): we cannot automatically track main here 72 | # see https://pre-commit.com/#using-the-latest-version-for-a-repository 73 | rev: e30d72fcbced0ab8a7b6d23be1dee129e2a7b849 74 | hooks: 75 | - id: kuttl-single-test-assert 76 | args: ["tests/kuttl"] 77 | -------------------------------------------------------------------------------- /.prow_ci.env: -------------------------------------------------------------------------------- 1 | export USE_IMAGE_DIGESTS=true 2 | export FAIL_FIPS_CHECK=true 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GOLANG_BUILDER=registry.access.redhat.com/ubi9/go-toolset:1.21 2 | ARG OPERATOR_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:latest 3 | 4 | # Build the manager binary 5 | FROM $GOLANG_BUILDER AS builder 6 | 7 | #Arguments required by OSBS build system 8 | ARG CACHITO_ENV_FILE=/remote-source/cachito.env 9 | 10 | ARG REMOTE_SOURCE=. 11 | ARG REMOTE_SOURCE_DIR=/remote-source 12 | ARG REMOTE_SOURCE_SUBDIR= 13 | ARG DEST_ROOT=/dest-root 14 | 15 | ARG GO_BUILD_EXTRA_ARGS="-tags strictfipsruntime" 16 | ARG GO_BUILD_EXTRA_ENV_ARGS="CGO_ENABLED=1 GO111MODULE=on" 17 | 18 | COPY $REMOTE_SOURCE $REMOTE_SOURCE_DIR 19 | WORKDIR $REMOTE_SOURCE_DIR/$REMOTE_SOURCE_SUBDIR 20 | 21 | USER root 22 | RUN mkdir -p ${DEST_ROOT}/usr/local/bin/ 23 | 24 | # cache deps before building and copying source so that we don't need to re-download as much 25 | # and so that source changes don't invalidate our downloaded layer 26 | RUN if [ ! -f $CACHITO_ENV_FILE ]; then go mod download ; fi 27 | 28 | # Build manager 29 | RUN if [ -f $CACHITO_ENV_FILE ] ; then source $CACHITO_ENV_FILE ; fi ; env ${GO_BUILD_EXTRA_ENV_ARGS} go build ${GO_BUILD_EXTRA_ARGS} -a -o ${DEST_ROOT}/manager main.go 30 | 31 | RUN cp -r templates ${DEST_ROOT}/templates 32 | 33 | # Use distroless as minimal base image to package the manager binary 34 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 35 | FROM $OPERATOR_BASE_IMAGE 36 | 37 | ARG DEST_ROOT=/dest-root 38 | # NONROOT default id https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8= 39 | ARG USER_ID=65532 40 | 41 | ARG IMAGE_COMPONENT="ironic-operator-container" 42 | ARG IMAGE_NAME="ironic-operator" 43 | ARG IMAGE_VERSION="1.0.0" 44 | ARG IMAGE_SUMMARY="Ironic Operator" 45 | ARG IMAGE_DESC="This image includes the ironic-operator" 46 | ARG IMAGE_TAGS="cn-openstack openstack" 47 | 48 | ### DO NOT EDIT LINES BELOW 49 | # Auto generated using CI tools from 50 | # https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci 51 | 52 | # Labels required by upstream and osbs build system 53 | LABEL com.redhat.component="${IMAGE_COMPONENT}" \ 54 | name="${IMAGE_NAME}" \ 55 | version="${IMAGE_VERSION}" \ 56 | summary="${IMAGE_SUMMARY}" \ 57 | io.k8s.name="${IMAGE_NAME}" \ 58 | io.k8s.description="${IMAGE_DESC}" \ 59 | io.openshift.tags="${IMAGE_TAGS}" 60 | ### DO NOT EDIT LINES ABOVE 61 | 62 | ENV USER_UID=$USER_ID \ 63 | OPERATOR_TEMPLATES=/usr/share/ironic-operator/templates/ 64 | 65 | WORKDIR / 66 | 67 | # Install operator binary to WORKDIR 68 | COPY --from=builder ${DEST_ROOT}/manager . 69 | 70 | # Install templates 71 | COPY --from=builder ${DEST_ROOT}/templates ${OPERATOR_TEMPLATES} 72 | 73 | USER $USER_ID 74 | 75 | ENV PATH="/:${PATH}" 76 | 77 | ENTRYPOINT ["/manager"] 78 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | approvers: 3 | - baremetal-approvers 4 | - ci-approvers 5 | - openstack-approvers 6 | 7 | reviewers: 8 | - baremetal-approvers 9 | - ci-approvers 10 | - openstack-approvers 11 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS_ALIASES docs: https://git.k8s.io/community/contributors/guide/owners.md#owners_aliases 2 | 3 | aliases: 4 | baremetal-approvers: 5 | - hjensas 6 | - juliakreger 7 | - steveb 8 | ci-approvers: 9 | - lewisdenny 10 | - frenzyfriday 11 | - viroel 12 | openstack-approvers: 13 | - abays 14 | - dprince 15 | - olliewalsh 16 | - stuggi 17 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: openstack.org 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: ironic-operator 8 | repo: github.com/openstack-k8s-operators/ironic-operator 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: openstack.org 15 | group: ironic 16 | kind: Ironic 17 | path: github.com/openstack-k8s-operators/ironic-operator/api/v1beta1 18 | version: v1beta1 19 | webhooks: 20 | validation: true 21 | defaulting: true 22 | webhookVersion: v1 23 | - api: 24 | crdVersion: v1 25 | namespaced: true 26 | controller: true 27 | domain: openstack.org 28 | group: ironic 29 | kind: IronicAPI 30 | path: github.com/openstack-k8s-operators/ironic-operator/api/v1beta1 31 | version: v1beta1 32 | - api: 33 | crdVersion: v1 34 | namespaced: true 35 | controller: true 36 | domain: openstack.org 37 | group: ironic 38 | kind: IronicConductor 39 | path: github.com/openstack-k8s-operators/ironic-operator/api/v1beta1 40 | version: v1beta1 41 | version: "3" 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ironic-operator 2 | A Kubernetes Operator built using the [Operator Framework](https://github.com/operator-framework) for Go. The Operator provides a way to easily install and manage an OpenStack Ironic installation 3 | on Kubernetes. This Operator was developed using [RDO](https://www.rdoproject.org/) containers for openStack. 4 | 5 | # Deployment 6 | 7 | The operator is intended to be deployed via OLM [Operator Lifecycle Manager](https://github.com/operator-framework/operator-lifecycle-manager) 8 | 9 | # Example 10 | 11 | The Operator creates a custom Ironic resource that can be used to create Ironic 12 | API and conductor instances within the cluster. Example CR to create an Ironic 13 | in your cluster: 14 | 15 | ```yaml 16 | apiVersion: ironic.openstack.org/v1beta1 17 | kind: Ironic 18 | metadata: 19 | name: ironic 20 | namespace: openstack 21 | spec: 22 | serviceUser: ironic 23 | customServiceConfig: | 24 | [DEFAULT] 25 | debug = true 26 | databaseInstance: openstack 27 | ironicAPI: 28 | replicas: 1 29 | containerImage: quay.io/podified-antelope-centos9/openstack-ironic-api:current-podified 30 | ironicConductors: 31 | - replicas: 1 32 | containerImage: quay.io/podified-antelope-centos9/openstack-ironic-conductor:current-podified 33 | pxeContainerImage: quay.io/podified-antelope-centos9/openstack-ironic-pxe:current-podified 34 | provisionNetwork: provision-net 35 | secret: ironic-secret 36 | ``` 37 | 38 | This example assumes the existence of additional network `provision_net` to use 39 | for exposing provision boot services (DHCP, TFTP, HTTP). Currently this 40 | additional network needs to be managed manually with 41 | `NetworkAttachmentDefinition` resources. For example, applying the following 42 | will result in network interface `eno1` being available in the conductor pod: 43 | 44 | ```yaml 45 | apiVersion: k8s.cni.cncf.io/v1 46 | kind: NetworkAttachmentDefinition 47 | metadata: 48 | name: provision-net 49 | spec: 50 | config: |- 51 | { 52 | "cniVersion": "0.3.1", 53 | "name": "provision-net", 54 | "type": "host-device", 55 | "device": "eno1" 56 | } 57 | ``` 58 | 59 | ## Example: expose IronicAPI or IronicInspector to an isolated network 60 | 61 | The Ironic spec can be used to register e.g. the internal endpoint to 62 | an isolated network. MetalLB is used for this scenario. 63 | 64 | As a pre requisite, MetalLB needs to be installed and worker nodes 65 | prepared to work as MetalLB nodes to serve the LoadBalancer service. 66 | 67 | In this example the following MetalLB IPAddressPool is used: 68 | 69 | ``` 70 | --- 71 | apiVersion: metallb.io/v1beta1 72 | kind: IPAddressPool 73 | metadata: 74 | name: osp-internalapi 75 | namespace: metallb-system 76 | spec: 77 | addresses: 78 | - 172.17.0.200-172.17.0.210 79 | autoAssign: false 80 | ``` 81 | 82 | The following represents an example of Ironic resource that can be used 83 | to trigger the service deployment, and have the internal ironicAPI endpoint 84 | registerd as a MetalLB service using the IPAddressPool `osp-internal`, 85 | request to use the IP `172.17.0.202` as the VIP and the IP is shared with 86 | other services. 87 | 88 | ``` 89 | apiVersion: ironic.openstack.org/v1beta1 90 | kind: Ironic 91 | metadata: 92 | name: ironic 93 | spec: 94 | ... 95 | ironicAPI: 96 | ... 97 | override: 98 | service: 99 | metadata: 100 | annotations: 101 | metallb.universe.tf/address-pool: internalapi 102 | metallb.universe.tf/allow-shared-ip: internalapi 103 | metallb.universe.tf/loadBalancerIPs: 172.17.0.202 104 | spec: 105 | type: LoadBalancer 106 | ... 107 | ... 108 | ``` 109 | 110 | The internal ironicAPI endpoint gets registered with its service name. This 111 | service name needs to resolve to the `LoadBalancerIP` on the isolated network 112 | either by DNS or via /etc/hosts: 113 | 114 | ``` 115 | # openstack endpoint list -c 'Service Name' -c Interface -c URL --service ironic 116 | +--------------+-----------+-------------------------------------------------+ 117 | | Service Name | Interface | URL | 118 | +--------------+-----------+-------------------------------------------------+ 119 | | ironic | internal | http://ironic-internal.openstack.svc:6385 | 120 | | ironic | public | http://ironic-public-openstack.apps-crc.testing | 121 | +--------------+-----------+-------------------------------------------------+ 122 | ``` 123 | 124 | # Design 125 | The current design takes care of the following: 126 | 127 | - Creates ironic config files via config maps 128 | - Creates an ironic deployment with the specified replicas 129 | - Creates an ironic service 130 | - Ironic bootstrap, and db sync are executed automatically on install and updates 131 | - ConfigMap is recreated on any changes Ironic object changes and the Deployment updated. 132 | -------------------------------------------------------------------------------- /api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openstack-k8s-operators/ironic-operator/api 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.20.1 7 | github.com/onsi/gomega v1.34.1 8 | github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250601112854-cbb0f7bc989f 9 | github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250605082218-a58074898dd7 10 | k8s.io/api v0.29.15 11 | k8s.io/apimachinery v0.29.15 12 | k8s.io/client-go v0.29.15 13 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 14 | sigs.k8s.io/controller-runtime v0.17.6 15 | ) 16 | 17 | require ( 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 21 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 23 | github.com/fsnotify/fsnotify v1.7.0 // indirect 24 | github.com/go-logr/logr v1.4.3 // indirect 25 | github.com/go-logr/zapr v1.3.0 // indirect 26 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 27 | github.com/go-openapi/jsonreference v0.21.0 // indirect 28 | github.com/go-openapi/swag v0.23.0 // indirect 29 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 30 | github.com/gogo/protobuf v1.3.2 // indirect 31 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 32 | github.com/golang/protobuf v1.5.4 // indirect 33 | github.com/google/gnostic-models v0.6.8 // indirect 34 | github.com/google/go-cmp v0.6.0 // indirect 35 | github.com/google/gofuzz v1.2.0 // indirect 36 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect 37 | github.com/google/uuid v1.6.0 // indirect 38 | github.com/imdario/mergo v0.3.16 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/mailru/easyjson v0.7.7 // indirect 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 43 | github.com/modern-go/reflect2 v1.0.2 // indirect 44 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 45 | github.com/openshift/api v3.9.0+incompatible // indirect 46 | github.com/pkg/errors v0.9.1 // indirect 47 | github.com/prometheus/client_golang v1.19.0 // indirect 48 | github.com/prometheus/client_model v0.6.0 // indirect 49 | github.com/prometheus/common v0.51.1 // indirect 50 | github.com/prometheus/procfs v0.13.0 // indirect 51 | github.com/spf13/pflag v1.0.5 // indirect 52 | go.uber.org/multierr v1.11.0 // indirect 53 | go.uber.org/zap v1.27.0 // indirect 54 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 55 | golang.org/x/net v0.28.0 // indirect 56 | golang.org/x/oauth2 v0.18.0 // indirect 57 | golang.org/x/sys v0.23.0 // indirect 58 | golang.org/x/term v0.23.0 // indirect 59 | golang.org/x/text v0.17.0 // indirect 60 | golang.org/x/time v0.5.0 // indirect 61 | golang.org/x/tools v0.24.0 // indirect 62 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 63 | google.golang.org/appengine v1.6.8 // indirect 64 | google.golang.org/protobuf v1.34.1 // indirect 65 | gopkg.in/inf.v0 v0.9.1 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | k8s.io/apiextensions-apiserver v0.29.15 // indirect 69 | k8s.io/component-base v0.29.15 // indirect 70 | k8s.io/klog/v2 v2.120.1 // indirect 71 | k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 // indirect 72 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 73 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 74 | sigs.k8s.io/yaml v1.4.0 // indirect 75 | ) 76 | 77 | // mschuppert: map to latest commit from release-4.13 tag 78 | // must consistent within modules and service operators 79 | replace github.com/openshift/api => github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 //allow-merging 80 | 81 | // custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.6.0_patches_tag) 82 | replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20241017142550-a3524acedd49 //allow-merging 83 | -------------------------------------------------------------------------------- /api/v1beta1/common_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" 22 | "k8s.io/apimachinery/pkg/util/validation/field" 23 | ) 24 | 25 | // IronicServiceTemplate defines the common input parameters for Ironic services 26 | type IronicServiceTemplate struct { 27 | // +kubebuilder:validation:Optional 28 | // +kubebuilder:default=1 29 | // +kubebuilder:validation:Maximum=32 30 | // +kubebuilder:validation:Minimum=0 31 | // Replicas - 32 | Replicas *int32 `json:"replicas"` 33 | 34 | // +kubebuilder:validation:Optional 35 | // NodeSelector to target subset of worker nodes running this service. Setting here overrides 36 | // any global NodeSelector settings within the Ironic CR 37 | NodeSelector *map[string]string `json:"nodeSelector,omitempty"` 38 | 39 | // +kubebuilder:validation:Optional 40 | // Resources - Compute Resources required by this service (Limits/Requests). 41 | // https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 42 | Resources corev1.ResourceRequirements `json:"resources,omitempty"` 43 | 44 | // +kubebuilder:validation:Optional 45 | // +kubebuilder:default="# add your customization here" 46 | // CustomServiceConfig - customize the service config using this parameter to change service defaults, 47 | // or overwrite rendered information using raw OpenStack config format. The content gets added to 48 | // to /etc//.conf.d directory as custom.conf file. 49 | CustomServiceConfig string `json:"customServiceConfig"` 50 | 51 | // +kubebuilder:validation:Optional 52 | // ConfigOverwrite - interface to overwrite default config files like e.g. policy.json. 53 | // But can also be used to add additional files. Those get added to the service config dir in /etc/ . 54 | // TODO: -> implement 55 | DefaultConfigOverwrite map[string]string `json:"defaultConfigOverwrite,omitempty"` 56 | 57 | // +kubebuilder:validation:Optional 58 | // TopologyRef to apply the Topology defined by the associated CR referenced 59 | // by name 60 | TopologyRef *topologyv1.TopoRef `json:"topologyRef,omitempty"` 61 | } 62 | 63 | // PasswordSelector to identify the AdminUser password from the Secret 64 | type PasswordSelector struct { 65 | // +kubebuilder:validation:Optional 66 | // +kubebuilder:default="IronicPassword" 67 | // Service - Selector to get the ironic service password from the Secret 68 | Service string `json:"service"` 69 | } 70 | 71 | // KeystoneEndpoints defines keystone endpoint parameters for service 72 | type KeystoneEndpoints struct { 73 | // +kubebuilder:validation:Optional 74 | // Internal endpoint URL 75 | Internal string `json:"internal"` 76 | // +kubebuilder:validation:Optional 77 | // Public endpoint URL 78 | Public string `json:"public"` 79 | } 80 | 81 | // ValidateTopology - 82 | func (instance *IronicServiceTemplate) ValidateTopology( 83 | basePath *field.Path, 84 | namespace string, 85 | ) field.ErrorList { 86 | var allErrs field.ErrorList 87 | allErrs = append(allErrs, topologyv1.ValidateTopologyRef( 88 | instance.TopologyRef, 89 | *basePath.Child("topologyRef"), namespace)...) 90 | return allErrs 91 | } 92 | -------------------------------------------------------------------------------- /api/v1beta1/conditions.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1beta1 17 | 18 | import ( 19 | condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 20 | ) 21 | 22 | // Ironic Condition Types used by API objects. 23 | const ( 24 | // IronicAPIReadyCondition Status=True condition which indicates if the IronicAPI is configured and operational 25 | IronicAPIReadyCondition condition.Type = "IronicAPIReady" 26 | 27 | // IronicConductorReadyCondition Status=True condition which indicates if the IronicConductor is configured and operational 28 | IronicConductorReadyCondition condition.Type = "IronicConductorReady" 29 | 30 | // IronicInspectorReadyCondition Status=True condition which indicates if the Inspector is configured and operational 31 | IronicInspectorReadyCondition condition.Type = "IronicInspectorReady" 32 | 33 | // IronicNeutronAgentReadyCondition Status=True condition which indicates if the ML2 baremetal Ironic Neutron Agent is configured and operational 34 | IronicNeutronAgentReadyCondition condition.Type = "IronicNeutronAgentReady" 35 | ) 36 | 37 | // Ironic Reasons used by API objects. 38 | const () 39 | 40 | // Common Messages used by API objects. 41 | const ( 42 | // 43 | // RabbitMqTransportURLReady condition messages 44 | // 45 | // RabbitMqTransportURLDisabledMessage 46 | RabbitMqTransportURLDisabledMessage = "RabbitMqTransportURL disabled" 47 | 48 | // 49 | // IronicAPIReady condition messages 50 | // 51 | // IronicAPIReadyInitMessage 52 | IronicAPIReadyInitMessage = "IronicAPI not started" 53 | 54 | // IronicAPIReadyErrorMessage 55 | IronicAPIReadyErrorMessage = "IronicAPI error occured %s" 56 | 57 | // 58 | // IronicConductorReady condition messages 59 | // 60 | // IronicConductorReadyInitMessage 61 | IronicConductorReadyInitMessage = "IronicConductor not started" 62 | 63 | // IronicConductorReadyErrorMessage 64 | IronicConductorReadyErrorMessage = "IronicConductor error occured %s" 65 | 66 | // 67 | // IronicInspectorReady condition messages 68 | // 69 | // IronicInspectorReadyInitMessage 70 | IronicInspectorReadyInitMessage = "IronicInspector not started" 71 | 72 | // IronicInspectorReadyErrorMessage 73 | IronicInspectorReadyErrorMessage = "IronicInspector error occured %s" 74 | 75 | // 76 | // IronicNeutronAgentReady condition messages 77 | // 78 | // IronicNeutronAgentReadyInitMessage 79 | IronicNeutronAgentReadyInitMessage = "IronicNeutronAgent not started" 80 | 81 | // IronicNeutronAgentReadyErrorMessage 82 | IronicNeutronAgentReadyErrorMessage = "IronicNeutronAgent error occured %s" 83 | ) 84 | -------------------------------------------------------------------------------- /api/v1beta1/funcs.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1beta1 17 | 18 | import ( 19 | "sigs.k8s.io/controller-runtime/pkg/client" 20 | ) 21 | 22 | // GetOwningIronicName - Given a IronicAPI, IronicConductor 23 | // object, returning the parent Ironic object that created it (if any) 24 | func GetOwningIronicName(instance client.Object) string { 25 | for _, ownerRef := range instance.GetOwnerReferences() { 26 | if ownerRef.Kind == "Ironic" { 27 | return ownerRef.Name 28 | } 29 | } 30 | 31 | return "" 32 | } 33 | -------------------------------------------------------------------------------- /api/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the ironic v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=ironic.openstack.org 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "ironic.openstack.org", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1beta1/ironicapi.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1beta1 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | 22 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | appsv1 "k8s.io/api/apps/v1" 26 | k8s_errors "k8s.io/apimachinery/pkg/api/errors" 27 | ) 28 | 29 | // GetIronicAPI - get ironicAPI object in namespace 30 | func GetIronicAPI( 31 | ctx context.Context, 32 | h *helper.Helper, 33 | namespace string, 34 | labelSelector map[string]string, 35 | ) (*IronicAPI, error) { 36 | ironicList := &IronicAPIList{} 37 | 38 | listOpts := []client.ListOption{ 39 | client.InNamespace(namespace), 40 | } 41 | 42 | if len(labelSelector) > 0 { 43 | labels := client.MatchingLabels(labelSelector) 44 | listOpts = append(listOpts, labels) 45 | } 46 | 47 | err := h.GetClient().List(ctx, ironicList, listOpts...) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | if len(ironicList.Items) > 1 { 53 | return nil, fmt.Errorf("more then one IronicAPI object found in namespace %s", namespace) 54 | } 55 | 56 | if len(ironicList.Items) == 0 { 57 | return nil, k8s_errors.NewNotFound( 58 | appsv1.Resource("IronicAPI"), 59 | fmt.Sprintf("No IronicAPI object found in namespace %s", namespace), 60 | ) 61 | } 62 | 63 | return &ironicList.Items[0], nil 64 | } 65 | -------------------------------------------------------------------------------- /api/v1beta1/webhook_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package v1beta1 15 | 16 | import ( 17 | "context" 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "path/filepath" 22 | "testing" 23 | "time" 24 | 25 | . "github.com/onsi/ginkgo/v2" 26 | . "github.com/onsi/gomega" 27 | 28 | admissionv1beta1 "k8s.io/api/admission/v1beta1" 29 | //+kubebuilder:scaffold:imports 30 | "k8s.io/apimachinery/pkg/runtime" 31 | "k8s.io/client-go/rest" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/envtest" 35 | logf "sigs.k8s.io/controller-runtime/pkg/log" 36 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 37 | "sigs.k8s.io/controller-runtime/pkg/webhook" 38 | 39 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 40 | ) 41 | 42 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 43 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 44 | 45 | var ( 46 | cfg *rest.Config 47 | k8sClient client.Client 48 | testEnv *envtest.Environment 49 | ctx context.Context 50 | cancel context.CancelFunc 51 | ) 52 | 53 | func TestAPIs(t *testing.T) { 54 | RegisterFailHandler(Fail) 55 | 56 | RunSpecs(t, "Webhook Suite") 57 | } 58 | 59 | var _ = BeforeSuite(func() { 60 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 61 | 62 | ctx, cancel = context.WithCancel(context.TODO()) 63 | 64 | By("bootstrapping test environment") 65 | testEnv = &envtest.Environment{ 66 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 67 | ErrorIfCRDPathMissing: false, 68 | WebhookInstallOptions: envtest.WebhookInstallOptions{ 69 | Paths: []string{filepath.Join("..", "..", "config", "webhook")}, 70 | }, 71 | } 72 | 73 | var err error 74 | // cfg is defined in this file globally. 75 | cfg, err = testEnv.Start() 76 | Expect(err).NotTo(HaveOccurred()) 77 | Expect(cfg).NotTo(BeNil()) 78 | 79 | scheme := runtime.NewScheme() 80 | err = AddToScheme(scheme) 81 | Expect(err).NotTo(HaveOccurred()) 82 | 83 | err = admissionv1beta1.AddToScheme(scheme) 84 | Expect(err).NotTo(HaveOccurred()) 85 | 86 | //+kubebuilder:scaffold:scheme 87 | 88 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 89 | Expect(err).NotTo(HaveOccurred()) 90 | Expect(k8sClient).NotTo(BeNil()) 91 | 92 | // start webhook server using Manager 93 | webhookInstallOptions := &testEnv.WebhookInstallOptions 94 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 95 | Scheme: scheme, 96 | Metrics: metricsserver.Options{ 97 | BindAddress: "0", 98 | }, 99 | WebhookServer: webhook.NewServer( 100 | webhook.Options{ 101 | Host: webhookInstallOptions.LocalServingHost, 102 | Port: webhookInstallOptions.LocalServingPort, 103 | CertDir: webhookInstallOptions.LocalServingCertDir, 104 | }), 105 | LeaderElection: false, 106 | }) 107 | Expect(err).NotTo(HaveOccurred()) 108 | 109 | err = (&Ironic{}).SetupWebhookWithManager(mgr) 110 | Expect(err).NotTo(HaveOccurred()) 111 | 112 | //+kubebuilder:scaffold:webhook 113 | 114 | go func() { 115 | defer GinkgoRecover() 116 | err = mgr.Start(ctx) 117 | Expect(err).NotTo(HaveOccurred()) 118 | }() 119 | 120 | // wait for the webhook server to get ready 121 | dialer := &net.Dialer{Timeout: time.Second} 122 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) 123 | Eventually(func() error { 124 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) 125 | if err != nil { 126 | return err 127 | } 128 | conn.Close() 129 | return nil 130 | }).Should(Succeed()) 131 | }) 132 | 133 | var _ = AfterSuite(func() { 134 | cancel() 135 | By("tearing down the test environment") 136 | err := testEnv.Stop() 137 | Expect(err).NotTo(HaveOccurred()) 138 | }) 139 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: issuer 9 | app.kubernetes.io/instance: selfsigned-issuer 10 | app.kubernetes.io/component: certificate 11 | app.kubernetes.io/created-by: ironic-operator 12 | app.kubernetes.io/part-of: ironic-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: selfsigned-issuer 15 | namespace: system 16 | spec: 17 | selfSigned: {} 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | labels: 23 | app.kubernetes.io/name: certificate 24 | app.kubernetes.io/instance: serving-cert 25 | app.kubernetes.io/component: certificate 26 | app.kubernetes.io/created-by: ironic-operator 27 | app.kubernetes.io/part-of: ironic-operator 28 | app.kubernetes.io/managed-by: kustomize 29 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 30 | namespace: system 31 | spec: 32 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 33 | dnsNames: 34 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 35 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 36 | issuerRef: 37 | kind: Issuer 38 | name: selfsigned-issuer 39 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 40 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/ironic.openstack.org_ironics.yaml 6 | - bases/ironic.openstack.org_ironicapis.yaml 7 | - bases/ironic.openstack.org_ironicconductors.yaml 8 | - bases/ironic.openstack.org_ironicinspectors.yaml 9 | - bases/ironic.openstack.org_ironicneutronagents.yaml 10 | #+kubebuilder:scaffold:crdkustomizeresource 11 | 12 | patchesStrategicMerge: 13 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 14 | # patches here are for enabling the conversion webhook for each CRD 15 | #- patches/webhook_in_ironics.yaml 16 | #- patches/webhook_in_ironicapis.yaml 17 | #- patches/webhook_in_ironicconductors.yaml 18 | #- patches/webhook_in_ironiinspectors.yaml 19 | #- patches/webhook_in_ironicneutronagents.yaml 20 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 21 | 22 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 23 | # patches here are for enabling the CA injection for each CRD 24 | - patches/cainjection_in_ironics.yaml 25 | #- patches/cainjection_in_ironicapis.yaml 26 | #- patches/cainjection_in_ironicconductors.yaml 27 | #- patches/cainjection_in_ironicinspectors.yaml 28 | #- patches/cainjection_in_ironicneutronagents.yaml 29 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 30 | 31 | # the following config is for teaching kustomize how to do kustomization for CRDs. 32 | configurations: 33 | - kustomizeconfig.yaml 34 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ironicapis.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ironicapis.ironic.openstack.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ironicconductors.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ironicconductorss.ironic.openstack.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ironicinspectors.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ironicinspectorss.ironic.openstack.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ironicneutronagents.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ironicneutronagents.ironic.openstack.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ironics.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ironics.ironic.openstack.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ironicapis.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ironicapis.ironic.openstack.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ironicconductors.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ironicconductors.ironic.openstack.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ironicinspectors.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ironicinspectorss.ironic.openstack.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ironicneutronagents.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ironicneutronagents.ironic.openstack.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ironics.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ironics.ironic.openstack.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: ironic-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: ironic-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | - manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | - webhookcainjection_patch.yaml 45 | 46 | # Injects our custom images (ENV variable settings) 47 | - manager_default_images.yaml 48 | 49 | # the following config is for teaching kustomize how to do var substitution 50 | vars: [] 51 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 52 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 53 | # objref: 54 | # kind: Certificate 55 | # group: cert-manager.io 56 | # version: v1 57 | # name: serving-cert # this name should match the one in certificate.yaml 58 | # fieldref: 59 | # fieldpath: metadata.namespace 60 | #- name: CERTIFICATE_NAME 61 | # objref: 62 | # kind: Certificate 63 | # group: cert-manager.io 64 | # version: v1 65 | # name: serving-cert # this name should match the one in certificate.yaml 66 | #- name: SERVICE_NAMESPACE # namespace of the service 67 | # objref: 68 | # kind: Service 69 | # version: v1 70 | # name: webhook-service 71 | # fieldref: 72 | # fieldpath: metadata.namespace 73 | #- name: SERVICE_NAME 74 | # objref: 75 | # kind: Service 76 | # version: v1 77 | # name: webhook-service 78 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | securityContext: 14 | allowPrivilegeEscalation: false 15 | # TODO(user): uncomment for common cases that do not require escalating privileges 16 | # capabilities: 17 | # drop: 18 | # - "ALL" 19 | image: quay.io/openstack-k8s-operators/kube-rbac-proxy:v0.16.0 20 | args: 21 | - "--secure-listen-address=0.0.0.0:8443" 22 | - "--upstream=http://127.0.0.1:8080/" 23 | - "--logtostderr=true" 24 | - "--v=0" 25 | ports: 26 | - containerPort: 8443 27 | protocol: TCP 28 | name: https 29 | resources: 30 | limits: 31 | cpu: 500m 32 | memory: 128Mi 33 | requests: 34 | cpu: 5m 35 | memory: 64Mi 36 | - name: manager 37 | args: 38 | - "--health-probe-bind-address=:8081" 39 | - "--metrics-bind-address=127.0.0.1:8080" 40 | - "--leader-elect" 41 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/default/manager_default_images.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject custom ENV settings to the manager container 2 | # Used to set our default image locations 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: manager 13 | env: 14 | - name: RELATED_IMAGE_IRONIC_API_IMAGE_URL_DEFAULT 15 | value: quay.io/podified-antelope-centos9/openstack-ironic-api:current-podified 16 | - name: RELATED_IMAGE_IRONIC_CONDUCTOR_IMAGE_URL_DEFAULT 17 | value: quay.io/podified-antelope-centos9/openstack-ironic-conductor:current-podified 18 | - name: RELATED_IMAGE_IRONIC_INSPECTOR_IMAGE_URL_DEFAULT 19 | value: quay.io/podified-antelope-centos9/openstack-ironic-inspector:current-podified 20 | - name: RELATED_IMAGE_IRONIC_PXE_IMAGE_URL_DEFAULT 21 | value: quay.io/podified-antelope-centos9/openstack-ironic-pxe:current-podified 22 | - name: RELATED_IMAGE_IRONIC_NEUTRON_AGENT_IMAGE_URL_DEFAULT 23 | value: quay.io/podified-antelope-centos9/openstack-ironic-neutron-agent:current-podified 24 | - name: RELATED_IMAGE_IRONIC_PYTHON_AGENT_IMAGE_URL_DEFAULT 25 | value: quay.io/podified-antelope-centos9/ironic-python-agent:current-podified 26 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | --- 4 | apiVersion: admissionregistration.k8s.io/v1 5 | kind: ValidatingWebhookConfiguration 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: validatingwebhookconfiguration 9 | app.kubernetes.io/instance: validating-webhook-configuration 10 | app.kubernetes.io/component: webhook 11 | app.kubernetes.io/created-by: ironic-operator 12 | app.kubernetes.io/part-of: ironic-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: validating-webhook-configuration 15 | annotations: 16 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 17 | --- 18 | apiVersion: admissionregistration.k8s.io/v1 19 | kind: MutatingWebhookConfiguration 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: mutatingwebhookconfiguration 23 | app.kubernetes.io/instance: mutating-webhook-configuration 24 | app.kubernetes.io/component: webhook 25 | app.kubernetes.io/created-by: ironic-operator 26 | app.kubernetes.io/part-of: ironic-operator 27 | app.kubernetes.io/managed-by: kustomize 28 | name: mutating-webhook-configuration 29 | annotations: 30 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 31 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: f92b5c2d.openstack.org 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: controller 16 | newTag: latest 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | labels: 8 | control-plane: controller-manager 9 | openstack.org/operator-name: ironic 10 | spec: 11 | selector: 12 | matchLabels: 13 | openstack.org/operator-name: ironic 14 | replicas: 1 15 | template: 16 | metadata: 17 | annotations: 18 | kubectl.kubernetes.io/default-container: manager 19 | labels: 20 | control-plane: controller-manager 21 | openstack.org/operator-name: ironic 22 | spec: 23 | securityContext: 24 | runAsNonRoot: true 25 | # TODO(user): For common cases that do not require escalating privileges 26 | # it is recommended to ensure that all your Pods/Containers are restrictive. 27 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 28 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 29 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 30 | # seccompProfile: 31 | # type: RuntimeDefault 32 | containers: 33 | - command: 34 | - /manager 35 | args: 36 | - --leader-elect 37 | image: controller:latest 38 | name: manager 39 | securityContext: 40 | allowPrivilegeEscalation: false 41 | # TODO(user): uncomment for common cases that do not require escalating privileges 42 | # capabilities: 43 | # drop: 44 | # - "ALL" 45 | livenessProbe: 46 | httpGet: 47 | path: /healthz 48 | port: 8081 49 | initialDelaySeconds: 15 50 | periodSeconds: 20 51 | readinessProbe: 52 | httpGet: 53 | path: /readyz 54 | port: 8081 55 | initialDelaySeconds: 5 56 | periodSeconds: 10 57 | # TODO(user): Configure the resources accordingly based on the project requirements. 58 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 59 | resources: 60 | limits: 61 | cpu: 500m 62 | memory: 256Mi 63 | requests: 64 | cpu: 10m 65 | memory: 128Mi 66 | serviceAccountName: controller-manager 67 | terminationGracePeriodSeconds: 10 68 | -------------------------------------------------------------------------------- /config/manifests/bases/ironic-operator.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Basic Install 7 | features.operators.openshift.io/disconnected: "true" 8 | features.operators.openshift.io/fips-compliant: "true" 9 | features.operators.openshift.io/proxy-aware: "false" 10 | features.operators.openshift.io/tls-profiles: "false" 11 | features.operators.openshift.io/token-auth-aws: "false" 12 | features.operators.openshift.io/token-auth-azure: "false" 13 | features.operators.openshift.io/token-auth-gcp: "false" 14 | operatorframework.io/suggested-namespace: openstack 15 | operators.operatorframework.io/operator-type: non-standalone 16 | name: ironic-operator.v0.0.0 17 | namespace: placeholder 18 | spec: 19 | apiservicedefinitions: {} 20 | customresourcedefinitions: 21 | owned: 22 | - description: IronicInspector is the Schema for the ironicinspectors 23 | displayName: Ironic Inspector 24 | kind: IronicInspector 25 | name: ironicinspectors.ironic.openstack.org 26 | version: v1beta1 27 | - description: IronicNeutronAgent is the Schema for the ML2 networking-baremetal's 28 | agent 29 | displayName: Ironic Neutron Agent 30 | kind: IronicNeutronAgent 31 | name: ironicneutronagents.ironic.openstack.org 32 | version: v1beta1 33 | - description: IronicAPI is the Schema for the ironicapis API 34 | displayName: Ironic API 35 | kind: IronicAPI 36 | name: ironicapis.ironic.openstack.org 37 | specDescriptors: 38 | - description: TLS - Parameters related to the TLS 39 | displayName: TLS 40 | path: tls 41 | version: v1beta1 42 | - description: IronicConductor is the Schema for the ironicconductors Conductor 43 | displayName: Ironic Conductor 44 | kind: IronicConductor 45 | name: ironicconductors.ironic.openstack.org 46 | specDescriptors: 47 | - description: TLS - Parameters related to the TLS 48 | displayName: TLS 49 | path: tls 50 | version: v1beta1 51 | - description: Ironic is the Schema for the ironics API 52 | displayName: Ironic 53 | kind: Ironic 54 | name: ironics.ironic.openstack.org 55 | specDescriptors: 56 | - description: TLS - Parameters related to the TLS 57 | displayName: TLS 58 | path: ironicAPI.tls 59 | - description: TLS - Parameters related to the TLS 60 | displayName: TLS 61 | path: ironicInspector.tls 62 | version: v1beta1 63 | description: Ironic Operator 64 | displayName: Ironic Operator 65 | install: 66 | spec: 67 | deployments: null 68 | strategy: "" 69 | installModes: 70 | - supported: true 71 | type: OwnNamespace 72 | - supported: true 73 | type: SingleNamespace 74 | - supported: false 75 | type: MultiNamespace 76 | - supported: true 77 | type: AllNamespaces 78 | keywords: 79 | - OpenStack 80 | - Identity 81 | - Ironic 82 | links: 83 | - name: Ironic Operator 84 | url: https://github.com/openstack-k8s-operators/ironic-operator 85 | maturity: beta 86 | provider: 87 | name: Red Hat Inc. 88 | url: https://redhat.com/ 89 | version: 0.0.0 90 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/ironic-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | openstack.org/operator-name: ironic 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | openstack.org/operator-name: ironic 16 | -------------------------------------------------------------------------------- /config/rbac/ironic_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ironics. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironic-editor-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironics 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ironic.openstack.org 21 | resources: 22 | - ironics/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/ironic_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ironics. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironic-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironics 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ironic.openstack.org 17 | resources: 18 | - ironics/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/ironicapi_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ironicapis. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironicapi-editor-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironicapis 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ironic.openstack.org 21 | resources: 22 | - ironicapis/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/ironicapi_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ironicapis. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironicapi-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironicapis 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ironic.openstack.org 17 | resources: 18 | - ironicapis/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/ironicconductor_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ironicconductors. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironicconductor-editor-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironicconductors 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ironic.openstack.org 21 | resources: 22 | - ironicconductors/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/ironicconductor_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ironicapis. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: ironicconductor-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ironic.openstack.org 9 | resources: 10 | - ironicconductors 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ironic.openstack.org 17 | resources: 18 | - ironicconductors/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: Ironic 3 | metadata: 4 | name: ironic 5 | spec: 6 | serviceUser: ironic 7 | secret: osp-secret 8 | customServiceConfig: | 9 | [DEFAULT] 10 | debug = true 11 | databaseInstance: openstack 12 | storageClass: local-storage 13 | ironicAPI: {} 14 | ironicConductors: 15 | - storageRequest: 10G 16 | ironicInspector: {} 17 | ironicNeutronAgent: {} 18 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironic_conductor_groups.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: Ironic 3 | metadata: 4 | name: ironic 5 | spec: 6 | serviceUser: ironic 7 | customServiceConfig: | 8 | [DEFAULT] 9 | debug = true 10 | databaseInstance: openstack 11 | storageClass: local-storage 12 | ironicAPI: {} 13 | ironicConductors: 14 | - storageRequest: 10G 15 | - conductorGroup: auckland 16 | storageRequest: 10G 17 | - conductorGroup: stockholm 18 | storageRequest: 10G 19 | ironicInspector: {} 20 | ironicNeutronAgent: {} 21 | secret: osp-secret 22 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironic_standalone.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: Ironic 3 | metadata: 4 | name: ironic 5 | spec: 6 | standalone: true 7 | serviceUser: ironic 8 | customServiceConfig: | 9 | [DEFAULT] 10 | debug = true 11 | databaseInstance: openstack 12 | storageClass: local-storage 13 | ironicAPI: {} 14 | ironicConductors: 15 | - storageRequest: 10G 16 | ironicInspector: {} 17 | ironicNeutronAgent: 18 | replicas: 0 # Neutron agent replicas is zero, i.e disabled for standalone. 19 | secret: osp-secret 20 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironic_tls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: Ironic 3 | metadata: 4 | name: ironic 5 | spec: 6 | serviceUser: ironic 7 | secret: osp-secret 8 | customServiceConfig: | 9 | [DEFAULT] 10 | debug = true 11 | databaseInstance: openstack 12 | storageClass: local-storage 13 | ironicAPI: 14 | tls: 15 | api: 16 | internal: 17 | secretName: cert-ironic-internal-svc 18 | public: 19 | secretName: cert-ironic-public-svc 20 | caBundleSecretName: combined-ca-bundle 21 | ironicConductors: 22 | - storageRequest: 10G 23 | ironicInspector: 24 | tls: 25 | api: 26 | internal: 27 | secretName: cert-ironic-inspector-internal-svc 28 | public: 29 | secretName: cert-ironic-inspector-public-svc 30 | caBundleSecretName: combined-ca-bundle 31 | ironicNeutronAgent: {} 32 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironicapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: IronicAPI 3 | metadata: 4 | name: ironicapi-sample 5 | spec: 6 | databaseHostname: openstack.ironic-kuttl-tests.svc 7 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironicconductor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: IronicConductor 3 | metadata: 4 | name: ironicconductor-sample 5 | spec: 6 | databaseHostname: openstack.ironic-kuttl-tests.svc 7 | storageRequest: 10G 8 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironicinspector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: IronicInspector 3 | metadata: 4 | name: ironicinspector-sample 5 | spec: 6 | secret: osp-secret 7 | -------------------------------------------------------------------------------- /config/samples/ironic_v1beta1_ironicneutronagent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: IronicNeutronAgent 3 | metadata: 4 | name: ironic-neutron-agent-sample 5 | namespace: openstack 6 | spec: 7 | secret: osp-secret 8 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - ironic_v1beta1_ironic.yaml 4 | - ironic_v1beta1_ironicapi.yaml 5 | - ironic_v1beta1_ironicconductor.yaml 6 | - ironic_v1beta1_ironicinspector.yaml 7 | - ironic_v1beta1_ironicneutronagent.yaml 8 | #+kubebuilder:scaffold:manifestskustomizesamples 9 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.22.2 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.22.2 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.22.2 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.22.2 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.22.2 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.22.2 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: ValidatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: MutatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: ValidatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: MutatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-ironic-openstack-org-v1beta1-ironic 14 | failurePolicy: Fail 15 | name: mironic.kb.io 16 | rules: 17 | - apiGroups: 18 | - ironic.openstack.org 19 | apiVersions: 20 | - v1beta1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - ironics 26 | sideEffects: None 27 | --- 28 | apiVersion: admissionregistration.k8s.io/v1 29 | kind: ValidatingWebhookConfiguration 30 | metadata: 31 | name: validating-webhook-configuration 32 | webhooks: 33 | - admissionReviewVersions: 34 | - v1 35 | clientConfig: 36 | service: 37 | name: webhook-service 38 | namespace: system 39 | path: /validate-ironic-openstack-org-v1beta1-ironic 40 | failurePolicy: Fail 41 | name: vironic.kb.io 42 | rules: 43 | - apiGroups: 44 | - ironic.openstack.org 45 | apiVersions: 46 | - v1beta1 47 | operations: 48 | - CREATE 49 | - UPDATE 50 | resources: 51 | - ironics 52 | sideEffects: None 53 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: service 6 | app.kubernetes.io/instance: webhook-service 7 | app.kubernetes.io/component: webhook 8 | app.kubernetes.io/created-by: ironic-operator 9 | app.kubernetes.io/part-of: ironic-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: webhook-service 12 | namespace: system 13 | spec: 14 | ports: 15 | - port: 443 16 | protocol: TCP 17 | targetPort: 9443 18 | selector: 19 | openstack.org/operator-name: ironic 20 | -------------------------------------------------------------------------------- /controllers/funcs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | 25 | "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 26 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | ) 29 | 30 | // fields to index to reconcile when change 31 | const ( 32 | passwordSecretField = ".spec.secret" 33 | caBundleSecretNameField = ".spec.tls.caBundleSecretName" 34 | tlsAPIInternalField = ".spec.tls.api.internal.secretName" 35 | tlsAPIPublicField = ".spec.tls.api.public.secretName" 36 | topologyField = ".spec.topologyRef.Name" 37 | ) 38 | 39 | var ( 40 | ironicAPIWatchFields = []string{ 41 | passwordSecretField, 42 | caBundleSecretNameField, 43 | tlsAPIInternalField, 44 | tlsAPIPublicField, 45 | topologyField, 46 | } 47 | ironicConductorWatchFields = []string{ 48 | passwordSecretField, 49 | caBundleSecretNameField, 50 | topologyField, 51 | } 52 | ironicInspectorWatchFields = []string{ 53 | passwordSecretField, 54 | caBundleSecretNameField, 55 | topologyField, 56 | } 57 | ironicNeutronAgentWatchFields = []string{ 58 | passwordSecretField, 59 | caBundleSecretNameField, 60 | topologyField, 61 | } 62 | ) 63 | 64 | func getCommonRbacRules() []rbacv1.PolicyRule { 65 | return []rbacv1.PolicyRule{ 66 | { 67 | APIGroups: []string{"security.openshift.io"}, 68 | ResourceNames: []string{"anyuid", "privileged"}, 69 | Resources: []string{"securitycontextconstraints"}, 70 | Verbs: []string{"use"}, 71 | }, 72 | { 73 | APIGroups: []string{""}, 74 | Resources: []string{"pods"}, 75 | Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, 76 | }, 77 | } 78 | } 79 | 80 | type conditionUpdater interface { 81 | Set(c *condition.Condition) 82 | MarkTrue(t condition.Type, messageFormat string, messageArgs ...interface{}) 83 | } 84 | 85 | type topologyHandler interface { 86 | GetSpecTopologyRef() *topologyv1.TopoRef 87 | GetLastAppliedTopology() *topologyv1.TopoRef 88 | SetLastAppliedTopology(t *topologyv1.TopoRef) 89 | } 90 | 91 | func ensureTopology( 92 | ctx context.Context, 93 | helper *helper.Helper, 94 | instance topologyHandler, 95 | finalizer string, 96 | conditionUpdater conditionUpdater, 97 | defaultLabelSelector metav1.LabelSelector, 98 | ) (*topologyv1.Topology, error) { 99 | 100 | topology, err := topologyv1.EnsureServiceTopology( 101 | ctx, 102 | helper, 103 | instance.GetSpecTopologyRef(), 104 | instance.GetLastAppliedTopology(), 105 | finalizer, 106 | defaultLabelSelector, 107 | ) 108 | if err != nil { 109 | conditionUpdater.Set(condition.FalseCondition( 110 | condition.TopologyReadyCondition, 111 | condition.ErrorReason, 112 | condition.SeverityWarning, 113 | condition.TopologyReadyErrorMessage, 114 | err.Error())) 115 | return nil, fmt.Errorf("waiting for Topology requirements: %w", err) 116 | } 117 | // update the Status with the last retrieved Topology (or set it to nil) 118 | instance.SetLastAppliedTopology(instance.GetSpecTopologyRef()) 119 | // update the Topology condition only when a Topology is referenced and has 120 | // been retrieved (err == nil) 121 | if tr := instance.GetSpecTopologyRef(); tr != nil { 122 | // update the TopologyRef associated condition 123 | conditionUpdater.MarkTrue( 124 | condition.TopologyReadyCondition, 125 | condition.TopologyReadyMessage, 126 | ) 127 | } 128 | return topology, nil 129 | } 130 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openstack-k8s-operators/ironic-operator 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-logr/logr v1.4.3 7 | github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.6 8 | github.com/onsi/ginkgo/v2 v2.20.1 9 | github.com/onsi/gomega v1.34.1 10 | github.com/openshift/api v3.9.0+incompatible 11 | github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250601112854-cbb0f7bc989f 12 | github.com/openstack-k8s-operators/ironic-operator/api v0.0.0-00010101000000-000000000000 13 | github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250605095955-a8616555fe6d 14 | github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250605082218-a58074898dd7 15 | github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20250605082218-a58074898dd7 16 | github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20250524131103-7ebaceec882b 17 | k8s.io/api v0.29.15 18 | k8s.io/apimachinery v0.29.15 19 | k8s.io/client-go v0.29.15 20 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 21 | sigs.k8s.io/controller-runtime v0.17.6 22 | ) 23 | 24 | require ( 25 | github.com/google/uuid v1.6.0 26 | go.uber.org/zap v1.27.0 27 | gopkg.in/yaml.v3 v3.0.1 28 | ) 29 | 30 | require ( 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 34 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 35 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 36 | github.com/fsnotify/fsnotify v1.7.0 // indirect 37 | github.com/go-logr/zapr v1.3.0 // indirect 38 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 39 | github.com/go-openapi/jsonreference v0.21.0 // indirect 40 | github.com/go-openapi/swag v0.23.0 // indirect 41 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 44 | github.com/golang/protobuf v1.5.4 // indirect 45 | github.com/google/gnostic-models v0.6.8 // indirect 46 | github.com/google/go-cmp v0.6.0 // indirect 47 | github.com/google/gofuzz v1.2.0 // indirect 48 | github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect 49 | github.com/gophercloud/gophercloud v1.14.1 // indirect 50 | github.com/imdario/mergo v0.3.16 // indirect 51 | github.com/josharian/intern v1.0.0 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/mailru/easyjson v0.7.7 // indirect 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 55 | github.com/modern-go/reflect2 v1.0.2 // indirect 56 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 57 | github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250508141203-be026d3164f7 // indirect 58 | github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250508141203-be026d3164f7 // indirect 59 | github.com/pkg/errors v0.9.1 // indirect 60 | github.com/prometheus/client_golang v1.19.0 // indirect 61 | github.com/prometheus/client_model v0.6.0 // indirect 62 | github.com/prometheus/common v0.51.1 // indirect 63 | github.com/prometheus/procfs v0.13.0 // indirect 64 | github.com/rabbitmq/cluster-operator/v2 v2.9.0 // indirect 65 | github.com/spf13/pflag v1.0.5 // indirect 66 | go.uber.org/multierr v1.11.0 // indirect 67 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 68 | golang.org/x/mod v0.20.0 // indirect 69 | golang.org/x/net v0.28.0 // indirect 70 | golang.org/x/oauth2 v0.18.0 // indirect 71 | golang.org/x/sys v0.23.0 // indirect 72 | golang.org/x/term v0.23.0 // indirect 73 | golang.org/x/text v0.17.0 // indirect 74 | golang.org/x/time v0.5.0 // indirect 75 | golang.org/x/tools v0.24.0 // indirect 76 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 77 | google.golang.org/appengine v1.6.8 // indirect 78 | google.golang.org/protobuf v1.34.1 // indirect 79 | gopkg.in/inf.v0 v0.9.1 // indirect 80 | gopkg.in/yaml.v2 v2.4.0 // indirect 81 | k8s.io/apiextensions-apiserver v0.29.15 // indirect 82 | k8s.io/component-base v0.29.15 // indirect 83 | k8s.io/klog/v2 v2.120.1 // indirect 84 | k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 // indirect 85 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 86 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 87 | sigs.k8s.io/yaml v1.4.0 // indirect 88 | ) 89 | 90 | replace github.com/openstack-k8s-operators/ironic-operator/api => ./api 91 | 92 | // mschuppert: map to latest commit from release-4.13 tag 93 | // must consistent within modules and service operators 94 | replace github.com/openshift/api => github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 //allow-merging 95 | 96 | // custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.6.0_patches_tag) 97 | replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20241017142550-a3524acedd49 //allow-merging 98 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/build-crd-schema-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | if [ -f "$INSTALL_DIR/crd-schema-checker" ]; then 5 | exit 0 6 | fi 7 | 8 | mkdir -p "$INSTALL_DIR/git-tmp" 9 | git clone https://github.com/openshift/crd-schema-checker.git \ 10 | -b "$CRD_SCHEMA_CHECKER_VERSION" "$INSTALL_DIR/git-tmp" 11 | pushd "$INSTALL_DIR/git-tmp" 12 | GOWORK=off make 13 | cp crd-schema-checker "$INSTALL_DIR/" 14 | popd 15 | rm -rf "$INSTALL_DIR/git-tmp" 16 | -------------------------------------------------------------------------------- /hack/clean_local_webhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | oc delete validatingwebhookconfiguration/vironic.kb.io --ignore-not-found 5 | oc delete mutatingwebhookconfiguration/mironic.kb.io --ignore-not-found 6 | -------------------------------------------------------------------------------- /hack/crd-schema-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | CHECKER=$INSTALL_DIR/crd-schema-checker 5 | 6 | TMP_DIR=$(mktemp -d) 7 | 8 | function cleanup { 9 | rm -rf "$TMP_DIR" 10 | } 11 | 12 | trap cleanup EXIT 13 | 14 | 15 | for crd in config/crd/bases/*.yaml; do 16 | mkdir -p "$(dirname "$TMP_DIR/$crd")" 17 | if git show "$BASE_REF:$crd" > "$TMP_DIR/$crd"; then 18 | $CHECKER check-manifests \ 19 | --existing-crd-filename="$TMP_DIR/$crd" \ 20 | --new-crd-filename="$crd" 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /kuttl-test.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # EXECUTION (from install_yamls repo root): 3 | # 4 | # make ironic_kuttl 5 | # 6 | # ASSUMPTIONS: 7 | # 8 | # 1. Latest version of kuttl is installed at /usr/local/bin/kubectl-kuttl 9 | # - wget https://github.com/kudobuilder/kuttl/releases/download/v0.11.1/kubectl-kuttl_0.11.1_linux_x86_64 10 | # - mv kubectl-kuttl_0.11.1_linux_x86_64 /usr/local/bin/kubectl-kuttl 11 | # - chmod 755 /usr/local/bin/kubectl-kuttl 12 | # 2. An OCP 4.10+ CRC cluster with Podified Operators has been deployed 13 | # 3. CLI user has access to $KUBECONFIG 14 | # 4. The environment variable INSTALL_YAMLS is set to the the path of the 15 | # install_yamls repo 16 | 17 | apiVersion: kuttl.dev/v1beta1 18 | kind: TestSuite 19 | namespace: ironic-kuttl-tests 20 | reportFormat: JSON 21 | reportName: kuttl-test-ironic 22 | timeout: 750 23 | parallel: 1 24 | suppress: 25 | - events # Remove spammy event logs 26 | -------------------------------------------------------------------------------- /pkg/ironic/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironic 17 | 18 | const ( 19 | // ServiceName - 20 | ServiceName = "ironic" 21 | // ServiceType - 22 | ServiceType = "baremetal" 23 | // DatabaseName - 24 | DatabaseName = "ironic" 25 | // DatabaseCRName - 26 | DatabaseCRName = "ironic" 27 | // IronicPublicPort - 28 | IronicPublicPort int32 = 6385 29 | // IronicInternalPort - 30 | IronicInternalPort int32 = 6385 31 | // ConductorComponent - 32 | ConductorComponent = "conductor" 33 | // HttpbootComponent - 34 | HttpbootComponent = "httpboot" 35 | // JSONRPCComponent - 36 | JSONRPCComponent = "jsonrpc" 37 | // DhcpComponent - 38 | DhcpComponent = "dhcp" 39 | // APIComponent - 40 | APIComponent = "api" 41 | // InspectorComponent - 42 | InspectorComponent = "inspector" 43 | // ConductorGroupSelector - 44 | ConductorGroupSelector = "conductorGroup" 45 | // ImageDirectory - 46 | ImageDirectory = "/var/lib/ironic/httpboot" 47 | // LogPath - Log path for Ironc API 48 | LogPath = "/var/log/ironic/ironic-api.log" 49 | ) 50 | -------------------------------------------------------------------------------- /pkg/ironic/dbsync.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironic 17 | 18 | import ( 19 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 20 | 21 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 22 | batchv1 "k8s.io/api/batch/v1" 23 | corev1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | const ( 28 | // DBSyncCommand - 29 | DBSyncCommand = "/usr/local/bin/container-scripts/dbsync.sh" 30 | ) 31 | 32 | // DbSyncJob func 33 | func DbSyncJob( 34 | instance *ironicv1.Ironic, 35 | labels map[string]string, 36 | ) *batchv1.Job { 37 | runAsUser := int64(0) 38 | 39 | args := []string{"-c", DBSyncCommand} 40 | 41 | envVars := map[string]env.Setter{} 42 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 43 | envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") 44 | 45 | volumes := GetVolumes(ServiceName) 46 | volumeMounts := GetDBSyncVolumeMounts() 47 | initVolumeMounts := GetInitVolumeMounts() 48 | 49 | // add CA cert if defined 50 | if instance.Spec.IronicAPI.TLS.CaBundleSecretName != "" { 51 | volumes = append(volumes, instance.Spec.IronicAPI.TLS.CreateVolume()) 52 | volumeMounts = append(volumeMounts, instance.Spec.IronicAPI.TLS.CreateVolumeMounts(nil)...) 53 | initVolumeMounts = append(initVolumeMounts, instance.Spec.IronicAPI.TLS.CreateVolumeMounts(nil)...) 54 | } 55 | 56 | job := &batchv1.Job{ 57 | ObjectMeta: metav1.ObjectMeta{ 58 | Name: ServiceName + "-db-sync", 59 | Namespace: instance.Namespace, 60 | Labels: labels, 61 | }, 62 | Spec: batchv1.JobSpec{ 63 | Template: corev1.PodTemplateSpec{ 64 | Spec: corev1.PodSpec{ 65 | RestartPolicy: corev1.RestartPolicyOnFailure, 66 | ServiceAccountName: instance.RbacResourceName(), 67 | Containers: []corev1.Container{ 68 | { 69 | Name: ServiceName + "-db-sync", 70 | Command: []string{ 71 | "/bin/bash", 72 | }, 73 | Args: args, 74 | Image: instance.Spec.Images.Conductor, 75 | SecurityContext: &corev1.SecurityContext{ 76 | RunAsUser: &runAsUser, 77 | }, 78 | Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), 79 | VolumeMounts: volumeMounts, 80 | }, 81 | }, 82 | Volumes: volumes, 83 | }, 84 | }, 85 | }, 86 | } 87 | 88 | initContainerDetails := APIDetails{ 89 | ContainerImage: instance.Spec.Images.Conductor, 90 | DatabaseHost: instance.Status.DatabaseHostname, 91 | DatabaseName: DatabaseName, 92 | OSPSecret: instance.Spec.Secret, 93 | UserPasswordSelector: instance.Spec.PasswordSelectors.Service, 94 | VolumeMounts: initVolumeMounts, 95 | } 96 | job.Spec.Template.Spec.InitContainers = InitContainer(initContainerDetails) 97 | 98 | if instance.Spec.NodeSelector != nil { 99 | job.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 100 | } 101 | 102 | return job 103 | } 104 | -------------------------------------------------------------------------------- /pkg/ironic/funcs.go: -------------------------------------------------------------------------------- 1 | package ironic 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 9 | 10 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 14 | 15 | rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" 16 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | k8snet "k8s.io/utils/net" 19 | ) 20 | 21 | // GetIngressDomain - Get the Ingress Domain of cluster 22 | func GetIngressDomain( 23 | ctx context.Context, 24 | helper *helper.Helper, 25 | ) (string, error) { 26 | Log := helper.GetLogger() 27 | 28 | ingress := &unstructured.Unstructured{} 29 | ingress.SetGroupVersionKind( 30 | schema.GroupVersionKind{ 31 | Group: "operator.openshift.io", 32 | Version: "v1", 33 | Kind: "IngressController", 34 | }, 35 | ) 36 | err := helper.GetClient().Get( 37 | ctx, 38 | client.ObjectKey{ 39 | Namespace: "openshift-ingress-operator", 40 | Name: "default", 41 | }, 42 | ingress, 43 | ) 44 | if err != nil { 45 | return "", fmt.Errorf("unable to retrieve ingress domain %w", err) 46 | } 47 | ingressDomain := "" 48 | 49 | ingressStatus := ingress.UnstructuredContent()["status"] 50 | ingressStatusMap, ok := ingressStatus.(map[string]interface{}) 51 | if !ok { 52 | return "", fmt.Errorf("unable to retrieve ingress domain - wanted type map[string]interface{}; got %T", ingressStatus) 53 | } 54 | for k, v := range ingressStatusMap { 55 | if k == "domain" { 56 | ingressDomain = v.(string) 57 | // Break out of the loop, we got what we need 58 | break 59 | } 60 | } 61 | if ingressDomain != "" { 62 | Log.Info(fmt.Sprintf("Found ingress domain: %s", ingressDomain)) 63 | } else { 64 | return "", fmt.Errorf("unable to retrieve ingress domain") 65 | } 66 | 67 | return ingressDomain, nil 68 | } 69 | 70 | // TransportURLCreateOrUpdate - creates or updates rabbitmq transport URL 71 | func TransportURLCreateOrUpdate( 72 | Name string, 73 | Namespace string, 74 | RabbitMqClusterName string, 75 | instance metav1.Object, 76 | helper *helper.Helper, 77 | ) (*rabbitmqv1.TransportURL, controllerutil.OperationResult, error) { 78 | transportURL := &rabbitmqv1.TransportURL{ 79 | ObjectMeta: metav1.ObjectMeta{ 80 | Name: fmt.Sprintf("%s-transport", Name), 81 | Namespace: Namespace, 82 | }, 83 | } 84 | op, err := controllerutil.CreateOrUpdate( 85 | context.TODO(), 86 | helper.GetClient(), 87 | transportURL, 88 | func() error { 89 | 90 | transportURL.Spec.RabbitmqClusterName = RabbitMqClusterName 91 | 92 | err := controllerutil.SetControllerReference( 93 | instance, transportURL, helper.GetScheme()) 94 | return err 95 | }) 96 | 97 | return transportURL, op, err 98 | } 99 | 100 | // PrefixOrNetmaskFromCIDR - Parses the CIDRs in DHCPRanges and extrapolate 101 | // dotted decimal Netmask (IPv4) or Prefix bit's (IPv6). Returns new DHCPRanges 102 | // list with Prefix or Netmask populated. 103 | func PrefixOrNetmaskFromCIDR( 104 | DHCPRanges []ironicv1.DHCPRange, 105 | ) ([]ironicv1.DHCPRange, error) { 106 | var newDhcpRanges []ironicv1.DHCPRange 107 | for _, dhcpRange := range DHCPRanges { 108 | _, cidr, err := net.ParseCIDR(dhcpRange.Cidr) 109 | if err != nil { 110 | return DHCPRanges, err 111 | } 112 | if k8snet.IsIPv4CIDR(cidr) { 113 | dhcpRange.Netmask = net.IP(cidr.Mask).String() 114 | } 115 | if k8snet.IsIPv6CIDR(cidr) { 116 | dhcpRange.Prefix, _ = cidr.Mask.Size() 117 | } 118 | newDhcpRanges = append(newDhcpRanges, dhcpRange) 119 | } 120 | 121 | return newDhcpRanges, nil 122 | } 123 | -------------------------------------------------------------------------------- /pkg/ironic/initcontainer.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironic 17 | 18 | import ( 19 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | ) 23 | 24 | // APIDetails information 25 | type APIDetails struct { 26 | ContainerImage string 27 | PxeContainerImage string 28 | IronicPythonAgentImage string 29 | DatabaseHost string 30 | DatabaseName string 31 | TransportURLSecret string 32 | OSPSecret string 33 | UserPasswordSelector string 34 | VolumeMounts []corev1.VolumeMount 35 | Privileged bool 36 | PxeInit bool 37 | ConductorInit bool 38 | DeployHTTPURL string 39 | IngressDomain string 40 | ProvisionNetwork string 41 | ImageDirectory string 42 | } 43 | 44 | const ( 45 | // InitContainerCommand - 46 | InitContainerCommand = "/usr/local/bin/container-scripts/init.sh" 47 | 48 | // PxeInitContainerCommand - 49 | PxeInitContainerCommand = "/usr/local/bin/container-scripts/pxe-init.sh" 50 | ) 51 | 52 | // InitContainer - init container for Ironic pods 53 | func InitContainer(init APIDetails) []corev1.Container { 54 | runAsUser := int64(0) 55 | 56 | envVars := map[string]env.Setter{} 57 | envVars["DatabaseHost"] = env.SetValue(init.DatabaseHost) 58 | envVars["DatabaseName"] = env.SetValue(init.DatabaseName) 59 | envVars["DeployHTTPURL"] = env.SetValue(init.DeployHTTPURL) 60 | envVars["IngressDomain"] = env.SetValue(init.IngressDomain) 61 | 62 | envs := []corev1.EnvVar{ 63 | { 64 | Name: "IronicPassword", 65 | ValueFrom: &corev1.EnvVarSource{ 66 | SecretKeyRef: &corev1.SecretKeySelector{ 67 | LocalObjectReference: corev1.LocalObjectReference{ 68 | Name: init.OSPSecret, 69 | }, 70 | Key: init.UserPasswordSelector, 71 | }, 72 | }, 73 | }, 74 | { 75 | Name: "PodName", 76 | ValueFrom: &corev1.EnvVarSource{ 77 | FieldRef: &corev1.ObjectFieldSelector{ 78 | FieldPath: "metadata.name", 79 | }, 80 | }, 81 | }, 82 | { 83 | Name: "PodNamespace", 84 | ValueFrom: &corev1.EnvVarSource{ 85 | FieldRef: &corev1.ObjectFieldSelector{ 86 | FieldPath: "metadata.namespace", 87 | }, 88 | }, 89 | }, 90 | { 91 | Name: "ProvisionNetwork", 92 | Value: init.ProvisionNetwork, 93 | }, 94 | } 95 | if init.TransportURLSecret != "" { 96 | envTransport := corev1.EnvVar{ 97 | Name: "TransportURL", 98 | ValueFrom: &corev1.EnvVarSource{ 99 | SecretKeyRef: &corev1.SecretKeySelector{ 100 | LocalObjectReference: corev1.LocalObjectReference{ 101 | Name: init.TransportURLSecret, 102 | }, 103 | Key: "transport_url", 104 | }, 105 | }, 106 | } 107 | envs = append(envs, envTransport) 108 | } 109 | envs = env.MergeEnvs(envs, envVars) 110 | imageCopyEnvs := []corev1.EnvVar{ 111 | { 112 | Name: "DEST_DIR", 113 | Value: init.ImageDirectory, 114 | }, 115 | } 116 | 117 | var containers []corev1.Container 118 | 119 | initContainer := corev1.Container{ 120 | Name: "init", 121 | Image: init.ContainerImage, 122 | SecurityContext: &corev1.SecurityContext{ 123 | RunAsUser: &runAsUser, 124 | }, 125 | Command: []string{ 126 | "/bin/bash", 127 | }, 128 | Args: []string{ 129 | "-c", 130 | InitContainerCommand, 131 | }, 132 | Env: envs, 133 | VolumeMounts: init.VolumeMounts, 134 | } 135 | containers = append(containers, initContainer) 136 | 137 | if init.ConductorInit { 138 | ipaInit := corev1.Container{ 139 | Name: "ironic-python-agent-init", 140 | Image: init.IronicPythonAgentImage, 141 | SecurityContext: &corev1.SecurityContext{ 142 | Privileged: &init.Privileged, 143 | }, 144 | Env: imageCopyEnvs, 145 | VolumeMounts: init.VolumeMounts, 146 | } 147 | containers = append(containers, ipaInit) 148 | } 149 | 150 | if init.PxeInit { 151 | pxeInit := corev1.Container{ 152 | Name: "pxe-init", 153 | Image: init.PxeContainerImage, 154 | SecurityContext: &corev1.SecurityContext{ 155 | RunAsUser: &runAsUser, 156 | Privileged: &init.Privileged, 157 | }, 158 | Command: []string{ 159 | "/bin/bash", 160 | }, 161 | Args: []string{ 162 | "-c", 163 | PxeInitContainerCommand, 164 | }, 165 | Env: envs, 166 | VolumeMounts: init.VolumeMounts, 167 | } 168 | containers = append(containers, pxeInit) 169 | } 170 | 171 | return containers 172 | } 173 | -------------------------------------------------------------------------------- /pkg/ironic/volumes.go: -------------------------------------------------------------------------------- 1 | package ironic 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // GetVolumes - 8 | func GetVolumes(name string) []corev1.Volume { 9 | var scriptsVolumeDefaultMode int32 = 0755 10 | var config0640AccessMode int32 = 0640 11 | 12 | return []corev1.Volume{ 13 | { 14 | Name: "scripts", 15 | VolumeSource: corev1.VolumeSource{ 16 | Secret: &corev1.SecretVolumeSource{ 17 | DefaultMode: &scriptsVolumeDefaultMode, 18 | SecretName: name + "-scripts", 19 | }, 20 | }, 21 | }, 22 | { 23 | Name: "config-data", 24 | VolumeSource: corev1.VolumeSource{ 25 | Secret: &corev1.SecretVolumeSource{ 26 | DefaultMode: &config0640AccessMode, 27 | SecretName: name + "-config-data", 28 | }, 29 | }, 30 | }, 31 | { 32 | Name: "config-data-merged", 33 | VolumeSource: corev1.VolumeSource{ 34 | EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}, 35 | }, 36 | }, 37 | { 38 | Name: "etc-podinfo", 39 | VolumeSource: corev1.VolumeSource{ 40 | DownwardAPI: &corev1.DownwardAPIVolumeSource{ 41 | Items: []corev1.DownwardAPIVolumeFile{ 42 | { 43 | Path: "network-status", 44 | FieldRef: &corev1.ObjectFieldSelector{ 45 | FieldPath: "metadata.annotations['k8s.v1.cni.cncf.io/network-status']", 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | }, 52 | } 53 | 54 | } 55 | 56 | // GetInitVolumeMounts - Ironic init task VolumeMounts 57 | func GetInitVolumeMounts() []corev1.VolumeMount { 58 | return []corev1.VolumeMount{ 59 | { 60 | Name: "scripts", 61 | MountPath: "/usr/local/bin/container-scripts", 62 | ReadOnly: true, 63 | }, 64 | { 65 | Name: "config-data", 66 | MountPath: "/var/lib/config-data/default", 67 | ReadOnly: true, 68 | }, 69 | { 70 | Name: "config-data-merged", 71 | MountPath: "/var/lib/config-data/merged", 72 | ReadOnly: false, 73 | }, 74 | { 75 | Name: "etc-podinfo", 76 | MountPath: "/etc/podinfo", 77 | ReadOnly: false, 78 | }, 79 | } 80 | 81 | } 82 | 83 | // GetVolumeMounts - Ironic VolumeMounts 84 | func GetVolumeMounts() []corev1.VolumeMount { 85 | return []corev1.VolumeMount{ 86 | { 87 | Name: "scripts", 88 | MountPath: "/usr/local/bin/container-scripts", 89 | ReadOnly: true, 90 | }, 91 | { 92 | Name: "config-data-merged", 93 | MountPath: "/var/lib/config-data/merged", 94 | ReadOnly: false, 95 | }, 96 | { 97 | Name: "etc-podinfo", 98 | MountPath: "/etc/podinfo", 99 | ReadOnly: false, 100 | }, 101 | } 102 | } 103 | 104 | // GetDBSyncVolumeMounts - Ironic VolumeMounts 105 | func GetDBSyncVolumeMounts() []corev1.VolumeMount { 106 | 107 | volumeMounts := []corev1.VolumeMount{ 108 | { 109 | Name: "config-data", 110 | MountPath: "/var/lib/kolla/config_files/config.json", 111 | SubPath: "db-sync-config.json", 112 | ReadOnly: true, 113 | }, 114 | } 115 | 116 | return append(GetVolumeMounts(), volumeMounts...) 117 | } 118 | -------------------------------------------------------------------------------- /pkg/ironicapi/volumes.go: -------------------------------------------------------------------------------- 1 | package ironicapi 2 | 3 | import ( 4 | "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic" 5 | corev1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | // GetVolumes - 9 | func GetVolumes(name string) []corev1.Volume { 10 | var config0640AccessMode int32 = 0640 11 | 12 | apiVolumes := []corev1.Volume{ 13 | { 14 | Name: "config-data-custom", 15 | VolumeSource: corev1.VolumeSource{ 16 | Secret: &corev1.SecretVolumeSource{ 17 | DefaultMode: &config0640AccessMode, 18 | SecretName: name + "-config-data", 19 | }, 20 | }, 21 | }, 22 | { 23 | Name: "logs", 24 | VolumeSource: corev1.VolumeSource{ 25 | EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}, 26 | }, 27 | }, 28 | } 29 | 30 | return append(ironic.GetVolumes(name), apiVolumes...) 31 | } 32 | 33 | // GetLogVolumeMount - Ironic API LogVolumeMount 34 | func GetLogVolumeMount() corev1.VolumeMount { 35 | return corev1.VolumeMount{ 36 | Name: "logs", 37 | MountPath: "/var/log/ironic", 38 | ReadOnly: false, 39 | } 40 | } 41 | 42 | // GetInitVolumeMounts - Ironic API init task VolumeMounts 43 | func GetInitVolumeMounts() []corev1.VolumeMount { 44 | 45 | initVolumeMounts := []corev1.VolumeMount{ 46 | { 47 | Name: "config-data-custom", 48 | MountPath: "/var/lib/config-data/custom", 49 | ReadOnly: true, 50 | }, 51 | } 52 | 53 | return append(ironic.GetInitVolumeMounts(), initVolumeMounts...) 54 | } 55 | 56 | // GetVolumeMounts - Ironic API VolumeMounts 57 | func GetVolumeMounts() []corev1.VolumeMount { 58 | volumeMounts := []corev1.VolumeMount{ 59 | { 60 | Name: "config-data", 61 | MountPath: "/var/lib/kolla/config_files/config.json", 62 | SubPath: "ironic-api-config.json", 63 | ReadOnly: true, 64 | }, 65 | GetLogVolumeMount(), 66 | } 67 | 68 | return append(ironic.GetVolumeMounts(), volumeMounts...) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/ironicconductor/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironicconductor 17 | 18 | const ( 19 | // LogPath 20 | LogPath = "/dev/stdout" 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/ironicconductor/pod.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironicconductor 17 | 18 | import ( 19 | "context" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | k8s_labels "k8s.io/apimachinery/pkg/labels" 24 | 25 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 26 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 27 | ) 28 | 29 | // ConductorPods - Query current running ironic-conductor pods managed by the statefulset 30 | func ConductorPods( 31 | ctx context.Context, 32 | instance *ironicv1.IronicConductor, 33 | helper *helper.Helper, 34 | serviceLabels map[string]string, 35 | ) (*corev1.PodList, error) { 36 | podSelectorString := k8s_labels.Set(serviceLabels).String() 37 | return helper.GetKClient().CoreV1().Pods(instance.Namespace).List(ctx, metav1.ListOptions{LabelSelector: podSelectorString}) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/ironicconductor/service.go: -------------------------------------------------------------------------------- 1 | package ironicconductor 2 | 3 | import ( 4 | routev1 "github.com/openshift/api/route/v1" 5 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 6 | ironic "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | intstr "k8s.io/apimachinery/pkg/util/intstr" 10 | ) 11 | 12 | // Service - Service for conductor pod services 13 | func Service( 14 | serviceName string, 15 | instance *ironicv1.IronicConductor, 16 | serviceLabels map[string]string, 17 | ) *corev1.Service { 18 | 19 | var ports []corev1.ServicePort 20 | 21 | // RPC Transport is json-rpc so expose the service 22 | if instance.Spec.RPCTransport == "json-rpc" { 23 | jsonRPCPort := corev1.ServicePort{ 24 | Name: ironic.JSONRPCComponent, 25 | Port: 8089, 26 | Protocol: corev1.ProtocolTCP, 27 | } 28 | ports = append(ports, jsonRPCPort) 29 | } 30 | 31 | // There is no provision network so expose the deploy HTTP interface 32 | // as a service to enable virtual media boot 33 | if instance.Spec.ProvisionNetwork == "" { 34 | httpbootPort := corev1.ServicePort{ 35 | Name: ironic.HttpbootComponent, 36 | Port: 8088, 37 | Protocol: corev1.ProtocolTCP, 38 | } 39 | ports = append(ports, httpbootPort) 40 | } 41 | 42 | if len(ports) == 0 { 43 | return nil 44 | } 45 | return &corev1.Service{ 46 | ObjectMeta: metav1.ObjectMeta{ 47 | Name: serviceName, 48 | Namespace: instance.Namespace, 49 | Labels: serviceLabels, 50 | }, 51 | Spec: corev1.ServiceSpec{ 52 | Selector: serviceLabels, 53 | Ports: ports, 54 | }, 55 | } 56 | } 57 | 58 | // Route - Route for httpboot service when no provisioning network 59 | func Route( 60 | serviceName string, 61 | instance *ironicv1.IronicConductor, 62 | routeLabels map[string]string, 63 | ) *routev1.Route { 64 | serviceRef := routev1.RouteTargetReference{ 65 | Kind: "Service", 66 | Name: serviceName, 67 | } 68 | routePort := &routev1.RoutePort{ 69 | TargetPort: intstr.FromString(ironic.HttpbootComponent), 70 | } 71 | return &routev1.Route{ 72 | ObjectMeta: metav1.ObjectMeta{ 73 | Name: serviceName, 74 | Namespace: instance.Namespace, 75 | Labels: routeLabels, 76 | }, 77 | Spec: routev1.RouteSpec{ 78 | To: serviceRef, 79 | Port: routePort, 80 | }, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/ironicconductor/volumes.go: -------------------------------------------------------------------------------- 1 | package ironicconductor 2 | 3 | import ( 4 | "fmt" 5 | 6 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 7 | "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic" 8 | corev1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | // GetVolumes - 12 | func GetVolumes(instance *ironicv1.IronicConductor) []corev1.Volume { 13 | var config0640AccessMode int32 = 0640 14 | conductorVolumes := []corev1.Volume{ 15 | { 16 | Name: "config-data-custom", 17 | VolumeSource: corev1.VolumeSource{ 18 | Secret: &corev1.SecretVolumeSource{ 19 | DefaultMode: &config0640AccessMode, 20 | SecretName: fmt.Sprintf("%s-config-data", instance.Name), 21 | }, 22 | }, 23 | }, 24 | } 25 | 26 | return append(ironic.GetVolumes(instance.Name), conductorVolumes...) 27 | } 28 | 29 | // GetInitVolumeMounts - Ironic Conductor init task VolumeMounts 30 | func GetInitVolumeMounts() []corev1.VolumeMount { 31 | 32 | initVolumeMounts := []corev1.VolumeMount{ 33 | { 34 | Name: "config-data-custom", 35 | MountPath: "/var/lib/config-data/custom", 36 | ReadOnly: true, 37 | }, 38 | { 39 | Name: "var-lib-ironic", 40 | MountPath: "/var/lib/ironic", 41 | ReadOnly: false, 42 | }, 43 | } 44 | 45 | return append(ironic.GetInitVolumeMounts(), initVolumeMounts...) 46 | } 47 | 48 | // GetVolumeMounts - Ironic Conductor VolumeMounts 49 | func GetVolumeMounts(serviceName string) []corev1.VolumeMount { 50 | volumeMounts := []corev1.VolumeMount{ 51 | { 52 | Name: "config-data", 53 | MountPath: "/var/lib/kolla/config_files/config.json", 54 | SubPath: serviceName + "-config.json", 55 | ReadOnly: true, 56 | }, 57 | { 58 | Name: "var-lib-ironic", 59 | MountPath: "/var/lib/ironic", 60 | ReadOnly: false, 61 | }, 62 | } 63 | return append(ironic.GetVolumeMounts(), volumeMounts...) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/ironicinspector/const.go: -------------------------------------------------------------------------------- 1 | package ironicinspector 2 | 3 | const ( 4 | // DatabaseName - 5 | DatabaseName = "ironic_inspector" 6 | // DatabaseCRName - 7 | DatabaseCRName = "ironic-inspector" 8 | // IronicInspectorPublicPort - 9 | IronicInspectorPublicPort int32 = 5050 10 | // IronicInspectorInternalPort - 11 | IronicInspectorInternalPort int32 = 5050 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/ironicinspector/dbsync.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironicinspector 17 | 18 | import ( 19 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 20 | ironic "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic" 21 | 22 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 23 | batchv1 "k8s.io/api/batch/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | ) 27 | 28 | const ( 29 | // DBSyncCommand - 30 | DBSyncCommand = "/usr/local/bin/kolla_set_configs && /bin/bash -c 'ironic-inspector-dbsync --config-file /etc/ironic-inspector/inspector.conf --config-dir /etc/ironic-inspector/inspector.conf.d upgrade'" 31 | ) 32 | 33 | // DbSyncJob func 34 | func DbSyncJob( 35 | instance *ironicv1.IronicInspector, 36 | labels map[string]string, 37 | ) *batchv1.Job { 38 | runAsUser := int64(0) 39 | 40 | args := []string{"-c", DBSyncCommand} 41 | 42 | envVars := map[string]env.Setter{} 43 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 44 | envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") 45 | 46 | volumes := GetVolumes(ironic.ServiceName + "-" + ironic.InspectorComponent) 47 | volumeMounts := GetVolumeMounts("db-sync") 48 | initVolumeMounts := GetInitVolumeMounts() 49 | 50 | // add CA cert if defined 51 | if instance.Spec.TLS.Ca.CaBundleSecretName != "" { 52 | volumes = append(volumes, instance.Spec.TLS.CreateVolume()) 53 | volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) 54 | initVolumeMounts = append(initVolumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) 55 | } 56 | 57 | job := &batchv1.Job{ 58 | ObjectMeta: metav1.ObjectMeta{ 59 | Name: ironic.ServiceName + "-" + ironic.InspectorComponent + "-db-sync", 60 | Namespace: instance.Namespace, 61 | Labels: labels, 62 | }, 63 | Spec: batchv1.JobSpec{ 64 | Template: corev1.PodTemplateSpec{ 65 | Spec: corev1.PodSpec{ 66 | RestartPolicy: corev1.RestartPolicyOnFailure, 67 | ServiceAccountName: instance.RbacResourceName(), 68 | Containers: []corev1.Container{ 69 | { 70 | Name: ironic.ServiceName + "-" + ironic.InspectorComponent + "-db-sync", 71 | Command: []string{ 72 | "/bin/bash", 73 | }, 74 | Args: args, 75 | Image: instance.Spec.ContainerImage, 76 | SecurityContext: &corev1.SecurityContext{ 77 | RunAsUser: &runAsUser, 78 | }, 79 | Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), 80 | VolumeMounts: volumeMounts, 81 | }, 82 | }, 83 | Volumes: volumes, 84 | }, 85 | }, 86 | }, 87 | } 88 | 89 | initContainerDetails := APIDetails{ 90 | ContainerImage: instance.Spec.ContainerImage, 91 | DatabaseHost: instance.Status.DatabaseHostname, 92 | DatabaseName: DatabaseName, 93 | OSPSecret: instance.Spec.Secret, 94 | UserPasswordSelector: instance.Spec.PasswordSelectors.Service, 95 | VolumeMounts: initVolumeMounts, 96 | } 97 | job.Spec.Template.Spec.InitContainers = InitContainer(initContainerDetails) 98 | 99 | if instance.Spec.NodeSelector != nil { 100 | job.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 101 | } 102 | 103 | return job 104 | } 105 | -------------------------------------------------------------------------------- /pkg/ironicinspector/initcontainer.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironicinspector 17 | 18 | import ( 19 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | ) 23 | 24 | // APIDetails information 25 | type APIDetails struct { 26 | ContainerImage string 27 | PxeInit bool 28 | PxeContainerImage string 29 | IpaInit bool 30 | IronicPythonAgentImage string 31 | DatabaseHost string 32 | DatabaseName string 33 | TransportURLSecret string 34 | OSPSecret string 35 | UserPasswordSelector string 36 | VolumeMounts []corev1.VolumeMount 37 | Privileged bool 38 | InspectorHTTPURL string 39 | IngressDomain string 40 | InspectionNetwork string 41 | ImageDirectory string 42 | } 43 | 44 | const ( 45 | // PxeInitContainerCommand - 46 | PxeInitContainerCommand = "/usr/local/bin/container-scripts/inspector-pxe-init.sh" 47 | 48 | InitCreateDirectoriesCommand = `mkdir -p /var/lib/ironic/httpboot /var/lib/ironic/ramdisk-logs` 49 | ) 50 | 51 | // InitContainer - init container for Ironic Inspector pods 52 | func InitContainer(init APIDetails) []corev1.Container { 53 | runAsUser := int64(0) 54 | 55 | envVars := map[string]env.Setter{} 56 | envVars["DatabaseHost"] = env.SetValue(init.DatabaseHost) 57 | envVars["DatabaseName"] = env.SetValue(init.DatabaseName) 58 | envVars["InspectorHTTPURL"] = env.SetValue(init.InspectorHTTPURL) 59 | envVars["IngressDomain"] = env.SetValue(init.IngressDomain) 60 | envVars["InspectionNetwork"] = env.SetValue(init.InspectionNetwork) 61 | 62 | envs := []corev1.EnvVar{ 63 | { 64 | Name: "IronicInspectorPassword", 65 | ValueFrom: &corev1.EnvVarSource{ 66 | SecretKeyRef: &corev1.SecretKeySelector{ 67 | LocalObjectReference: corev1.LocalObjectReference{ 68 | Name: init.OSPSecret, 69 | }, 70 | Key: init.UserPasswordSelector, 71 | }, 72 | }, 73 | }, 74 | { 75 | Name: "PodName", 76 | ValueFrom: &corev1.EnvVarSource{ 77 | FieldRef: &corev1.ObjectFieldSelector{ 78 | FieldPath: "metadata.name", 79 | }, 80 | }, 81 | }, 82 | { 83 | Name: "PodNamespace", 84 | ValueFrom: &corev1.EnvVarSource{ 85 | FieldRef: &corev1.ObjectFieldSelector{ 86 | FieldPath: "metadata.namespace", 87 | }, 88 | }, 89 | }, 90 | } 91 | if init.TransportURLSecret != "" { 92 | envTransport := corev1.EnvVar{ 93 | Name: "TransportURL", 94 | ValueFrom: &corev1.EnvVarSource{ 95 | SecretKeyRef: &corev1.SecretKeySelector{ 96 | LocalObjectReference: corev1.LocalObjectReference{ 97 | Name: init.TransportURLSecret, 98 | }, 99 | Key: "transport_url", 100 | }, 101 | }, 102 | } 103 | envs = append(envs, envTransport) 104 | } 105 | envs = env.MergeEnvs(envs, envVars) 106 | imageCopyEnvs := []corev1.EnvVar{ 107 | { 108 | Name: "DEST_DIR", 109 | Value: init.ImageDirectory, 110 | }, 111 | } 112 | 113 | containers := []corev1.Container{} 114 | 115 | if init.IpaInit { 116 | ipaInit := corev1.Container{ 117 | Name: "ironic-python-agent-init", 118 | Image: init.IronicPythonAgentImage, 119 | SecurityContext: &corev1.SecurityContext{ 120 | Privileged: &init.Privileged, 121 | }, 122 | Command: []string{ 123 | "/bin/bash", 124 | }, 125 | Args: []string{"-c", InitCreateDirectoriesCommand}, 126 | Env: imageCopyEnvs, 127 | VolumeMounts: init.VolumeMounts, 128 | } 129 | containers = append(containers, ipaInit) 130 | } 131 | 132 | if init.PxeInit { 133 | pxeInit := corev1.Container{ 134 | Name: "inspector-pxe-init", 135 | Image: init.PxeContainerImage, 136 | SecurityContext: &corev1.SecurityContext{ 137 | RunAsUser: &runAsUser, 138 | Capabilities: &corev1.Capabilities{ 139 | Add: []corev1.Capability{ 140 | "SYS_CHROOT", 141 | "SETFCAP", 142 | }, 143 | }, 144 | }, 145 | Command: []string{ 146 | "/bin/bash", 147 | }, 148 | Args: []string{"-c", PxeInitContainerCommand}, 149 | Env: envs, 150 | VolumeMounts: init.VolumeMounts, 151 | } 152 | containers = append(containers, pxeInit) 153 | } 154 | 155 | return containers 156 | } 157 | -------------------------------------------------------------------------------- /pkg/ironicinspector/pod.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package ironicinspector 17 | 18 | import ( 19 | "context" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | k8s_labels "k8s.io/apimachinery/pkg/labels" 24 | 25 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 26 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 27 | ) 28 | 29 | // InspectorPods - Query current running ironic-inspector pods managed by the statefulset 30 | func InspectorPods( 31 | ctx context.Context, 32 | instance *ironicv1.IronicInspector, 33 | helper *helper.Helper, 34 | serviceLabels map[string]string, 35 | ) (*corev1.PodList, error) { 36 | podSelectorString := k8s_labels.Set(serviceLabels).String() 37 | return helper.GetKClient().CoreV1().Pods(instance.Namespace).List(ctx, metav1.ListOptions{LabelSelector: podSelectorString}) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/ironicinspector/service.go: -------------------------------------------------------------------------------- 1 | package ironicinspector 2 | 3 | import ( 4 | routev1 "github.com/openshift/api/route/v1" 5 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 6 | ironic "github.com/openstack-k8s-operators/ironic-operator/pkg/ironic" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | intstr "k8s.io/apimachinery/pkg/util/intstr" 10 | ) 11 | 12 | // Service - Service for conductor pod services 13 | func Service( 14 | serviceName string, 15 | instance *ironicv1.IronicInspector, 16 | serviceLabels map[string]string, 17 | ) *corev1.Service { 18 | 19 | var ports []corev1.ServicePort 20 | 21 | // There is no provision network so expose the deploy HTTP interface 22 | // as a service to enable virtual media boot 23 | if instance.Spec.InspectionNetwork == "" { 24 | httpbootPort := corev1.ServicePort{ 25 | Name: ironic.HttpbootComponent, 26 | Port: 8088, 27 | Protocol: corev1.ProtocolTCP, 28 | } 29 | ports = append(ports, httpbootPort) 30 | } 31 | 32 | if len(ports) == 0 { 33 | return nil 34 | } 35 | return &corev1.Service{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Name: serviceName, 38 | Namespace: instance.Namespace, 39 | Labels: serviceLabels, 40 | }, 41 | Spec: corev1.ServiceSpec{ 42 | Selector: serviceLabels, 43 | Ports: ports, 44 | }, 45 | } 46 | } 47 | 48 | // Route - Route for httpboot service when no inspection network 49 | func Route( 50 | serviceName string, 51 | instance *ironicv1.IronicInspector, 52 | routeLabels map[string]string, 53 | ) *routev1.Route { 54 | serviceRef := routev1.RouteTargetReference{ 55 | Kind: "Service", 56 | Name: serviceName, 57 | } 58 | routePort := &routev1.RoutePort{ 59 | TargetPort: intstr.FromString(ironic.HttpbootComponent), 60 | } 61 | return &routev1.Route{ 62 | ObjectMeta: metav1.ObjectMeta{ 63 | Name: serviceName, 64 | Namespace: instance.Namespace, 65 | Labels: routeLabels, 66 | }, 67 | Spec: routev1.RouteSpec{ 68 | To: serviceRef, 69 | Port: routePort, 70 | }, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/ironicinspector/volumes.go: -------------------------------------------------------------------------------- 1 | package ironicinspector 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | ) 6 | 7 | // GetVolumes - 8 | func GetVolumes(name string) []corev1.Volume { 9 | var scriptsVolumeDefaultMode int32 = 0755 10 | var config0640AccessMode int32 = 0640 11 | 12 | return []corev1.Volume{ 13 | { 14 | Name: "scripts", 15 | VolumeSource: corev1.VolumeSource{ 16 | Secret: &corev1.SecretVolumeSource{ 17 | DefaultMode: &scriptsVolumeDefaultMode, 18 | SecretName: name + "-scripts", 19 | }, 20 | }, 21 | }, 22 | { 23 | Name: "config", 24 | VolumeSource: corev1.VolumeSource{ 25 | Secret: &corev1.SecretVolumeSource{ 26 | DefaultMode: &config0640AccessMode, 27 | SecretName: name + "-config-data", 28 | }, 29 | }, 30 | }, 31 | { 32 | Name: "var-lib-ironic", 33 | VolumeSource: corev1.VolumeSource{ 34 | EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}, 35 | }, 36 | }, 37 | { 38 | Name: "var-lib-ironic-inspector-dhcp-hostsdir", 39 | VolumeSource: corev1.VolumeSource{ 40 | EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}, 41 | }, 42 | }, 43 | { 44 | Name: "etc-podinfo", 45 | VolumeSource: corev1.VolumeSource{ 46 | DownwardAPI: &corev1.DownwardAPIVolumeSource{ 47 | Items: []corev1.DownwardAPIVolumeFile{ 48 | { 49 | Path: "network-status", 50 | FieldRef: &corev1.ObjectFieldSelector{ 51 | FieldPath: "metadata.annotations['k8s.v1.cni.cncf.io/network-status']", 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | } 59 | 60 | } 61 | 62 | // GetInitVolumeMounts - Ironic Inspector init task VolumeMounts 63 | func GetInitVolumeMounts() []corev1.VolumeMount { 64 | 65 | return []corev1.VolumeMount{ 66 | { 67 | Name: "scripts", 68 | MountPath: "/usr/local/bin/container-scripts", 69 | ReadOnly: true, 70 | }, 71 | { 72 | Name: "config", 73 | MountPath: "/var/lib/config-data/default", 74 | ReadOnly: true, 75 | }, 76 | { 77 | Name: "var-lib-ironic", 78 | MountPath: "/var/lib/ironic", 79 | ReadOnly: false, 80 | }, 81 | { 82 | Name: "etc-podinfo", 83 | MountPath: "/etc/podinfo", 84 | ReadOnly: false, 85 | }, 86 | } 87 | 88 | } 89 | 90 | // GetVolumeMounts - Common VolumeMounts 91 | func GetVolumeMounts(serviceName string) []corev1.VolumeMount { 92 | return []corev1.VolumeMount{ 93 | { 94 | Name: "scripts", 95 | MountPath: "/usr/local/bin/container-scripts", 96 | ReadOnly: true, 97 | }, 98 | { 99 | Name: "config", 100 | MountPath: "/var/lib/config-data/default", 101 | ReadOnly: true, 102 | }, 103 | { 104 | Name: "config", 105 | MountPath: "/var/lib/kolla/config_files/config.json", 106 | SubPath: serviceName + "-config.json", 107 | ReadOnly: true, 108 | }, 109 | { 110 | Name: "var-lib-ironic", 111 | MountPath: "/var/lib/ironic", 112 | ReadOnly: false, 113 | }, 114 | { 115 | Name: "var-lib-ironic-inspector-dhcp-hostsdir", 116 | MountPath: "/var/lib/ironic-inspector/dhcp-hostsdir", 117 | ReadOnly: false, 118 | }, 119 | { 120 | Name: "etc-podinfo", 121 | MountPath: "/etc/podinfo", 122 | ReadOnly: false, 123 | }, 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /pkg/ironicneutronagent/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ironicneutronagent 18 | 19 | const ( 20 | // ServiceName - 21 | ServiceName = "ironic-neutron-agent" 22 | 23 | // ServiceType - 24 | ServiceType = "baremetal" 25 | 26 | // ServiceCommand - 27 | ServiceCommand = "/usr/local/bin/kolla_set_configs && /usr/local/bin/kolla_start" 28 | ) 29 | -------------------------------------------------------------------------------- /pkg/ironicneutronagent/deployment.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ironicneutronagent 18 | 19 | import ( 20 | topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" 21 | ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" 22 | common "github.com/openstack-k8s-operators/lib-common/modules/common" 23 | affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" 24 | env "github.com/openstack-k8s-operators/lib-common/modules/common/env" 25 | 26 | appsv1 "k8s.io/api/apps/v1" 27 | corev1 "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | ) 30 | 31 | // Deployment func 32 | func Deployment( 33 | instance *ironicv1.IronicNeutronAgent, 34 | configHash string, 35 | labels map[string]string, 36 | topology *topologyv1.Topology, 37 | ) *appsv1.Deployment { 38 | runAsUser := int64(0) 39 | 40 | livenessProbe := &corev1.Probe{ 41 | TimeoutSeconds: 5, 42 | PeriodSeconds: 5, 43 | InitialDelaySeconds: 3, 44 | } 45 | readinessProbe := &corev1.Probe{ 46 | TimeoutSeconds: 5, 47 | PeriodSeconds: 5, 48 | InitialDelaySeconds: 3, 49 | } 50 | 51 | args := []string{"-c", ServiceCommand} 52 | // https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ 53 | livenessProbe.Exec = &corev1.ExecAction{ 54 | Command: []string{ 55 | "/bin/true", 56 | }, 57 | } 58 | readinessProbe.Exec = &corev1.ExecAction{ 59 | Command: []string{ 60 | "/bin/true", 61 | }, 62 | } 63 | 64 | envVars := map[string]env.Setter{} 65 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 66 | envVars["CONFIG_HASH"] = env.SetValue(configHash) 67 | 68 | volumes := GetVolumes(instance.Name) 69 | volumeMounts := GetVolumeMounts() 70 | 71 | // Add the CA bundle 72 | if instance.Spec.TLS.CaBundleSecretName != "" { 73 | volumes = append(volumes, instance.Spec.TLS.CreateVolume()) 74 | volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) 75 | } 76 | 77 | // Default oslo.service graceful_shutdown_timeout is 60, so align with that 78 | terminationGracePeriod := int64(60) 79 | 80 | deployment := &appsv1.Deployment{ 81 | ObjectMeta: metav1.ObjectMeta{ 82 | Name: ServiceName, 83 | Namespace: instance.Namespace, 84 | }, 85 | Spec: appsv1.DeploymentSpec{ 86 | Selector: &metav1.LabelSelector{ 87 | MatchLabels: labels, 88 | }, 89 | Replicas: instance.Spec.Replicas, 90 | Template: corev1.PodTemplateSpec{ 91 | ObjectMeta: metav1.ObjectMeta{ 92 | Labels: labels, 93 | }, 94 | Spec: corev1.PodSpec{ 95 | ServiceAccountName: instance.RbacResourceName(), 96 | Containers: []corev1.Container{ 97 | { 98 | Name: ServiceName, 99 | Command: []string{ 100 | "/bin/bash", 101 | }, 102 | Args: args, 103 | Image: instance.Spec.ContainerImage, 104 | SecurityContext: &corev1.SecurityContext{ 105 | RunAsUser: &runAsUser, 106 | }, 107 | Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), 108 | VolumeMounts: volumeMounts, 109 | Resources: instance.Spec.Resources, 110 | ReadinessProbe: readinessProbe, 111 | LivenessProbe: livenessProbe, 112 | }, 113 | }, 114 | TerminationGracePeriodSeconds: &terminationGracePeriod, 115 | Volumes: volumes, 116 | }, 117 | }, 118 | }, 119 | } 120 | if instance.Spec.NodeSelector != nil { 121 | deployment.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 122 | } 123 | if topology != nil { 124 | topology.ApplyTo(&deployment.Spec.Template) 125 | } else { 126 | // If possible two pods of the same service should not 127 | // run on the same worker node. If this is not possible 128 | // the get still created on the same worker node. 129 | deployment.Spec.Template.Spec.Affinity = affinity.DistributePods( 130 | common.AppSelector, 131 | []string{ 132 | ServiceName, 133 | }, 134 | corev1.LabelHostname, 135 | ) 136 | } 137 | 138 | return deployment 139 | } 140 | -------------------------------------------------------------------------------- /pkg/ironicneutronagent/volumes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Red Hat Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ironicneutronagent 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | ) 22 | 23 | // GetVolumes - 24 | func GetVolumes(name string) []corev1.Volume { 25 | var config0640AccessMode int32 = 0640 26 | 27 | return []corev1.Volume{ 28 | { 29 | Name: "config", 30 | VolumeSource: corev1.VolumeSource{ 31 | Secret: &corev1.SecretVolumeSource{ 32 | DefaultMode: &config0640AccessMode, 33 | SecretName: name + "-config-data", 34 | }, 35 | }, 36 | }, 37 | } 38 | 39 | } 40 | 41 | // GetVolumeMounts - IronicNeutronAgent VolumeMounts 42 | func GetVolumeMounts() []corev1.VolumeMount { 43 | return []corev1.VolumeMount{ 44 | { 45 | Name: "config", 46 | MountPath: "/var/lib/config-data/default", 47 | ReadOnly: true, 48 | }, 49 | { 50 | Name: "config", 51 | MountPath: "/var/lib/kolla/config_files/config.json", 52 | SubPath: "ironic-neutron-agent-config.json", 53 | ReadOnly: true, 54 | }, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>openstack-k8s-operators/renovate-config:default.json5" 4 | ], 5 | "baseBranches": ["main"], 6 | "useBaseBranchConfig": "merge", 7 | "packageRules": [ 8 | { 9 | "matchPackageNames": ["github.com/openstack-k8s-operators/ironic-operator/api"], 10 | "enabled": false 11 | } 12 | ], 13 | "postUpgradeTasks": { 14 | "commands": ["make gowork", "make tidy", "make manifests generate"], 15 | "fileFilters": ["**/go.mod", "**/go.sum", "**/*.go", "**/*.yaml"], 16 | "executionMode": "update" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/common/bin/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | set -e 18 | 19 | function merge_config_dir { 20 | echo merge config dir $1 21 | for conf in $(find $1 -type f); do 22 | conf_base=$(basename $conf) 23 | 24 | # If CFG already exist in ../merged and is not a json or ipxe file, 25 | # we expect for now it can be merged using crudini. 26 | # Else, just copy the full file. 27 | if [[ -f /var/lib/config-data/merged/${conf_base} && ${conf_base} != *.json && ${conf_base} != *.ipxe ]]; then 28 | echo merging ${conf} into /var/lib/config-data/merged/${conf_base} 29 | crudini --merge /var/lib/config-data/merged/${conf_base} < ${conf} 30 | else 31 | echo copy ${conf} to /var/lib/config-data/merged/ 32 | cp -f ${conf} /var/lib/config-data/merged/ 33 | fi 34 | done 35 | } 36 | 37 | function common_ironic_config { 38 | # Secrets are obtained from ENV variables. 39 | export IRONICPASSWORD=${IronicPassword:?"Please specify a IronicPassword variable."} 40 | export TRANSPORTURL=${TransportURL:-""} 41 | # TODO: nova password 42 | #export NOVAPASSWORD=${NovaPassword:?"Please specify a NovaPassword variable."} 43 | 44 | export CUSTOMCONF=${CustomConf:-""} 45 | 46 | SVC_CFG=/etc/ironic/ironic.conf 47 | SVC_CFG_MERGED=/var/lib/config-data/merged/ironic.conf 48 | 49 | # Copy default service config from container image as base 50 | cp -a ${SVC_CFG} ${SVC_CFG_MERGED} 51 | 52 | # Merge all templates from config-data defaults first, then custom 53 | # NOTE: custom.conf files (for both the umbrella Ironic CR in config-data/defaults 54 | # and each custom.conf for each sub-service in config-data/custom) still need 55 | # to be handled separately below because the "merge_config_dir" function will 56 | # not merge custom.conf into ironic.conf (because the files obviously have 57 | # different names) 58 | for dir in /var/lib/config-data/default /var/lib/config-data/custom; do 59 | merge_config_dir ${dir} 60 | done 61 | 62 | # TODO: a cleaner way to handle this? 63 | # Merge custom.conf with ironic.conf, since the Kolla config doesn't seem 64 | # to allow us to customize the ironic command (it calls httpd instead). 65 | # Can we just put custom.conf in something like /etc/ironic/ironic.conf.d/custom.conf 66 | # and have it automatically detected, or would we have to somehow change the call 67 | # to the ironic binary to tell it to use that custom conf dir? 68 | echo merging /var/lib/config-data/default/custom.conf into ${SVC_CFG_MERGED} 69 | crudini --merge ${SVC_CFG_MERGED} < /var/lib/config-data/default/custom.conf 70 | 71 | # TODO: a cleaner way to handle this? 72 | # There might be service-specific extra custom conf that needs to be merged 73 | # with the main ironic.conf for this particular service 74 | if [ -n "$CUSTOMCONF" ]; then 75 | echo merging /var/lib/config-data/custom/${CUSTOMCONF} into ${SVC_CFG_MERGED} 76 | crudini --merge ${SVC_CFG_MERGED} < /var/lib/config-data/custom/${CUSTOMCONF} 77 | fi 78 | 79 | # set secrets 80 | # Only set rpc_transport and transport_url if $TRANSPORTURL 81 | if [ -n "$TRANSPORTURL" ]; then 82 | crudini --set ${SVC_CFG_MERGED} DEFAULT transport_url $TRANSPORTURL 83 | crudini --set ${SVC_CFG_MERGED} DEFAULT rpc_transport oslo 84 | fi 85 | crudini --set ${SVC_CFG_MERGED} keystone_authtoken password $IRONICPASSWORD 86 | crudini --set ${SVC_CFG_MERGED} service_catalog password $IRONICPASSWORD 87 | crudini --set ${SVC_CFG_MERGED} cinder password $IRONICPASSWORD 88 | crudini --set ${SVC_CFG_MERGED} glance password $IRONICPASSWORD 89 | crudini --set ${SVC_CFG_MERGED} neutron password $IRONICPASSWORD 90 | crudini --set ${SVC_CFG_MERGED} nova password $IRONICPASSWORD 91 | crudini --set ${SVC_CFG_MERGED} swift password $IRONICPASSWORD 92 | crudini --set ${SVC_CFG_MERGED} inspector password $IRONICPASSWORD 93 | } 94 | -------------------------------------------------------------------------------- /templates/common/bin/get_net_ip: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # 3 | # Copyright 2023 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | import json 19 | import sys 20 | 21 | from tenacity import Retrying, RetryError, stop_after_attempt, wait_exponential 22 | 23 | # Uses network status from pod: 24 | # metadata.annotations['k8s.v1.cni.cncf.io/network-status'] 25 | # 26 | # Example network status json string: 27 | # 28 | # [ 29 | # { "name": "openshift-sdn", 30 | # "interface": "eth0", 31 | # "ips": [ "10.131.0.76" ], 32 | # "default": true, "dns": {} 33 | # }, 34 | # { "name": "openstack/baremetal-net", 35 | # "interface": "net1", 36 | # "ips": [ "192.168.24.1" ], 37 | # "mac": "5e:ff:15:d9:66:84", 38 | # "dns": {} 39 | # } 40 | # ] 41 | # 42 | 43 | def get_ip_from_network_status(): 44 | namespace = os.environ.get('PodNamespace') 45 | network_status_content = None 46 | try: 47 | for attempt in Retrying( 48 | stop=stop_after_attempt(5), 49 | wait=wait_exponential(multiplier=1, min=2, max=10)): 50 | with attempt: 51 | with open('/etc/podinfo/network-status') as f: 52 | network_status_content = f.read() 53 | net_status = json.loads(network_status_content) 54 | except RetryError: 55 | raise Exception( 56 | f"Unable to load pod network status - /etc/podinfo/network-status contains: " 57 | f"{network_status_content}") 58 | 59 | net_attachment_name = sys.argv[1] 60 | 61 | for net in net_status: 62 | if net.get('name') == '/'.join([namespace, net_attachment_name]): 63 | ips = net.get('ips', []) 64 | if len(ips) > 0: 65 | print(ips[0]) 66 | return 67 | raise Exception( 68 | f"Network {net_attachment_name} does not have any " 69 | "IP address.") 70 | 71 | raise Exception( 72 | f"Network {net_attachment_name} not in pod network_status: " 73 | f"{net_status}") 74 | 75 | if __name__ == '__main__': 76 | get_ip_from_network_status() 77 | -------------------------------------------------------------------------------- /templates/common/bin/ironic-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2023 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | # expect that the common.sh is in the same dir as the calling script 19 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 20 | . ${SCRIPTPATH}/common.sh --source-only 21 | 22 | common_ironic_config 23 | -------------------------------------------------------------------------------- /templates/common/bin/pxe-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | 19 | # Create TFTP, HTTP serving directories 20 | if [ ! -d "/var/lib/ironic/tftpboot/pxelinux.cfg" ]; then 21 | mkdir -p /var/lib/ironic/tftpboot/pxelinux.cfg 22 | fi 23 | if [ ! -d "/var/lib/ironic/httpboot" ]; then 24 | mkdir -p /var/lib/ironic/httpboot 25 | fi 26 | # Check for expected EFI directories 27 | if [ -d "/boot/efi/EFI/centos" ]; then 28 | efi_dir=centos 29 | elif [ -d "/boot/efi/EFI/redhat" ]; then 30 | efi_dir=redhat 31 | else 32 | echo "No EFI directory detected" 33 | exit 1 34 | fi 35 | 36 | # Copy iPXE and grub files to tftpboot, httpboot 37 | for dir in httpboot tftpboot; do 38 | cp /usr/share/ipxe/ipxe-snponly-x86_64.efi /var/lib/ironic/$dir/snponly.efi 39 | cp /usr/share/ipxe/undionly.kpxe /var/lib/ironic/$dir/undionly.kpxe 40 | cp /usr/share/ipxe/ipxe.lkrn /var/lib/ironic/$dir/ipxe.lkrn 41 | cp /boot/efi/EFI/$efi_dir/shimx64.efi /var/lib/ironic/$dir/bootx64.efi 42 | cp /boot/efi/EFI/$efi_dir/grubx64.efi /var/lib/ironic/$dir/grubx64.efi 43 | # Ensure all files are readable 44 | chmod -R +r /var/lib/ironic/$dir 45 | done 46 | 47 | # Patch ironic-python-agent with custom CA certificates 48 | if [ -f "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" ] && [ -f "/var/lib/ironic/httpboot/ironic-python-agent.initramfs" ]; then 49 | # Extract the initramfs 50 | cd / 51 | mkdir initramfs 52 | pushd initramfs 53 | zcat /var/lib/ironic/httpboot/ironic-python-agent.initramfs | cpio -idmV 54 | popd 55 | 56 | # Copy the CA certificates 57 | cp /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /initramfs/etc/pki/ca-trust/source/anchors/ 58 | echo update-ca-trust | unshare -r chroot ./initramfs 59 | 60 | # Repack the initramfs 61 | pushd initramfs 62 | find . | cpio -o -c --quiet -R root:root | gzip -1 > /var/lib/ironic/httpboot/ironic-python-agent.initramfs 63 | fi 64 | 65 | # Build an ESP image 66 | pushd /var/lib/ironic/httpboot 67 | if ! command -v dd || ! command -v mkfs.msdos || ! command -v mmd; then 68 | echo "WARNING: esp.img will not be created because dd/mkfs.msdos/mmd are missing. Please patch the OpenstackVersion to update container images." 69 | elif [ ! -a "esp.img" ]; then 70 | dd if=/dev/zero of=esp.img bs=4096 count=1024 71 | mkfs.msdos -F 12 -n 'ESP_IMAGE' esp.img 72 | 73 | mmd -i esp.img EFI 74 | mmd -i esp.img EFI/BOOT 75 | mcopy -i esp.img -v bootx64.efi ::EFI/BOOT 76 | mcopy -i esp.img -v grubx64.efi ::EFI/BOOT 77 | mdir -i esp.img ::EFI/BOOT; 78 | fi 79 | popd 80 | -------------------------------------------------------------------------------- /templates/common/bin/runlogwatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ramdisk logs path 4 | LOG_DIR=${LOG_DIR:-/var/lib/ironic/ramdisk-logs} 5 | 6 | inotifywait -m "${LOG_DIR}" -e close_write | 7 | while read -r path _action file; do 8 | echo "************ Contents of ${path}${file} ramdisk log file bundle **************" 9 | tar -xOzvvf "${path}${file}" | sed -e "s/^/${file}: /" 10 | rm -f "${path}/${file}" 11 | done 12 | -------------------------------------------------------------------------------- /templates/common/config/dnsmasq.conf: -------------------------------------------------------------------------------- 1 | # Disable listening for DNS 2 | port=0 3 | enable-tftp 4 | tftp-root=/var/lib/ironic/tftpboot 5 | 6 | log-dhcp 7 | 8 | {{ range .DHCPRanges }} 9 | {{- /* 10 | The following sed can be used to filter configuration by PodIndex: 11 | sed -e '/BLOCK_PODINDEX_${PODINDEX}_BEGIN/,/BLOCK_PODINDEX_${PODINDEX}_END/p' \ 12 | -e '/BLOCK_PODINDEX_.*_BEGIN/,/BLOCK_PODINDEX_.*_END/d' \ 13 | -i ${DNSMASQ_CFG} 14 | */ -}} 15 | ### BLOCK_PODINDEX_{{ .PodIndex }}_BEGIN ### 16 | # DHCP Range {{ .Start }} - {{ .End }} 17 | dhcp-range={{ if .Name }}set:{{ .Name }},{{ end }}{{ .Start }},{{ .End }},{{ if .Prefix }}{{ .Prefix }},{{ else if .Netmask }}{{ .Netmask }},{{ end }}10m 18 | {{ if .Gateway -}} 19 | dhcp-option={{ if .Name }}tag:{{ .Name }},{{ end }}option:router,{{ .Gateway }} 20 | {{ else -}} 21 | # Disable router on over provision network 22 | dhcp-option={{ if .Name }}tag:{{ .Name }},{{ end }}3 23 | {{ end -}} 24 | {{ if .MTU -}} 25 | # Set the MTU option 26 | dhcp-option-force={{ if .Name }}tag:{{ .Name }},{{ end }}option:mtu,{{ .MTU }} 27 | {{ end -}} 28 | ### BLOCK_PODINDEX_{{ .PodIndex }}_END ### 29 | 30 | {{ end }} 31 | 32 | 33 | # Disable DNS over provisioning network 34 | dhcp-option=6 35 | 36 | conf-dir=/etc/dnsmasq.d 37 | # dhcp-optsdir=/etc/dnsmasq.d/optsdir.d 38 | # dhcp-hostsdir=/etc/dnsmasq.d/hostsdir.d 39 | log-facility=- 40 | user=root 41 | 42 | # Boot for Etherboot gPXE. The idea is to send two different 43 | # filenames, the first loads gPXE, and the second tells gPXE what to 44 | # load. The dhcp-match sets the gpxe tag for requests from gPXE. 45 | dhcp-userclass=set:gpxe,"gPXE" 46 | dhcp-boot=tag:gpxe,/ipxe.pxe 47 | 48 | dhcp-match=set:ipxe,175 # iPXE sends a 175 option. 49 | dhcp-match=set:efi,option:client-arch,7 50 | dhcp-match=set:efi,option:client-arch,9 51 | dhcp-match=set:efi,option:client-arch,11 52 | -------------------------------------------------------------------------------- /templates/ironic/bin/dbsync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2023 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | /usr/local/bin/kolla_set_configs 19 | 20 | # prepare for 'upgrade check' loading all drivers 21 | if [ ! -d "/var/lib/ironic/tmp" ]; then 22 | mkdir /var/lib/ironic/tmp 23 | fi 24 | if [ ! -d "/var/lib/ironic/httpboot" ]; then 25 | mkdir -p /var/lib/ironic/httpboot 26 | fi 27 | 28 | ironic-status upgrade check && ret_val=$? || ret_val=$? 29 | if [ $ret_val -gt 1 ] ; then 30 | # NOTE(TheJulia): We need to evaluate the return code from the 31 | # upgrade status check as the framework defines 32 | # Warnings are permissible and returned as status code 1, errors are 33 | # returned as greater than 1 which means there is a major upgrade 34 | # stopping issue which needs to be addressed. 35 | echo "WARNING: Status check failed, we're going to attempt to apply the schema update and then re-evaluate." 36 | ironic-dbsync --config-file=/etc/ironic/ironic.conf upgrade 37 | ironic-status upgrade check && ret_val=$? || ret_val=$? 38 | if [ $ret_val -gt 1 ] ; then 39 | echo $LINENO "Ironic DB Status check failed, returned: $ret_val" 40 | exit $ret_val 41 | fi 42 | fi 43 | ironic-dbsync --config-file /etc/ironic/ironic.conf 44 | 45 | ironic-dbsync --config-file /etc/ironic/ironic.conf online_data_migrations 46 | -------------------------------------------------------------------------------- /templates/ironic/config/db-sync-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/local/bin/container-scripts/bootstrap.sh", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/merged/ironic.conf", 6 | "dest": "/etc/ironic/ironic.conf", 7 | "owner": "ironic", 8 | "perm": "0600" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/merged/custom.conf", 12 | "dest": "/etc/ironic/ironic.conf.d/custom.conf", 13 | "owner": "ironic", 14 | "perm": "0600" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/merged/my.cnf", 18 | "dest": "/etc/my.cnf", 19 | "owner": "ironic", 20 | "perm": "0644" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /templates/ironicapi/bin/api-prep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | # TODO(sbaker): remove when https://review.opendev.org/c/openstack/tripleo-common/+/854459 is in the image 19 | mkdir -p /var/www/cgi-bin/ironic 20 | cp -a /usr/bin/ironic-api-wsgi /var/www/cgi-bin/ironic/app 21 | -------------------------------------------------------------------------------- /templates/ironicapi/config/ironic-api-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/httpd -DFOREGROUND", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/merged/ironic.conf", 6 | "dest": "/etc/ironic/ironic.conf", 7 | "owner": "ironic", 8 | "perm": "0600" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/merged/custom.conf", 12 | "dest": "/etc/ironic/ironic.conf.d/custom.conf", 13 | "owner": "ironic", 14 | "perm": "0600" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/merged/ironic-api-httpd.conf", 18 | "dest": "/etc/httpd/conf/httpd.conf", 19 | "owner": "root", 20 | "perm": "0644" 21 | }, 22 | { 23 | "source": "/var/lib/config-data/merged/ssl.conf", 24 | "dest": "/etc/httpd/conf.d/ssl.conf", 25 | "owner": "root", 26 | "perm": "0644" 27 | }, 28 | { 29 | "source": "/var/lib/config-data/tls/certs/*", 30 | "dest": "/etc/pki/tls/certs/", 31 | "owner": "root", 32 | "perm": "0640", 33 | "optional": true, 34 | "merge": true 35 | }, 36 | { 37 | "source": "/var/lib/config-data/tls/private/*", 38 | "dest": "/etc/pki/tls/private/", 39 | "owner": "root", 40 | "perm": "0600", 41 | "optional": true, 42 | "merge": true 43 | }, 44 | { 45 | "source": "/var/lib/config-data/merged/my.cnf", 46 | "dest": "/etc/my.cnf", 47 | "owner": "ironic", 48 | "perm": "0644" 49 | } 50 | ], 51 | "permissions": [ 52 | { 53 | "path": "/var/log/ironic", 54 | "owner": "ironic:ironic", 55 | "recurse": true 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /templates/ironicapi/config/ironic-api-httpd.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | TraceEnable Off 4 | PidFile run/httpd.pid 5 | ServerRoot "/etc/httpd" 6 | ServerName "localhost.localdomain" 7 | 8 | User apache 9 | Group apache 10 | 11 | Listen 6385 12 | 13 | TypesConfig /etc/mime.types 14 | 15 | Include conf.modules.d/*.conf 16 | Include conf.d/*.conf 17 | 18 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 19 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy 20 | 21 | ErrorLog /dev/stdout 22 | ServerSignature Off 23 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded 24 | CustomLog /dev/stdout combined env=!forwarded 25 | CustomLog /dev/stdout proxy env=forwarded 26 | 27 | {{ range $endpt, $vhost := .VHosts }} 28 | # {{ $endpt }} vhost {{ $vhost.ServerName }} configuration 29 | 30 | ServerName {{ $vhost.ServerName }} 31 | 32 | ## Vhost docroot 33 | DocumentRoot "/var/www/cgi-bin/ironic" 34 | 35 | ## Directories, there should at least be a declaration for /var/www/cgi-bin/ironic 36 | 37 | Options -Indexes +FollowSymLinks +MultiViews 38 | AllowOverride None 39 | Require all granted 40 | 41 | 42 | {{- if $vhost.TLS }} 43 | SetEnvIf X-Forwarded-Proto https HTTPS=1 44 | 45 | ## SSL directives 46 | SSLEngine on 47 | SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" 48 | SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" 49 | {{- else }} 50 | SetEnvIf X-Forwarded-Proto http HTTPS=0 51 | {{- end }} 52 | 53 | ## WSGI configuration 54 | WSGIApplicationGroup %{GLOBAL} 55 | WSGIDaemonProcess {{ $endpt }} display-name={{ $endpt }} group=ironic processes=1 threads=15 user=ironic 56 | WSGIProcessGroup {{ $endpt }} 57 | WSGIScriptAlias / "/var/www/cgi-bin/ironic/app" 58 | WSGIPassAuthorization On 59 | 60 | TimeOut {{ $vhost.TimeOut }} 61 | 62 | {{ end }} 63 | -------------------------------------------------------------------------------- /templates/ironicapi/config/ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | SSLRandomSeed startup builtin 3 | SSLRandomSeed startup file:/dev/urandom 512 4 | SSLRandomSeed connect builtin 5 | SSLRandomSeed connect file:/dev/urandom 512 6 | 7 | AddType application/x-x509-ca-cert .crt 8 | AddType application/x-pkcs7-crl .crl 9 | 10 | SSLPassPhraseDialog builtin 11 | SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" 12 | SSLSessionCacheTimeout 300 13 | Mutex default 14 | SSLCryptoDevice builtin 15 | SSLHonorCipherOrder On 16 | SSLUseStapling Off 17 | SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" 18 | SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES 19 | SSLProtocol all -SSLv2 -SSLv3 -TLSv1 20 | SSLOptions StdEnvVars 21 | 22 | -------------------------------------------------------------------------------- /templates/ironicconductor/bin/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2020 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | # Get the statefulset pod index 19 | export PODINDEX=$(echo ${HOSTNAME##*-}) 20 | 21 | # expect that the common.sh is in the same dir as the calling script 22 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 23 | . ${SCRIPTPATH}/common.sh --source-only 24 | 25 | common_ironic_config 26 | 27 | if [ -n "${ProvisionNetwork}" ]; then 28 | export ProvisionNetworkIP=$(/usr/local/bin/container-scripts/get_net_ip ${ProvisionNetwork}) 29 | crudini --set ${SVC_CFG_MERGED} DEFAULT my_ip $ProvisionNetworkIP 30 | fi 31 | export DEPLOY_HTTP_URL=$(python3 -c 'import os; print(os.environ["DeployHTTPURL"] % os.environ)') 32 | SVC_CFG_MERGED=/var/lib/config-data/merged/ironic.conf 33 | crudini --set ${SVC_CFG_MERGED} deploy http_url ${DEPLOY_HTTP_URL} 34 | crudini --set ${SVC_CFG_MERGED} conductor bootloader ${DEPLOY_HTTP_URL}esp.img 35 | crudini --set ${SVC_CFG_MERGED} conductor deploy_kernel ${DEPLOY_HTTP_URL}ironic-python-agent.kernel 36 | crudini --set ${SVC_CFG_MERGED} conductor deploy_ramdisk ${DEPLOY_HTTP_URL}ironic-python-agent.initramfs 37 | crudini --set ${SVC_CFG_MERGED} conductor rescue_kernel ${DEPLOY_HTTP_URL}ironic-python-agent.kernel 38 | crudini --set ${SVC_CFG_MERGED} conductor rescue_ramdisk ${DEPLOY_HTTP_URL}ironic-python-agent.initramfs 39 | 40 | export DNSMASQ_CFG=/var/lib/config-data/merged/dnsmasq.conf 41 | sed -e "/BLOCK_PODINDEX_${PODINDEX}_BEGIN/,/BLOCK_PODINDEX_${PODINDEX}_END/p" \ 42 | -e "/BLOCK_PODINDEX_.*_BEGIN/,/BLOCK_PODINDEX_.*_END/d" \ 43 | -i ${DNSMASQ_CFG} 44 | sed -e "/BLOCK_PODINDEX_${PODINDEX}_BEGIN/d" \ 45 | -e "/BLOCK_PODINDEX_${PODINDEX}_END/d" \ 46 | -i ${DNSMASQ_CFG} 47 | 48 | if [ ! -d "/var/lib/ironic/tmp" ]; then 49 | mkdir /var/lib/ironic/tmp 50 | fi 51 | if [ ! -d "/var/lib/ironic/httpboot" ]; then 52 | mkdir /var/lib/ironic/httpboot 53 | fi 54 | if [ ! -d "/var/lib/ironic/ramdisk-logs" ]; then 55 | mkdir /var/lib/ironic/ramdisk-logs 56 | fi 57 | -------------------------------------------------------------------------------- /templates/ironicconductor/config/dnsmasq-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/dnsmasq -k", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/merged/dnsmasq.conf", 6 | "dest": "/etc/dnsmasq.conf", 7 | "owner": "dnsmasq", 8 | "perm": "0644" 9 | } 10 | ], 11 | "permissions": [ 12 | { 13 | "path": "/var/lib/ironic", 14 | "owner": "ironic:ironic", 15 | "recurse": true 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /templates/ironicconductor/config/httpboot-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/httpd -DFOREGROUND", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/merged/httpboot-httpd.conf", 6 | "dest": "/etc/httpd/conf/httpd.conf", 7 | "owner": "root", 8 | "perm": "0644" 9 | } 10 | ], 11 | "permissions": [ 12 | { 13 | "path": "/var/lib/ironic", 14 | "owner": "ironic:ironic", 15 | "recurse": true 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /templates/ironicconductor/config/httpboot-httpd.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | TraceEnable Off 4 | PidFile run/httpd.pid 5 | ServerRoot "/etc/httpd" 6 | ServerName "localhost.localdomain" 7 | 8 | User apache 9 | Group apache 10 | 11 | Listen 8088 12 | 13 | TypesConfig /etc/mime.types 14 | 15 | Include conf.modules.d/*.conf 16 | Include conf.d/*.conf 17 | 18 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 19 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy 20 | 21 | ErrorLog /dev/stdout 22 | ServerSignature Off 23 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded 24 | CustomLog /dev/stdout combined env=!forwarded 25 | CustomLog /dev/stdout proxy env=forwarded 26 | 27 | 28 | ServerName httpboot_vhost 29 | 30 | ## Vhost docroot 31 | DocumentRoot "/var/lib/ironic/httpboot" 32 | 33 | ## Directories, there should at least be a declaration for /var/lib/ironic/httpboot 34 | 35 | 36 | Options Indexes FollowSymLinks 37 | AllowOverride None 38 | Require all granted 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/ironicconductor/config/ironic-conductor-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/bin/ironic-conductor", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/merged/ironic.conf", 6 | "dest": "/etc/ironic/ironic.conf", 7 | "owner": "ironic", 8 | "perm": "0600" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/merged/custom.conf", 12 | "dest": "/etc/ironic/ironic.conf.d/custom.conf", 13 | "owner": "ironic", 14 | "perm": "0600" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/merged/my.cnf", 18 | "dest": "/etc/my.cnf", 19 | "owner": "ironic", 20 | "perm": "0644" 21 | } 22 | ], 23 | "permissions": [ 24 | { 25 | "path": "/var/lib/ironic", 26 | "owner": "ironic:ironic", 27 | "recurse": true 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /templates/ironicconductor/config/ramdisk-logs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/local/bin/container-scripts/runlogwatch.sh" 3 | } 4 | -------------------------------------------------------------------------------- /templates/ironicinspector/bin/inspector-pxe-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2023 Red Hat Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | set -ex 17 | 18 | # Get the statefulset pod index 19 | export PODINDEX=$(echo ${HOSTNAME##*-}) 20 | 21 | # DHCP server configuration 22 | export InspectorNetworkIP=$(/usr/local/bin/container-scripts/get_net_ip ${InspectionNetwork}) 23 | export INSPECTOR_HTTP_URL=$(python3 -c 'import os; print(os.environ["InspectorHTTPURL"] % os.environ)') 24 | 25 | # Copy required config to modifiable location 26 | cp /var/lib/config-data/default/dnsmasq.conf /var/lib/ironic/ 27 | cp /var/lib/config-data/default/inspector.ipxe /var/lib/ironic/ 28 | 29 | export DNSMASQ_CFG=/var/lib/ironic/dnsmasq.conf 30 | sed -e "/BLOCK_PODINDEX_${PODINDEX}_BEGIN/,/BLOCK_PODINDEX_${PODINDEX}_END/p" \ 31 | -e "/BLOCK_PODINDEX_.*_BEGIN/,/BLOCK_PODINDEX_.*_END/d" \ 32 | -i ${DNSMASQ_CFG} 33 | sed -e "/BLOCK_PODINDEX_${PODINDEX}_BEGIN/d" \ 34 | -e "/BLOCK_PODINDEX_${PODINDEX}_END/d" \ 35 | -i ${DNSMASQ_CFG} 36 | 37 | # OSPCIX-870: Copy the config to tempdir to avoid race condition where pipe to 38 | # tee may wipe the file. 39 | export TMP_DNSMASQ_CFG=/var/tmp/dnsmasq.conf 40 | cp ${DNSMASQ_CFG} ${TMP_DNSMASQ_CFG} 41 | envsubst < ${TMP_DNSMASQ_CFG} | tee ${DNSMASQ_CFG} 42 | 43 | export INSPECTOR_IPXE=/var/lib/ironic/inspector.ipxe 44 | # OSPCIX-870: Copy the config to tempdir to avoid race condition where pipe to 45 | # tee may wipe the file. 46 | export TMP_INSPECTOR_IPXE=/var/tmp/inspector.ipxe 47 | cp ${INSPECTOR_IPXE} ${TMP_INSPECTOR_IPXE} 48 | envsubst < ${TMP_INSPECTOR_IPXE} | tee ${INSPECTOR_IPXE} 49 | 50 | # run common pxe-init script 51 | /usr/local/bin/container-scripts/pxe-init.sh 52 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/01-inspector.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | auth_strategy={{if .Standalone}}noauth{{else}}keystone{{end}} 3 | log_file=/dev/stdout 4 | transport_url = {{ .TransportURL }} 5 | listen_address = localhost 6 | listen_port = 5051 7 | 8 | [cors] 9 | allowed_origin=* 10 | expose_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma 11 | max_age=3600 12 | allow_methods=GET,POST,PUT,DELETE,OPTIONS,PATCH 13 | allow_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Auth-Token 14 | 15 | [dnsmasq_pxe_filter] 16 | dhcp_hostsdir=/var/lib/ironic-inspector/dhcp-hostsdir 17 | purge_dhcp_hostsdir=False 18 | 19 | [database] 20 | connection={{ .DatabaseConnection }} 21 | 22 | {{if .Standalone}} 23 | [ironic] 24 | auth_type=none 25 | endpoint_override={{ .IronicInternalURL }} 26 | {{else}} 27 | [ironic] 28 | auth_type=password 29 | auth_url={{ .KeystoneInternalURL }} 30 | username={{ .ServiceUser }} 31 | password = {{ .ServicePassword }} 32 | user_domain_name=Default 33 | project_name=service 34 | project_domain_name=Default 35 | max_retries=6 36 | retry_interval=10 37 | 38 | [keystone_authtoken] 39 | auth_type=password 40 | auth_url={{ .KeystoneInternalURL }} 41 | username={{ .ServiceUser }} 42 | password = {{ .ServicePassword }} 43 | www_authenticate_uri={{ .KeystonePublicURL }} 44 | project_domain_name=Default 45 | user_domain_name=Default 46 | project_name=service 47 | 48 | [service_catalog] 49 | auth_type=password 50 | auth_url={{ .KeystoneInternalURL }} 51 | username={{ .ServiceUser }} 52 | password = {{ .ServicePassword }} 53 | user_domain_name=Default 54 | project_name=service 55 | project_domain_name=Default 56 | 57 | [swift] 58 | auth_type=password 59 | auth_url={{ .KeystoneInternalURL }} 60 | username={{ .ServiceUser }} 61 | password = {{ .ServicePassword }} 62 | project_domain_name=Default 63 | project_name=services 64 | user_domain_name=Default 65 | 66 | [oslo_policy] 67 | enforce_scope=True 68 | enforce_new_defaults=True 69 | {{end}} 70 | 71 | [processing] 72 | keep_ports=all 73 | processing_hooks=$default_processing_hooks,extra_hardware,lldp_basic,local_link_connection,physnet_cidr_map 74 | ramdisk_logs_dir=/var/lib/ironic/ramdisk-logs/ 75 | always_store_ramdisk_logs=True 76 | store_data=database 77 | 78 | [pxe_filter] 79 | driver=dnsmasq 80 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/db-sync-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/local/bin/container-scripts/bootstrap.sh", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/01-inspector.conf", 6 | "dest": "/etc/ironic-inspector/inspector.conf.d/01-inspector.conf", 7 | "owner": "root:ironic-inspector", 8 | "perm": "0640" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/02-inspector-custom.conf", 12 | "dest": "/etc/ironic-inspector/inspector.conf.d/02-inspector-custom.conf", 13 | "owner": "root:ironic-inspector", 14 | "perm": "0640" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/default/my.cnf", 18 | "dest": "/etc/my.cnf", 19 | "owner": "ironic-inspector", 20 | "perm": "0644" 21 | } 22 | ], 23 | "permissions": [ 24 | { 25 | "path": "/var/log/ironic-inspector", 26 | "owner": "ironic-inspector:ironic-inspector", 27 | "recurse": true 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/dnsmasq-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/dnsmasq -k", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/ironic/dnsmasq.conf", 6 | "dest": "/etc/dnsmasq.conf", 7 | "owner": "dnsmasq", 8 | "perm": "0644" 9 | } 10 | ], 11 | "permissions": [ 12 | { 13 | "path": "/var/log/ironic-inspector", 14 | "owner": "ironic-inspector:ironic-inspector", 15 | "recurse": true 16 | }, 17 | { 18 | "path": "/var/lib/ironic-inspector/dhcp-hostsdir", 19 | "owner": "ironic-inspector:ironic-inspector", 20 | "recurse": true 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/dnsmasq.conf: -------------------------------------------------------------------------------- 1 | # Disable listening for DNS 2 | port=0 3 | enable-tftp 4 | tftp-root=/var/lib/ironic/tftpboot 5 | 6 | log-dhcp 7 | 8 | {{ range .DHCPRanges }} 9 | {{- /* 10 | The following sed can be used to filter configuration by PodIndex: 11 | sed -e '/BLOCK_PODINDEX_${PODINDEX}_BEGIN/,/BLOCK_PODINDEX_${PODINDEX}_END/p' \ 12 | -e '/BLOCK_PODINDEX_.*_BEGIN/,/BLOCK_PODINDEX_.*_END/d' \ 13 | -i ${DNSMASQ_CFG} 14 | */ -}} 15 | ### BLOCK_PODINDEX_{{ .PodIndex }}_BEGIN ### 16 | # DHCP Range {{ .Start }} - {{ .End }} 17 | dhcp-range={{ if .Name }}set:{{ .Name }},{{ end }}{{ .Start }},{{ .End }},{{ if .Prefix }}{{ .Prefix }},{{ else if .Netmask }}{{ .Netmask }},{{ end }}10m 18 | {{ if .Gateway -}} 19 | dhcp-option={{ if .Name }}tag:{{ .Name }},{{ end }}option:router,{{ .Gateway }} 20 | {{ else -}} 21 | # Disable router on over provision network 22 | dhcp-option={{ if .Name }}tag:{{ .Name }},{{ end }}3 23 | {{ end -}} 24 | {{ if .MTU -}} 25 | # Set the MTU option 26 | dhcp-option-force={{ if .Name }}tag:{{ .Name }},{{ end }}option:mtu,{{ .MTU }} 27 | {{ end -}} 28 | ### BLOCK_PODINDEX_{{ .PodIndex }}_END ### 29 | 30 | {{ end }} 31 | 32 | 33 | # Disable default router(s) and DNS over provisioning network 34 | dhcp-option=6 35 | 36 | conf-dir=/etc/dnsmasq.d 37 | dhcp-hostsdir=/var/lib/ironic-inspector/dhcp-hostsdir 38 | log-facility=- 39 | user=root 40 | 41 | # Boot for Etherboot gPXE. The idea is to send two different 42 | # filenames, the first loads gPXE, and the second tells gPXE what to 43 | # load. The dhcp-match sets the gpxe tag for requests from gPXE. 44 | dhcp-userclass=set:gpxe,"gPXE" 45 | dhcp-boot=tag:gpxe,/ipxe.pxe 46 | 47 | dhcp-match=set:ipxe,175 # iPXE sends a 175 option. 48 | dhcp-match=set:efi,option:client-arch,7 49 | dhcp-match=set:efi,option:client-arch,9 50 | dhcp-match=set:efi,option:client-arch,11 51 | 52 | # dhcpv6s for Client System Architecture Type (61) 53 | dhcp-match=set:efi6,option6:61,0007 54 | dhcp-match=set:efi6,option6:61,0009 55 | dhcp-match=set:efi6,option6:61,0011 56 | dhcp-userclass=set:ipxe6,iPXE 57 | 58 | # Client is already running iPXE; move to next stage of chainloading 59 | dhcp-boot=tag:ipxe,${INSPECTOR_HTTP_URL}inspector.ipxe 60 | dhcp-option=tag:ipxe6,option6:bootfile-url,${INSPECTOR_HTTP_URL}inspector.ipxe 61 | 62 | # Client is PXE booting over EFI without iPXE ROM; send EFI version of iPXE chainloader 63 | dhcp-boot=tag:efi,tag:!ipxe,snponly.efi 64 | dhcp-option=tag:efi6,tag:!ipxe6,option6:bootfile-url,tftp://${InspectorNetworkIP}/snponly.efi 65 | 66 | # Client is running PXE over BIOS; send BIOS version of iPXE chainloader 67 | dhcp-boot=undionly.kpxe,localhost.localdomain,${InspectorNetworkIP} 68 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/httpboot-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/httpd -DFOREGROUND", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/httpboot-httpd.conf", 6 | "dest": "/etc/httpd/conf/httpd.conf", 7 | "owner": "root", 8 | "perm": "0644" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/inspector.ipxe", 12 | "dest": "/var/lib/ironic/httpboot/inspector.ipxe", 13 | "owner": "root:ironic", 14 | "perm": "0644" 15 | } 16 | ], 17 | "permissions": [ 18 | { 19 | "path": "/var/log/ironic", 20 | "owner": "ironic:ironic", 21 | "recurse": true 22 | }, 23 | { 24 | "path": "/var/lib/ironic", 25 | "owner": "ironic:ironic", 26 | "recurse": true 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/httpboot-httpd.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | TraceEnable Off 4 | PidFile run/httpd.pid 5 | ServerRoot "/etc/httpd" 6 | ServerName "localhost.localdomain" 7 | 8 | User apache 9 | Group apache 10 | 11 | Listen 8088 12 | 13 | TypesConfig /etc/mime.types 14 | 15 | Include conf.modules.d/*.conf 16 | Include conf.d/*.conf 17 | 18 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 19 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy 20 | 21 | ErrorLog /dev/stdout 22 | ServerSignature Off 23 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded 24 | CustomLog /dev/stdout combined env=!forwarded 25 | CustomLog /dev/stdout proxy env=forwarded 26 | 27 | 28 | ServerName httpboot_vhost 29 | 30 | ## Vhost docroot 31 | DocumentRoot "/var/lib/ironic/httpboot" 32 | 33 | ## Directories, there should at least be a declaration for /var/lib/ironic/httpboot 34 | 35 | 36 | Options Indexes FollowSymLinks 37 | AllowOverride None 38 | Require all granted 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/httpd-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/httpd -DFOREGROUND", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/httpd.conf", 6 | "dest": "/etc/httpd/conf/httpd.conf", 7 | "owner": "root", 8 | "perm": "0644" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/ssl.conf", 12 | "dest": "/etc/httpd/conf.d/ssl.conf", 13 | "owner": "root", 14 | "perm": "0644" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/tls/certs/*", 18 | "dest": "/etc/pki/tls/certs/", 19 | "owner": "root", 20 | "perm": "0640", 21 | "optional": true, 22 | "merge": true 23 | }, 24 | { 25 | "source": "/var/lib/config-data/tls/private/*", 26 | "dest": "/etc/pki/tls/private/", 27 | "owner": "root", 28 | "perm": "0600", 29 | "optional": true, 30 | "merge": true 31 | } 32 | ], 33 | "permissions": [ 34 | { 35 | "path": "/var/log/ironic-inspector", 36 | "owner": "ironic-inspector:ironic-inspector", 37 | "recurse": true 38 | }, 39 | { 40 | "path": "/var/lib/ironic-inspector", 41 | "owner": "ironic-inspector:ironic-inspector", 42 | "recurse": true 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/httpd.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | TraceEnable Off 4 | PidFile run/httpd.pid 5 | ServerRoot "/etc/httpd" 6 | ServerName "localhost.localdomain" 7 | 8 | User apache 9 | Group apache 10 | 11 | Listen 5050 12 | 13 | TypesConfig /etc/mime.types 14 | 15 | Include conf.modules.d/*.conf 16 | Include conf.d/*.conf 17 | 18 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 19 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy 20 | 21 | ErrorLog /dev/stdout 22 | ServerSignature Off 23 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded 24 | CustomLog /dev/stdout combined env=!forwarded 25 | CustomLog /dev/stdout proxy env=forwarded 26 | 27 | {{ range $endpt, $vhost := .VHosts }} 28 | # {{ $endpt }} vhost {{ $vhost.ServerName }} configuration 29 | 30 | ServerName {{ $vhost.ServerName }} 31 | 32 | ## Request header rules 33 | ## as per http://httpd.apache.org/docs/2.4/mod/mod_headers.html#requestheader 34 | {{- if $vhost.TLS }} 35 | RequestHeader setIfEmpty X-Forwarded-Proto "https" 36 | {{- else }} 37 | RequestHeader setIfEmpty X-Forwarded-Proto "http" 38 | {{- end }} 39 | 40 | ## Proxy rules 41 | ProxyRequests Off 42 | ProxyPreserveHost Off 43 | ProxyPass / http://localhost:5051/ retry=10 44 | ProxyPassReverse / http://localhost:5051/ 45 | 46 | {{- if $vhost.TLS }} 47 | SetEnvIf X-Forwarded-Proto https HTTPS=1 48 | 49 | ## SSL directives 50 | SSLEngine on 51 | SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" 52 | SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" 53 | {{- else }} 54 | SetEnvIf X-Forwarded-Proto http HTTPS=0 55 | {{- end }} 56 | 57 | TimeOut {{ $.TimeOut }} 58 | 59 | {{ end }} 60 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/inspector.ipxe: -------------------------------------------------------------------------------- 1 | #!ipxe 2 | 3 | :retry_boot 4 | imgfree 5 | kernel --timeout 60000 ${INSPECTOR_HTTP_URL}ironic-python-agent.kernel ipa-inspection-callback-url=http://${InspectorNetworkIP}:5050/v1/continue ipa-inspection-collectors=default,extra-hardware,numa-topology,logs systemd.journald.forward_to_console=yes BOOTIF=${mac} ipa-inspection-dhcp-all-interfaces=1 ipa-collect-lldp=1 initrd=ironic-python-agent.initramfs || goto retry_boot 6 | initrd --timeout 60000 ${INSPECTOR_HTTP_URL}ironic-python-agent.initramfs || goto retry_boot 7 | boot 8 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/ironic-inspector-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/bin/ironic-inspector --config-file /etc/ironic-inspector/inspector.conf --config-dir /etc/ironic-inspector/inspector.conf.d", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/01-inspector.conf", 6 | "dest": "/etc/ironic-inspector/inspector.conf.d/01-inspector.conf", 7 | "owner": "root:ironic-inspector", 8 | "perm": "0640" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/02-inspector-custom.conf", 12 | "dest": "/etc/ironic-inspector/inspector.conf.d/02-inspector-custom.conf", 13 | "owner": "root:ironic-inspector", 14 | "perm": "0640" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/default/my.cnf", 18 | "dest": "/etc/my.cnf", 19 | "owner": "ironic-inspector", 20 | "perm": "0644" 21 | } 22 | ], 23 | "permissions": [ 24 | { 25 | "path": "/var/log/ironic-inspector", 26 | "owner": "ironic-inspector:ironic-inspector", 27 | "recurse": true 28 | }, 29 | { 30 | "path": "/var/lib/ironic-inspector", 31 | "owner": "ironic-inspector:ironic-inspector", 32 | "recurse": true 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/ramdisk-logs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/local/bin/container-scripts/runlogwatch.sh" 3 | } 4 | -------------------------------------------------------------------------------- /templates/ironicinspector/config/ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | SSLRandomSeed startup builtin 3 | SSLRandomSeed startup file:/dev/urandom 512 4 | SSLRandomSeed connect builtin 5 | SSLRandomSeed connect file:/dev/urandom 512 6 | 7 | AddType application/x-x509-ca-cert .crt 8 | AddType application/x-pkcs7-crl .crl 9 | 10 | SSLPassPhraseDialog builtin 11 | SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" 12 | SSLSessionCacheTimeout 300 13 | Mutex default 14 | SSLCryptoDevice builtin 15 | SSLHonorCipherOrder On 16 | SSLUseStapling Off 17 | SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" 18 | SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES 19 | SSLProtocol all -SSLv2 -SSLv3 -TLSv1 20 | SSLOptions StdEnvVars 21 | 22 | -------------------------------------------------------------------------------- /templates/ironicneutronagent/config/01-ironic_neutron_agent.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | auth_strategy = keystone 3 | transport_url = {{ .TransportURL }} 4 | 5 | [keystone_authtoken] 6 | project_domain_name=Default 7 | user_domain_name=Default 8 | project_name=service 9 | username={{ .ServiceUser }} 10 | www_authenticate_uri={{ .KeystonePublicURL }} 11 | auth_url={{ .KeystoneInternalURL }} 12 | auth_type=password 13 | password = {{ .ServicePassword }} 14 | 15 | [service_catalog] 16 | auth_type=password 17 | auth_url={{ .KeystoneInternalURL }} 18 | username={{ .ServiceUser }} 19 | user_domain_name=Default 20 | project_name=service 21 | project_domain_name=Default 22 | password = {{ .ServicePassword }} 23 | 24 | [ironic] 25 | auth_type=password 26 | auth_url={{ .KeystoneInternalURL }} 27 | username={{ .ServiceUser }} 28 | user_domain_name=Default 29 | project_name=service 30 | project_domain_name=Default 31 | password = {{ .ServicePassword }} 32 | 33 | [agent] 34 | report_interval=30 35 | -------------------------------------------------------------------------------- /templates/ironicneutronagent/config/ironic-neutron-agent-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/bin/ironic-neutron-agent --config-dir /etc/neutron/neutron.conf.d", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/01-ironic_neutron_agent.conf", 6 | "dest": "/etc/neutron/neutron.conf.d/01-ironic_neutron_agent.conf", 7 | "owner": "root:neutron", 8 | "perm": "0640" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/02-ironic_neutron_agent-custom.conf", 12 | "dest": "/etc/neutron/neutron.conf.d/02-ironic_neutron_agent-custom.conf", 13 | "owner": "root:neutron", 14 | "perm": "0640" 15 | } 16 | ], 17 | "permissions": [ 18 | { 19 | "path": "/var/log/neutron", 20 | "owner": "neutron:neutron", 21 | "recurse": true 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tests/kuttl/common/assert_tls_cert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 4 tls cert secrets 5 | # - 1 tls ca bundle secrets 6 | 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: cert-ironic-internal-svc 11 | --- 12 | apiVersion: v1 13 | kind: Secret 14 | metadata: 15 | name: cert-ironic-public-svc 16 | --- 17 | apiVersion: v1 18 | kind: Secret 19 | metadata: 20 | name: cert-ironic-inspector-internal-svc 21 | --- 22 | apiVersion: v1 23 | kind: Secret 24 | metadata: 25 | name: cert-ironic-inspector-public-svc 26 | --- 27 | apiVersion: v1 28 | kind: Secret 29 | metadata: 30 | name: combined-ca-bundle 31 | -------------------------------------------------------------------------------- /tests/kuttl/common/cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | delete: 4 | - apiVersion: ironic.openstack.org/v1beta1 5 | kind: Ironic 6 | name: ironic 7 | -------------------------------------------------------------------------------- /tests/kuttl/common/errors-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # No IronicAPI CR 5 | # No Deployment for IronicAPI CR 6 | # No Pods in ironic Deployment 7 | # No Ironic Services 8 | # 9 | apiVersion: ironic.openstack.org/v1beta1 10 | kind: IronicAPI 11 | metadata: 12 | finalizers: 13 | - openstack.org/ironicapi 14 | name: ironic 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: ironic 20 | --- 21 | # the openshift annotations can't be checked through the deployment above 22 | apiVersion: v1 23 | kind: Pod 24 | metadata: 25 | annotations: 26 | openshift.io/scc: anyuid 27 | labels: 28 | service: ironic 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | labels: 34 | endpoint: internal 35 | service: ironic 36 | name: ironic-internal 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | labels: 42 | endpoint: public 43 | service: ironic 44 | name: ironic-public 45 | namespace: openstack 46 | -------------------------------------------------------------------------------- /tests/kuttl/common/tls_ca_bundle.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Internal CA created with: 3 | # 4 | # apiVersion: cert-manager.io/v1 5 | # kind: Issuer 6 | # metadata: 7 | # name: rootca-kuttl-internal 8 | # namespace: openstack 9 | # spec: 10 | # ca: 11 | # secretName: rootca-kuttl-internal 12 | # 13 | # apiVersion: cert-manager.io/v1 14 | # kind: Certificate 15 | # metadata: 16 | # name: rootca-kuttl-internal 17 | # namespace: openstack 18 | # spec: 19 | # commonName: rootca-kuttl-internal 20 | # duration: 87600h0m0s 21 | # isCA: true 22 | # issuerRef: 23 | # name: selfsigned-issuer 24 | # privateKey: 25 | # algorithm: ECDSA 26 | # size: 256 27 | # secretName: rootca-kuttl-internal 28 | # 29 | # External CA created with: 30 | # 31 | # apiVersion: cert-manager.io/v1 32 | # kind: Issuer 33 | # metadata: 34 | # name: rootca-kuttl-public 35 | # namespace: openstack 36 | # spec: 37 | # ca: 38 | # secretName: rootca-kuttl-public 39 | # 40 | # apiVersion: cert-manager.io/v1 41 | # kind: Certificate 42 | # metadata: 43 | # name: rootca-kuttl-public 44 | # namespace: openstack 45 | # spec: 46 | # commonName: rootca-kuttl-public 47 | # duration: 87600h0m0s 48 | # isCA: true 49 | # issuerRef: 50 | # name: selfsigned-issuer 51 | # privateKey: 52 | # algorithm: ECDSA 53 | # size: 256 54 | # secretName: rootca-kuttl-public 55 | # 56 | # Then extracted both CAs and created added them as the bundle: 57 | apiVersion: v1 58 | data: 59 | tls-ca-bundle.pem: IyByb290Y2EtaW50ZXJuYWwKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJmekNDQVNhZ0F3SUJBZ0lRUWxlcTNZcDBtU2kwVDNiTm03Q29UVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZ5YjI5MFkyRXRhM1YwZEd3dGFXNTBaWEp1WVd3d0hoY05NalF3TVRFMU1URTBOelUwV2hjTgpNelF3TVRFeU1URTBOelUwV2pBZ01SNHdIQVlEVlFRREV4VnliMjkwWTJFdGEzVjBkR3d0YVc1MFpYSnVZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRk9rNHJPUldVUGhoTjUrK09EN1I2MW5Gb1lBY0QKenpvUS91SW93NktjeGhwRWNQTDFxb3ZZUGxUYUJabEh3c2FpNE50VHA4aDA1RHVRSGZKOE9JNXFvMEl3UURBTwpCZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXE3TGtFSk1TCm1MOVpKWjBSOUluKzZkclhycEl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnVlN1K00ydnZ3QlF3eTJHMVlhdkkKQld2RGtSNlRla0I5U0VqdzJIblRSMWtDSUZSNFNkWGFPQkFGWjVHa2RLWCtSY2IzaDFIZm52eFJEVW96bTl2agphenp3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KIyByb290Y2EtcHVibGljCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZXpDQ0FTS2dBd0lCQWdJUU5IREdZc0JzNzk4aWJERDdxL28ybGpBS0JnZ3Foa2pPUFFRREFqQWVNUnd3CkdnWURWUVFERXhOeWIyOTBZMkV0YTNWMGRHd3RjSFZpYkdsak1CNFhEVEkwTURFeE5URXdNVFV6TmxvWERUTTAKTURFeE1qRXdNVFV6Tmxvd0hqRWNNQm9HQTFVRUF4TVRjbTl2ZEdOaExXdDFkSFJzTFhCMVlteHBZekJaTUJNRwpCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkQ3OGF2WHFocmhDNXc4czlXa2Q0SXBiZUV1MDNDUitYWFVkCmtEek9SeXhhOXdjY0lkRGl2YkdKakpGWlRUY1ZtYmpxMUJNWXNqcjEyVUlFNUVUM1ZscWpRakJBTUE0R0ExVWQKRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUS0ppeldVSjllVUtpMQpkczBscjZjNnNEN0VCREFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUJJWndZcTYxQnFNSmFCNlVjRm9Sc3hlY3dICjV6L3pNT2RyT3llMG1OaThKZ0lnUUxCNHdES3JwZjl0WDJsb00rMHVUb3BBRFNZSW5yY2ZWdTRGQnVZVTNJZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 60 | kind: Secret 61 | metadata: 62 | labels: 63 | combined-ca-bundle: "" 64 | name: combined-ca-bundle 65 | type: Opaque 66 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/10-assert-deploy-ironic.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # Ironic 5 | # IronicAPI 6 | # IronicConductor 7 | # IronicInspector 8 | # IronicNeutronAgent 9 | # 10 | 11 | apiVersion: ironic.openstack.org/v1beta1 12 | kind: Ironic 13 | metadata: 14 | finalizers: 15 | - openstack.org/ironic 16 | name: ironic 17 | spec: 18 | customServiceConfig: | 19 | [DEFAULT] 20 | debug = true 21 | databaseInstance: openstack 22 | ironicAPI: 23 | replicas: 1 24 | ironicConductors: 25 | - replicas: 1 26 | storageRequest: 10G 27 | ironicInspector: 28 | customServiceConfig: '# add your customization here' 29 | databaseAccount: ironic-inspector 30 | passwordSelectors: 31 | service: IronicInspectorPassword 32 | preserveJobs: true 33 | replicas: 1 34 | serviceUser: ironic-inspector 35 | ironicNeutronAgent: 36 | customServiceConfig: "# add your customization here" 37 | replicas: 1 38 | databaseAccount: ironic 39 | passwordSelectors: 40 | service: IronicPassword 41 | preserveJobs: true 42 | rabbitMqClusterName: rabbitmq 43 | rpcTransport: json-rpc 44 | secret: osp-secret 45 | serviceUser: ironic 46 | standalone: false 47 | status: 48 | databaseHostname: openstack.ironic-kuttl-tests.svc 49 | ironicAPIReadyCount: 1 50 | ironicConductorReadyCount: 51 | null_conductor_group_null: 1 52 | ironicNeutronAgentReadyCount: 1 53 | --- 54 | apiVersion: ironic.openstack.org/v1beta1 55 | kind: IronicAPI 56 | metadata: 57 | finalizers: 58 | - openstack.org/ironicapi 59 | name: ironic-api 60 | ownerReferences: 61 | - apiVersion: ironic.openstack.org/v1beta1 62 | blockOwnerDeletion: true 63 | controller: true 64 | kind: Ironic 65 | name: ironic 66 | spec: 67 | customServiceConfig: '# add your customization here' 68 | databaseHostname: openstack.ironic-kuttl-tests.svc 69 | databaseAccount: ironic 70 | passwordSelectors: 71 | service: IronicPassword 72 | replicas: 1 73 | resources: {} 74 | rpcTransport: json-rpc 75 | secret: osp-secret 76 | serviceUser: ironic 77 | standalone: false 78 | status: 79 | readyCount: 1 80 | --- 81 | apiVersion: ironic.openstack.org/v1beta1 82 | kind: IronicConductor 83 | metadata: 84 | finalizers: 85 | - openstack.org/ironicconductor 86 | name: ironic-conductor 87 | ownerReferences: 88 | - apiVersion: ironic.openstack.org/v1beta1 89 | blockOwnerDeletion: true 90 | controller: true 91 | kind: Ironic 92 | name: ironic 93 | spec: 94 | conductorGroup: "" 95 | customServiceConfig: '# add your customization here' 96 | databaseHostname: openstack.ironic-kuttl-tests.svc 97 | databaseAccount: ironic 98 | passwordSelectors: 99 | service: IronicPassword 100 | replicas: 1 101 | resources: {} 102 | rpcTransport: json-rpc 103 | secret: osp-secret 104 | serviceUser: ironic 105 | standalone: false 106 | storageRequest: 10G 107 | status: 108 | readyCount: 1 109 | --- 110 | apiVersion: ironic.openstack.org/v1beta1 111 | kind: IronicInspector 112 | metadata: 113 | finalizers: 114 | - openstack.org/ironicinspector 115 | name: ironic-inspector 116 | ownerReferences: 117 | - apiVersion: ironic.openstack.org/v1beta1 118 | blockOwnerDeletion: true 119 | controller: true 120 | kind: Ironic 121 | name: ironic 122 | spec: 123 | customServiceConfig: '# add your customization here' 124 | databaseInstance: openstack 125 | databaseAccount: ironic-inspector 126 | passwordSelectors: 127 | service: IronicInspectorPassword 128 | preserveJobs: true 129 | rabbitMqClusterName: rabbitmq 130 | replicas: 1 131 | resources: {} 132 | rpcTransport: json-rpc 133 | secret: osp-secret 134 | serviceUser: ironic-inspector 135 | standalone: false 136 | status: 137 | databaseHostname: openstack.ironic-kuttl-tests.svc 138 | readyCount: 1 139 | --- 140 | apiVersion: ironic.openstack.org/v1beta1 141 | kind: IronicNeutronAgent 142 | metadata: 143 | finalizers: 144 | - openstack.org/ironicneutronagent 145 | name: ironic-ironic-neutron-agent 146 | ownerReferences: 147 | - apiVersion: ironic.openstack.org/v1beta1 148 | blockOwnerDeletion: true 149 | controller: true 150 | kind: Ironic 151 | name: ironic 152 | spec: 153 | customServiceConfig: "# add your customization here" 154 | passwordSelectors: 155 | service: IronicPassword 156 | rabbitMqClusterName: rabbitmq 157 | replicas: 1 158 | resources: {} 159 | secret: osp-secret 160 | serviceUser: ironic 161 | status: 162 | readyCount: 1 163 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/10-deploy-ironic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc apply -n $NAMESPACE -f ../../../../config/samples/ironic_v1beta1_ironic.yaml 6 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/15-assert-endpoints.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert-endpoints.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/20-assert-deploy-ironic-conductor-groups.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ironic.openstack.org/v1beta1 2 | kind: Ironic 3 | metadata: 4 | name: ironic 5 | spec: 6 | serviceUser: ironic 7 | customServiceConfig: | 8 | [DEFAULT] 9 | debug = true 10 | databaseInstance: openstack 11 | ironicAPI: 12 | replicas: 1 13 | ironicConductors: 14 | - replicas: 1 15 | storageRequest: 10G 16 | - conductorGroup: auckland 17 | replicas: 1 18 | storageRequest: 10G 19 | - conductorGroup: stockholm 20 | replicas: 1 21 | storageRequest: 10G 22 | ironicInspector: 23 | replicas: 1 24 | secret: osp-secret 25 | status: 26 | databaseHostname: openstack.ironic-kuttl-tests.svc 27 | ironicAPIReadyCount: 1 28 | ironicConductorReadyCount: 29 | null_conductor_group_null: 1 30 | auckland: 1 31 | stockholm: 1 32 | ironicNeutronAgentReadyCount: 1 33 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/20-deploy-ironic-conductor-groups.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc apply -n $NAMESPACE -f ../../../../config/samples/ironic_v1beta1_ironic_conductor_groups.yaml 6 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/99-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-ironic.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy/99-errors-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors-cleanup-ironic.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_tls_cert.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-tls_ca_bundle.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_ca_bundle.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-tls_cert_ironic-inspector-internal-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_ironic-inspector-internal-svc.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-tls_cert_ironic-inspector-public-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_ironic-inspector-public-svc.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-tls_cert_ironic-internal-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_ironic-internal-svc.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/00-tls_cert_ironic-public-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_ironic-public-svc.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/10-assert-deploy-ironic.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # Ironic 5 | # IronicAPI 6 | # IronicConductor 7 | # IronicInspector 8 | # IronicNeutronAgent 9 | # Registered TLS endpoints for api and inspector 10 | # 11 | 12 | apiVersion: ironic.openstack.org/v1beta1 13 | kind: Ironic 14 | metadata: 15 | name: ironic 16 | spec: 17 | databaseInstance: openstack 18 | ironicAPI: 19 | replicas: 1 20 | tls: 21 | api: 22 | internal: 23 | secretName: cert-ironic-internal-svc 24 | public: 25 | secretName: cert-ironic-public-svc 26 | caBundleSecretName: combined-ca-bundle 27 | ironicConductors: 28 | - replicas: 1 29 | ironicInspector: 30 | replicas: 1 31 | tls: 32 | api: 33 | internal: 34 | secretName: cert-ironic-inspector-internal-svc 35 | public: 36 | secretName: cert-ironic-inspector-public-svc 37 | caBundleSecretName: combined-ca-bundle 38 | ironicNeutronAgent: 39 | replicas: 1 40 | status: 41 | databaseHostname: openstack.ironic-kuttl-tests.svc 42 | ironicAPIReadyCount: 1 43 | ironicConductorReadyCount: 44 | null_conductor_group_null: 1 45 | ironicInspectorReadyCount: 1 46 | ironicNeutronAgentReadyCount: 1 47 | --- 48 | apiVersion: ironic.openstack.org/v1beta1 49 | kind: IronicAPI 50 | metadata: 51 | name: ironic-api 52 | spec: 53 | replicas: 1 54 | tls: 55 | api: 56 | internal: 57 | secretName: cert-ironic-internal-svc 58 | public: 59 | secretName: cert-ironic-public-svc 60 | caBundleSecretName: combined-ca-bundle 61 | status: 62 | readyCount: 1 63 | --- 64 | apiVersion: ironic.openstack.org/v1beta1 65 | kind: IronicConductor 66 | metadata: 67 | name: ironic-conductor 68 | spec: 69 | replicas: 1 70 | tls: 71 | caBundleSecretName: combined-ca-bundle 72 | status: 73 | readyCount: 1 74 | --- 75 | apiVersion: ironic.openstack.org/v1beta1 76 | kind: IronicInspector 77 | metadata: 78 | name: ironic-inspector 79 | spec: 80 | replicas: 1 81 | tls: 82 | api: 83 | internal: 84 | secretName: cert-ironic-inspector-internal-svc 85 | public: 86 | secretName: cert-ironic-inspector-public-svc 87 | caBundleSecretName: combined-ca-bundle 88 | status: 89 | databaseHostname: openstack.ironic-kuttl-tests.svc 90 | readyCount: 1 91 | --- 92 | apiVersion: ironic.openstack.org/v1beta1 93 | kind: IronicNeutronAgent 94 | metadata: 95 | name: ironic-ironic-neutron-agent 96 | spec: 97 | replicas: 1 98 | tls: 99 | caBundleSecretName: combined-ca-bundle 100 | status: 101 | readyCount: 1 102 | --- 103 | # the actual addresses of the api endpoints are platform specific, so we can't rely on 104 | # kuttl asserts to check them. This short script gathers the addresses and checks that 105 | # the two endpoints are defined and their addresses follow the default pattern 106 | apiVersion: kuttl.dev/v1beta1 107 | kind: TestAssert 108 | commands: 109 | - script: | 110 | set -euxo pipefail 111 | template='{{.spec.endpoints.internal}}{{":"}}{{.spec.endpoints.public}}{{"\n"}}' 112 | regex="https:\/\/ironic-internal\.$NAMESPACE\..*:https:\/\/ironic-public\.$NAMESPACE\..*" 113 | apiEndpoints=$(oc get -n $NAMESPACE KeystoneEndpoint ironic -o go-template="$template") 114 | matches=$(echo "$apiEndpoints" | sed -e "s?$regex??") 115 | if [[ -n "$matches" ]]; then 116 | exit 1 117 | fi 118 | exit 0 119 | # the actual addresses of the api endpoints are platform specific, so we can't rely on 120 | # kuttl asserts to check them. This short script gathers the addresses and checks that 121 | # the two endpoints are defined and their addresses follow the default pattern 122 | - script: | 123 | set -euxo pipefail 124 | template='{{.spec.endpoints.internal}}{{":"}}{{.spec.endpoints.public}}{{"\n"}}' 125 | regex="https:\/\/ironic-inspector-internal\.$NAMESPACE\..*:https:\/\/ironic-inspector-public\.$NAMESPACE\..*" 126 | apiEndpoints=$(oc get -n $NAMESPACE KeystoneEndpoint ironic-inspector -o go-template="$template") 127 | matches=$(echo "$apiEndpoints" | sed -e "s?$regex??") 128 | if [[ -n "$matches" ]]; then 129 | exit 1 130 | fi 131 | exit 0 132 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/10-deploy-ironic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc apply -n $NAMESPACE -f ../../../../config/samples/ironic_v1beta1_ironic_tls.yaml 6 | -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/99-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-ironic.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/deploy_tls/99-errors-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors-cleanup-ironic.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/standalone/10-assert-deploy-ironic-standalone.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # Ironic 5 | # IronicAPI 6 | # IronicConductor 7 | # IronicInspector 8 | # 9 | 10 | apiVersion: ironic.openstack.org/v1beta1 11 | kind: Ironic 12 | metadata: 13 | finalizers: 14 | - openstack.org/ironic 15 | name: ironic 16 | spec: 17 | customServiceConfig: | 18 | [DEFAULT] 19 | debug = true 20 | databaseInstance: openstack 21 | databaseAccount: ironic 22 | ironicAPI: 23 | replicas: 1 24 | ironicConductors: 25 | - replicas: 1 26 | storageRequest: 10G 27 | ironicInspector: 28 | customServiceConfig: '# add your customization here' 29 | databaseAccount: ironic-inspector 30 | passwordSelectors: 31 | service: IronicInspectorPassword 32 | preserveJobs: true 33 | replicas: 1 34 | serviceUser: ironic-inspector 35 | passwordSelectors: 36 | service: IronicPassword 37 | preserveJobs: true 38 | rabbitMqClusterName: rabbitmq 39 | rpcTransport: json-rpc 40 | secret: osp-secret 41 | serviceUser: ironic 42 | standalone: true 43 | status: 44 | databaseHostname: openstack.ironic-kuttl-tests.svc 45 | ironicAPIReadyCount: 1 46 | ironicConductorReadyCount: 47 | null_conductor_group_null: 1 48 | --- 49 | apiVersion: ironic.openstack.org/v1beta1 50 | kind: IronicAPI 51 | metadata: 52 | finalizers: 53 | - openstack.org/ironicapi 54 | name: ironic-api 55 | ownerReferences: 56 | - apiVersion: ironic.openstack.org/v1beta1 57 | blockOwnerDeletion: true 58 | controller: true 59 | kind: Ironic 60 | name: ironic 61 | spec: 62 | customServiceConfig: '# add your customization here' 63 | databaseHostname: openstack.ironic-kuttl-tests.svc 64 | databaseAccount: ironic 65 | passwordSelectors: 66 | service: IronicPassword 67 | replicas: 1 68 | resources: {} 69 | rpcTransport: json-rpc 70 | secret: osp-secret 71 | serviceUser: ironic 72 | standalone: true 73 | status: 74 | readyCount: 1 75 | --- 76 | apiVersion: ironic.openstack.org/v1beta1 77 | kind: IronicConductor 78 | metadata: 79 | finalizers: 80 | - openstack.org/ironicconductor 81 | name: ironic-conductor 82 | ownerReferences: 83 | - apiVersion: ironic.openstack.org/v1beta1 84 | blockOwnerDeletion: true 85 | controller: true 86 | kind: Ironic 87 | name: ironic 88 | spec: 89 | conductorGroup: "" 90 | customServiceConfig: '# add your customization here' 91 | databaseHostname: openstack.ironic-kuttl-tests.svc 92 | databaseAccount: ironic 93 | passwordSelectors: 94 | service: IronicPassword 95 | replicas: 1 96 | resources: {} 97 | rpcTransport: json-rpc 98 | secret: osp-secret 99 | serviceUser: ironic 100 | standalone: true 101 | storageRequest: 10G 102 | status: 103 | readyCount: 1 104 | --- 105 | apiVersion: ironic.openstack.org/v1beta1 106 | kind: IronicInspector 107 | metadata: 108 | finalizers: 109 | - openstack.org/ironicinspector 110 | name: ironic-inspector 111 | ownerReferences: 112 | - apiVersion: ironic.openstack.org/v1beta1 113 | blockOwnerDeletion: true 114 | controller: true 115 | kind: Ironic 116 | name: ironic 117 | spec: 118 | customServiceConfig: '# add your customization here' 119 | databaseInstance: openstack 120 | databaseAccount: ironic-inspector 121 | passwordSelectors: 122 | service: IronicInspectorPassword 123 | preserveJobs: true 124 | rabbitMqClusterName: rabbitmq 125 | replicas: 1 126 | resources: {} 127 | rpcTransport: json-rpc 128 | secret: osp-secret 129 | serviceUser: ironic-inspector 130 | standalone: true 131 | status: 132 | databaseHostname: openstack.ironic-kuttl-tests.svc 133 | readyCount: 1 134 | -------------------------------------------------------------------------------- /tests/kuttl/tests/standalone/10-deploy-ironic-standalone.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc apply -n $NAMESPACE -f ../../../../config/samples/ironic_v1beta1_ironic_standalone.yaml 6 | -------------------------------------------------------------------------------- /tests/kuttl/tests/standalone/15-assert-endpoints.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert-endpoints.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/standalone/99-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-ironic.yaml -------------------------------------------------------------------------------- /tests/kuttl/tests/standalone/99-errors-cleanup-ironic.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors-cleanup-ironic.yaml -------------------------------------------------------------------------------- /zuul.d/projects.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - project: 3 | name: openstack-k8s-operators/ironic-operator 4 | templates: 5 | - podified-ironic-operator-pipeline 6 | --------------------------------------------------------------------------------