├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── checks.yml │ └── docs.yml ├── .gitignore ├── .golangci.yml ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── DEVELOPMENT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── UPGRADE.md ├── api └── v1 │ ├── component_spec.go │ ├── deletion_policy.go │ ├── groupversion_info.go │ ├── ipfamilies.go │ ├── linstorcluster_types.go │ ├── linstornodeconnection_types.go │ ├── linstorsatellite_types.go │ ├── linstorsatelliteconfiguration_types.go │ ├── patch.go │ ├── properties.go │ ├── storagepool.go │ ├── tls.go │ └── zz_generated.deepcopy.go ├── charts └── piraeus │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── config.yaml │ ├── crds.yaml │ ├── deployment.yaml │ ├── metrics-service.yaml │ ├── rbac.yaml │ ├── validating-webhook-configuration.yaml │ └── webhook-service.yaml │ └── values.yaml ├── cmd ├── gencert │ ├── gencert.go │ └── gencert_test.go └── main.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── piraeus.io_linstorclusters.yaml │ │ ├── piraeus.io_linstornodeconnections.yaml │ │ ├── piraeus.io_linstorsatelliteconfigurations.yaml │ │ └── piraeus.io_linstorsatellites.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_linstorclusters.yaml │ │ ├── cainjection_in_linstornodeconnections.yaml │ │ ├── cainjection_in_linstorsatelliteconfigurations.yaml │ │ ├── cainjection_in_linstorsatellites.yaml │ │ ├── webhook_in_linstorclusters.yaml │ │ ├── webhook_in_linstornodeconnections.yaml │ │ ├── webhook_in_linstorsatelliteconfigurations.yaml │ │ └── webhook_in_linstorsatellites.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── extras │ ├── monitoring │ │ ├── alerts.yaml │ │ ├── kustomization.yaml │ │ ├── linstor-controller-monitor.yaml │ │ ├── linstor-satellite-monitor.yaml │ │ └── piraeus-dashboard.json │ └── network-policy │ │ ├── kustomization.yaml │ │ └── network-policy.yaml ├── gencert │ ├── gencert.yaml │ ├── kustomization.yaml │ └── rbac.yaml ├── manager │ ├── 0_piraeus_datastore_images.yaml │ ├── 0_sig_storage_images.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── piraeus-operator.clusterserviceversion.yaml │ ├── csv-resource-patch.yaml │ ├── csv-status-patch.yaml │ └── kustomization.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── linstorcluster_editor_role.yaml │ ├── linstorcluster_viewer_role.yaml │ ├── linstornodeconnection_editor_role.yaml │ ├── linstornodeconnection_viewer_role.yaml │ ├── linstorsatellite_editor_role.yaml │ ├── linstorsatellite_viewer_role.yaml │ ├── linstorsatelliteconfiguration_editor_role.yaml │ ├── linstorsatelliteconfiguration_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── kustomization.yaml │ ├── linstorcluster.yaml │ ├── linstornodeconnection.yaml │ └── linstorsatelliteconfiguration.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── docs ├── CHANGELOG.md ├── README.md ├── assets │ ├── favicon.png │ ├── grafana-piraeus-datastore-dashboard.png │ ├── mok-continue.png │ ├── mok-enroll.png │ ├── mok-password.png │ ├── mok-reboot.png │ ├── prometheus-console-drbd_version.png │ └── prometheus-console-linstor_info.png ├── explanation │ ├── README.md │ └── components.md ├── how-to │ ├── README.md │ ├── api-tls.md │ ├── drbd-host-networking.md │ ├── drbd-loader.md │ ├── drbd-tls.md │ ├── external-controller.md │ ├── flatcar.md │ ├── http-proxy.md │ ├── install-kernel-headers.md │ ├── internal-tls.md │ ├── k0s.md │ ├── linstor-affinity-controller.md │ ├── microk8s.md │ ├── monitoring.md │ ├── network-policy.md │ ├── openshift.md │ ├── restore-linstor-db.md │ ├── secure-boot.md │ ├── talos.md │ └── upgrade │ │ └── collect-operator-v1-information.sh ├── overrides │ └── .icons │ │ └── piraeus.svg ├── reference │ ├── README.md │ ├── linstorcluster.md │ ├── linstornodeconnection.md │ ├── linstorsatellite.md │ └── linstorsatelliteconfiguration.md ├── tutorial │ ├── README.md │ ├── get-started.md │ ├── replicated-volumes.md │ └── snapshots.md └── upgrade │ ├── README.md │ └── migration │ ├── 1-migrate-database.md │ ├── 2-collect-information.md │ ├── 3-remove-operator-v1.md │ ├── 4-install-operator-v2.md │ ├── README.md │ └── collect-operator-v1-information.sh ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── copy-image-config-to-chart.sh ├── crd-charts-copy.sh ├── csvinflater │ └── csvinflater.go ├── image-updates.sh └── make-release.sh ├── internal ├── controller │ ├── linstorcluster_controller.go │ ├── linstorcluster_test.go │ ├── linstornodeconnection_controller.go │ ├── linstornodeconnection_test.go │ ├── linstorsatellite_controller.go │ ├── linstorsatellite_test.go │ ├── patches.go │ ├── patches_test.go │ ├── ratelimit.go │ └── suite_test.go └── webhook │ ├── storageclass.go │ ├── v1 │ ├── component_spec.go │ ├── linstorcluster_webhook.go │ ├── linstorcluster_webhook_test.go │ ├── linstornodeconnection_webhook.go │ ├── linstornodeconnection_webhook_test.go │ ├── linstorsatellite_webhook.go │ ├── linstorsatellite_webhook_test.go │ ├── linstorsatelliteconfiguration_webhook.go │ ├── linstorsatelliteconfiguration_webhook_test.go │ ├── patch.go │ ├── properties.go │ ├── storagepool.go │ └── webhook_suite_test.go │ └── webhook_test.go ├── mkdocs.yml └── pkg ├── barepodpatch ├── barepodpatch.go └── barepodpatch_test.go ├── conditions ├── conditions.go └── conditions_test.go ├── fakelinstor └── fakelinstor.go ├── imageversions ├── dynamic.go ├── dynamic_test.go ├── versions.go └── versions_test.go ├── k8sgc ├── k8sgc.go └── k8sgc_test.go ├── linstorhelper ├── client.go ├── client_test.go ├── log.go ├── properties.go └── properties_test.go ├── merge ├── linstorsatelliteconfiguration.go └── linstorsatelliteconfiguration_test.go ├── resources ├── cluster │ ├── controller │ │ ├── cert-manager │ │ │ ├── api-client │ │ │ │ ├── api-client-cert.yaml │ │ │ │ └── kustomization.yaml │ │ │ ├── api │ │ │ │ ├── api-cert.yaml │ │ │ │ └── kustomization.yaml │ │ │ └── internal │ │ │ │ ├── internal-cert.yaml │ │ │ │ └── kustomization.yaml │ │ ├── controller-cluster-role-binding.yaml │ │ ├── controller-cluster-role.yaml │ │ ├── controller-config.yaml │ │ ├── controller-deployment.yaml │ │ ├── controller-role-binding.yaml │ │ ├── controller-role.yaml │ │ ├── controller-service-account.yaml │ │ ├── controller-service.yaml │ │ └── kustomization.yaml │ ├── csi-controller │ │ ├── cert-manager │ │ │ ├── csi-controller-cert.yaml │ │ │ └── kustomization.yaml │ │ ├── csi-controller-deployment.yaml │ │ ├── csi-controller-service-account.yaml │ │ ├── csi-driver.yaml │ │ └── kustomization.yaml │ ├── csi-node │ │ ├── cert-manager │ │ │ ├── csi-node-cert.yaml │ │ │ └── kustomization.yaml │ │ ├── csi-node-daemon-set.yaml │ │ ├── csi-node-service-account.yaml │ │ └── kustomization.yaml │ ├── ha-controller │ │ ├── daemonset.yaml │ │ ├── kustomization.yaml │ │ └── rbac.yaml │ ├── patches │ │ ├── api-endpoint.yaml │ │ ├── api-tls-cert-manager.yaml │ │ ├── api-tls-client-cert-manager.yaml │ │ ├── api-tls-csi-controller.yaml │ │ ├── api-tls-csi-node.yaml │ │ ├── api-tls.yaml │ │ ├── csi-controller-node-affinity.yaml │ │ ├── csi-controller-selector.yaml │ │ ├── csi-driver-no-selinux.yaml │ │ ├── csi-node-node-affinity.yaml │ │ ├── csi-node-selector.yaml │ │ ├── ha-controller-node-affinity.yaml │ │ ├── ha-controller-node-selector.yaml │ │ ├── internal-tls-cert-manager.yaml │ │ ├── internal-tls.yaml │ │ ├── linstor-controller-node-affinity.yaml │ │ ├── linstor-controller-selector.yaml │ │ ├── passphrase.yaml │ │ ├── pod-template.yaml │ │ ├── pull-secret.yaml │ │ └── tolerations.yaml │ ├── resources.go │ ├── satellite-common │ │ ├── kustomization.yaml │ │ └── service-account.yaml │ └── satellite │ │ ├── kustomization.yaml │ │ └── linstor-satellite.yaml ├── loader.go ├── loader_test.go ├── satellite │ ├── patches │ │ ├── common-node.yaml │ │ ├── host-path-volume-env.yaml │ │ ├── host-path-volume.yaml │ │ ├── internal-tls-cert-manager.yaml │ │ ├── internal-tls.yaml │ │ ├── precompiled-module.yaml │ │ └── tlshd.yaml │ ├── resources.go │ └── satellite │ │ ├── cert-manager │ │ ├── certificate.yaml │ │ └── kustomization.yaml │ │ ├── config.yaml │ │ ├── daemonset.yaml │ │ └── kustomization.yaml └── test │ ├── basic │ ├── kustomization.yaml │ ├── namespace.yaml │ └── serviceaccount.yaml │ ├── empty │ └── kustomization.yaml │ └── resources.go ├── utils ├── anyerror.go ├── fieldpath │ ├── doc.go │ ├── fieldpath.go │ └── fieldpath_test.go ├── jsonpatch.go ├── patch_convert.go ├── patch_convert_test.go ├── property_resolution.go ├── property_resolution_test.go ├── prune.go ├── prune_test.go ├── tolerations │ └── tolerations.go ├── version.go └── version_test.go └── vars ├── branding.go ├── defaults.go └── vars.go /.gitattributes: -------------------------------------------------------------------------------- 1 | api/v1/zz_generated.deepcopy.go linguist-generated 2 | config/crd/bases/* linguist-generated 3 | config/manifests/csv-resource-patch.yaml linguist-generated 4 | charts/piraeus/templates/config.yaml linguist-generated 5 | charts/piraeus/templates/crds.yaml linguist-generated 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | reviewers: 6 | - WanzenBug 7 | schedule: 8 | interval: "weekly" 9 | allow: 10 | - dependency-type: all # Also include indirect dependencies 11 | groups: 12 | gomod: 13 | patterns: 14 | - "*" # Include all go mod update in one PR 15 | - package-ecosystem: github-actions 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | reviewers: 20 | - WanzenBug 21 | groups: 22 | ci: 23 | patterns: 24 | - "*" # Include all github-actions update in one PR 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - v2 9 | pull_request: 10 | jobs: 11 | build-and-push: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up QEMU 18 | uses: docker/setup-qemu-action@v3 19 | with: 20 | platforms: linux/amd64,linux/arm64 21 | - name: Set up Docker Buildx 22 | id: buildx 23 | uses: docker/setup-buildx-action@v3 24 | - name: login to registry 25 | if: ${{ github.event_name != 'pull_request' }} 26 | env: 27 | QUAYIO_USERNAME: ${{ secrets.DOCKER_USERNAME }} 28 | QUAYIO_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 29 | run: | 30 | docker login --username=${QUAYIO_USERNAME} --password-stdin quay.io <<< "${QUAYIO_PASSWORD}" 31 | - name: Docker meta 32 | id: meta 33 | uses: docker/metadata-action@v5 34 | with: 35 | images: | 36 | quay.io/piraeusdatastore/piraeus-operator 37 | tags: | 38 | type=sha 39 | type=raw,value=${{ github.ref_name }} 40 | type=ref,event=tag 41 | type=ref,event=pr 42 | - name: Build 43 | uses: docker/build-push-action@v6 44 | with: 45 | builder: ${{ steps.buildx.outputs.name }} 46 | platforms: linux/amd64,linux/arm64 47 | push: ${{ github.event_name != 'pull_request' }} 48 | tags: ${{ steps.meta.outputs.tags }} 49 | labels: ${{ steps.meta.outputs.labels }} 50 | no-cache: ${{ github.ref_type == 'tag' }} 51 | cache-from: type=gha 52 | cache-to: type=gha,mode=max 53 | build-args: | 54 | VERSION=${{ github.ref_name }}-${{ github.sha }} 55 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - v2 9 | pull_request: 10 | jobs: 11 | golangci: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: '1.24' 18 | - uses: golangci/golangci-lint-action@v8 19 | with: 20 | args: --timeout=3m 21 | 22 | pre-commit: 23 | runs-on: ubuntu-22.04 24 | env: 25 | # These hooks will run separately in github actions 26 | SKIP: golangci-lint,check-docs 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Setup Go environment 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version: '1.24' 33 | - uses: actions/setup-python@v5 34 | - name: Cache envtest assets 35 | uses: actions/cache@v4 36 | with: 37 | key: envtest-base 38 | path: | 39 | bin/k8s 40 | - name: Run pre-commit checks on changes files 41 | uses: pre-commit/action@v3.0.1 42 | compat-tests: 43 | runs-on: ubuntu-22.04 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions/setup-go@v5 47 | with: 48 | go-version: '1.24' 49 | - name: Cache envtest assets 50 | uses: actions/cache@v4 51 | with: 52 | key: envtest-compat 53 | path: | 54 | bin/k8s 55 | - name: Run compat test 56 | run: | 57 | make compat-test 58 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - v2 9 | pull_request: 10 | jobs: 11 | check-docs: 12 | if: ${{ github.event_name == 'pull_request' }} 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: 3.x 19 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 20 | - uses: actions/cache@v4 21 | with: 22 | key: mkdocs-material-${{ env.cache_id }} 23 | path: .cache 24 | restore-keys: | 25 | mkdocs-material- 26 | - run: pip install mkdocs-material[imaging] mike 27 | - run: mkdocs build -s 28 | 29 | deploy-docs: 30 | if: ${{ github.event_name != 'pull_request' }} 31 | runs-on: ubuntu-latest 32 | permissions: 33 | contents: write 34 | steps: 35 | - uses: actions/checkout@v4 36 | with: 37 | fetch-depth: '0' 38 | - uses: webfactory/ssh-agent@v0.9.1 39 | with: 40 | ssh-private-key: ${{ secrets.GH_PAGES_REPO_KEY }} 41 | - name: Configure Git 42 | run: | 43 | git config user.name github-actions[bot] 44 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 45 | # Deployed docs are in another repo for nice URLs 46 | git remote add gh-pages git@github.com:piraeusdatastore/docs.git 47 | git fetch gh-pages gh-pages --depth=1 48 | - uses: actions/setup-python@v5 49 | with: 50 | python-version: 3.x 51 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 52 | - uses: actions/cache@v4 53 | with: 54 | key: mkdocs-material-${{ env.cache_id }} 55 | path: .cache 56 | restore-keys: | 57 | mkdocs-material- 58 | - run: pip install mkdocs-material[imaging] mike 59 | - name: Deploy latest docs 60 | if: ${{ github.ref_type == 'branch' }} 61 | run: mike deploy -u -r gh-pages -p v2 latest 62 | - name: Deploy release docs 63 | if: ${{ github.ref_type == 'tag' }} 64 | run: mike deploy -u -r gh-pages -p ${{ github.ref_name }} stable 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Build Files 2 | build/_test 3 | # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | # Org-mode 15 | .org-id-locations 16 | *_archive 17 | # flymake-mode 18 | *_flymake.* 19 | # eshell files 20 | /eshell/history 21 | /eshell/lastdir 22 | # elpa packages 23 | /elpa/ 24 | # reftex files 25 | *.rel 26 | # AUCTeX auto folder 27 | /auto/ 28 | # cask packages 29 | .cask/ 30 | dist/ 31 | # Flycheck 32 | flycheck_*.el 33 | # server auth directory 34 | /server/ 35 | # projectiles files 36 | .projectile 37 | projectile-bookmarks.eld 38 | # directory configuration 39 | .dir-locals.el 40 | # saveplace 41 | places 42 | # url cache 43 | url/cache/ 44 | # cedet 45 | ede-projects.el 46 | # smex 47 | smex-items 48 | # company-statistics 49 | company-statistics-cache.el 50 | # anaconda-mode 51 | anaconda-mode/ 52 | ### Go ### 53 | # Binaries for programs and plugins 54 | *.exe 55 | *.exe~ 56 | *.dll 57 | *.so 58 | *.dylib 59 | bin 60 | testbin/* 61 | # Test binary, build with 'go test -c' 62 | *.test 63 | # Output of the go coverage tool, specifically when used with LiteIDE 64 | *.out 65 | ### Vim ### 66 | # swap 67 | .sw[a-p] 68 | .*.sw[a-p] 69 | # session 70 | Session.vim 71 | # temporary 72 | .netrwhist 73 | # auto-generated tag files 74 | tags 75 | ### VisualStudioCode ### 76 | .vscode/* 77 | .history 78 | # End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 79 | .DS_Store 80 | 81 | # helm chart dependencies 82 | /charts/piraeus/charts/*.tgz 83 | 84 | # OLM Bundle 85 | /bundle.Dockerfile 86 | /bundle 87 | 88 | # mkdocs files 89 | /.cache 90 | /site 91 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: standard 4 | settings: 5 | staticcheck: 6 | # Default: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] 7 | # Disabled QF1008 ("Omit embedded fields from selector expression"): using embedded fields is often more readable. 8 | checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1008"] 9 | exclusions: 10 | paths: 11 | - ".*/zz_generated.*\\.go$" 12 | 13 | formatters: 14 | enable: 15 | - gofumpt 16 | exclusions: 17 | paths: 18 | - ".*/zz_generated.*\\.go$" 19 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | exclude: | 11 | (?x)^( 12 | charts/.*/templates/.*| 13 | mkdocs.yml 14 | )$ 15 | args: 16 | - --multi 17 | - id: check-added-large-files 18 | - repo: https://github.com/Bahjat/pre-commit-golang 19 | rev: v1.0.5 20 | hooks: 21 | - id: gofumpt 22 | types: [go] 23 | exclude: ".*/zz_generated.*\\.go$" 24 | - repo: local 25 | hooks: 26 | - id: generate-deep-copy 27 | name: generated deep-copy code must be up-to-date 28 | language: system 29 | pass_filenames: false 30 | entry: make manifests 31 | - id: golangci-lint 32 | name: Run golangci-lint 33 | types: [go] 34 | language: system 35 | pass_filenames: false 36 | entry: golangci-lint run --new-from-rev=HEAD 37 | - id: run-tests 38 | name: Run go tests 39 | language: system 40 | pass_filenames: false 41 | entry: make test 42 | - id: sync-chart 43 | name: Run make sync-chart 44 | language: system 45 | pass_filenames: false 46 | entry: make sync-chart 47 | - id: check-docs 48 | name: check docs builds 49 | language: system 50 | pass_filenames: false 51 | entry: mkdocs build -s 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | docs/CHANGELOG.md -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development recommendations 2 | 3 | Before continuing, make sure you've read the [README](./README.md) 4 | 5 | ## Development tools 6 | 7 | This is a (probably incomplete) list of software used to develop the operator: 8 | 9 | * [`golang >= v1.21`](https://golang.org/) ...the operator is written in it... 10 | * [`operator-sdk`](https://sdk.operatorframework.io/) Provides all the plumbing and project structure 11 | * [`pre-commit`](https://pre-commit.com/) Used to ensure all files are formatted, generated code is up to date and more 12 | * [`gofumpt`](https://github.com/mvdan/gofumpt) Used for code formatting 13 | * [`golangci-lint`](https://github.com/golangci/golangci-lint) Lints for go code 14 | 15 | Some additional software you may find useful: 16 | 17 | * [`virter`](https://github.com/linbit/virter) can create virtual machines (for example a virtual kubernetes cluster) 18 | 19 | ### Commit hooks 20 | 21 | Commit hooks ensure that all new and changed code is formatted and tested before committing. To set up commit hooks 22 | install `pre-commit` (see above) and run: 23 | 24 | ``` 25 | $ pre-commit install 26 | ``` 27 | 28 | Now, if you try to commit a file that for example is not properly formatted, you will receive a message like: 29 | 30 | ``` 31 | $ git commit -a -m "important fix; no time to check for errors" 32 | Trim Trailing Whitespace.................................................Passed 33 | Fix End of Files.........................................................Passed 34 | Check Yaml...........................................(no files to check)Skipped 35 | Check for added large files..............................................Passed 36 | gofumpt..................................................................Failed 37 | - hook id: gofumpt 38 | - exit code: 1 39 | - files were modified by this hook 40 | 41 | version/version.go 42 | 43 | golangci-lint............................................................Failed 44 | - hook id: golangci-lint 45 | - exit code: 1 46 | ``` 47 | 48 | ## Tests 49 | 50 | Use `make test` to run our test suite. It will download the required control plane binaries to execute the webhook and 51 | controller tests. 52 | 53 | For basic unit testing use the basic go test framework. If something you want to test relies on the Kubernetes API, 54 | check out the test suite for the [`controllers`](./internal/controller/suite_test.go) 55 | 56 | As of right now, there is no recommended way to end-to-end test the operator. It probably involves some 57 | virtual machines running a basic kubernetes cluster. 58 | 59 | ## Automated Tests 60 | 61 | On every pull request, we run a set of tests, specified in `.github/workflows`. The checks include 62 | * `go test` 63 | * `golandci-lint` 64 | * `pre-commit run` 65 | 66 | ## Commits 67 | 68 | This repository enforces [Developer Certificate of Origin](https://developercertificate.org/) for every commit. Every 69 | commit needs a `Signed-off-by` line. You can add them by passing the `-s` flag to `git commit`: 70 | 71 | ``` 72 | $ git commit -s -m 'This is my commit message' 73 | ``` 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | # Build the manager binary 3 | FROM --platform=$BUILDPLATFORM golang:1 as builder 4 | 5 | WORKDIR /workspace 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | # cache deps before building and copying source so that we don't need to re-download as much 10 | # and so that source changes don't invalidate our downloaded layer 11 | RUN go mod download 12 | 13 | # Copy the go source 14 | COPY cmd/ cmd/ 15 | COPY api/ api/ 16 | COPY internal/ internal/ 17 | COPY pkg/ pkg/ 18 | 19 | # Build 20 | ARG VERSION=devel 21 | ARG TARGETARCH 22 | ARG TARGETOS 23 | RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -ldflags "-X github.com/piraeusdatastore/piraeus-operator/v2/pkg/vars.Version=$VERSION" -o manager ./cmd 24 | RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -ldflags "-X github.com/piraeusdatastore/piraeus-operator/v2/pkg/vars.Version=$VERSION" -o gencert ./cmd/gencert 25 | 26 | # Use distroless as minimal base image to package the manager binary 27 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 28 | FROM gcr.io/distroless/static:nonroot 29 | WORKDIR / 30 | COPY --from=builder /workspace/manager /workspace/gencert / 31 | USER 65532:65532 32 | 33 | ENTRYPOINT ["/manager"] 34 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: piraeus.io 6 | layout: 7 | - go.kubebuilder.io/v4 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: piraeus-operator 12 | repo: github.com/piraeusdatastore/piraeus-operator/v2 13 | resources: 14 | - api: 15 | crdVersion: v1 16 | domain: piraeus.io 17 | kind: LinstorCluster 18 | path: github.com/piraeusdatastore/piraeus-operator/v2/api/v1 19 | version: v1 20 | webhooks: 21 | validation: true 22 | webhookVersion: v1 23 | - api: 24 | crdVersion: v1 25 | domain: piraeus.io 26 | kind: LinstorSatelliteConfiguration 27 | path: github.com/piraeusdatastore/piraeus-operator/v2/api/v1 28 | version: v1 29 | webhooks: 30 | validation: true 31 | webhookVersion: v1 32 | - api: 33 | crdVersion: v1 34 | domain: piraeus.io 35 | kind: LinstorSatellite 36 | path: github.com/piraeusdatastore/piraeus-operator/v2/api/v1 37 | version: v1 38 | webhooks: 39 | validation: true 40 | webhookVersion: v1 41 | - api: 42 | crdVersion: v1 43 | controller: true 44 | domain: piraeus.io 45 | kind: LinstorNodeConnection 46 | path: github.com/piraeusdatastore/piraeus-operator/v2/api/v1 47 | version: v1 48 | webhooks: 49 | validation: true 50 | webhookVersion: v1 51 | version: "3" 52 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | docs/upgrade/README.md -------------------------------------------------------------------------------- /api/v1/component_spec.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type ComponentSpec struct { 8 | // Enable the component. 9 | // +kubebuilder:default:=true 10 | // +kubebuilder:validation:Optional 11 | Enabled bool `json:"enabled,omitempty"` 12 | 13 | // Template to apply to Pods of the component. 14 | // 15 | // The template is applied as a patch to the default deployment, so it can be "sparse", not listing any 16 | // containers or volumes that should remain unchanged. 17 | // See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates 18 | // +kubebuilder:validation:Optional 19 | // +kubebuilder:validation:Schemaless 20 | // +kubebuilder:validation:Type=object 21 | // +kubebuilder:pruning:PreserveUnknownFields 22 | // +structType=atomic 23 | PodTemplate json.RawMessage `json:"podTemplate,omitempty"` 24 | } 25 | 26 | func (c *ComponentSpec) IsEnabled() bool { 27 | return c == nil || c.Enabled 28 | } 29 | 30 | func (c *ComponentSpec) GetTemplate() json.RawMessage { 31 | if c == nil { 32 | return nil 33 | } 34 | 35 | return c.PodTemplate 36 | } 37 | -------------------------------------------------------------------------------- /api/v1/deletion_policy.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // DeletionPolicy configures the way LinstorSatellite resources are deleted. 4 | // 5 | // A LinstorSatellite may be deleted because: 6 | // * It no longer matches the affinity and node selector of the LinstorCluster resource. 7 | // * The node it references has been removed from Kubernetes. 8 | // * It was manually deleted outside the Operator. 9 | // 10 | // A LinstorSatellite may store the last copy of a volume, in which case it is not desirable to unconditionally remove 11 | // the satellite from the cluster. For this reason, the following deletion policies exist: 12 | // 13 | // * DeletionPolicyEvacuate will start evacuation of the LINSTOR Satellite and wait until it completes before removing the LinstorSatellite object, comparable to the "linstor node evacuate" command. 14 | // * DeletionPolicyRetain will retain the LINSTOR Satellite, keeping it registered in LINSTOR, but removing associated Kubernetes resources. 15 | // * DeletionPolicyDelete will remove the LINSTOR Satellite from the LINSTOR Cluster without prior eviction, comparable to the "linstor node lost" command. 16 | // +kubebuilder:validation:Enum:=Evacuate;Retain;Delete 17 | type DeletionPolicy string 18 | 19 | const ( 20 | DeletionPolicyEvacuate DeletionPolicy = "Evacuate" 21 | DeletionPolicyRetain DeletionPolicy = "Retain" 22 | DeletionPolicyDelete DeletionPolicy = "Delete" 23 | ) 24 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 v1 contains API Schema definitions for the v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=piraeus.io 20 | package v1 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: "piraeus.io", Version: "v1"} 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/v1/ipfamilies.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import corev1 "k8s.io/api/core/v1" 4 | 5 | // IPFamily represents the IP Family (IPv4 or IPv6). 6 | // +kubebuilder:validation:Enum:=IPv4;IPv6 7 | type IPFamily corev1.IPFamily 8 | -------------------------------------------------------------------------------- /api/v1/properties.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | type LinstorControllerProperty struct { 4 | // Name of the property to set. 5 | //+kubebuilder:validation:MinLength=1 6 | //+kubebuilder:validation:Required 7 | Name string `json:"name"` 8 | 9 | // Value to set the property to. 10 | Value string `json:"value,omitempty"` 11 | } 12 | 13 | type LinstorNodeProperty struct { 14 | // Name of the property to set. 15 | //+kubebuilder:validation:MinLength=1 16 | //+kubebuilder:validation:Required 17 | Name string `json:"name"` 18 | 19 | // Value to set the property to. 20 | //+kubebuilder:validation:Optional 21 | Value string `json:"value,omitempty"` 22 | 23 | // ValueFrom sets the value from an existing resource. 24 | //+kubebuilder:validation:Optional 25 | ValueFrom *LinstorNodePropertyValueFrom `json:"valueFrom,omitempty"` 26 | 27 | // ExpandFrom can reference multiple resource fields at once. 28 | // It either sets the property to an aggregate value based on matched resource fields, or expands to multiple 29 | // properties. 30 | //+kubebuilder:validation:Optional 31 | ExpandFrom *LinstorNodePropertyExpandFrom `json:"expandFrom,omitempty"` 32 | 33 | // Optional values are only set if they have a non-empty value 34 | //+kubebuilder:validation:Optional 35 | Optional bool `json:"optional,omitempty"` 36 | } 37 | 38 | type LinstorNodePropertyValueFrom struct { 39 | // Select a field of the node. Supports `metadata.name`, `metadata.labels['']`, `metadata.annotations['']`. 40 | //+kubebuilder:validation:MinLength=1 41 | //+kubebuilder:validation:Required 42 | NodeFieldRef string `json:"nodeFieldRef,omitempty"` 43 | } 44 | 45 | type LinstorNodePropertyExpandFrom struct { 46 | LinstorNodePropertyValueFrom `json:",inline"` 47 | 48 | // NameTemplate defines how the property key is expanded. 49 | // If set, the template is appended to the defined property name, creating multiple properties instead of one 50 | // aggregate. 51 | // * $1 is replaced with the matched key. 52 | // * $2 is replaced with the matched value. 53 | //+kubebuilder:validation:Optional 54 | NameTemplate string `json:"nameTemplate,omitempty"` 55 | 56 | // ValueTemplate defines how the property value is expanded. 57 | // * $1 is replaced with the matched key. 58 | // * $2 is replaced with the matched value. 59 | //+kubebuilder:validation:Optional 60 | ValueTemplate string `json:"valueTemplate,omitempty"` 61 | 62 | // Delimiter used to join multiple key and value pairs together. 63 | //+kubebuilder:validation:Optional 64 | Delimiter string `json:"delimiter,omitempty"` 65 | } 66 | -------------------------------------------------------------------------------- /charts/piraeus/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/piraeus/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: piraeus 3 | description: | 4 | The Piraeus Operator manages software defined storage clusters using LINSTOR in Kubernetes. 5 | type: application 6 | version: 2.8.1 7 | appVersion: "v2.8.1" 8 | maintainers: 9 | - name: Piraeus Datastore 10 | url: https://piraeus.io 11 | home: https://piraeus.io 12 | icon: https://raw.githubusercontent.com/piraeusdatastore/piraeus/master/artwork/sandbox-artwork/icon/color.svg 13 | keywords: 14 | - storage 15 | sources: 16 | - https://github.com/piraeusdatastore/piraeus-operator 17 | - https://github.com/piraeusdatastore/linstor-csi 18 | - https://github.com/LINBIT/linstor-server 19 | -------------------------------------------------------------------------------- /charts/piraeus/README.md: -------------------------------------------------------------------------------- 1 | # Piraeus Operator 2 | 3 | Deploys the [Piraeus Operator](https://github.com/piraeusdatastore/piraeus-operator) which deploys and manages a simple 4 | and resilient storage solution for Kubernetes. 5 | 6 | The main deployment method for Piraeus Operator switched to [`kustomize`](../../docs/tutorial) 7 | in release `v2.0.0`. This chart is intended for users who want to continue using Helm. 8 | 9 | This chart **only** configures the Operator, but does not create the `LinstorCluster` resource creating the actual 10 | storage system. Refer to the existing [tutorials](../../docs/tutorial) 11 | and [how-to guides](../../docs/how-to). 12 | 13 | ## Deploying Piraeus Operator 14 | 15 | To deploy Piraeus Operator with Helm, clone this repository and deploy the chart: 16 | 17 | ``` 18 | $ git clone --branch v2 https://github.com/piraeusdatastore/piraeus-operator 19 | $ cd piraeus-operator 20 | $ helm install piraeus-operator charts/piraeus-operator --create-namespace -n piraeus-datastore 21 | ``` 22 | 23 | Follow the instructions printed by Helm to create your storage cluster: 24 | 25 | ``` 26 | $ kubectl apply -f - < 16 | 17 | - :material-clock-fast:{ .lg .middle } __Tutorials__ 18 | 19 | --- 20 | Get started with Piraeus Datastore. These tutorials will guide you through the basics of setting up 21 | Piraeus Datastore. 22 | 23 | [:octicons-arrow-right-24: Tutorials](./tutorial/README.md) 24 | 25 | - :material-frequently-asked-questions:{ .lg .middle } __How-To Guides__ 26 | 27 | --- 28 | 29 | How-To Guides show you how to configure a specific aspect or achieve a specific task with Piraeus Datastore. 30 | 31 | [:octicons-arrow-right-24: How-To Guides](./how-to/README.md) 32 | 33 | - :material-update:{ .lg .middle } __Upgrades__ 34 | 35 | --- 36 | 37 | Read how to upgrade a Piraeus Datastore deployment 38 | 39 | [:octicons-arrow-right-24: Upgrades](./upgrade/README.md) 40 | 41 | - :material-school:{ .lg .middle } __Understanding Piraeus Datastore__ 42 | 43 | --- 44 | 45 | Read how Piraeus Datastore and it's components work, and why they work the way they do. 46 | 47 | [:octicons-arrow-right-24: Understanding Piraeus](./explanation/README.md) 48 | 49 | - :octicons-checklist-24:{ .lg .middle } __Reference__ 50 | 51 | --- 52 | 53 | The API Reference for the Piraeus Operator. Contains documentation of the LINSTOR related resources that the user 54 | can modify or observe. 55 | 56 | [:octicons-arrow-right-24: Reference](./reference/README.md) 57 | 58 | -------------------------------------------------------------------------------- /docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/assets/grafana-piraeus-datastore-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/grafana-piraeus-datastore-dashboard.png -------------------------------------------------------------------------------- /docs/assets/mok-continue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/mok-continue.png -------------------------------------------------------------------------------- /docs/assets/mok-enroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/mok-enroll.png -------------------------------------------------------------------------------- /docs/assets/mok-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/mok-password.png -------------------------------------------------------------------------------- /docs/assets/mok-reboot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/mok-reboot.png -------------------------------------------------------------------------------- /docs/assets/prometheus-console-drbd_version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/prometheus-console-drbd_version.png -------------------------------------------------------------------------------- /docs/assets/prometheus-console-linstor_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraeusdatastore/piraeus-operator/f4b63a6a9f04e13f0ea6a789726a330fc64ec9b5/docs/assets/prometheus-console-linstor_info.png -------------------------------------------------------------------------------- /docs/explanation/README.md: -------------------------------------------------------------------------------- 1 | # Understanding Piraeus Datastore 2 | 3 | The following documents explain how Piraeus Datastore works, and why it works the way it does. 4 | 5 | ### [Understanding Piraeus Datastore Components](./components.md) 6 | 7 | A short introduction for every component of Piraeus Datastore. 8 | -------------------------------------------------------------------------------- /docs/how-to/README.md: -------------------------------------------------------------------------------- 1 | # How To 2 | 3 | These guides show you how to configure a specific aspect or achieve a specific task with Piraeus Datastore. 4 | 5 | ## Advanced Deployments 6 |
7 | 8 | * Use Piraeus Datastore with an Existing LINSTOR Cluster 9 | 10 | [:octicons-arrow-right-24: Guide](./external-controller.md) 11 | 12 | * Configure the DRBD Module Loader 13 | 14 | [:octicons-arrow-right-24: Guide](./drbd-loader.md) 15 | 16 |
17 | 18 | ## Securing Components 19 |
20 | 21 | * Configure TLS Between LINSTOR Controller and LINSTOR Satellite 22 | 23 | [:octicons-arrow-right-24: Guide](./internal-tls.md) 24 | 25 | * Configure TLS for the LINSTOR API 26 | 27 | [:octicons-arrow-right-24: Guide](./api-tls.md) 28 | 29 | * Configure TLS for DRBD Replication 30 | 31 | [:octicons-arrow-right-24: Guide](./drbd-tls.md) 32 | 33 | * Load DRBD with SecureBoot Enabled 34 | 35 | [:octicons-arrow-right-24: Guide](./secure-boot.md) 36 | 37 |
38 | 39 | ## Kubernetes Distributions 40 |
41 | 42 | * Deploy Piraeus Datastore on OpenShift 43 | 44 | [:octicons-arrow-right-24: Guide](./openshift.md) 45 | 46 | * Deploy Piraeus Datastore on Talos Linux 47 | 48 | [:octicons-arrow-right-24: Guide](./talos.md) 49 | 50 | * Deploy Piraeus Datastore on Flatcar Container Linux 51 | 52 | [:octicons-arrow-right-24: Guide](./flatcar.md) 53 | 54 | * Deploy Piraeus Datastore on MicroK8s 55 | 56 | [:octicons-arrow-right-24: Guide](./microk8s.md) 57 | 58 | * Deploy Piraeus Datastore on k0s 59 | 60 | [:octicons-arrow-right-24: Guide](./k0s.md) 61 | 62 |
63 | 64 | ## Networking 65 |
66 | 67 | * Deploy Piraeus Datastore behind an HTTP Proxy 68 | 69 | [:octicons-arrow-right-24: Guide](./http-proxy.md) 70 | 71 | * Deploy a NetworkPolicy for Piraeus Datastore 72 | 73 | [:octicons-arrow-right-24: Guide](./network-policy.md) 74 | 75 | * Use the Host Network for DRBD Replication 76 | 77 | [:octicons-arrow-right-24: Guide](./drbd-host-networking.md) 78 | 79 |
80 | 81 | ## Maintenance Tasks 82 |
83 | 84 | * Monitor Piraeus Datastore with Prometheus Operator 85 | 86 | [:octicons-arrow-right-24: Guide](./monitoring.md) 87 | 88 | * Keep Persistent Volume Affinity Updated with LINSTOR Affinity Controller 89 | 90 | [:octicons-arrow-right-24: Guide](./linstor-affinity-controller.md) 91 | 92 | * Restore a LINSTOR Database Backup 93 | 94 | [:octicons-arrow-right-24: Guide](./restore-linstor-db.md) 95 | 96 |
97 | -------------------------------------------------------------------------------- /docs/how-to/flatcar.md: -------------------------------------------------------------------------------- 1 | # How to Load DRBD on Flatcar Container Linux 2 | 3 | This guide shows you how to set up the DRBD® Module Loader when using [Flatcar Container Linux](https://www.flatcar.org). 4 | 5 | To complete this guide, you should be familiar with: 6 | 7 | * editing `LinstorSatelliteConfiguration` resources. 8 | 9 | ## Configure the DRBD Module Loader 10 | 11 | Flatcar Container Linux uses a read-only `/usr` file system. For building DRBD from source on Flatcar Container Linux, 12 | the default bind mount for the not existing `/usr/src` directory needs to be disabled for the `drbd-module-loader` init container. 13 | 14 | To change the configuration for the `drbd-module-loader` container, apply the following `LinstorSatelliteConfiguration`: 15 | 16 | ```yaml 17 | --- 18 | apiVersion: piraeus.io/v1 19 | kind: LinstorSatelliteConfiguration 20 | metadata: 21 | name: no-usr-src-mount 22 | spec: 23 | podTemplate: 24 | spec: 25 | volumes: 26 | - name: usr-src 27 | $patch: delete 28 | initContainers: 29 | - name: drbd-module-loader 30 | volumeMounts: 31 | - mountPath: /usr/src 32 | name: usr-src 33 | $patch: delete 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/how-to/http-proxy.md: -------------------------------------------------------------------------------- 1 | # How to Use Piraeus Datastore with an HTTP Proxy 2 | 3 | This guide shows you how to configure the DRBD® Module Loader when using a HTTP Proxy. 4 | 5 | To complete this guide, you should be familiar with: 6 | 7 | * editing `LinstorSatelliteConfiguration` resources. 8 | * using the `kubectl` command line tool to access the Kubernetes cluster. 9 | 10 | ## Configuration 11 | 12 | We will use environment variables to configure the proxy, this tells the drbd-module-loader component to use the proxy for outgoing communication. 13 | 14 | Configure the sample below according to your environment and apply the configuration using `kubectl apply -f filename.yml`. 15 | 16 | This sample configuration assumes that a HTTP proxy is reacheable at `http://10.0.0.1:3128`. 17 | 18 | ```yaml 19 | apiVersion: piraeus.io/v1 20 | kind: LinstorSatelliteConfiguration 21 | metadata: 22 | name: http-proxy 23 | spec: 24 | podTemplate: 25 | spec: 26 | initContainers: 27 | - name: drbd-module-loader 28 | env: 29 | - name: HTTP_PROXY 30 | value: http://10.0.0.1:3128 # Add your proxy connection here 31 | - name: HTTPS_PROXY 32 | value: http://10.0.0.1:3128 # Add your proxy connection here 33 | - name: NO_PROXY 34 | value: localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12 # Add internal IP ranges and domains here 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/how-to/install-kernel-headers.md: -------------------------------------------------------------------------------- 1 | # Installing Linux Kernel Headers 2 | 3 | Piraeus Datastore uses [DRBD®](https://linbit.com/DRBD/) to mirror your volume data to one or more nodes. This document 4 | aims to describe the way to install the necessary files for common Linux distributions. 5 | 6 | To check if your nodes already have the necessary files installed, try running: 7 | 8 | ``` 9 | $ test -d /lib/modules/$(uname -r)/build/ && echo found headers 10 | ``` 11 | 12 | If the command prints `found headers`, your nodes are good to go. 13 | 14 | ## Installing on Ubuntu 15 | 16 | Installation of Linux Kernel headers on Ubuntu can be done through the `apt` package manager: 17 | 18 | ``` 19 | $ sudo apt-get update 20 | $ sudo apt-get install -y linux-headers-$(uname -r) 21 | ``` 22 | 23 | In addition, you can install the `linux-headers-virtual` package. This causes `apt upgrade` to install the headers 24 | matching any newly installed kernel versions. 25 | 26 | ``` 27 | $ sudo apt-get update 28 | $ sudo apt-get install -y linux-headers-virtual 29 | ``` 30 | 31 | ## Installing on Debian 32 | 33 | Installation of Linux Kernel headers on Debian can be done through the `apt` package manager: 34 | 35 | ``` 36 | $ sudo apt-get update 37 | $ sudo apt-get install -y linux-headers-$(uname -r) 38 | ``` 39 | 40 | In addition, you can install an additional package, that causes `apt` to also install Kernel headers on upgrade: 41 | 42 | ``` 43 | $ sudo apt-get update 44 | $ sudo apt-get install -y linux-headers-$(dpkg --print-architecture) 45 | ``` 46 | 47 | ## Installing on RedHat Enterprise Linux 48 | 49 | Installing on RedHat Enterprise Linux or compatible distributions, such as AlmaLinux or Rocky Linux can be done through 50 | the `dnf` package manager: 51 | 52 | ``` 53 | $ sudo dnf install -y kernel-devel-$(uname -r) 54 | ``` 55 | 56 | In addition, you can install an additional package, that causes `yum` to also install Kernel headers on upgrade: 57 | 58 | ``` 59 | $ sudo dnf install -y kernel-devel 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/how-to/k0s.md: -------------------------------------------------------------------------------- 1 | # How to Configure Piraeus Datastore on k0s 2 | 3 | This guide shows you how to configure Piraeus Datastore when using [k0s](https://k0sproject.io). 4 | 5 | To complete this guide, you should be familiar with: 6 | 7 | * editing `LinstorCluster` resources. 8 | 9 | ## Configure the CSI Driver 10 | 11 | Because k0s store their state in a separate directory (`/var/lib/k0s`) the LINSTOR CSI Driver needs to be updated to use a new path for mounting volumes. 12 | 13 | To change the LINSTOR CSI Driver, so that it uses the k0s state paths, apply the following `LinstorCluster`: 14 | 15 | ```yaml 16 | apiVersion: piraeus.io/v1 17 | kind: LinstorCluster 18 | metadata: 19 | name: linstorcluster 20 | spec: 21 | csiNode: 22 | enabled: true 23 | podTemplate: 24 | spec: 25 | containers: 26 | - name: linstor-csi 27 | volumeMounts: 28 | - mountPath: /var/lib/k0s/kubelet 29 | name: publish-dir 30 | mountPropagation: Bidirectional 31 | - name: csi-node-driver-registrar 32 | args: 33 | - --v=5 34 | - --csi-address=/csi/csi.sock 35 | - --kubelet-registration-path=/var/lib/k0s/kubelet/plugins/linstor.csi.linbit.com/csi.sock 36 | - --health-port=9809 37 | volumes: 38 | - name: publish-dir 39 | hostPath: 40 | path: /var/lib/k0s/kubelet 41 | - name: registration-dir 42 | hostPath: 43 | path: /var/lib/k0s/kubelet/plugins_registry 44 | - name: plugin-dir 45 | hostPath: 46 | path: /var/lib/k0s/kubelet/plugins/linstor.csi.linbit.com 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/how-to/linstor-affinity-controller.md: -------------------------------------------------------------------------------- 1 | # How to Deploy the LINSTOR Affinity Controller 2 | 3 | This guide shows you how to deploy the [LINSTOR Affinity Controller] for Piraeus Datastore. 4 | 5 | The LINSTOR Affinity Controller keeps the affinity of your volumes in sync between Kubernetes and LINSTOR. 6 | 7 | When using a strict volume affinity setting, such as `allowRemoteVolumeAccess: false`, the Persistent Volume (PV) 8 | resource created by Piraeus Datastore will have a fixed affinity. When the volume is moved to a different node, for 9 | example because one of the existing replicas is being evacuated, the PV is not updated automatically. 10 | 11 | The LINSTOR Affinity controller watches PVs and LINSTOR resource and keeps the affinity up-to-date. 12 | 13 | To complete this guide, you should be familiar with: 14 | 15 | * Deploying workloads in Kubernetes using [`helm`](https://helm.sh/) 16 | 17 | ## Add the Piraeus Datastore Chart Repository 18 | 19 | Piraeus Datastore maintains a helm chart repository for commonly deployed components, including the LINSTOR Affinity 20 | Controller. To add the repository to your helm configuration, run: 21 | 22 | ``` 23 | $ helm repo add piraeus-charts https://piraeus.io/helm-charts/ 24 | ``` 25 | 26 | ## Deploy the LINSTOR Affinity Controller 27 | 28 | After adding the repository, deploy the LINSTOR Affinity Controller: 29 | 30 | ``` 31 | $ helm install linstor-affinity-controller piraeus-charts/linstor-affinity-controller 32 | NAME: linstor-affinity-controller 33 | LAST DEPLOYED: Mon Dec 4 09:14:07 2023 34 | NAMESPACE: piraeus-datastore 35 | STATUS: deployed 36 | REVISION: 1 37 | TEST SUITE: None 38 | NOTES: 39 | LINSTOR Affinity Controller deployed. 40 | 41 | Used LINSTOR URL: http://linstor-controller.piraeus-datastore.svc:3370 42 | ``` 43 | 44 | If you deploy the LINSTOR Affinity Controller to the same namespace as the Piraeus Operator, the deployment will 45 | automatically determine the necessary parameters for connecting to LINSTOR. 46 | 47 | In some cases, helm may not be able to determine the connection parameters. In this case, you need to manually provide 48 | the following values: 49 | 50 | ```yaml 51 | linstor: 52 | # The URL of the LINSTOR Controller API. This example contains the default value. 53 | endpoint: http://linstor-controller.piraeus-datastore.svc:3370 54 | # This is the default URL when using TLS for securing the API 55 | #endpoint: https://linstor-controller.piraeus-datastore.svc:3371 56 | # This is the name of the secret containing TLS key and certificates for connecting to the LINSTOR API with TLS. 57 | clientSecret: "" 58 | options: 59 | # This is the namespace used by Piraeus Operator to sync Kubernetes Node labels with LINSTOR node properties. 60 | # For Piraeus Operator, this needs to be set to the following value: 61 | propertyNamespace: Aux/topology 62 | ``` 63 | 64 | [LINSTOR Affinity Controller]: https://github.com/piraeusdatastore/linstor-affinity-controller 65 | -------------------------------------------------------------------------------- /docs/how-to/microk8s.md: -------------------------------------------------------------------------------- 1 | # How to Configure Piraeus Datastore on MicroK8s 2 | 3 | This guide shows you how to configure Piraeus Datastore when using [MicroK8s](https://microk8s.io/). 4 | 5 | To complete this guide, you should be familiar with: 6 | 7 | * editing `LinstorCluster` resources. 8 | 9 | ## Configure the CSI Driver 10 | 11 | MicroK8s is distributed as a [Snap](https://ubuntu.com/core/services/guide/snaps-intro). Because Snaps store their state 12 | in a separate directory (`/var/snap`) the LINSTOR CSI Driver needs to be updated to use a new path for mounting volumes. 13 | 14 | To change the LINSTOR CSI Driver, so that it uses the MicroK8s state paths, apply the following `LinstorCluster`: 15 | 16 | ```yaml 17 | apiVersion: piraeus.io/v1 18 | kind: LinstorCluster 19 | metadata: 20 | name: linstorcluster 21 | spec: 22 | patches: 23 | - target: 24 | name: linstor-csi-node 25 | kind: DaemonSet 26 | patch: | 27 | apiVersion: apps/v1 28 | kind: DaemonSet 29 | metadata: 30 | name: linstor-csi-node 31 | spec: 32 | template: 33 | spec: 34 | containers: 35 | - name: linstor-csi 36 | volumeMounts: 37 | - mountPath: /var/lib/kubelet 38 | name: publish-dir 39 | $patch: delete 40 | - mountPath: /var/snap/microk8s/common/var/lib/kubelet 41 | name: publish-dir 42 | mountPropagation: Bidirectional 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/how-to/network-policy.md: -------------------------------------------------------------------------------- 1 | # How to Deploy a NetworkPolicy for Piraeus Datastore 2 | 3 | [Network Policies] offer a way to restrict network access of specific pods. They can be used to block undesired 4 | network connections to specific Pods. 5 | 6 | Piraeus Datastore provides basic Network Policies that configure restricted access to the LINSTOR Satellites. 7 | The provided policy restricts incoming network connections to LINSTOR Satellite to only the following sources: 8 | 9 | * Other LINSTOR Satellite Pods to allow for DRBD replication 10 | * The LINSTOR Controller Pod, to facilitate LINSTOR Cluster operations 11 | * Metrics collection from DRBD Reactor. 12 | 13 | To deploy the Network Policy, apply the following remote resource: 14 | 15 | ``` 16 | $ kubectl apply --server-side -k "https://github.com/piraeusdatastore/piraeus-operator//config/extras/network-policy?ref=v2" 17 | networkpolicy.networking.k8s.io/satellite serverside-applied 18 | ``` 19 | 20 | [Network Policies]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ 21 | -------------------------------------------------------------------------------- /docs/how-to/openshift.md: -------------------------------------------------------------------------------- 1 | # How to Load DRBD on OpenShift 2 | 3 | This guide shows you how to set the DRBD® Module Loader when using OpenShift. 4 | 5 | To complete this guide, you should be familiar with: 6 | 7 | * editing `LinstorSatelliteConfiguration` resources. 8 | * using the `oc` command line tool to access the OpenShift cluster. 9 | 10 | ## Find the Driver Toolkit Image 11 | 12 | The [Driver Toolkit](https://github.com/openshift/driver-toolkit) image contains the necessary files for building DRBD. 13 | Every OpenShift release includes a version of the Driver Toolkit image matching the operating system version of the 14 | release. You can use `oc` to print the correct image version: 15 | 16 | ``` 17 | $ oc adm release info --image-for=driver-toolkit 18 | quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:1328c4e7944b6d8eda40a8f789471a1aec63abda75ac1199ce098b965ec16709 19 | ``` 20 | 21 | ## Configure the DRBD Module Loader 22 | 23 | By default, the DRBD Module Loader will try to find the necessary header files to build DRBD from source on the host 24 | system. In OpenShift, these header files are not included in the host system. Instead, they are included in the Driver 25 | Toolkit image. 26 | 27 | To change the DRBD Module Loader, so that it uses the header files included in the Driver Toolkit image, apply the 28 | following `LinstorSatelliteConfiguration`: 29 | 30 | ```yaml 31 | apiVersion: piraeus.io/v1 32 | kind: LinstorSatelliteConfiguration 33 | metadata: 34 | name: openshift-loader-override 35 | spec: 36 | podTemplate: 37 | spec: 38 | volumes: 39 | - name: usr-src 40 | emptyDir: { } 41 | hostPath: 42 | $patch: delete 43 | initContainers: 44 | - name: kernel-header-copy 45 | image: quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:1328c4e7944b6d8eda40a8f789471a1aec63abda75ac1199ce098b965ec16709 46 | args: 47 | - cp 48 | - -avt 49 | - /container/usr/src 50 | - /usr/src/kernels 51 | volumeMounts: 52 | - name: usr-src 53 | mountPath: /container/usr/src 54 | securityContext: 55 | privileged: true 56 | - name: drbd-module-loader 57 | securityContext: 58 | privileged: true 59 | ``` 60 | 61 | **NOTE**: Replace the `image` of the `kernel-header-copy` container with the image returned by `oc adm release info`. 62 | 63 | After the automatic restart of the LINSTOR® Satellite Pods, DRBD will be built from source, using the correct header 64 | files. 65 | 66 | ## OpenShift Update Considerations 67 | 68 | OpenShift updates also update the host operating system. Since every node will be rebooted during the update, the DRBD 69 | Module loader needs to rebuild DRBD once after the restart for the new host operating system. 70 | 71 | To ensure that the DRBD rebuild is successful, you need to update the image in the `openshift-loader-override` 72 | configuration before starting the upgrade process. You can extract the Driver Toolkit image for the new OpenShift 73 | version before starting the upgrade like this: 74 | 75 | ``` 76 | $ oc adm release info 4.12.2 --image-for=driver-toolkit 77 | quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:1328c4e7944b6d8eda40a8f789471a1aec63abda75ac1199ce098b965ec16709 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/how-to/upgrade/collect-operator-v1-information.sh: -------------------------------------------------------------------------------- 1 | ../../upgrade/migration/collect-operator-v1-information.sh -------------------------------------------------------------------------------- /docs/overrides/.icons/piraeus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/reference/README.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | This is the API Reference for Piraeus Operator. A user may make modifications to these resources to change the cluster 4 | state (`LinstorCluster` or `LinstorSatelliteConfiguration`) or check the status of a resource (`LinstorSatellite`). 5 | 6 |
7 | 8 | * __LinstorCluster__ 9 | 10 | --- 11 | 12 | This resource controls the state of the LINSTOR® cluster and integration with Kubernetes. 13 | 14 | [:octicons-arrow-right-24: Reference](./linstorcluster.md) 15 | 16 | * __LinstorSatelliteConfiguration__ 17 | 18 | --- 19 | 20 | This resource controls the state of the LINSTOR Satellites, optionally applying it to only a subset of nodes. 21 | 22 | [:octicons-arrow-right-24: Reference](./linstorsatelliteconfiguration.md) 23 | 24 | * __LinstorNodeConnection__ 25 | 26 | --- 27 | 28 | This resource controls the state of the LINSTOR® node connections. 29 | 30 | [:octicons-arrow-right-24: Reference](./linstornodeconnection.md) 31 | 32 | * __LinstorSatellite__ 33 | 34 | --- 35 | 36 | This resource controls the state of a single LINSTOR Satellite. This resource is not intended to be changed directly, 37 | instead it is created by the Piraeus Operator by merging all matching `LinstorSatelliteConfiguration` resources. 38 | 39 | [:octicons-arrow-right-24: Reference](./linstorsatellite.md) 40 | 41 |
42 | -------------------------------------------------------------------------------- /docs/reference/linstorsatellite.md: -------------------------------------------------------------------------------- 1 | # `LinstorSatellite` 2 | 3 | This resource controls the state of a LINSTOR® satellite. 4 | 5 | **NOTE:** This resource is not intended to be changed directly, instead it is created by the Piraeus Operator 6 | by merging all matching [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md) resources. 7 | 8 | ## `.spec` 9 | 10 | Holds the desired state the satellite. 11 | 12 | ### `.spec.repository` 13 | 14 | Holds the default image registry to use for all Piraeus images. Inherited from 15 | [`LinstorCluster`](./linstorcluster.md#specrepository). 16 | 17 | If empty (the default), the operator will use `quay.io/piraeusdatastore`. 18 | 19 | ### `.spec.clusterRef` 20 | 21 | Holds a reference to the [`LinstorCluster`](./linstorcluster.md) that controls this satellite. 22 | 23 | ### `.spec.storagePools` 24 | 25 | Holds the storage pools to configure on the node. Inherited from matching 26 | [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md#specstoragepools) resources. 27 | 28 | ### `.spec.properties` 29 | 30 | Holds the properties which should be set on the node level. Inherited from matching 31 | [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md#specproperties) resources. 32 | 33 | ### `.spec.internalTLS` 34 | 35 | Configures a TLS secret used by the LINSTOR Satellite. Inherited from matching 36 | [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md#specproperties) resources. 37 | 38 | ### `.spec.deletionPolicy` 39 | 40 | Configures the deletion policy to use for LINSTOR Satellites. Inherited from matching 41 | [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md#specdeletionpolicy) resources. 42 | 43 | ### `.spec.patches` 44 | 45 | Holds patches to apply to the Kubernetes resources. Inherited from matching 46 | [`LinstorSatelliteConfiguration`](./linstorsatelliteconfiguration.md#specpatches) resources. 47 | 48 | ## `.status` 49 | 50 | Reports the actual state of the satellite. 51 | 52 | ### `.status.conditions` 53 | 54 | The Operator reports the current state of the LINSTOR Satellite through a set of conditions. Conditions are 55 | identified by their `type`. 56 | 57 | | `type` | Explanation | 58 | |-----------------------|------------------------------------------------------------------------------------------------------| 59 | | `Applied` | All Kubernetes resources were applied. | 60 | | `Available` | The LINSTOR Satellite is connected to the LINSTOR Controller | 61 | | `Configured` | Storage Pools and Properties are configured on the Satellite | 62 | | `EvacuationCompleted` | Only available when the Satellite is being deleted: Indicates progress of the eviction of resources. | 63 | -------------------------------------------------------------------------------- /docs/tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | These tutorials help you get started with Piraeus Datastore 4 | 5 | ### [Get Started](./get-started.md) 6 | 7 | Get started with Piraeus Datastore by deploying Piraeus Operator and provisioning your first volume. 8 | 9 | ### [Creating Replicated Volumes](./replicated-volumes.md) 10 | 11 | Create replicated volumes, making your data accessible on any cluster node. 12 | 13 | ### [Creating and Restoring from Snapshots](./snapshots.md) 14 | 15 | Create snapshots and restore a volume, safeguarding data against accidental deletion. 16 | -------------------------------------------------------------------------------- /docs/upgrade/migration/3-remove-operator-v1.md: -------------------------------------------------------------------------------- 1 | # Remove the Piraeus Operator v1 Deployment 2 | 3 | Migrating to the new Piraeus Operator deployment requires temporarily removing the existing deployment. 4 | 5 | After this step no new volumes can be created and no existing volume can be attached or detached, until the 6 | new deployment is rolled out. 7 | 8 | This is the third step when migrating Piraeus Operator from version 1 (v1) to version 2 (v2). 9 | [Click here to get back to the overview](./README.md). 10 | 11 | ## Prerequisites 12 | 13 | * [Install `kubectl`](https://kubernetes.io/docs/tasks/tools/) 14 | * [Install `helm`](https://docs.helm.sh/docs/intro/install/) 15 | 16 | ## Scale Down the Operator Deployment 17 | 18 | To prevent modification of the existing cluster, scale down the existing Piraeus Operator deployment. 19 | 20 | ``` 21 | $ helm upgrade piraeus-op ./charts/piraeus --set operator.replicas=0 22 | $ kubectl rollout status -w deploy/piraeus-op-operator 23 | deployment "piraeus-op-operator" successfully rolled out 24 | ``` 25 | 26 | ## Remove the Finalizers from the Piraeus Resources 27 | 28 | The Operator sets Finalizers on the resource it controls. This prevents the deletion of these resources when the 29 | Operator is not running. Remove the Finalizers by applying a patch: 30 | 31 | ``` 32 | $ kubectl patch linstorsatellitesets piraeus-op-ns --type merge --patch '{"metadata": {"finalizers": []}}' 33 | linstorsatelliteset.piraeus.linbit.com/piraeus-op-ns patched 34 | $ kubectl patch linstorcontrollers piraeus-op-cs --type merge --patch '{"metadata": {"finalizers": []}}' 35 | linstorcontroller.piraeus.linbit.com/piraeus-op-cs patched 36 | ``` 37 | 38 | ## Remove the Piraeus Resources 39 | 40 | Having removed the Finalizers, you can delete the Piraeus Resources. This will stop the LINSTOR Cluster, and no 41 | new volumes can be created, and existing volumes will not attach or detach. Volumes already attached to a Pod will 42 | continue to replicate. 43 | 44 | ``` 45 | $ kubectl delete linstorcsidrivers/piraeus-op 46 | $ kubectl delete linstorsatellitesets/piraeus-op-ns 47 | $ kubectl delete linstorcontrollers/piraeus-op-cs 48 | ``` 49 | 50 | ## Remove the Piraeus Deployment 51 | 52 | As a last step, you can completely remove the helm deployment, deleting additional resources such as service accounts 53 | and RBAC resources. In addition, also clean up the Custom Resource Definitions: 54 | 55 | ``` 56 | $ helm uninstall piraeus-op 57 | $ kubectl delete crds linstorcsidrivers.piraeus.linbit.com linstorsatellitesets.piraeus.linbit.com linstorcontrollers.piraeus.linbit.com 58 | ``` 59 | 60 | ## Optional: Remove additional Piraeus Components 61 | 62 | If you have deployed additional components from Piraeus, such as the HA Controller or LINSTOR Affinity Controller, you 63 | will need to remove them, too. After completing the migration, you can install them again, if needed. 64 | 65 | ``` 66 | $ helm uninstall piraeus-ha-controller 67 | $ helm uninstall linstor-affinity-controller 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/upgrade/migration/README.md: -------------------------------------------------------------------------------- 1 | # Upgrading Piraeus Operator from Version 1 to Version 2 2 | 3 | The following document guides you through the upgrade process for Piraeus Operator from version 1 ("v1") 4 | to version 2 ("v2"). 5 | 6 | Piraeus Operator v2 offers improved convenience and customization. This however made it necessary to make significant 7 | changes to the way Piraeus Operator manages Piraeus Datastore. As such, upgrading from v1 to v2 is a procedure 8 | requiring manual oversight. 9 | 10 | Upgrading Piraeus Operator is done in four steps: 11 | 12 | * [Step 1]: (Optional) Migrate the LINSTOR database to use the `k8s` backend. 13 | * [Step 2]: Collect information about the current deployment. 14 | * [Step 3]: Remove the Piraeus Operator v1 deployment, keeping existing volumes untouched. 15 | * [Step 4]: Deploy Piraeus Operator v2 using the information gathered in step 2. 16 | 17 | [Step 1]: ./1-migrate-database.md 18 | [Step 2]: ./2-collect-information.md 19 | [Step 3]: ./3-remove-operator-v1.md 20 | [Step 4]: ./4-install-operator-v2.md 21 | 22 | ## Prerequisites 23 | 24 | This guide assumes: 25 | 26 | * You used Helm to create the original deployment and are familiar with upgrading Helm deployments. 27 | * Your Piraeus Datastore deployment is up-to-date with the latest v1 release. Check the releases [here](https://github.com/piraeusdatastore/piraeus-operator/releases?q=v1&expanded=true). 28 | * You have the following command line tools available: 29 | - [`kubectl`](https://kubernetes.io/docs/tasks/tools/) 30 | - [`helm`](https://docs.helm.sh/docs/intro/install/) 31 | - [`jq`](https://jqlang.github.io/jq/download/) 32 | * You are familiar with the `linstor` command line utility, specifically to verify the cluster state. 33 | 34 | ## Key Changes Between Operator V1 and V2 35 | 36 | ### Administrative Changes 37 | 38 | * The resources `LinstorController`, `LinstorSatelliteSet` and `LinstorCSIDriver` have been replaced by 39 | [`LinstorCluster`](../../reference/linstorcluster.md) and 40 | [`LinstorSatelliteConfgiuration`](../../reference/linstorsatelliteconfiguration.md). 41 | * The default deployment runs the LINSTOR Satellite in the [container network](../../how-to/drbd-host-networking.md). 42 | The migration script will propose changing to the host network. 43 | 44 | ### Operational Changes 45 | 46 | * In Operator v1, all labels on the Kubernetes node resource were replicated on the satellite, making them usable in the 47 | `replicasOnSame` and `replicasOnDifferent` parameters on the storage class. In Operator v2, only the following 48 | labels are automatically synchronized. 49 | * `kubernetes.io/hostname` 50 | * `topology.kubernetes.io/region` 51 | * `topology.kubernetes.io/zone` 52 | 53 | Use [`LinstorSatelliteConfiguration.spec.properties`](../../reference/linstorsatelliteconfiguration.md#specproperties) 54 | to synchronize additional labels. 55 | 56 | * The following settings are applied by Operator v2 cluster-wide: 57 | * `DrbdOptions/Net/rr-conflict: retry-connect` 58 | * `DrbdOptions/Resource/on-suspended-primary-outdated: force-secondary` 59 | * `DrbdOptions/Resource/on-no-data-accessible: suspend-io` 60 | * `DrbdOptions/Resource/on-no-quorum: suspend-io` 61 | * Operator v2 also includes a [High-Availability Controller](https://github.com/piraeusdatastore/piraeus-ha-controller) 62 | deployment to prevent stuck nodes caused by suspended DRBD devices. 63 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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/copy-image-config-to-chart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cat < charts/piraeus/templates/config.yaml 5 | # DO NOT EDIT; Automatically created by hack/copy-image-config-to-chart.sh 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: {{ include "piraeus-operator.fullname" . }}-image-config 10 | labels: 11 | {{- include "piraeus-operator.labels" . | nindent 4 }} 12 | data: 13 | 0_piraeus_datastore_images.yaml: | 14 | EOF 15 | 16 | sed 's/^/ /' config/manager/0_piraeus_datastore_images.yaml >> charts/piraeus/templates/config.yaml 17 | cat <> charts/piraeus/templates/config.yaml 18 | 0_sig_storage_images.yaml: | 19 | EOF 20 | sed 's/^/ /' config/manager/0_sig_storage_images.yaml >> charts/piraeus/templates/config.yaml 21 | 22 | cat <> charts/piraeus/templates/config.yaml 23 | {{- range \$idx, \$value := .Values.imageConfigOverride }} 24 | {{ add \$idx 1 }}_helm_override.yaml: | 25 | {{- \$value | toYaml | nindent 4 }} 26 | {{- end }} 27 | EOF 28 | -------------------------------------------------------------------------------- /hack/crd-charts-copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | echo ' {{ if .Values.installCRDs }}' 4 | find ./config/crd/bases -type f | sort | xargs --no-run-if-empty cat 5 | echo '{{ end }}' 6 | -------------------------------------------------------------------------------- /hack/image-updates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | YQ="${YQ:-yq}" 4 | KUSTOMIZE="${KUSTOMIZE:-kustomize}" 5 | CRANE="${CRANE:-crane}" 6 | 7 | NEED_CHART_UPDATE=n 8 | 9 | for CONFIG_FILE in ./config/manager/*_images.yaml; do 10 | BASE="$(yq -e .base "${CONFIG_FILE}")" 11 | for KEY in $(yq -e ".components | keys | .[]" "${CONFIG_FILE}"); do 12 | IMG="${BASE}/$(yq eval -e ".components.${KEY}.image" "${CONFIG_FILE}")" 13 | TAG="$(crane ls "${IMG}" | grep -P '^v(\d+\.)?(\d+\.)?(\*|\d+)$' | sort --version-sort | tail -1)" 14 | if [ "$(yq -e ".components.${KEY}.tag" "${CONFIG_FILE}" )" != "${TAG}" ] ; then 15 | echo "⬆ ${IMG}" 16 | yq -ie ".components.${KEY}.tag = \"${TAG}\"" "${CONFIG_FILE}" 17 | NEED_CHART_UPDATE=y 18 | else 19 | echo "= ${IMG}" 20 | fi 21 | done 22 | done 23 | 24 | if [ "$NEED_CHART_UPDATE" = y ]; then 25 | make sync-chart 26 | fi 27 | -------------------------------------------------------------------------------- /hack/make-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | YQ="${YQ:-yq}" 4 | KUSTOMIZE="${KUSTOMIZE:-kustomize}" 5 | VERSION="$1" 6 | IMG=${IMG:-quay.io/piraeusdatastore/piraeus-operator} 7 | 8 | die() { 9 | echo "$@" >&2 10 | exit 1 11 | } 12 | 13 | if [ -z "$VERSION" ]; then 14 | die "Usage: $0 " 15 | fi 16 | 17 | # check that version has expected format 18 | # regex taken from https://semver.org/ 19 | if ! echo -e "$VERSION" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$$' ; then 20 | die "$VERSION does not match format: .." 21 | fi 22 | 23 | # check that version does not exist 24 | if git rev-parse --verify --quiet "v$VERSION" ; then 25 | die "git tag v$VERSION already exists" 26 | fi 27 | 28 | # check that working tree is clean 29 | if ! git diff-index --quiet HEAD -- ; then 30 | die "Refusing to create release from dirty repository" 31 | fi 32 | 33 | # replace changelog header "Unreleased" with version and replace link target 34 | sed "s/^## \[Unreleased\]/## [v$VERSION] - $(date +%Y-%m-%d)/" -i ./docs/CHANGELOG.md 35 | sed "s#^\[Unreleased\]: \(.*\)HEAD\$#[v$VERSION]: \\1v$VERSION#" -i ./docs/CHANGELOG.md 36 | 37 | # Set image version for kustomize 38 | pushd config/default 39 | $KUSTOMIZE edit set image controller="$IMG:v$VERSION" 40 | popd 41 | 42 | # replace deployment instructions in docs 43 | for FILE in ./README.md ./docs/tutorial/get-started.md ./docs/upgrade/README.md ; do 44 | sed -e "s/ref=v[0-9\.]\+/ref=v$VERSION/" -i "$FILE" 45 | done 46 | 47 | # replace version in Makefile 48 | sed -e "s/^VERSION ?=.*/VERSION ?= $VERSION/" -i Makefile 49 | 50 | # replace chart version+appVersion 51 | $YQ ".version = \"$VERSION\"" -i charts/piraeus/Chart.yaml 52 | $YQ ".appVersion = \"v$VERSION\"" -i charts/piraeus/Chart.yaml 53 | 54 | # commit as current release 55 | # We don't do git tag v$(VERSION) here, as the commit will change once its merged in github 56 | git commit -aevm "Release v$VERSION" --signoff 57 | 58 | # add "Unreleased" section at top + create comparison link against current master 59 | sed "s/^## \[v$VERSION\]/## [Unreleased]\n\n## [v$VERSION]/" -i ./docs/CHANGELOG.md 60 | echo "[Unreleased]: https://github.com/piraeusdatastore/piraeus-operator/compare/v$VERSION...HEAD" >> ./docs/CHANGELOG.md 61 | 62 | # Reset image version for kustomize 63 | pushd config/default 64 | $KUSTOMIZE edit set image controller="$IMG:v2" 65 | popd 66 | 67 | # commit begin of new dev cycle 68 | git commit -aevm "Prepare next dev cycle" --signoff 69 | -------------------------------------------------------------------------------- /internal/controller/ratelimit.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "time" 5 | 6 | "k8s.io/client-go/util/workqueue" 7 | ) 8 | 9 | // DefaultRateLimiter is a modified workqueue.DefaultControllerRateLimiter. 10 | // 11 | // It reduced the maximum delay between reconcile attempts from 1000 seconds to 30 seconds. 12 | func DefaultRateLimiter[T comparable]() workqueue.TypedRateLimiter[T] { 13 | return workqueue.NewTypedWithMaxWaitRateLimiter[T](workqueue.DefaultTypedControllerRateLimiter[T](), 30*time.Second) 14 | } 15 | -------------------------------------------------------------------------------- /internal/webhook/storageclass.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "context" 5 | 6 | linstorcsi "github.com/piraeusdatastore/linstor-csi/pkg/linstor" 7 | "github.com/piraeusdatastore/linstor-csi/pkg/volume" 8 | storagev1 "k8s.io/api/storage/v1" 9 | apierrors "k8s.io/apimachinery/pkg/api/errors" 10 | "k8s.io/apimachinery/pkg/api/meta" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/apimachinery/pkg/runtime/schema" 13 | "k8s.io/apimachinery/pkg/util/validation/field" 14 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 15 | ) 16 | 17 | //+kubebuilder:webhook:path=/validate-storage-k8s-io-v1-storageclass,mutating=false,failurePolicy=fail,sideEffects=None,groups=storage.k8s.io,resources=storageclasses,verbs=create;update,versions=v1,name=vstorageclass.kb.io,admissionReviewVersions=v1 18 | 19 | type StorageClass struct{} 20 | 21 | func (s *StorageClass) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 22 | warnings, errs := s.validate(nil, obj.(*storagev1.StorageClass)) 23 | if len(errs) != 0 { 24 | accessor, _ := meta.Accessor(obj) 25 | return warnings, apierrors.NewInvalid(schema.GroupKind{Group: storagev1.GroupName, Kind: "StorageClass"}, accessor.GetName(), errs) 26 | } 27 | 28 | return warnings, nil 29 | } 30 | 31 | func (s *StorageClass) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { 32 | warnings, errs := s.validate(oldObj.(*storagev1.StorageClass), newObj.(*storagev1.StorageClass)) 33 | if len(errs) != 0 { 34 | accessor, _ := meta.Accessor(newObj) 35 | return warnings, apierrors.NewInvalid(schema.GroupKind{Group: storagev1.GroupName, Kind: "StorageClass"}, accessor.GetName(), errs) 36 | } 37 | 38 | return warnings, nil 39 | } 40 | 41 | func (s *StorageClass) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 42 | return nil, nil 43 | } 44 | 45 | func (s *StorageClass) validate(old, new *storagev1.StorageClass) (admission.Warnings, field.ErrorList) { 46 | var errs field.ErrorList 47 | 48 | if new.Provisioner != linstorcsi.DriverName { 49 | return nil, nil 50 | } 51 | 52 | _, err := volume.NewParameters(new.Parameters, "Aux/topology") 53 | if err != nil { 54 | errs = append(errs, field.Invalid(field.NewPath("parameters"), new.Parameters, err.Error())) 55 | } 56 | 57 | return nil, errs 58 | } 59 | -------------------------------------------------------------------------------- /internal/webhook/v1/component_spec.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/util/validation/field" 9 | 10 | piraeusiov1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 11 | ) 12 | 13 | func ValidatePodTemplate(template json.RawMessage, fieldPrefix *field.Path) field.ErrorList { 14 | if len(template) == 0 { 15 | return nil 16 | } 17 | 18 | var decoded corev1.PodTemplateSpec 19 | err := json.Unmarshal(template, &decoded) 20 | if err != nil { 21 | return field.ErrorList{field.Invalid( 22 | fieldPrefix, 23 | string(template), 24 | fmt.Sprintf("invalid pod template: %s", err), 25 | )} 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func ValidateComponentSpec(curSpec *piraeusiov1.ComponentSpec, fieldPrefix *field.Path) field.ErrorList { 32 | if curSpec == nil { 33 | return nil 34 | } 35 | 36 | return ValidatePodTemplate(curSpec.PodTemplate, fieldPrefix.Child("podTemplate")) 37 | } 38 | -------------------------------------------------------------------------------- /internal/webhook/v1/patch.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/apimachinery/pkg/util/validation/field" 7 | 8 | piraeusv1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 9 | ) 10 | 11 | func ValidatePatch(patch *piraeusv1.Patch, path *field.Path) field.ErrorList { 12 | var result field.ErrorList 13 | 14 | _, smErr := patch.GetStrategicMergePatch() 15 | _, jsErr := patch.GetJsonPatch() 16 | if smErr != nil && jsErr != nil { 17 | result = append(result, field.Invalid(path.Child("patch"), patch.Patch, fmt.Sprintf("Failed to parse patch as either Strategic Merge Patch (%s) or JSON Patch (%s)", smErr, jsErr))) 18 | } 19 | 20 | if patch.GetTarget() == nil { 21 | result = append(result, field.Required(path.Child("target"), "Patch does not have a target and is not a valid Strategic Merge Patch")) 22 | } 23 | 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /internal/webhook/v1/properties.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/util/validation/field" 9 | 10 | piraeusv1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 11 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/utils/fieldpath" 12 | ) 13 | 14 | func ValidateNodeProperties(props []piraeusv1.LinstorNodeProperty, path *field.Path) field.ErrorList { 15 | var result field.ErrorList 16 | 17 | for i := range props { 18 | p := &props[i] 19 | 20 | sourcesSet := 0 21 | 22 | if p.Value != "" { 23 | sourcesSet++ 24 | } 25 | 26 | if p.ValueFrom != nil { 27 | sourcesSet++ 28 | } 29 | 30 | if p.ExpandFrom != nil { 31 | sourcesSet++ 32 | } 33 | 34 | if sourcesSet != 1 { 35 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i)), p, "Expected exactly one of 'value', 'valueFrom' or 'joinValuesFrom' to be set")) 36 | } 37 | 38 | if p.ValueFrom != nil { 39 | _, keys, err := fieldpath.ExtractFieldPath(&corev1.Node{}, p.ValueFrom.NodeFieldRef) 40 | if err != nil { 41 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i), "valueFrom", "nodeFieldRef"), p.ValueFrom.NodeFieldRef, fmt.Sprintf("Invalid reference format: %s", err))) 42 | } 43 | 44 | if keys != nil { 45 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i), "valueFrom", "nodeFieldRef"), p.ValueFrom.NodeFieldRef, "Wildcard property not allowed, use expandFrom instead")) 46 | } 47 | } 48 | 49 | if p.ExpandFrom != nil { 50 | _, keys, err := fieldpath.ExtractFieldPath(&corev1.Node{}, p.ExpandFrom.NodeFieldRef) 51 | if err != nil { 52 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i), "expandFrom", "nodeFieldRef"), p.ExpandFrom.NodeFieldRef, fmt.Sprintf("Invalid reference format: %s", err))) 53 | } 54 | 55 | if keys == nil { 56 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i), "expandFrom", "nodeFieldRef"), p.ExpandFrom.NodeFieldRef, "Wildcard property required")) 57 | } 58 | 59 | if p.ExpandFrom.NameTemplate != "" && p.ExpandFrom.Delimiter != "" { 60 | result = append(result, field.Invalid(path.Child(strconv.Itoa(i), "expandFrom"), p.ExpandFrom, "Expected only one of 'nameTemplate' and 'delimiter' to be set")) 61 | } 62 | } 63 | } 64 | 65 | return result 66 | } 67 | -------------------------------------------------------------------------------- /pkg/barepodpatch/barepodpatch_test.go: -------------------------------------------------------------------------------- 1 | package barepodpatch_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | piraeusiov1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 9 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/barepodpatch" 10 | ) 11 | 12 | func TestConvertBarePodPatch(t *testing.T) { 13 | t.Parallel() 14 | testcases := []struct { 15 | name string 16 | patch piraeusiov1.Patch 17 | expected piraeusiov1.Patch 18 | }{ 19 | { 20 | name: "ignore-non-pod-patch", 21 | patch: piraeusiov1.Patch{ 22 | Patch: `apiVersion: v1 23 | kind: ConfigMap 24 | metadata: 25 | name: reactor-config 26 | data: 27 | foo: bar 28 | `, 29 | }, 30 | expected: piraeusiov1.Patch{ 31 | Patch: `apiVersion: v1 32 | kind: ConfigMap 33 | metadata: 34 | name: reactor-config 35 | data: 36 | foo: bar 37 | `, 38 | }, 39 | }, 40 | { 41 | name: "convert-strategic-merge-patch", 42 | patch: piraeusiov1.Patch{ 43 | Patch: `apiVersion: v1 44 | kind: Pod 45 | metadata: 46 | annotations: 47 | foobar: baz 48 | labels: 49 | example.com/foo: bar 50 | name: satellite 51 | spec: 52 | hostNetwork: true 53 | initContainers: 54 | - $patch: delete 55 | name: drbd-shutdown-guard 56 | `, 57 | }, 58 | expected: piraeusiov1.Patch{ 59 | Target: &piraeusiov1.Selector{ 60 | Group: "apps", 61 | Version: "v1", 62 | Kind: "DaemonSet", 63 | Name: "linstor-satellite", 64 | }, 65 | Patch: `apiVersion: apps/v1 66 | kind: DaemonSet 67 | metadata: 68 | name: linstor-satellite 69 | spec: 70 | template: 71 | metadata: 72 | annotations: 73 | foobar: baz 74 | labels: 75 | example.com/foo: bar 76 | spec: 77 | hostNetwork: true 78 | initContainers: 79 | - $patch: delete 80 | name: drbd-shutdown-guard 81 | `, 82 | }, 83 | }, 84 | { 85 | name: "convert-json-patch", 86 | patch: piraeusiov1.Patch{ 87 | Target: &piraeusiov1.Selector{ 88 | Kind: "Pod", 89 | Name: "satellite", 90 | }, 91 | Patch: `- op: add 92 | path: /metadata/labels/foo 93 | value: bar 94 | - from: /metadata/labels/bar 95 | op: move 96 | path: /metadata/labels/baz 97 | `, 98 | }, 99 | expected: piraeusiov1.Patch{ 100 | Target: &piraeusiov1.Selector{ 101 | Group: "apps", 102 | Version: "v1", 103 | Kind: "DaemonSet", 104 | Name: "linstor-satellite", 105 | }, 106 | Patch: `- op: add 107 | path: /spec/template/metadata/labels/foo 108 | value: bar 109 | - from: /spec/template/metadata/labels/bar 110 | op: move 111 | path: /spec/template/metadata/labels/baz 112 | `, 113 | }, 114 | }, 115 | } 116 | for i := range testcases { 117 | tcase := &testcases[i] 118 | t.Run(tcase.name, func(t *testing.T) { 119 | t.Parallel() 120 | 121 | actual, err := barepodpatch.ConvertBarePodPatch(tcase.patch) 122 | assert.NoError(t, err) 123 | assert.Equal(t, &tcase.expected, &actual[0]) 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /pkg/conditions/conditions.go: -------------------------------------------------------------------------------- 1 | package conditions 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | type ( 12 | CondType string 13 | Reason string 14 | ) 15 | 16 | const ( 17 | Applied CondType = "Applied" 18 | Available CondType = "Available" 19 | Configured CondType = "Configured" 20 | 21 | ReasonNotObserved Reason = "NotObserved" 22 | ReasonAsExpected Reason = "AsExpected" 23 | ReasonUnknown Reason = "Unknown" 24 | ReasonError Reason = "Error" 25 | ) 26 | 27 | var conditionPriority = map[Reason]int{ 28 | ReasonNotObserved: 0, 29 | ReasonAsExpected: 1, 30 | ReasonUnknown: 2, 31 | ReasonError: 3, 32 | } 33 | 34 | type condition struct { 35 | Status metav1.ConditionStatus 36 | Reason Reason 37 | Errors []error 38 | Message []string 39 | } 40 | 41 | type Conditions map[CondType]*condition 42 | 43 | func New() Conditions { 44 | return map[CondType]*condition{ 45 | Applied: {Reason: ReasonNotObserved, Status: metav1.ConditionUnknown}, 46 | Available: {Reason: ReasonNotObserved, Status: metav1.ConditionUnknown}, 47 | Configured: {Reason: ReasonNotObserved, Status: metav1.ConditionUnknown}, 48 | } 49 | } 50 | 51 | func (c Conditions) get(condType CondType) *condition { 52 | res, ok := c[condType] 53 | if ok { 54 | return res 55 | } 56 | 57 | c[condType] = &condition{Reason: ReasonNotObserved, Status: metav1.ConditionUnknown} 58 | return c[condType] 59 | } 60 | 61 | func (c Conditions) AddSuccess(condType CondType, message string) { 62 | ct := c.get(condType) 63 | ct.Message = append(ct.Message, message) 64 | if conditionPriority[ct.Reason] < conditionPriority[ReasonAsExpected] { 65 | ct.Status = metav1.ConditionTrue 66 | ct.Reason = ReasonAsExpected 67 | } 68 | } 69 | 70 | func (c Conditions) AddError(condType CondType, err error) { 71 | ct := c.get(condType) 72 | ct.Message = append(ct.Message, fmt.Sprintf("Error: %v", err)) 73 | if conditionPriority[ct.Reason] < conditionPriority[ReasonError] { 74 | ct.Status = metav1.ConditionFalse 75 | ct.Reason = ReasonError 76 | } 77 | } 78 | 79 | func (c Conditions) AddUnknown(condType CondType, message string) { 80 | ct := c.get(condType) 81 | ct.Message = append(ct.Message, message) 82 | if conditionPriority[ct.Reason] < conditionPriority[ReasonUnknown] { 83 | ct.Status = metav1.ConditionUnknown 84 | ct.Reason = ReasonUnknown 85 | } 86 | } 87 | 88 | func (c Conditions) ToConditions(generation int64) []metav1.Condition { 89 | var result []metav1.Condition 90 | for k, v := range c { 91 | message := strings.Join(v.Message, "\n") 92 | 93 | result = append(result, metav1.Condition{ 94 | Type: string(k), 95 | Status: v.Status, 96 | Reason: string(v.Reason), 97 | Message: message, 98 | ObservedGeneration: generation, 99 | }) 100 | } 101 | 102 | sort.Slice(result, func(i, j int) bool { 103 | return result[i].Type < result[j].Type 104 | }) 105 | 106 | return result 107 | } 108 | -------------------------------------------------------------------------------- /pkg/imageversions/dynamic.go: -------------------------------------------------------------------------------- 1 | package imageversions 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "maps" 7 | "slices" 8 | 9 | lclient "github.com/LINBIT/golinstor/client" 10 | corev1 "k8s.io/api/core/v1" 11 | "k8s.io/apimachinery/pkg/types" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/log" 14 | "sigs.k8s.io/yaml" 15 | ) 16 | 17 | func FromConfigMap(ctx context.Context, client client.Client, name types.NamespacedName) (Configs, error) { 18 | var cfg corev1.ConfigMap 19 | err := client.Get(ctx, name, &cfg) 20 | if err != nil { 21 | return nil, fmt.Errorf("failed to fetch image config map: %w", err) 22 | } 23 | 24 | var cfgs []*Config 25 | for _, name := range slices.Sorted(maps.Keys(cfg.Data)) { 26 | var config Config 27 | err = yaml.Unmarshal([]byte(cfg.Data[name]), &config) 28 | if err != nil { 29 | return nil, fmt.Errorf("failed to decode image configuration: %w", err) 30 | } 31 | cfgs = append(cfgs, &config) 32 | } 33 | 34 | return cfgs, nil 35 | } 36 | 37 | // SetFromExternalCluster sets the LINSTOR Satellite image tag to the version of the LINSTOR Controller. 38 | // 39 | // The Configs will be updated so that the first entry containing a LINSTOR Satellite image is updated. 40 | // Returns an error is the LINSTOR Controller could not be reached, or no LINSTOR Satellite image was found. 41 | func SetFromExternalCluster(ctx context.Context, client *lclient.Client, configs Configs) error { 42 | version, err := client.Controller.GetVersion(ctx) 43 | if err != nil { 44 | return fmt.Errorf("failed to fetch controller version: %w", err) 45 | } 46 | 47 | newTag := "v" + version.Version 48 | logger := log.FromContext(ctx) 49 | 50 | for name, config := range configs { 51 | if v, ok := config.Components["linstor-satellite"]; ok { 52 | logger.WithValues("configName", name, "oldTag", v.Tag, "newTag", newTag).Info("updating image version based on external cluster") 53 | v.Tag = newTag 54 | config.Components["linstor-satellite"] = v 55 | return nil 56 | } 57 | } 58 | 59 | return fmt.Errorf("no linstor-satellite component found in configs") 60 | } 61 | -------------------------------------------------------------------------------- /pkg/imageversions/versions.go: -------------------------------------------------------------------------------- 1 | package imageversions 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "regexp" 7 | 8 | kusttypes "sigs.k8s.io/kustomize/api/types" 9 | ) 10 | 11 | // Configs is a list of Config, where later 12 | type Configs []*Config 13 | 14 | // Config represents a default image mapping used by the operator. 15 | type Config struct { 16 | Base string `yaml:"base"` 17 | Components map[string]ComponentConfig `yaml:"components"` 18 | } 19 | 20 | type ComponentConfig struct { 21 | Tag string `yaml:"tag"` 22 | Match []OsMatch `yaml:"match"` 23 | Image string `yaml:"image"` 24 | } 25 | 26 | type OsMatch struct { 27 | OsImage string `yaml:"osImage"` 28 | Image string `yaml:"image"` 29 | Precompiled bool `yaml:"precompiled"` 30 | } 31 | 32 | func (c Configs) GetVersions(base string, osImage string) ([]kusttypes.Image, bool) { 33 | uniqImages := make(map[string]*kusttypes.Image) 34 | precompiled := false 35 | 36 | for _, cfg := range c { 37 | imgs, compiled := cfg.GetVersions(base, osImage) 38 | precompiled = precompiled || compiled 39 | for i := range imgs { 40 | uniqImages[imgs[i].Name] = &imgs[i] 41 | } 42 | } 43 | 44 | result := make([]kusttypes.Image, 0, len(uniqImages)) 45 | for _, img := range uniqImages { 46 | result = append(result, *img) 47 | } 48 | 49 | return result, precompiled 50 | } 51 | 52 | func (f *Config) GetVersions(base string, osImage string) ([]kusttypes.Image, bool) { 53 | result := make([]kusttypes.Image, 0, len(f.Components)) 54 | 55 | precompiled := false 56 | 57 | for c := range f.Components { 58 | name, tag, compiled := f.get(f.Components[c], base, osImage) 59 | 60 | precompiled = precompiled || compiled 61 | 62 | if name != "" { 63 | result = append(result, kusttypes.Image{ 64 | Name: string(c), 65 | NewName: name, 66 | NewTag: tag, 67 | }) 68 | } 69 | } 70 | 71 | return result, precompiled 72 | } 73 | 74 | func (f *Config) get(img ComponentConfig, base string, osImage string) (string, string, bool) { 75 | if base == "" { 76 | base = f.Base 77 | } 78 | 79 | for _, matchRule := range img.Match { 80 | if ok, _ := regexp.MatchString(matchRule.OsImage, osImage); ok { 81 | return fmt.Sprintf("%s/%s", base, matchRule.Image), img.Tag, matchRule.Precompiled 82 | } 83 | } 84 | 85 | if img.Image == "" { 86 | return "", "", false 87 | } 88 | 89 | return fmt.Sprintf("%s/%s", base, img.Image), img.Tag, false 90 | } 91 | -------------------------------------------------------------------------------- /pkg/linstorhelper/log.go: -------------------------------------------------------------------------------- 1 | package linstorhelper 2 | 3 | import ( 4 | "fmt" 5 | 6 | lapi "github.com/LINBIT/golinstor/client" 7 | "github.com/go-logr/logr" 8 | ) 9 | 10 | type logrAdapter struct { 11 | logr.Logger 12 | } 13 | 14 | var _ lapi.LeveledLogger = &logrAdapter{} 15 | 16 | func (l *logrAdapter) Errorf(s string, i ...interface{}) { 17 | l.Logger.Error(fmt.Errorf(s, i...), "error occurred") 18 | } 19 | 20 | func (l *logrAdapter) Warnf(s string, i ...interface{}) { 21 | l.Logger.V(0).Info(fmt.Sprintf(s, i...)) 22 | } 23 | 24 | func (l *logrAdapter) Infof(s string, i ...interface{}) { 25 | l.Logger.V(0).Info(fmt.Sprintf(s, i...)) 26 | } 27 | 28 | func (l *logrAdapter) Debugf(s string, i ...interface{}) { 29 | l.Logger.V(1).Info(fmt.Sprintf(s, i...)) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/linstorhelper/properties.go: -------------------------------------------------------------------------------- 1 | package linstorhelper 2 | 3 | import ( 4 | "encoding/json" 5 | "sort" 6 | 7 | linstor "github.com/LINBIT/golinstor" 8 | lclient "github.com/LINBIT/golinstor/client" 9 | 10 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/vars" 11 | ) 12 | 13 | const ( 14 | LastApplyProperty = linstor.NamespcAuxiliary + "/" + vars.ApplyAnnotation 15 | ManagedByProperty = linstor.NamespcAuxiliary + "/" + vars.ManagedByLabel 16 | ) 17 | 18 | // MakePropertiesModification returns the modification that need to be applied to update the current properties. 19 | func MakePropertiesModification(current, expected map[string]string) *lclient.GenericPropsModify { 20 | modify := false 21 | result := &lclient.GenericPropsModify{ 22 | OverrideProps: make(map[string]string), 23 | } 24 | 25 | expected = UpdateLastApplyProperty(expected) 26 | 27 | for k, v := range expected { 28 | if current, ok := current[k]; !ok || current != v { 29 | modify = true 30 | result.OverrideProps[k] = v 31 | } 32 | } 33 | 34 | // Check what properties we applied last to find if we need to delete something 35 | if !modify && current[LastApplyProperty] == expected[LastApplyProperty] { 36 | return nil 37 | } 38 | 39 | // See what properties need to be deleted from the previous run. 40 | var lastApplied []string 41 | _ = json.Unmarshal([]byte(current[LastApplyProperty]), &lastApplied) 42 | 43 | for _, k := range lastApplied { 44 | if _, ok := expected[k]; !ok { 45 | result.DeleteProps = append(result.DeleteProps, k) 46 | } 47 | } 48 | 49 | if len(expected) == 0 { 50 | result.DeleteProps = append(result.DeleteProps, LastApplyProperty) 51 | } 52 | 53 | return result 54 | } 55 | 56 | // UpdateLastApplyProperty ensures the LastApplyProperty is up-to-date. 57 | func UpdateLastApplyProperty(props map[string]string) map[string]string { 58 | result := make(map[string]string) 59 | allKeys := make([]string, 0) 60 | 61 | for k, v := range props { 62 | if k != LastApplyProperty { 63 | result[k] = v 64 | allKeys = append(allKeys, k) 65 | } 66 | } 67 | 68 | if len(allKeys) == 0 { 69 | return nil 70 | } 71 | 72 | // Sort for consistent order 73 | sort.Strings(allKeys) 74 | 75 | toApply, err := json.Marshal(allKeys) 76 | if err != nil { 77 | // pretty much can't happen, and if it happens, you can't apply the properties. 78 | return result 79 | } 80 | 81 | result[LastApplyProperty] = string(toApply) 82 | return result 83 | } 84 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/api-client/api-client-cert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-client-tls 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: linstor-client-tls 10 | commonName: linstor-client 11 | dnsNames: 12 | - linstor-client 13 | usages: 14 | - "client auth" 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/api-client/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - api-client-cert.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/api/api-cert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-api-tls 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: linstor-api-tls 10 | dnsNames: 11 | - linstor-controller.piraeus-datastore.svc.cluster.local 12 | - linstor-controller.piraeus-datastore.svc 13 | - linstor-controller 14 | usages: 15 | - "server auth" 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/api/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - api-cert.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/internal/internal-cert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-controller-internal-tls 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: linstor-controller-internal-tls 10 | commonName: linstor-controller 11 | dnsNames: 12 | - linstor-controller 13 | usages: 14 | - "client auth" 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/cert-manager/internal/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - internal-cert.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: linstor-controller 12 | subjects: 13 | - kind: ServiceAccount 14 | name: linstor-controller 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | rules: 9 | - apiGroups: 10 | - apiextensions.k8s.io 11 | resources: 12 | - customresourcedefinitions 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - create 18 | - patch 19 | - update 20 | - delete 21 | - apiGroups: 22 | - internal.linstor.linbit.com 23 | resources: 24 | - "*" 25 | verbs: 26 | - get 27 | - list 28 | - watch 29 | - create 30 | - patch 31 | - update 32 | - delete 33 | - deletecollection 34 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: linstor-controller-config 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | data: 9 | linstor.toml: | 10 | [db] 11 | connection_url = "k8s" 12 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: Role 11 | name: linstor-controller 12 | subjects: 13 | - kind: ServiceAccount 14 | name: linstor-controller 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | rules: 9 | - apiGroups: 10 | - coordination.k8s.io 11 | resources: 12 | - leases 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - create 18 | - patch 19 | - update 20 | - delete 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - secrets 25 | verbs: 26 | - get 27 | - create 28 | - patch 29 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/controller-service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: linstor-controller 6 | labels: 7 | app.kubernetes.io/component: linstor-controller 8 | spec: 9 | selector: 10 | app.kubernetes.io/component: linstor-controller 11 | ipFamilyPolicy: PreferDualStack 12 | ports: 13 | - port: 3370 14 | name: api 15 | protocol: TCP 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - controller-deployment.yaml 6 | - controller-config.yaml 7 | - controller-service.yaml 8 | - controller-service-account.yaml 9 | - controller-cluster-role.yaml 10 | - controller-cluster-role-binding.yaml 11 | - controller-role.yaml 12 | - controller-role-binding.yaml 13 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-controller/cert-manager/csi-controller-cert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-csi-controller-tls 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: linstor-csi-controller-tls 10 | commonName: linstor-csi-controller 11 | dnsNames: 12 | - linstor-csi-controller 13 | usages: 14 | - "client auth" 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-controller/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - csi-controller-cert.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-controller/csi-driver.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: storage.k8s.io/v1 3 | kind: CSIDriver 4 | metadata: 5 | name: linstor.csi.linbit.com 6 | spec: 7 | attachRequired: true 8 | fsGroupPolicy: ReadWriteOnceWithFSType 9 | podInfoOnMount: true 10 | volumeLifecycleModes: 11 | - Persistent 12 | storageCapacity: true 13 | requiresRepublish: false 14 | seLinuxMount: true 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - csi-driver.yaml 6 | - csi-controller-deployment.yaml 7 | - csi-controller-service-account.yaml 8 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-node/cert-manager/csi-node-cert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-csi-node-tls 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: linstor-csi-node-tls 10 | commonName: linstor-csi-node 11 | dnsNames: 12 | - linstor-csi-node 13 | usages: 14 | - "client auth" 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-node/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - csi-node-cert.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-node/csi-node-service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: linstor-csi-node 6 | labels: 7 | app.kubernetes.io/component: linstor-csi-node 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: Role 11 | metadata: 12 | name: linstor-csi-node 13 | labels: 14 | app.kubernetes.io/component: linstor-csi-node 15 | rules: 16 | - apiGroups: 17 | - security.openshift.io 18 | resources: 19 | - securitycontextconstraints 20 | resourceNames: 21 | - privileged 22 | verbs: 23 | - use 24 | --- 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: RoleBinding 27 | metadata: 28 | name: linstor-csi-node 29 | labels: 30 | app.kubernetes.io/name: linstor-csi-node 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: Role 34 | name: linstor-csi-node 35 | subjects: 36 | - kind: ServiceAccount 37 | name: linstor-csi-node 38 | -------------------------------------------------------------------------------- /pkg/resources/cluster/csi-node/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - csi-node-daemon-set.yaml 6 | - csi-node-service-account.yaml 7 | -------------------------------------------------------------------------------- /pkg/resources/cluster/ha-controller/daemonset.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: ha-controller 6 | labels: 7 | app.kubernetes.io/component: ha-controller 8 | spec: 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/component: ha-controller 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/component: ha-controller 16 | spec: 17 | serviceAccountName: ha-controller 18 | priorityClassName: system-cluster-critical 19 | containers: 20 | - name: ha-controller 21 | args: 22 | - /agent 23 | - --v=1 24 | securityContext: 25 | privileged: true 26 | readOnlyRootFilesystem: true 27 | image: ha-controller 28 | env: 29 | - name: NODE_NAME 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: spec.nodeName 33 | startupProbe: 34 | httpGet: 35 | port: 8000 36 | path: /healthz 37 | livenessProbe: 38 | httpGet: 39 | port: 8000 40 | path: /healthz 41 | -------------------------------------------------------------------------------- /pkg/resources/cluster/ha-controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | resources: 3 | - rbac.yaml 4 | - daemonset.yaml 5 | -------------------------------------------------------------------------------- /pkg/resources/cluster/ha-controller/rbac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: ha-controller 6 | labels: 7 | app.kubernetes.io/component: ha-controller 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: ClusterRole 11 | metadata: 12 | name: ha-controller 13 | labels: 14 | app.kubernetes.io/component: ha-controller 15 | rules: 16 | - apiGroups: 17 | - "" 18 | resources: 19 | - pods 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - delete 25 | - apiGroups: 26 | - events.k8s.io 27 | resources: 28 | - events 29 | verbs: 30 | - create 31 | - patch 32 | - apiGroups: 33 | - "" 34 | resources: 35 | - pods/eviction 36 | verbs: 37 | - create 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - nodes 42 | verbs: 43 | - get 44 | - list 45 | - watch 46 | - patch 47 | - update 48 | - apiGroups: 49 | - "" 50 | resources: 51 | - persistentvolumes 52 | verbs: 53 | - get 54 | - list 55 | - watch 56 | - apiGroups: 57 | - storage.k8s.io 58 | resources: 59 | - volumeattachments 60 | verbs: 61 | - get 62 | - list 63 | - watch 64 | - delete 65 | - apiGroups: 66 | - security.openshift.io 67 | resources: 68 | - securitycontextconstraints 69 | resourceNames: 70 | - privileged 71 | verbs: 72 | - use 73 | --- 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | kind: ClusterRoleBinding 76 | metadata: 77 | name: ha-controller 78 | labels: 79 | app.kubernetes.io/component: ha-controller 80 | roleRef: 81 | apiGroup: rbac.authorization.k8s.io 82 | kind: ClusterRole 83 | name: ha-controller 84 | subjects: 85 | - kind: ServiceAccount 86 | name: ha-controller 87 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-endpoint.yaml: -------------------------------------------------------------------------------- 1 | - target: 2 | group: apps 3 | version: v1 4 | kind: Deployment 5 | name: linstor-csi-controller 6 | patch: | 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: linstor-csi-controller 11 | spec: 12 | template: 13 | spec: 14 | initContainers: 15 | - name: linstor-wait-api-online 16 | env: 17 | - name: LS_CONTROLLERS 18 | value: $LINSTOR_CONTROLLER_URL 19 | containers: 20 | - name: linstor-csi 21 | env: 22 | - name: LS_CONTROLLERS 23 | value: $LINSTOR_CONTROLLER_URL 24 | - target: 25 | group: apps 26 | version: v1 27 | kind: DaemonSet 28 | name: linstor-csi-node 29 | patch: | 30 | apiVersion: apps/v1 31 | kind: DaemonSet 32 | metadata: 33 | name: linstor-csi-node 34 | spec: 35 | template: 36 | spec: 37 | initContainers: 38 | - name: linstor-wait-node-online 39 | env: 40 | - name: LS_CONTROLLERS 41 | value: $LINSTOR_CONTROLLER_URL 42 | containers: 43 | - name: linstor-csi 44 | env: 45 | - name: LS_CONTROLLERS 46 | value: $LINSTOR_CONTROLLER_URL 47 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-tls-cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: cert-manager.io 4 | version: v1 5 | kind: Certificate 6 | name: linstor-api-tls 7 | patch: | 8 | apiVersion: cert-manager.io/v1 9 | kind: Certificate 10 | metadata: 11 | name: linstor-api-tls 12 | spec: 13 | secretName: $LINSTOR_API_TLS_SECRET_NAME 14 | issuerRef: $LINSTOR_API_TLS_CERT_ISSUER 15 | dnsNames: $LINSTOR_API_TLS_DNS_NAMES 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-tls-client-cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - patch: | 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | name: $CERTIFICATE_NAME 7 | spec: 8 | secretName: $LINSTOR_API_TLS_CLIENT_SECRET_NAME 9 | issuerRef: $LINSTOR_API_TLS_CLIENT_CERT_ISSUER 10 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-tls-csi-controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-csi-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-csi-controller 12 | spec: 13 | template: 14 | spec: 15 | initContainers: 16 | - name: linstor-wait-api-online 17 | env: 18 | - name: LS_ROOT_CA 19 | valueFrom: $LINSTOR_CSI_CONTROLLER_API_TLS_CA_SOURCE 20 | - name: LS_USER_CERTIFICATE 21 | valueFrom: 22 | secretKeyRef: 23 | name: $LINSTOR_CSI_CONTROLLER_API_TLS_SECRET_NAME 24 | key: tls.crt 25 | - name: LS_USER_KEY 26 | valueFrom: 27 | secretKeyRef: 28 | name: $LINSTOR_CSI_CONTROLLER_API_TLS_SECRET_NAME 29 | key: tls.key 30 | containers: 31 | - name: linstor-csi 32 | env: 33 | - name: LS_ROOT_CA 34 | valueFrom: $LINSTOR_CSI_CONTROLLER_API_TLS_CA_SOURCE 35 | - name: LS_USER_CERTIFICATE 36 | valueFrom: 37 | secretKeyRef: 38 | name: $LINSTOR_CSI_CONTROLLER_API_TLS_SECRET_NAME 39 | key: tls.crt 40 | - name: LS_USER_KEY 41 | valueFrom: 42 | secretKeyRef: 43 | name: $LINSTOR_CSI_CONTROLLER_API_TLS_SECRET_NAME 44 | key: tls.key 45 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-tls-csi-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-csi-node 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-csi-node 12 | spec: 13 | template: 14 | spec: 15 | initContainers: 16 | - name: linstor-wait-node-online 17 | env: 18 | - name: LS_ROOT_CA 19 | valueFrom: $LINSTOR_CSI_NODE_API_TLS_CA_SOURCE 20 | - name: LS_USER_CERTIFICATE 21 | valueFrom: 22 | secretKeyRef: 23 | name: $LINSTOR_CSI_NODE_API_TLS_SECRET_NAME 24 | key: tls.crt 25 | - name: LS_USER_KEY 26 | valueFrom: 27 | secretKeyRef: 28 | name: $LINSTOR_CSI_NODE_API_TLS_SECRET_NAME 29 | key: tls.key 30 | containers: 31 | - name: linstor-csi 32 | env: 33 | - name: LS_ROOT_CA 34 | valueFrom: $LINSTOR_CSI_NODE_API_TLS_CA_SOURCE 35 | - name: LS_USER_CERTIFICATE 36 | valueFrom: 37 | secretKeyRef: 38 | name: $LINSTOR_CSI_NODE_API_TLS_SECRET_NAME 39 | key: tls.crt 40 | - name: LS_USER_KEY 41 | valueFrom: 42 | secretKeyRef: 43 | name: $LINSTOR_CSI_NODE_API_TLS_SECRET_NAME 44 | key: tls.key 45 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/api-tls.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-controller 12 | spec: 13 | template: 14 | spec: 15 | initContainers: 16 | - name: run-migration 17 | volumeMounts: 18 | - name: api-tls 19 | mountPath: /etc/linstor/https-pem 20 | readOnly: true 21 | - name: java-api-tls 22 | mountPath: /etc/linstor/https 23 | containers: 24 | - name: linstor-controller 25 | volumeMounts: 26 | - name: api-tls 27 | mountPath: /etc/linstor/https-pem 28 | readOnly: true 29 | - name: java-api-tls 30 | mountPath: /etc/linstor/https 31 | - name: client-tls 32 | mountPath: /etc/linstor/client 33 | readOnly: true 34 | ports: 35 | - name: secure-api 36 | containerPort: 3371 37 | protocol: TCP 38 | volumes: 39 | - name: java-api-tls 40 | emptyDir: { } 41 | - name: api-tls 42 | projected: 43 | sources: 44 | - secret: 45 | name: $LINSTOR_API_TLS_SECRET_NAME 46 | - $LINSTOR_API_TLS_CA_PROJECTION 47 | - name: client-tls 48 | projected: 49 | sources: 50 | - secret: 51 | name: $LINSTOR_API_TLS_CLIENT_SECRET_NAME 52 | - $LINSTOR_API_TLS_CLIENT_CA_PROJECTION 53 | - target: 54 | version: v1 55 | kind: ConfigMap 56 | name: linstor-controller-config 57 | patch: | 58 | apiVersion: v1 59 | kind: ConfigMap 60 | metadata: 61 | name: linstor-controller-config 62 | data: 63 | linstor-client.conf: | 64 | [global] 65 | controllers = https://linstor-controller:3371 66 | cafile = /etc/linstor/client/ca.crt 67 | certfile = /etc/linstor/client/tls.crt 68 | keyfile = /etc/linstor/client/tls.key 69 | 70 | linstor.toml: | 71 | [https] 72 | enabled = true 73 | keystore = "/etc/linstor/https/keystore.jks" 74 | keystore_password = "linstor" 75 | truststore = "/etc/linstor/https/truststore.jks" 76 | truststore_password = "linstor" 77 | 78 | [db] 79 | connection_url = "k8s" 80 | - target: 81 | version: v1 82 | kind: Service 83 | name: linstor-controller 84 | patch: | 85 | apiVersion: v1 86 | kind: Service 87 | metadata: 88 | name: linstor-controller 89 | spec: 90 | ports: 91 | - port: 3371 92 | name: secure-api 93 | protocol: TCP 94 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/csi-controller-node-affinity.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-csi-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-csi-controller 12 | spec: 13 | template: 14 | spec: 15 | affinity: 16 | nodeAffinity: 17 | requiredDuringSchedulingIgnoredDuringExecution: $NODE_AFFINITY 18 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/csi-controller-selector.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-csi-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-csi-controller 12 | spec: 13 | template: 14 | spec: 15 | nodeSelector: $NODE_SELECTOR 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/csi-driver-no-selinux.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - patch: | 3 | apiVersion: storage.k8s.io/v1 4 | kind: CSIDriver 5 | metadata: 6 | name: linstor.csi.linbit.com 7 | spec: 8 | seLinuxMount: null 9 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/csi-node-node-affinity.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-csi-node 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-csi-node 12 | spec: 13 | template: 14 | spec: 15 | affinity: 16 | nodeAffinity: 17 | requiredDuringSchedulingIgnoredDuringExecution: $NODE_AFFINITY 18 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/csi-node-selector.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-csi-node 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-csi-node 12 | spec: 13 | template: 14 | spec: 15 | nodeSelector: $NODE_SELECTOR 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/ha-controller-node-affinity.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: ha-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: ha-controller 12 | spec: 13 | template: 14 | spec: 15 | affinity: 16 | nodeAffinity: 17 | requiredDuringSchedulingIgnoredDuringExecution: $NODE_AFFINITY 18 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/ha-controller-node-selector.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: ha-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: ha-controller 12 | spec: 13 | template: 14 | spec: 15 | nodeSelector: $NODE_SELECTOR 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/internal-tls-cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: cert-manager.io 4 | version: v1 5 | kind: Certificate 6 | name: linstor-controller-internal-tls 7 | patch: | 8 | apiVersion: cert-manager.io/v1 9 | kind: Certificate 10 | metadata: 11 | name: linstor-controller-internal-tls 12 | spec: 13 | secretName: $LINSTOR_INTERNAL_TLS_SECRET_NAME 14 | issuerRef: $LINSTOR_INTERNAL_TLS_CERT_ISSUER 15 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/internal-tls.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-controller 12 | spec: 13 | template: 14 | spec: 15 | volumes: 16 | - name: internal-tls 17 | projected: 18 | sources: 19 | - secret: 20 | name: $LINSTOR_INTERNAL_TLS_SECRET_NAME 21 | - $LINSTOR_INTERNAL_TLS_CA_PROJECTION 22 | - name: java-internal-tls 23 | emptyDir: {} 24 | initContainers: 25 | - name: run-migration 26 | volumeMounts: 27 | - name: internal-tls 28 | mountPath: /etc/linstor/ssl-pem 29 | readOnly: true 30 | - name: java-internal-tls 31 | mountPath: /etc/linstor/ssl 32 | containers: 33 | - name: linstor-controller 34 | volumeMounts: 35 | - name: internal-tls 36 | mountPath: /etc/linstor/ssl-pem 37 | readOnly: true 38 | - name: java-internal-tls 39 | mountPath: /etc/linstor/ssl 40 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/linstor-controller-node-affinity.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-controller 12 | spec: 13 | template: 14 | spec: 15 | affinity: 16 | nodeAffinity: 17 | requiredDuringSchedulingIgnoredDuringExecution: $NODE_AFFINITY 18 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/linstor-controller-selector.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-controller 12 | spec: 13 | template: 14 | spec: 15 | nodeSelector: $NODE_SELECTOR 16 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/passphrase.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: Deployment 6 | name: linstor-controller 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: linstor-controller 12 | spec: 13 | template: 14 | spec: 15 | containers: 16 | - name: linstor-controller 17 | env: 18 | - name: MASTER_PASSPHRASE 19 | valueFrom: 20 | secretKeyRef: 21 | name: $LINSTOR_PASSPHRASE_SECRET_NAME 22 | key: MASTER_PASSPHRASE 23 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/pod-template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: {} 3 | patch: | 4 | apiVersion: apps/v1 5 | kind: $KIND 6 | metadata: 7 | name: $NAME 8 | spec: 9 | template: $TEMPLATE 10 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/pull-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | version: v1 4 | kind: ServiceAccount 5 | patch: | 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: default 10 | imagePullSecrets: 11 | - name: $IMAGE_PULL_SECRET 12 | -------------------------------------------------------------------------------- /pkg/resources/cluster/patches/tolerations.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: {} 3 | patch: | 4 | apiVersion: apps/v1 5 | kind: $KIND 6 | metadata: 7 | name: $NAME 8 | spec: 9 | template: 10 | spec: 11 | tolerations: $TOLERATIONS 12 | -------------------------------------------------------------------------------- /pkg/resources/cluster/resources.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import "embed" 4 | 5 | //go:embed satellite-common controller csi-controller csi-node satellite patches ha-controller 6 | var Resources embed.FS 7 | -------------------------------------------------------------------------------- /pkg/resources/cluster/satellite-common/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - service-account.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/satellite-common/service-account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: satellite 6 | labels: 7 | app.kubernetes.io/component: linstor-satellite 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: Role 11 | metadata: 12 | name: satellite 13 | labels: 14 | app.kubernetes.io/component: linstor-satellite 15 | rules: 16 | - apiGroups: 17 | - security.openshift.io 18 | resources: 19 | - securitycontextconstraints 20 | resourceNames: 21 | - privileged 22 | verbs: 23 | - use 24 | --- 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: RoleBinding 27 | metadata: 28 | name: satellite 29 | labels: 30 | app.kubernetes.io/name: linstor-satellite 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: Role 34 | name: satellite 35 | subjects: 36 | - kind: ServiceAccount 37 | name: satellite 38 | -------------------------------------------------------------------------------- /pkg/resources/cluster/satellite/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - linstor-satellite.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/cluster/satellite/linstor-satellite.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: piraeus.io/v1 3 | kind: LinstorSatellite 4 | metadata: 5 | name: satellite 6 | spec: 7 | clusterRef: 8 | name: CLUSTER 9 | patches: [] 10 | storagePools: [] 11 | properties: 12 | - name: Aux/topology/linbit.com/hostname 13 | valueFrom: 14 | nodeFieldRef: metadata.name 15 | - name: Aux/topology/kubernetes.io/hostname 16 | valueFrom: 17 | nodeFieldRef: metadata.labels['kubernetes.io/hostname'] 18 | - name: Aux/topology/topology.kubernetes.io/region 19 | optional: true 20 | valueFrom: 21 | nodeFieldRef: metadata.labels['topology.kubernetes.io/region'] 22 | - name: Aux/topology/topology.kubernetes.io/zone 23 | optional: true 24 | valueFrom: 25 | nodeFieldRef: metadata.labels['topology.kubernetes.io/zone'] 26 | -------------------------------------------------------------------------------- /pkg/resources/loader.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | 7 | "sigs.k8s.io/kustomize/api/krusty" 8 | "sigs.k8s.io/kustomize/api/resmap" 9 | "sigs.k8s.io/kustomize/api/types" 10 | "sigs.k8s.io/kustomize/kyaml/filesys" 11 | "sigs.k8s.io/kustomize/kyaml/openapi" 12 | "sigs.k8s.io/yaml" 13 | ) 14 | 15 | type Kustomizer struct { 16 | fsys filesys.FileSystem 17 | kustomizer *krusty.Kustomizer 18 | } 19 | 20 | func init() { 21 | // We need to inform kustomize that our own CRD are cluster scoped, i.e. they don't have a namespace. 22 | // Otherwise, we would have namespaces after kustomization on object that do not support it. This would then 23 | // potentially trip up our pruning logic. So we add a simple schema here, which then gets used internally by 24 | // kustomize to determine that our resources are cluster scoped. 25 | _ = openapi.AddSchema([]byte(` 26 | { 27 | "definitions": {}, 28 | "paths": { 29 | "/apis/piraeus.io/v1/linstorclusters": { 30 | "get": { 31 | "x-kubernetes-action": "get", 32 | "x-kubernetes-group-version-kind": { 33 | "group": "piraeus.io", 34 | "kind": "LinstorCluster", 35 | "version": "v1" 36 | } 37 | } 38 | }, 39 | "/apis/piraeus.io/v1/linstorsatelliteconfigurations": { 40 | "get": { 41 | "x-kubernetes-action": "get", 42 | "x-kubernetes-group-version-kind": { 43 | "group": "piraeus.io", 44 | "kind": "LinstorSatelliteConfiguration", 45 | "version": "v1" 46 | } 47 | } 48 | }, 49 | "/apis/piraeus.io/v1/linstorsatellites": { 50 | "get": { 51 | "x-kubernetes-action": "get", 52 | "x-kubernetes-group-version-kind": { 53 | "group": "piraeus.io", 54 | "kind": "LinstorSatellite", 55 | "version": "v1" 56 | } 57 | } 58 | } 59 | } 60 | } 61 | `)) 62 | } 63 | 64 | func NewKustomizer(base *embed.FS, options *krusty.Options) (*Kustomizer, error) { 65 | fsys := filesys.MakeFsInMemory() 66 | 67 | err := fs.WalkDir(base, ".", func(path string, d fs.DirEntry, err error) error { 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if d.IsDir() { 73 | err := fsys.Mkdir(path) 74 | if err != nil { 75 | return err 76 | } 77 | } else { 78 | content, err := base.ReadFile(path) 79 | if err != nil { 80 | return err 81 | } 82 | err = fsys.WriteFile(path, content) 83 | if err != nil { 84 | return err 85 | } 86 | } 87 | 88 | return nil 89 | }) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | return &Kustomizer{ 95 | fsys: fsys, 96 | kustomizer: krusty.MakeKustomizer(options), 97 | }, nil 98 | } 99 | 100 | func (k *Kustomizer) Kustomize(kustomization *types.Kustomization) (resmap.ResMap, error) { 101 | rawK, err := yaml.Marshal(kustomization) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | err = k.fsys.WriteFile("/kustomization.yaml", rawK) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | return k.kustomizer.Run(k.fsys, "/") 112 | } 113 | -------------------------------------------------------------------------------- /pkg/resources/loader_test.go: -------------------------------------------------------------------------------- 1 | package resources_test 2 | 3 | import ( 4 | "embed" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "sigs.k8s.io/kustomize/api/krusty" 9 | "sigs.k8s.io/kustomize/api/types" 10 | "sigs.k8s.io/kustomize/kyaml/openapi" 11 | "sigs.k8s.io/kustomize/kyaml/yaml" 12 | 13 | piraeusv1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 14 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/resources" 15 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/resources/test" 16 | ) 17 | 18 | func TestNewKustomizer(t *testing.T) { 19 | t.Parallel() 20 | 21 | testcases := []struct { 22 | name string 23 | fs *embed.FS 24 | kustomize *types.Kustomization 25 | expected string 26 | }{ 27 | { 28 | name: "empty", 29 | fs: &test.EmptyResources, 30 | kustomize: &types.Kustomization{Resources: []string{"empty"}}, 31 | }, 32 | { 33 | name: "basic", 34 | fs: &test.BasicResources, 35 | kustomize: &types.Kustomization{Resources: []string{"basic"}}, 36 | expected: `apiVersion: v1 37 | kind: Namespace 38 | metadata: 39 | name: example 40 | --- 41 | apiVersion: v1 42 | kind: ServiceAccount 43 | metadata: 44 | name: example-sa 45 | namespace: example 46 | `, 47 | }, 48 | { 49 | name: "basic-patch", 50 | fs: &test.BasicResources, 51 | kustomize: &types.Kustomization{ 52 | Resources: []string{"basic"}, 53 | NamePrefix: "patch-", 54 | Namespace: "patched", 55 | }, 56 | expected: `apiVersion: v1 57 | kind: Namespace 58 | metadata: 59 | name: patched 60 | --- 61 | apiVersion: v1 62 | kind: ServiceAccount 63 | metadata: 64 | name: patch-example-sa 65 | namespace: patched 66 | `, 67 | }, 68 | } 69 | 70 | for i := range testcases { 71 | tcase := &testcases[i] 72 | t.Run(tcase.name, func(t *testing.T) { 73 | t.Parallel() 74 | 75 | kstmzr, err := resources.NewKustomizer(tcase.fs, krusty.MakeDefaultOptions()) 76 | assert.NoError(t, err) 77 | 78 | resmap, err := kstmzr.Kustomize(tcase.kustomize) 79 | assert.NoError(t, err) 80 | actual, err := resmap.AsYaml() 81 | assert.NoError(t, err) 82 | assert.Equal(t, tcase.expected, string(actual)) 83 | }) 84 | } 85 | } 86 | 87 | func TestOpenApiInit(t *testing.T) { 88 | t.Parallel() 89 | 90 | testcases := []struct { 91 | Kind string 92 | APIVersion string 93 | }{ 94 | {Kind: "LinstorCluster", APIVersion: piraeusv1.GroupVersion.String()}, 95 | {Kind: "LinstorSatelliteConfiguration", APIVersion: piraeusv1.GroupVersion.String()}, 96 | {Kind: "LinstorSatellite", APIVersion: piraeusv1.GroupVersion.String()}, 97 | } 98 | 99 | for i := range testcases { 100 | tcase := &testcases[i] 101 | t.Run(tcase.Kind, func(t *testing.T) { 102 | t.Parallel() 103 | 104 | actual := openapi.IsCertainlyClusterScoped(yaml.TypeMeta{Kind: tcase.Kind, APIVersion: tcase.APIVersion}) 105 | assert.True(t, actual) 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/common-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | affinity: 16 | nodeAffinity: 17 | requiredDuringSchedulingIgnoredDuringExecution: 18 | nodeSelectorTerms: 19 | - matchFields: 20 | - key: metadata.name 21 | operator: In 22 | values: 23 | - $NODE_NAME 24 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/host-path-volume-env.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | containers: 16 | - name: linstor-satellite 17 | env: 18 | - name: LOSETUP_CONTAINER_BIND_MOUNTS 19 | value: $HOST_PATHS 20 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/host-path-volume.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | volumes: 16 | - name: $VOLUME_NAME 17 | hostPath: 18 | path: $HOST_PATH 19 | type: DirectoryOrCreate 20 | containers: 21 | - name: linstor-satellite 22 | volumeMounts: 23 | - name: $VOLUME_NAME 24 | mountPath: $HOST_PATH 25 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/internal-tls-cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: cert-manager.io 4 | version: v1 5 | kind: Certificate 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: cert-manager.io/v1 9 | kind: Certificate 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | secretName: $LINSTOR_INTERNAL_TLS_SECRET_NAME 14 | issuerRef: $LINSTOR_INTERNAL_TLS_CERT_ISSUER 15 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/internal-tls.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | volumes: 16 | - name: internal-tls 17 | projected: 18 | sources: 19 | - secret: 20 | name: $LINSTOR_INTERNAL_TLS_SECRET_NAME 21 | - $LINSTOR_INTERNAL_TLS_CA_PROJECTION 22 | - name: java-internal-tls 23 | emptyDir: { } 24 | containers: 25 | - name: linstor-satellite 26 | ports: 27 | - containerPort: 3366 28 | protocol: TCP 29 | $patch: delete 30 | - containerPort: 3367 31 | name: linstor 32 | protocol: TCP 33 | volumeMounts: 34 | - name: internal-tls 35 | mountPath: /etc/linstor/ssl-pem 36 | readOnly: true 37 | - name: java-internal-tls 38 | mountPath: /etc/linstor/ssl 39 | - target: 40 | version: v1 41 | kind: ConfigMap 42 | name: satellite-config 43 | patch: | 44 | apiVersion: v1 45 | kind: ConfigMap 46 | metadata: 47 | name: satellite-config 48 | data: 49 | linstor_satellite.toml: | 50 | [netcom] 51 | type = "ssl" 52 | port = 3367 53 | server_certificate = "/etc/linstor/ssl/keystore.jks" 54 | key_password = "linstor" 55 | keystore_password = "linstor" 56 | trusted_certificates = "/etc/linstor/ssl/certificates.jks" 57 | truststore_password = "linstor" 58 | ssl_protocol = "TLSv1.2" 59 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/precompiled-module.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | volumes: 16 | - name: usr-src 17 | $patch: delete 18 | initContainers: 19 | - name: drbd-module-loader 20 | volumeMounts: 21 | - name: usr-src 22 | mountPath: /usr/src 23 | $patch: delete 24 | env: 25 | - name: LB_HOW 26 | value: shipped_modules 27 | -------------------------------------------------------------------------------- /pkg/resources/satellite/patches/tlshd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - target: 3 | group: apps 4 | version: v1 5 | kind: DaemonSet 6 | name: linstor-satellite 7 | patch: | 8 | apiVersion: apps/v1 9 | kind: DaemonSet 10 | metadata: 11 | name: linstor-satellite 12 | spec: 13 | template: 14 | spec: 15 | containers: 16 | - name: ktls-utils 17 | image: ktls-utils 18 | securityContext: 19 | capabilities: 20 | add: 21 | - NET_ADMIN 22 | drop: 23 | - ALL 24 | privileged: true 25 | readOnlyRootFilesystem: true 26 | volumeMounts: 27 | - name: internal-tls 28 | mountPath: /etc/tlshd.d 29 | readOnly: true 30 | -------------------------------------------------------------------------------- /pkg/resources/satellite/resources.go: -------------------------------------------------------------------------------- 1 | package satellite 2 | 3 | import "embed" 4 | 5 | //go:embed satellite patches 6 | var Resources embed.FS 7 | -------------------------------------------------------------------------------- /pkg/resources/satellite/satellite/cert-manager/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: linstor-satellite 6 | spec: 7 | issuerRef: 8 | name: FILLME 9 | secretName: tls 10 | dnsNames: 11 | - FILLME 12 | usages: 13 | - "server auth" 14 | -------------------------------------------------------------------------------- /pkg/resources/satellite/satellite/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - certificate.yaml 6 | -------------------------------------------------------------------------------- /pkg/resources/satellite/satellite/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: satellite-config 6 | labels: 7 | app.kubernetes.io/component: linstor-satellite 8 | data: 9 | linstor_satellite.toml: "" 10 | --- 11 | apiVersion: v1 12 | kind: ConfigMap 13 | metadata: 14 | name: reactor-config 15 | labels: 16 | app.kubernetes.io/component: linstor-satellite 17 | data: 18 | prometheus.toml: | 19 | [[prometheus]] 20 | enums = true 21 | address = ":9942" 22 | -------------------------------------------------------------------------------- /pkg/resources/satellite/satellite/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - daemonset.yaml 6 | - config.yaml 7 | -------------------------------------------------------------------------------- /pkg/resources/test/basic/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Kustomization 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | resources: 5 | - namespace.yaml 6 | - serviceaccount.yaml 7 | -------------------------------------------------------------------------------- /pkg/resources/test/basic/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: example 6 | -------------------------------------------------------------------------------- /pkg/resources/test/basic/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: example-sa 6 | namespace: example 7 | -------------------------------------------------------------------------------- /pkg/resources/test/empty/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Kustomization 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | resources: [] 5 | -------------------------------------------------------------------------------- /pkg/resources/test/resources.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "embed" 4 | 5 | var ( 6 | //go:embed empty 7 | EmptyResources embed.FS 8 | 9 | //go:embed basic 10 | BasicResources embed.FS 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/utils/anyerror.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | 6 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 7 | ) 8 | 9 | // AnyResult returns an error if any given error is not nil, otherwise it just returns the result 10 | func AnyResult(result reconcile.Result, errs ...error) (reconcile.Result, error) { 11 | err := errors.Join(errs...) 12 | if err != nil { 13 | return reconcile.Result{}, err 14 | } 15 | 16 | return result, nil 17 | } 18 | -------------------------------------------------------------------------------- /pkg/utils/fieldpath/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 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 fieldpath supplies methods for extracting fields from objects 18 | // given a path to a field. 19 | package fieldpath 20 | -------------------------------------------------------------------------------- /pkg/utils/jsonpatch.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type Op string 4 | 5 | const ( 6 | Add Op = "add" 7 | Remove Op = "remove" 8 | Replace Op = "replace" 9 | Move Op = "move" 10 | Copy Op = "copy" 11 | Test Op = "test" 12 | ) 13 | 14 | type JsonPatch struct { 15 | Op Op `json:"op"` 16 | Path string `json:"path"` 17 | From string `json:"from,omitempty"` 18 | Value any `json:"value,omitempty"` 19 | } 20 | -------------------------------------------------------------------------------- /pkg/utils/patch_convert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | kusttypes "sigs.k8s.io/kustomize/api/types" 9 | "sigs.k8s.io/kustomize/kyaml/resid" 10 | "sigs.k8s.io/yaml" 11 | 12 | piraeusiov1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 13 | ) 14 | 15 | func ToEncodedPatch(target *kusttypes.Selector, patch any) (*kusttypes.Patch, error) { 16 | switch p := patch.(type) { 17 | case string: 18 | return &kusttypes.Patch{ 19 | Target: target, 20 | Patch: p, 21 | }, nil 22 | default: 23 | encoded, err := json.Marshal(p) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return &kusttypes.Patch{ 29 | Target: target, 30 | Patch: string(encoded), 31 | }, nil 32 | } 33 | } 34 | 35 | func MakeKustPatches(patches ...piraeusiov1.Patch) []kusttypes.Patch { 36 | var result []kusttypes.Patch 37 | for i := range patches { 38 | result = append(result, kusttypes.Patch{ 39 | Target: makeKustSelector(patches[i].Target), 40 | Patch: patches[i].Patch, 41 | Options: patches[i].Options, 42 | }) 43 | } 44 | 45 | return result 46 | } 47 | 48 | func makeKustSelector(selector *piraeusiov1.Selector) *kusttypes.Selector { 49 | if selector == nil { 50 | return nil 51 | } 52 | 53 | return &kusttypes.Selector{ 54 | ResId: resid.ResId{ 55 | Gvk: resid.NewGvk(selector.Group, selector.Version, selector.Kind), 56 | Name: selector.Name, 57 | Namespace: selector.Namespace, 58 | }, 59 | AnnotationSelector: selector.AnnotationSelector, 60 | LabelSelector: selector.LabelSelector, 61 | } 62 | } 63 | 64 | func RenderPatches(params map[string]any, patches ...kusttypes.Patch) ([]kusttypes.Patch, error) { 65 | result := make([]kusttypes.Patch, len(patches)) 66 | 67 | for i := range patches { 68 | var decoded any 69 | err := yaml.Unmarshal([]byte(patches[i].Patch), &decoded) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | replaced, err := replaceVal(params, decoded) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | p, err := yaml.Marshal(&replaced) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | result[i].Target = patches[i].Target 85 | result[i].Options = patches[i].Options 86 | result[i].Patch = string(p) 87 | } 88 | 89 | return result, nil 90 | } 91 | 92 | func replaceVal(params map[string]any, v any) (any, error) { 93 | switch vv := v.(type) { 94 | case string: 95 | if strings.HasPrefix(vv, "$") { 96 | replacement, ok := params[vv[1:]] 97 | if !ok { 98 | return nil, fmt.Errorf("parameter '%s' has no value", vv) 99 | } 100 | 101 | return replacement, nil 102 | } 103 | 104 | return vv, nil 105 | case []any: 106 | for i := range vv { 107 | r, err := replaceVal(params, vv[i]) 108 | if err != nil { 109 | return nil, err 110 | } 111 | vv[i] = r 112 | } 113 | 114 | return vv, nil 115 | case map[string]any: 116 | for k, v := range vv { 117 | r, err := replaceVal(params, v) 118 | if err != nil { 119 | return nil, err 120 | } 121 | vv[k] = r 122 | } 123 | 124 | return vv, nil 125 | } 126 | 127 | return v, nil 128 | } 129 | -------------------------------------------------------------------------------- /pkg/utils/patch_convert_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" 8 | kusttypes "sigs.k8s.io/kustomize/api/types" 9 | "sigs.k8s.io/kustomize/kyaml/resid" 10 | 11 | piraeusv1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 12 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/utils" 13 | ) 14 | 15 | func TestToEncodedPatch(t *testing.T) { 16 | t.Parallel() 17 | 18 | exampleSelector := &kusttypes.Selector{ 19 | ResId: resid.NewResIdKindOnly("Pod", "example"), 20 | LabelSelector: "controlled-by=test", 21 | } 22 | 23 | testcases := []struct { 24 | name string 25 | selector *kusttypes.Selector 26 | patch any 27 | result *kusttypes.Patch 28 | }{ 29 | { 30 | name: "convert-string", 31 | patch: "a-string-patch", 32 | selector: exampleSelector, 33 | result: &kusttypes.Patch{ 34 | Target: exampleSelector, 35 | Patch: "a-string-patch", 36 | }, 37 | }, 38 | { 39 | name: "convert-apply-obj", 40 | patch: applycorev1.Pod("example", ""). 41 | WithSpec(applycorev1.PodSpec(). 42 | WithHostNetwork(true)), 43 | selector: exampleSelector, 44 | result: &kusttypes.Patch{ 45 | Target: exampleSelector, 46 | Patch: "{\"kind\":\"Pod\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"example\",\"namespace\":\"\"},\"spec\":{\"hostNetwork\":true}}", 47 | }, 48 | }, 49 | } 50 | 51 | for i := range testcases { 52 | tcase := &testcases[i] 53 | t.Run(tcase.name, func(t *testing.T) { 54 | t.Parallel() 55 | 56 | actual, err := utils.ToEncodedPatch(tcase.selector, tcase.patch) 57 | assert.NoError(t, err) 58 | assert.Equal(t, tcase.result, actual) 59 | }) 60 | } 61 | } 62 | 63 | func TestMakeKustPatches(t *testing.T) { 64 | t.Parallel() 65 | 66 | testcases := []struct { 67 | name string 68 | patches []piraeusv1.Patch 69 | result []kusttypes.Patch 70 | }{ 71 | { 72 | name: "empty", 73 | }, 74 | { 75 | name: "basic-patches", 76 | patches: []piraeusv1.Patch{ 77 | {Patch: "patch-without-selector"}, 78 | {Patch: "patch-with-selector", Target: &piraeusv1.Selector{Kind: "Deployment", Version: "v1", Group: "apps", Name: "foo", Namespace: "test-ns", AnnotationSelector: "annotation1=val1", LabelSelector: "label1=val2"}}, 79 | {Patch: "patch-with-options", Options: map[string]bool{"opt1": true, "opt2": false}}, 80 | }, 81 | result: []kusttypes.Patch{ 82 | {Patch: "patch-without-selector"}, 83 | {Patch: "patch-with-selector", Target: &kusttypes.Selector{ResId: resid.ResId{Gvk: resid.Gvk{Kind: "Deployment", Version: "v1", Group: "apps"}, Name: "foo", Namespace: "test-ns"}, AnnotationSelector: "annotation1=val1", LabelSelector: "label1=val2"}}, 84 | {Patch: "patch-with-options", Options: map[string]bool{"opt1": true, "opt2": false}}, 85 | }, 86 | }, 87 | } 88 | 89 | for i := range testcases { 90 | tcase := &testcases[i] 91 | t.Run(tcase.name, func(t *testing.T) { 92 | t.Parallel() 93 | 94 | actual := utils.MakeKustPatches(tcase.patches...) 95 | assert.Equal(t, tcase.result, actual) 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pkg/utils/property_resolution.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | 9 | v1 "github.com/piraeusdatastore/piraeus-operator/v2/api/v1" 10 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/utils/fieldpath" 11 | ) 12 | 13 | func ResolveNodeProperties(node *corev1.Node, props ...v1.LinstorNodeProperty) (map[string]string, error) { 14 | result := make(map[string]string) 15 | for i := range props { 16 | k := props[i].Name 17 | switch { 18 | case props[i].Value != "": 19 | result[k] = props[i].Value 20 | case props[i].ValueFrom != nil: 21 | vals, keys, err := fieldpath.ExtractFieldPath(node, props[i].ValueFrom.NodeFieldRef) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if keys != nil { 27 | return nil, fmt.Errorf("wildcards not allowed in 'valueFrom': '%s'", props[i].ValueFrom.NodeFieldRef) 28 | } 29 | 30 | if len(vals) > 0 { 31 | result[k] = vals[0] 32 | } else if !props[i].Optional { 33 | result[k] = "" 34 | } 35 | case props[i].ExpandFrom != nil: 36 | vals, keys, err := fieldpath.ExtractFieldPath(node, props[i].ExpandFrom.NodeFieldRef) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if len(keys) != len(vals) { 42 | return nil, fmt.Errorf("wildcards are required in 'expandFrom': '%s'", props[i].ExpandFrom.NodeFieldRef) 43 | } 44 | 45 | if props[i].ExpandFrom.NameTemplate != "" { 46 | for j := range vals { 47 | key := props[i].ExpandFrom.NameTemplate 48 | key = strings.ReplaceAll(key, "$1", keys[j]) 49 | key = strings.ReplaceAll(key, "$2", vals[j]) 50 | val := props[i].ExpandFrom.ValueTemplate 51 | val = strings.ReplaceAll(val, "$1", keys[j]) 52 | val = strings.ReplaceAll(val, "$2", vals[j]) 53 | result[props[i].Name+key] = val 54 | } 55 | } else { 56 | toJoin := make([]string, 0, len(vals)) 57 | for j := range vals { 58 | val := props[i].ExpandFrom.ValueTemplate 59 | val = strings.ReplaceAll(val, "$1", keys[j]) 60 | val = strings.ReplaceAll(val, "$2", vals[j]) 61 | toJoin = append(toJoin, val) 62 | } 63 | 64 | // No need to sort, keys (and their vals) are guaranteed in sorted order. 65 | result[k] = strings.Join(toJoin, props[i].ExpandFrom.Delimiter) 66 | } 67 | } 68 | } 69 | 70 | return result, nil 71 | } 72 | 73 | func ResolveClusterProperties(defaults map[string]string, props ...v1.LinstorControllerProperty) map[string]string { 74 | result := make(map[string]string) 75 | 76 | for k, v := range defaults { 77 | result[k] = v 78 | } 79 | 80 | for i := range props { 81 | result[props[i].Name] = props[i].Value 82 | } 83 | 84 | return result 85 | } 86 | -------------------------------------------------------------------------------- /pkg/utils/prune.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "k8s.io/apimachinery/pkg/api/meta" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/kustomize/api/resmap" 12 | "sigs.k8s.io/kustomize/kyaml/resid" 13 | ) 14 | 15 | // PruneResources removes all resources that are controlled by the given controller, but that should (no longer) exist. 16 | func PruneResources(ctx context.Context, cl client.Client, controller client.Object, namespace string, toKeep resmap.ResMap, kindsToPrune ...client.Object) error { 17 | for _, kind := range kindsToPrune { 18 | gvks, _, err := cl.Scheme().ObjectKinds(kind) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | if len(gvks) < 1 { 24 | return fmt.Errorf("'%T' is not known in scheme", kind) 25 | } 26 | 27 | u := &unstructured.UnstructuredList{} 28 | u.SetGroupVersionKind(gvks[0]) 29 | err = cl.List(ctx, u, client.InNamespace(namespace)) 30 | if err != nil { 31 | if meta.IsNoMatchError(err) { 32 | // Nothing to prune, as there is not even a schema definition for that type. 33 | continue 34 | } 35 | 36 | return err 37 | } 38 | 39 | for _, item := range u.Items { 40 | _, err = toKeep.GetByCurrentId(resid.NewResIdWithNamespace(resid.NewGvk(gvks[0].Group, gvks[0].Version, gvks[0].Kind), item.GetName(), item.GetNamespace())) 41 | if err == nil { 42 | continue 43 | } 44 | 45 | if !metav1.IsControlledBy(&item, controller) { 46 | continue 47 | } 48 | 49 | err := cl.Delete(ctx, &item) 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/utils/version.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "cmp" 5 | "fmt" 6 | "strconv" 7 | 8 | "k8s.io/client-go/discovery" 9 | "k8s.io/client-go/rest" 10 | ) 11 | 12 | func NewAPIVersionFromConfigWithFallback(conf *rest.Config, fallback *APIVersion) *APIVersion { 13 | client, err := discovery.NewDiscoveryClientForConfig(conf) 14 | if err != nil { 15 | return fallback 16 | } 17 | 18 | version, err := client.ServerVersion() 19 | if err != nil { 20 | return fallback 21 | } 22 | 23 | major, err := strconv.Atoi(version.Major) 24 | if err != nil { 25 | return fallback 26 | } 27 | 28 | minor, err := strconv.Atoi(version.Minor) 29 | if err != nil { 30 | return fallback 31 | } 32 | 33 | return &APIVersion{ 34 | Major: major, 35 | Minor: minor, 36 | } 37 | } 38 | 39 | type APIVersion struct { 40 | Major int 41 | Minor int 42 | } 43 | 44 | func (a *APIVersion) String() string { 45 | return fmt.Sprintf("v%d.%d", a.Major, a.Minor) 46 | } 47 | 48 | func (a *APIVersion) Compare(b *APIVersion) int { 49 | majorDiff := cmp.Compare(a.Major, b.Major) 50 | if majorDiff != 0 { 51 | return majorDiff 52 | } 53 | 54 | return cmp.Compare(a.Minor, b.Minor) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/utils/version_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/piraeusdatastore/piraeus-operator/v2/pkg/utils" 10 | ) 11 | 12 | func TestAPIVersion(t *testing.T) { 13 | t.Parallel() 14 | 15 | testcases := []struct { 16 | a utils.APIVersion 17 | b utils.APIVersion 18 | expected int 19 | }{ 20 | { 21 | a: utils.APIVersion{Major: 1, Minor: 20}, 22 | b: utils.APIVersion{Major: 1, Minor: 20}, 23 | expected: 0, 24 | }, 25 | { 26 | a: utils.APIVersion{Major: 1, Minor: 20}, 27 | b: utils.APIVersion{Major: 1, Minor: 19}, 28 | expected: 1, 29 | }, 30 | { 31 | a: utils.APIVersion{Major: 1, Minor: 20}, 32 | b: utils.APIVersion{Major: 1, Minor: 21}, 33 | expected: -1, 34 | }, 35 | { 36 | a: utils.APIVersion{Major: 1, Minor: 20}, 37 | b: utils.APIVersion{Major: 2, Minor: 19}, 38 | expected: -1, 39 | }, 40 | { 41 | a: utils.APIVersion{Major: 1, Minor: 20}, 42 | b: utils.APIVersion{Major: 0, Minor: 21}, 43 | expected: 1, 44 | }, 45 | } 46 | 47 | for i := range testcases { 48 | tcase := &testcases[i] 49 | t.Run(fmt.Sprintf("%s<=>%s", &tcase.a, &tcase.b), func(t *testing.T) { 50 | t.Parallel() 51 | assert.Equal(t, tcase.expected, tcase.a.Compare(&tcase.b)) 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/vars/branding.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | const ( 4 | ProjectName = "piraeus-datastore" 5 | OperatorName = "piraeus-operator" 6 | Domain = "piraeus.io" 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/vars/defaults.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import linstor "github.com/LINBIT/golinstor" 4 | 5 | var DefaultControllerProperties = map[string]string{ 6 | // TODO: Remove once DRBD does not need this anymore 7 | linstor.NamespcDrbdNetOptions + "/rr-conflict": "retry-connect", 8 | linstor.NamespcDrbdResourceOptions + "/on-suspended-primary-outdated": "force-secondary", 9 | linstor.NamespcDrbdResourceOptions + "/on-no-data-accessible": "suspend-io", 10 | linstor.NamespcDrbdResourceOptions + "/on-no-quorum": "suspend-io", 11 | // The operator can do its own eviction when necessary 12 | linstor.NamespcDrbdOptions + "/AutoEvictAllowEviction": "false", 13 | } 14 | -------------------------------------------------------------------------------- /pkg/vars/vars.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import "github.com/piraeusdatastore/piraeus-operator/v2/pkg/utils" 4 | 5 | var ( 6 | Version = "2.0.0" 7 | ExtraLabels = map[string]string{ 8 | "app.kubernetes.io/version": Version, 9 | "app.kubernetes.io/managed-by": OperatorName, 10 | } 11 | FallbackAPIVersion = utils.APIVersion{ 12 | Major: 1, 13 | Minor: 20, 14 | } 15 | ) 16 | 17 | const ( 18 | FieldOwner = Domain + "/operator" 19 | ApplyAnnotation = Domain + "/last-applied" 20 | NodeInterfaceAnnotation = Domain + "/configured-interfaces" 21 | ManagedByLabel = Domain + "/managed-by" 22 | SatelliteNodeLabel = Domain + "/linstor-satellite" 23 | SatelliteFinalizer = Domain + "/satellite-protection" 24 | GenCertLeaderElectionID = OperatorName + "-gencert" 25 | ) 26 | --------------------------------------------------------------------------------