├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json └── workflows │ ├── codeql-analysis.yml │ ├── golangci-lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .markdownlint.json ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1 │ ├── account_types.go │ ├── common.go │ ├── condition_types.go │ ├── dnsrecord_types.go │ ├── doc.go │ ├── groupversion_info.go │ ├── ip_types.go │ ├── zone_types.go │ └── zz_generated.deepcopy.go ├── cmd └── main.go ├── config ├── crd │ ├── bases │ │ ├── cloudflare-operator.io_accounts.yaml │ │ ├── cloudflare-operator.io_dnsrecords.yaml │ │ ├── cloudflare-operator.io_ips.yaml │ │ └── cloudflare-operator.io_zones.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── grafana │ │ └── dashboards │ │ │ └── overview.json │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── account_editor_role.yaml │ ├── account_viewer_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── dnsrecord_editor_role.yaml │ ├── dnsrecord_viewer_role.yaml │ ├── ip_editor_role.yaml │ ├── ip_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── service_account.yaml │ ├── zone_editor_role.yaml │ └── zone_viewer_role.yaml ├── samples │ ├── cloudflareoperatorio_v1_account.yaml │ ├── cloudflareoperatorio_v1_dnsrecord.yaml │ ├── cloudflareoperatorio_v1_ip.yaml │ ├── cloudflareoperatorio_v1_zone.yaml │ ├── kustomization.yaml │ └── networking_v1_ingress.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── go.mod ├── go.sum ├── hack ├── api-docs │ ├── config.json │ └── template │ │ ├── members.tpl │ │ ├── pkg.tpl │ │ └── type.tpl └── boilerplate.go.txt ├── internal ├── conditions │ ├── conditions.go │ └── conditions_test.go ├── controller │ ├── account_controller.go │ ├── account_controller_test.go │ ├── dnsrecord_controller.go │ ├── dnsrecord_controller_test.go │ ├── ingress_controller.go │ ├── ingress_controller_test.go │ ├── ip_controller.go │ ├── ip_controller_test.go │ ├── suite_test.go │ ├── zone_controller.go │ └── zone_controller_test.go ├── errors │ ├── ignore.go │ └── is.go ├── metrics │ └── metrics.go └── predicates │ ├── predicates.go │ └── predicates_test.go └── test ├── e2e ├── e2e_suite_test.go └── e2e_test.go └── utils └── utils.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | - [ ] The PR has a meaningful title. It will be used to auto generate the 4 | changelog. 5 | The PR has a meaningful description that sums up the change. It will be 6 | linked in the changelog. 7 | - [ ] PR contains a single logical change (to build a better changelog). 8 | - [ ] Update the documentation. 9 | - [ ] Categorize the PR by adding one of the labels: 10 | `bug`, `enhancement`, `documentation`, `change`, `breaking`, `dependency` 11 | as they show up in the changelog. 12 | - [ ] Link this PR to related issues or PRs. 13 | 14 | 22 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:recommended"], 3 | "labels": ["dependency"], 4 | "postUpdateOptions": ["gomodTidy"], 5 | "packageRules": [ 6 | { 7 | "matchPackageNames": ["k8s.io/client-go"], 8 | "allowedVersions": "< 1.0.0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | paths-ignore: 18 | - .github/** 19 | pull_request: 20 | # The branches below must be a subset of the branches above 21 | branches: [master] 22 | paths-ignore: 23 | - .github/** 24 | schedule: 25 | - cron: "18 7 * * 0" 26 | 27 | jobs: 28 | analyze: 29 | name: Analyze 30 | runs-on: ubuntu-latest 31 | permissions: 32 | actions: read 33 | contents: read 34 | security-events: write 35 | 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | language: ["go"] 40 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 41 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | 47 | # Initializes the CodeQL tools for scanning. 48 | - name: Initialize CodeQL 49 | uses: github/codeql-action/init@v3 50 | with: 51 | languages: ${{ matrix.language }} 52 | # If you wish to specify custom queries, you can do so here or in a config file. 53 | # By default, queries listed here will override any specified in a config file. 54 | # Prefix the list here with "+" to use these queries and those in the config file. 55 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v3 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 https://git.io/JvXDl 64 | 65 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 66 | # and modify them (or add more) to build your code if your project 67 | # uses a compiled language 68 | 69 | #- run: | 70 | # make bootstrap 71 | # make release 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v3 75 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: golangci-lint 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - .github/** 9 | pull_request: 10 | paths-ignore: 11 | - .github/** 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | golangci: 18 | name: lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Determine Go version from go.mod 23 | run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV 24 | - name: Set up Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ env.GO_VERSION }} 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v7 30 | with: 31 | args: --issues-exit-code=0 --timeout=3m ./... 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Determine Go version from go.mod 18 | run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ env.GO_VERSION }} 23 | - name: Set up QEMU 24 | uses: docker/setup-qemu-action@v3 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | - name: Cache Go modules 28 | uses: actions/cache@v4 29 | with: 30 | path: ~/go/pkg/mod 31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-go- 34 | - name: Login to DockerHub 35 | uses: docker/login-action@v3 36 | with: 37 | username: ${{ secrets.DOCKERHUB_USERNAME }} 38 | password: ${{ secrets.DOCKERHUB_TOKEN }} 39 | - name: Log in to GitHub Container registry 40 | uses: docker/login-action@v3 41 | with: 42 | registry: ghcr.io 43 | username: ${{ github.actor }} 44 | password: ${{ secrets.GITHUB_TOKEN }} 45 | - name: Generate CRDs 46 | run: make kustomize && make crd && git reset --hard 47 | - name: Run GoReleaser 48 | uses: goreleaser/goreleaser-action@v6 49 | if: success() && startsWith(github.ref, 'refs/tags/') 50 | with: 51 | version: latest 52 | args: release 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Run tests 3 | 4 | on: 5 | workflow_call: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | workflow_dispatch: 13 | 14 | env: 15 | KUBERNETES_VERSION: "1.32.0" 16 | 17 | jobs: 18 | tests: 19 | name: Run Tests 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | contents: read 24 | 25 | steps: 26 | - name: Check out code 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | submodules: recursive 31 | 32 | - name: Set up Go 33 | uses: actions/setup-go@v5 34 | with: 35 | go-version-file: "go.mod" 36 | check-latest: true 37 | cache: true 38 | 39 | - name: Set up Kind Cluster 40 | run: | 41 | make kind 42 | 43 | - name: Run Unit Tests 44 | env: 45 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 46 | CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }} 47 | run: | 48 | make test 49 | 50 | - name: Run E2E Tests 51 | env: 52 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 53 | run: | 54 | make test-e2e 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin/* 9 | Dockerfile.cross 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Go workspace file 18 | go.work 19 | 20 | # Kubernetes Generated files - skip generated files, except for vendored files 21 | !vendor/**/zz_generated.* 22 | 23 | # editor and IDE paraphernalia 24 | .idea 25 | .vscode 26 | *.swp 27 | *.swo 28 | *~ 29 | 30 | dist/ 31 | crds.yaml 32 | api-ref.html 33 | .github/release-notes.md 34 | testdata/ 35 | .envrc 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2" 3 | run: 4 | allow-parallel-runners: true 5 | linters: 6 | default: none 7 | enable: 8 | - copyloopvar 9 | - dupl 10 | - errcheck 11 | - ginkgolinter 12 | - goconst 13 | - gocyclo 14 | - govet 15 | - ineffassign 16 | - lll 17 | - misspell 18 | - nakedret 19 | - prealloc 20 | - revive 21 | - staticcheck 22 | - unconvert 23 | - unparam 24 | - unused 25 | settings: 26 | revive: 27 | rules: 28 | - name: comment-spacings 29 | exclusions: 30 | generated: lax 31 | rules: 32 | - linters: 33 | - lll 34 | path: api/* 35 | - linters: 36 | - dupl 37 | - lll 38 | path: internal/* 39 | paths: 40 | - third_party$ 41 | - builtin$ 42 | - examples$ 43 | formatters: 44 | enable: 45 | - gofmt 46 | - goimports 47 | exclusions: 48 | generated: lax 49 | paths: 50 | - third_party$ 51 | - builtin$ 52 | - examples$ 53 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | project_name: cloudflare-operator 4 | before: 5 | hooks: 6 | - go mod tidy 7 | - go generate ./... 8 | builds: 9 | - main: ./cmd/main.go 10 | env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | goarch: 15 | - amd64 16 | - arm64 17 | dockers: 18 | - image_templates: 19 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-amd64 20 | - containeroo/cloudflare-operator:{{ .Tag }}-amd64 21 | use: buildx 22 | dockerfile: Dockerfile 23 | extra_files: 24 | - go.mod 25 | - go.sum 26 | - cmd/main.go 27 | - api 28 | - internal 29 | build_flag_templates: 30 | - --pull 31 | - --platform=linux/amd64 32 | - --label=org.opencontainers.image.title={{ .ProjectName }} 33 | - --label=org.opencontainers.image.description={{ .ProjectName }} 34 | - --label=org.opencontainers.image.url=https://github.com/containeroo/cloudflare-operator 35 | - --label=org.opencontainers.image.source=https://github.com/containeroo/cloudflare-operator 36 | - --label=org.opencontainers.image.version={{ .Version }} 37 | - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} 38 | - --label=org.opencontainers.image.revision={{ .FullCommit }} 39 | - --label=org.opencontainers.image.licenses="GNU General Public License v3.0" 40 | - image_templates: 41 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-arm64 42 | - containeroo/cloudflare-operator:{{ .Tag }}-arm64 43 | use: buildx 44 | dockerfile: Dockerfile 45 | extra_files: 46 | - go.mod 47 | - go.sum 48 | - cmd/main.go 49 | - api 50 | - internal 51 | goarch: arm64 52 | build_flag_templates: 53 | - --pull 54 | - --platform=linux/arm64 55 | - --label=org.opencontainers.image.title={{ .ProjectName }} 56 | - --label=org.opencontainers.image.description={{ .ProjectName }} 57 | - --label=org.opencontainers.image.url=https://github.com/containeroo/cloudflare-operator 58 | - --label=org.opencontainers.image.source=https://github.com/containeroo/cloudflare-operator 59 | - --label=org.opencontainers.image.version={{ .Version }} 60 | - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} 61 | - --label=org.opencontainers.image.revision={{ .FullCommit }} 62 | - --label=org.opencontainers.image.licenses="GNU General Public License v3.0" 63 | docker_manifests: 64 | - name_template: containeroo/cloudflare-operator:{{ .Tag }} 65 | image_templates: 66 | - containeroo/cloudflare-operator:{{ .Tag }}-amd64 67 | - containeroo/cloudflare-operator:{{ .Tag }}-arm64 68 | - name_template: ghcr.io/containeroo/cloudflare-operator:{{ .Tag }} 69 | image_templates: 70 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-amd64 71 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-arm64 72 | - name_template: containeroo/cloudflare-operator:latest 73 | image_templates: 74 | - containeroo/cloudflare-operator:{{ .Tag }}-amd64 75 | - containeroo/cloudflare-operator:{{ .Tag }}-arm64 76 | - name_template: ghcr.io/containeroo/cloudflare-operator:latest 77 | image_templates: 78 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-amd64 79 | - ghcr.io/containeroo/cloudflare-operator:{{ .Tag }}-arm64 80 | release: 81 | extra_files: 82 | - glob: ./crds.yaml 83 | prerelease: auto 84 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json", 3 | "line-length": false 4 | } 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.24 AS builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY api/ api/ 17 | COPY internal/ internal/ 18 | 19 | # Build 20 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 21 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 22 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 23 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 24 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go 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 . 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: cloudflare-operator.io 6 | layout: 7 | - go.kubebuilder.io/v4 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: cloudflare-operator 12 | repo: github.com/containeroo/cloudflare-operator 13 | resources: 14 | - api: 15 | crdVersion: v1 16 | namespaced: true 17 | controller: true 18 | domain: cloudflare-operator.io 19 | kind: DNSRecord 20 | path: github.com/containeroo/cloudflare-operator/api/v1 21 | version: v1 22 | - controller: true 23 | domain: k8s.io 24 | group: networking 25 | kind: Ingress 26 | path: k8s.io/api/networking/v1 27 | version: v1 28 | - api: 29 | crdVersion: v1 30 | controller: true 31 | domain: cloudflare-operator.io 32 | kind: IP 33 | path: github.com/containeroo/cloudflare-operator/api/v1 34 | version: v1 35 | - api: 36 | crdVersion: v1 37 | controller: true 38 | domain: cloudflare-operator.io 39 | kind: Account 40 | path: github.com/containeroo/cloudflare-operator/api/v1 41 | version: v1 42 | - api: 43 | crdVersion: v1 44 | controller: true 45 | domain: cloudflare-operator.io 46 | kind: Zone 47 | path: github.com/containeroo/cloudflare-operator/api/v1 48 | version: v1 49 | version: "3" 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudflare-operator 2 | 3 | The goal of cloudflare-operator is to manage Cloudflare DNS records using Kubernetes objects. 4 | 5 | cloudflare-operator is built from the ground up to use Kubernetes' API extension system. 6 | 7 | ## Who is cloudflare-operator for? 8 | 9 | cloudflare-operator helps to: 10 | 11 | - Manage Cloudflare DNS records using Kubernetes objects 12 | - Keep Cloudflare DNS records up to date 13 | - Update your external IP address on Cloudflare DNS records 14 | 15 | ## What can I do with cloudflare-operator? 16 | 17 | cloudflare-operator is based on a set of Kubernetes API extensions ("custom resources"), which control Cloudflare DNS records. 18 | 19 | ## Where do I start? 20 | 21 | Following [this](https://containeroo.ch/docs/cloudflare-operator) guide will just take a couple of minutes to complete. After installing the cloudflare-operator helm chart and adding some annotation to your ingresses, cloudflare-operator will take care of your Cloudflare DNS records. 22 | 23 | ## More detail on what’s in cloudflare-operator 24 | 25 | Features: 26 | 27 | - Add, update and delete Cloudflare DNS records 28 | - Update Cloudflare DNS records if your external IP address changes 29 | 30 | ## Disclaimer 31 | 32 | This is not an official Cloudflare project. Use at your own risk. 33 | 34 | If you encounter any issues, please open an issue on GitHub. 35 | 36 | If everything works fine, please send your compliments to Cloudflare. 37 | 38 | If everthing does not work fine, please send your complaints to us :D 39 | 40 | Cloudflare is a registered trademark of Cloudflare, Inc. 41 | -------------------------------------------------------------------------------- /api/v1/account_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | type AccountSpecApiToken struct { 25 | // Secret containing the API token (key must be named "apiToken") 26 | SecretRef corev1.SecretReference `json:"secretRef"` 27 | } 28 | 29 | // AccountSpec defines the desired state of Account 30 | type AccountSpec struct { 31 | // Cloudflare API token 32 | ApiToken AccountSpecApiToken `json:"apiToken"` 33 | // Interval to check account status 34 | // +kubebuilder:default="5m" 35 | // +optional 36 | Interval metav1.Duration `json:"interval,omitempty"` 37 | // List of zone names that should be managed by cloudflare-operator 38 | // Deprecated and will be removed in a future release 39 | // +optional 40 | // +deprecated 41 | ManagedZones []string `json:"managedZones,omitempty"` 42 | } 43 | 44 | // AccountStatus defines the observed state of Account 45 | type AccountStatus struct { 46 | // Conditions contains the different condition statuses for the Account object. 47 | // +optional 48 | Conditions []metav1.Condition `json:"conditions"` 49 | } 50 | 51 | // +kubebuilder:object:root=true 52 | // +kubebuilder:subresource:status 53 | // +kubebuilder:resource:scope=Cluster 54 | 55 | // Account is the Schema for the accounts API 56 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` 57 | type Account struct { 58 | metav1.TypeMeta `json:",inline"` 59 | metav1.ObjectMeta `json:"metadata,omitempty"` 60 | 61 | Spec AccountSpec `json:"spec,omitempty"` 62 | Status AccountStatus `json:"status,omitempty"` 63 | } 64 | 65 | // GetConditions returns the status conditions of the object. 66 | func (in *Account) GetConditions() []metav1.Condition { 67 | return in.Status.Conditions 68 | } 69 | 70 | // SetConditions sets the status conditions on the object. 71 | func (in *Account) SetConditions(conditions []metav1.Condition) { 72 | in.Status.Conditions = conditions 73 | } 74 | 75 | // +kubebuilder:object:root=true 76 | 77 | // AccountList contains a list of Account 78 | type AccountList struct { 79 | metav1.TypeMeta `json:",inline"` 80 | metav1.ListMeta `json:"metadata,omitempty"` 81 | Items []Account `json:"items"` 82 | } 83 | 84 | func init() { 85 | SchemeBuilder.Register(&Account{}, &AccountList{}) 86 | } 87 | -------------------------------------------------------------------------------- /api/v1/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | const CloudflareOperatorFinalizer = "cloudflare-operator.io/finalizer" 20 | -------------------------------------------------------------------------------- /api/v1/condition_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | const ( 20 | // ConditionTypeReady represents the fact that the object is ready. 21 | ConditionTypeReady string = "Ready" 22 | 23 | // ConditionReasonReady represents the fact that the object is ready. 24 | ConditionReasonReady string = "Ready" 25 | 26 | // ConditionReasonNotReady represents the fact that the object is not ready. 27 | ConditionReasonNotReady string = "NotReady" 28 | 29 | // ConditionReasonFailed represents the fact that the object has failed. 30 | ConditionReasonFailed string = "Failed" 31 | ) 32 | -------------------------------------------------------------------------------- /api/v1/dnsrecord_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | import ( 20 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | type DNSRecordSpecIPRef struct { 25 | // Name of the IP object 26 | // +optional 27 | Name string `json:"name,omitempty"` 28 | } 29 | 30 | // DNSRecordSpec defines the desired state of DNSRecord 31 | type DNSRecordSpec struct { 32 | // DNS record name (e.g. example.com) 33 | // +kubebuilder:validation:MaxLength=255 34 | Name string `json:"name"` 35 | // DNS record content (e.g. 127.0.0.1) 36 | // +optional 37 | Content string `json:"content,omitempty"` 38 | // Reference to an IP object 39 | // +optional 40 | IPRef DNSRecordSpecIPRef `json:"ipRef,omitempty"` 41 | // DNS record type 42 | // +kubebuilder:default=A 43 | // +optional 44 | Type string `json:"type,omitempty"` 45 | // Whether the record is receiving the performance and security benefits of Cloudflare 46 | // +kubebuilder:default=true 47 | // +optional 48 | Proxied *bool `json:"proxied,omitempty"` 49 | // Time to live, in seconds, of the DNS record. Must be between 60 and 86400, or 1 for "automatic" (e.g. 3600) 50 | // +kubebuilder:validation:Minimum=1 51 | // +kubebuilder:validation:Maximum=86400 52 | // +kubebuilder:default=1 53 | // +optional 54 | TTL int `json:"ttl,omitempty"` 55 | // Data holds arbitrary key-value pairs used to further configure the DNS record 56 | // +optional 57 | Data *apiextensionsv1.JSON `json:"data,omitempty"` 58 | // Required for MX, SRV and URI records; unused by other record types. Records with lower priorities are preferred. 59 | // +kubebuilder:validation:Minimum=0 60 | // +kubebuilder:validation:Maximum=65535 61 | // +optional 62 | Priority *uint16 `json:"priority,omitempty"` 63 | // Interval to check DNSRecord 64 | // +kubebuilder:default="5m" 65 | // +optional 66 | Interval metav1.Duration `json:"interval,omitempty"` 67 | } 68 | 69 | // DNSRecordStatus defines the observed state of DNSRecord 70 | type DNSRecordStatus struct { 71 | // Conditions contains the different condition statuses for the DNSRecord object. 72 | // +optional 73 | Conditions []metav1.Condition `json:"conditions"` 74 | // Cloudflare DNS record ID 75 | // +optional 76 | RecordID string `json:"recordID,omitempty"` 77 | } 78 | 79 | const ( 80 | IPRefIndexKey string = ".spec.ipRef.name" 81 | OwnerRefUIDIndexKey string = ".metadata.ownerReferences.uid" 82 | ) 83 | 84 | // +kubebuilder:object:root=true 85 | // +kubebuilder:subresource:status 86 | 87 | // DNSRecord is the Schema for the dnsrecords API 88 | // +kubebuilder:printcolumn:name="Record Name",type="string",JSONPath=".spec.name" 89 | // +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" 90 | // +kubebuilder:printcolumn:name="Content",type="string",JSONPath=".spec.content",priority=1 91 | // +kubebuilder:printcolumn:name="Proxied",type="boolean",JSONPath=".spec.proxied",priority=1 92 | // +kubebuilder:printcolumn:name="TTL",type="integer",JSONPath=".spec.ttl",priority=1 93 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` 94 | type DNSRecord struct { 95 | metav1.TypeMeta `json:",inline"` 96 | metav1.ObjectMeta `json:"metadata,omitempty"` 97 | 98 | Spec DNSRecordSpec `json:"spec,omitempty"` 99 | Status DNSRecordStatus `json:"status,omitempty"` 100 | } 101 | 102 | // GetConditions returns the status conditions of the object. 103 | func (in *DNSRecord) GetConditions() []metav1.Condition { 104 | return in.Status.Conditions 105 | } 106 | 107 | // SetConditions sets the status conditions on the object. 108 | func (in *DNSRecord) SetConditions(conditions []metav1.Condition) { 109 | in.Status.Conditions = conditions 110 | } 111 | 112 | // +kubebuilder:object:root=true 113 | 114 | // DNSRecordList contains a list of DNSRecord 115 | type DNSRecordList struct { 116 | metav1.TypeMeta `json:",inline"` 117 | metav1.ListMeta `json:"metadata,omitempty"` 118 | Items []DNSRecord `json:"items"` 119 | } 120 | 121 | func init() { 122 | SchemeBuilder.Register(&DNSRecord{}, &DNSRecordList{}) 123 | } 124 | -------------------------------------------------------------------------------- /api/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 source v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=cloudflare-operator.io 20 | package v1 21 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 cloudflare-operator.io v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=cloudflare-operator.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: "cloudflare-operator.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/ip_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | type IPSpecIPSources struct { 26 | // URL of the IP source (e.g. https://checkip.amazonaws.com) 27 | URL string `json:"url,omitempty"` 28 | // RequestBody to be sent to the URL 29 | // +optional 30 | RequestBody string `json:"requestBody,omitempty"` 31 | // RequestHeaders to be sent to the URL 32 | // +optional 33 | RequestHeaders *apiextensionsv1.JSON `json:"requestHeaders,omitempty"` 34 | // RequestHeadersSecretRef is a secret reference to the headers to be sent to the URL (e.g. for authentication) 35 | // where the key is the header name and the value is the header value 36 | // +optional 37 | RequestHeadersSecretRef corev1.SecretReference `json:"requestHeadersSecretRef,omitempty"` 38 | // RequestMethod defines the HTTP method to be used 39 | // +kubebuilder:validation:Enum=GET;POST;PUT;DELETE 40 | // +optional 41 | RequestMethod string `json:"requestMethod,omitempty"` 42 | // ResponseJQFilter applies a JQ filter to the response to extract the IP 43 | // +optional 44 | ResponseJQFilter string `json:"responseJQFilter,omitempty"` 45 | // PostProcessingRegex defines the regular expression to be used to extract the IP from the response or a JQ filter result 46 | // +optional 47 | PostProcessingRegex string `json:"postProcessingRegex,omitempty"` 48 | // InsecureSkipVerify defines whether to skip TLS certificate verification 49 | // +optional 50 | InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` 51 | } 52 | 53 | // IPSpec defines the desired state of IP 54 | type IPSpec struct { 55 | // IP address (omit if type is dynamic) 56 | // +optional 57 | Address string `json:"address,omitempty"` 58 | // IP address type (static or dynamic) 59 | // +kubebuilder:validation:Enum=static;dynamic 60 | // +kubebuilder:default=static 61 | // +optional 62 | Type string `json:"type,omitempty"` 63 | // Interval at which a dynamic IP should be checked 64 | // +optional 65 | Interval *metav1.Duration `json:"interval,omitempty"` 66 | // IPSources can be configured to get an IP from an external source (e.g. an API or public IP echo service) 67 | // +optional 68 | IPSources []IPSpecIPSources `json:"ipSources,omitempty"` 69 | } 70 | 71 | // IPStatus defines the observed state of IP 72 | type IPStatus struct { 73 | // Conditions contains the different condition statuses for the IP object. 74 | // +optional 75 | Conditions []metav1.Condition `json:"conditions"` 76 | } 77 | 78 | // +kubebuilder:object:root=true 79 | // +kubebuilder:subresource:status 80 | // +kubebuilder:resource:scope=Cluster 81 | 82 | // IP is the Schema for the ips API 83 | // +kubebuilder:printcolumn:name="Address",type="string",JSONPath=".spec.address" 84 | // +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" 85 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` 86 | type IP struct { 87 | metav1.TypeMeta `json:",inline"` 88 | metav1.ObjectMeta `json:"metadata,omitempty"` 89 | 90 | Spec IPSpec `json:"spec,omitempty"` 91 | Status IPStatus `json:"status,omitempty"` 92 | } 93 | 94 | // GetConditions returns the status conditions of the object. 95 | func (in *IP) GetConditions() []metav1.Condition { 96 | return in.Status.Conditions 97 | } 98 | 99 | // SetConditions sets the status conditions on the object. 100 | func (in *IP) SetConditions(conditions []metav1.Condition) { 101 | in.Status.Conditions = conditions 102 | } 103 | 104 | // +kubebuilder:object:root=true 105 | 106 | // IPList contains a list of IP 107 | type IPList struct { 108 | metav1.TypeMeta `json:",inline"` 109 | metav1.ListMeta `json:"metadata,omitempty"` 110 | Items []IP `json:"items"` 111 | } 112 | 113 | func init() { 114 | SchemeBuilder.Register(&IP{}, &IPList{}) 115 | } 116 | -------------------------------------------------------------------------------- /api/v1/zone_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // ZoneSpec defines the desired state of Zone 24 | type ZoneSpec struct { 25 | // Name of the zone 26 | Name string `json:"name"` 27 | // Prune determines whether DNS records in the zone that are not managed by cloudflare-operator should be automatically removed 28 | // +kubebuilder:default=false 29 | // +optional 30 | Prune bool `json:"prune"` 31 | // Interval to check zone status 32 | // +kubebuilder:default="5m" 33 | // +optional 34 | Interval metav1.Duration `json:"interval,omitempty"` 35 | } 36 | 37 | // ZoneStatus defines the observed state of Zone 38 | type ZoneStatus struct { 39 | // ID of the zone 40 | // +optional 41 | ID string `json:"id,omitempty"` 42 | // Conditions contains the different condition statuses for the Zone object. 43 | // +optional 44 | Conditions []metav1.Condition `json:"conditions"` 45 | } 46 | 47 | const ( 48 | ZoneNameIndexKey string = ".spec.name" 49 | ) 50 | 51 | // +kubebuilder:object:root=true 52 | // +kubebuilder:subresource:status 53 | // +kubebuilder:resource:scope=Cluster 54 | 55 | // Zone is the Schema for the zones API 56 | // +kubebuilder:printcolumn:name="Zone Name",type="string",JSONPath=".spec.name" 57 | // +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id" 58 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` 59 | type Zone struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ObjectMeta `json:"metadata,omitempty"` 62 | 63 | Spec ZoneSpec `json:"spec,omitempty"` 64 | Status ZoneStatus `json:"status,omitempty"` 65 | } 66 | 67 | // GetConditions returns the status conditions of the object. 68 | func (in *Zone) GetConditions() []metav1.Condition { 69 | return in.Status.Conditions 70 | } 71 | 72 | // SetConditions sets the status conditions on the object. 73 | func (in *Zone) SetConditions(conditions []metav1.Condition) { 74 | in.Status.Conditions = conditions 75 | } 76 | 77 | // +kubebuilder:object:root=true 78 | 79 | // ZoneList contains a list of Zone 80 | type ZoneList struct { 81 | metav1.TypeMeta `json:",inline"` 82 | metav1.ListMeta `json:"metadata,omitempty"` 83 | Items []Zone `json:"items"` 84 | } 85 | 86 | func init() { 87 | SchemeBuilder.Register(&Zone{}, &ZoneList{}) 88 | } 89 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 containeroo 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 main 18 | 19 | import ( 20 | "crypto/tls" 21 | "flag" 22 | "os" 23 | "time" 24 | 25 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 26 | // to ensure that exec-entrypoint and run can make use of them. 27 | _ "k8s.io/client-go/plugin/pkg/client/auth" 28 | 29 | "k8s.io/apimachinery/pkg/runtime" 30 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/healthz" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 36 | "sigs.k8s.io/controller-runtime/pkg/webhook" 37 | 38 | "github.com/cloudflare/cloudflare-go" 39 | cloudflareoperatoriov1 "github.com/containeroo/cloudflare-operator/api/v1" 40 | "github.com/containeroo/cloudflare-operator/internal/controller" 41 | // +kubebuilder:scaffold:imports 42 | ) 43 | 44 | var ( 45 | scheme = runtime.NewScheme() 46 | setupLog = ctrl.Log.WithName("setup") 47 | ) 48 | 49 | const version = "v1.5.1" 50 | 51 | func init() { 52 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 53 | 54 | utilruntime.Must(cloudflareoperatoriov1.AddToScheme(scheme)) 55 | // +kubebuilder:scaffold:scheme 56 | } 57 | 58 | func main() { 59 | var ( 60 | metricsAddr string 61 | enableLeaderElection bool 62 | probeAddr string 63 | secureMetrics bool 64 | enableHTTP2 bool 65 | retryInterval time.Duration 66 | ipReconcilerHTTPClientTimeout time.Duration 67 | defaultReconcileInterval time.Duration 68 | cloudflareAPI cloudflare.API 69 | ctx = ctrl.SetupSignalHandler() 70 | ) 71 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 72 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 73 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 74 | "Enable leader election for controller manager. "+ 75 | "Enabling this will ensure there is only one active controller manager.") 76 | flag.BoolVar(&secureMetrics, "metrics-secure", false, 77 | "If set the metrics endpoint is served securely") 78 | flag.BoolVar(&enableHTTP2, "enable-http2", false, 79 | "If set, HTTP/2 will be enabled for the metrics and webhook servers") 80 | flag.DurationVar(&ipReconcilerHTTPClientTimeout, "ip-reconciler-http-client-timeout", 10*time.Second, 81 | "The HTTP client timeout for the IP reconciler") 82 | flag.DurationVar(&retryInterval, "retry-interval", 10*time.Second, "The interval at which to retry failed operations") 83 | flag.DurationVar(&defaultReconcileInterval, "default-reconcile-interval", 5*time.Minute, 84 | "The default interval at which to reconcile resources") 85 | opts := zap.Options{ 86 | Development: true, 87 | } 88 | opts.BindFlags(flag.CommandLine) 89 | flag.Parse() 90 | 91 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 92 | 93 | // if the enable-http2 flag is false (the default), http/2 should be disabled 94 | // due to its vulnerabilities. More specifically, disabling http/2 will 95 | // prevent from being vulnerable to the HTTP/2 Stream Cancelation and 96 | // Rapid Reset CVEs. For more information see: 97 | // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 98 | // - https://github.com/advisories/GHSA-4374-p667-p6c8 99 | disableHTTP2 := func(c *tls.Config) { 100 | setupLog.Info("disabling http/2") 101 | c.NextProtos = []string{"http/1.1"} 102 | } 103 | 104 | tlsOpts := []func(*tls.Config){} 105 | if !enableHTTP2 { 106 | tlsOpts = append(tlsOpts, disableHTTP2) 107 | } 108 | 109 | webhookServer := webhook.NewServer(webhook.Options{ 110 | TLSOpts: tlsOpts, 111 | }) 112 | 113 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 114 | Scheme: scheme, 115 | Metrics: metricsserver.Options{ 116 | BindAddress: metricsAddr, 117 | SecureServing: secureMetrics, 118 | TLSOpts: tlsOpts, 119 | }, 120 | WebhookServer: webhookServer, 121 | HealthProbeBindAddress: probeAddr, 122 | LeaderElection: enableLeaderElection, 123 | LeaderElectionID: "447b530a.cloudflare-operator.io", 124 | LeaderElectionReleaseOnCancel: true, 125 | }) 126 | if err != nil { 127 | setupLog.Error(err, "unable to start manager") 128 | os.Exit(1) 129 | } 130 | 131 | if err = (&controller.AccountReconciler{ 132 | Client: mgr.GetClient(), 133 | Scheme: mgr.GetScheme(), 134 | CloudflareAPI: &cloudflareAPI, 135 | RetryInterval: retryInterval, 136 | }).SetupWithManager(mgr); err != nil { 137 | setupLog.Error(err, "unable to create controller", "controller", "Account") 138 | os.Exit(1) 139 | } 140 | if err = (&controller.ZoneReconciler{ 141 | Client: mgr.GetClient(), 142 | Scheme: mgr.GetScheme(), 143 | CloudflareAPI: &cloudflareAPI, 144 | RetryInterval: retryInterval, 145 | }).SetupWithManager(ctx, mgr); err != nil { 146 | setupLog.Error(err, "unable to create controller", "controller", "Zone") 147 | os.Exit(1) 148 | } 149 | if err = (&controller.IPReconciler{ 150 | Client: mgr.GetClient(), 151 | Scheme: mgr.GetScheme(), 152 | HTTPClientTimeout: ipReconcilerHTTPClientTimeout, 153 | RetryInterval: retryInterval, 154 | DefaultReconcileInterval: defaultReconcileInterval, 155 | }).SetupWithManager(mgr); err != nil { 156 | setupLog.Error(err, "unable to create controller", "controller", "IP") 157 | os.Exit(1) 158 | } 159 | if err = (&controller.IngressReconciler{ 160 | Client: mgr.GetClient(), 161 | Scheme: mgr.GetScheme(), 162 | RetryInterval: retryInterval, 163 | DefaultReconcileInterval: defaultReconcileInterval, 164 | }).SetupWithManager(mgr); err != nil { 165 | setupLog.Error(err, "unable to create controller", "controller", "Ingress") 166 | os.Exit(1) 167 | } 168 | if err = (&controller.DNSRecordReconciler{ 169 | Client: mgr.GetClient(), 170 | Scheme: mgr.GetScheme(), 171 | CloudflareAPI: &cloudflareAPI, 172 | RetryInterval: retryInterval, 173 | }).SetupWithManager(ctx, mgr); err != nil { 174 | setupLog.Error(err, "unable to create controller", "controller", "DNSRecord") 175 | os.Exit(1) 176 | } 177 | // +kubebuilder:scaffold:builder 178 | 179 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 180 | setupLog.Error(err, "unable to set up health check") 181 | os.Exit(1) 182 | } 183 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 184 | setupLog.Error(err, "unable to set up ready check") 185 | os.Exit(1) 186 | } 187 | 188 | setupLog.Info("starting cloudflare-operator " + version) 189 | if err := mgr.Start(ctx); err != nil { 190 | setupLog.Error(err, "problem running manager") 191 | os.Exit(1) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /config/crd/bases/cloudflare-operator.io_accounts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.17.2 7 | name: accounts.cloudflare-operator.io 8 | spec: 9 | group: cloudflare-operator.io 10 | names: 11 | kind: Account 12 | listKind: AccountList 13 | plural: accounts 14 | singular: account 15 | scope: Cluster 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .status.conditions[?(@.type == "Ready")].status 19 | name: Ready 20 | type: string 21 | name: v1 22 | schema: 23 | openAPIV3Schema: 24 | description: Account is the Schema for the accounts API 25 | properties: 26 | apiVersion: 27 | description: |- 28 | APIVersion defines the versioned schema of this representation of an object. 29 | Servers should convert recognized schemas to the latest internal value, and 30 | may reject unrecognized values. 31 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 32 | type: string 33 | kind: 34 | description: |- 35 | Kind is a string value representing the REST resource this object represents. 36 | Servers may infer this from the endpoint the client submits requests to. 37 | Cannot be updated. 38 | In CamelCase. 39 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 40 | type: string 41 | metadata: 42 | type: object 43 | spec: 44 | description: AccountSpec defines the desired state of Account 45 | properties: 46 | apiToken: 47 | description: Cloudflare API token 48 | properties: 49 | secretRef: 50 | description: Secret containing the API token (key must be named 51 | "apiToken") 52 | properties: 53 | name: 54 | description: name is unique within a namespace to reference 55 | a secret resource. 56 | type: string 57 | namespace: 58 | description: namespace defines the space within which the 59 | secret name must be unique. 60 | type: string 61 | type: object 62 | x-kubernetes-map-type: atomic 63 | required: 64 | - secretRef 65 | type: object 66 | interval: 67 | default: 5m 68 | description: Interval to check account status 69 | type: string 70 | managedZones: 71 | description: |- 72 | List of zone names that should be managed by cloudflare-operator 73 | Deprecated and will be removed in a future release 74 | items: 75 | type: string 76 | type: array 77 | required: 78 | - apiToken 79 | type: object 80 | status: 81 | description: AccountStatus defines the observed state of Account 82 | properties: 83 | conditions: 84 | description: Conditions contains the different condition statuses 85 | for the Account object. 86 | items: 87 | description: Condition contains details for one aspect of the current 88 | state of this API Resource. 89 | properties: 90 | lastTransitionTime: 91 | description: |- 92 | lastTransitionTime is the last time the condition transitioned from one status to another. 93 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 94 | format: date-time 95 | type: string 96 | message: 97 | description: |- 98 | message is a human readable message indicating details about the transition. 99 | This may be an empty string. 100 | maxLength: 32768 101 | type: string 102 | observedGeneration: 103 | description: |- 104 | observedGeneration represents the .metadata.generation that the condition was set based upon. 105 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 106 | with respect to the current state of the instance. 107 | format: int64 108 | minimum: 0 109 | type: integer 110 | reason: 111 | description: |- 112 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 113 | Producers of specific condition types may define expected values and meanings for this field, 114 | and whether the values are considered a guaranteed API. 115 | The value should be a CamelCase string. 116 | This field may not be empty. 117 | maxLength: 1024 118 | minLength: 1 119 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 120 | type: string 121 | status: 122 | description: status of the condition, one of True, False, Unknown. 123 | enum: 124 | - "True" 125 | - "False" 126 | - Unknown 127 | type: string 128 | type: 129 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 130 | maxLength: 316 131 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 132 | type: string 133 | required: 134 | - lastTransitionTime 135 | - message 136 | - reason 137 | - status 138 | - type 139 | type: object 140 | type: array 141 | type: object 142 | type: object 143 | served: true 144 | storage: true 145 | subresources: 146 | status: {} 147 | -------------------------------------------------------------------------------- /config/crd/bases/cloudflare-operator.io_dnsrecords.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.17.2 7 | name: dnsrecords.cloudflare-operator.io 8 | spec: 9 | group: cloudflare-operator.io 10 | names: 11 | kind: DNSRecord 12 | listKind: DNSRecordList 13 | plural: dnsrecords 14 | singular: dnsrecord 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.name 19 | name: Record Name 20 | type: string 21 | - jsonPath: .spec.type 22 | name: Type 23 | type: string 24 | - jsonPath: .spec.content 25 | name: Content 26 | priority: 1 27 | type: string 28 | - jsonPath: .spec.proxied 29 | name: Proxied 30 | priority: 1 31 | type: boolean 32 | - jsonPath: .spec.ttl 33 | name: TTL 34 | priority: 1 35 | type: integer 36 | - jsonPath: .status.conditions[?(@.type == "Ready")].status 37 | name: Ready 38 | type: string 39 | name: v1 40 | schema: 41 | openAPIV3Schema: 42 | description: DNSRecord is the Schema for the dnsrecords API 43 | properties: 44 | apiVersion: 45 | description: |- 46 | APIVersion defines the versioned schema of this representation of an object. 47 | Servers should convert recognized schemas to the latest internal value, and 48 | may reject unrecognized values. 49 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 50 | type: string 51 | kind: 52 | description: |- 53 | Kind is a string value representing the REST resource this object represents. 54 | Servers may infer this from the endpoint the client submits requests to. 55 | Cannot be updated. 56 | In CamelCase. 57 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 58 | type: string 59 | metadata: 60 | type: object 61 | spec: 62 | description: DNSRecordSpec defines the desired state of DNSRecord 63 | properties: 64 | content: 65 | description: DNS record content (e.g. 127.0.0.1) 66 | type: string 67 | data: 68 | description: Data holds arbitrary key-value pairs used to further 69 | configure the DNS record 70 | x-kubernetes-preserve-unknown-fields: true 71 | interval: 72 | default: 5m 73 | description: Interval to check DNSRecord 74 | type: string 75 | ipRef: 76 | description: Reference to an IP object 77 | properties: 78 | name: 79 | description: Name of the IP object 80 | type: string 81 | type: object 82 | name: 83 | description: DNS record name (e.g. example.com) 84 | maxLength: 255 85 | type: string 86 | priority: 87 | description: Required for MX, SRV and URI records; unused by other 88 | record types. Records with lower priorities are preferred. 89 | maximum: 65535 90 | minimum: 0 91 | type: integer 92 | proxied: 93 | default: true 94 | description: Whether the record is receiving the performance and security 95 | benefits of Cloudflare 96 | type: boolean 97 | ttl: 98 | default: 1 99 | description: Time to live, in seconds, of the DNS record. Must be 100 | between 60 and 86400, or 1 for "automatic" (e.g. 3600) 101 | maximum: 86400 102 | minimum: 1 103 | type: integer 104 | type: 105 | default: A 106 | description: DNS record type 107 | type: string 108 | required: 109 | - name 110 | type: object 111 | status: 112 | description: DNSRecordStatus defines the observed state of DNSRecord 113 | properties: 114 | conditions: 115 | description: Conditions contains the different condition statuses 116 | for the DNSRecord object. 117 | items: 118 | description: Condition contains details for one aspect of the current 119 | state of this API Resource. 120 | properties: 121 | lastTransitionTime: 122 | description: |- 123 | lastTransitionTime is the last time the condition transitioned from one status to another. 124 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 125 | format: date-time 126 | type: string 127 | message: 128 | description: |- 129 | message is a human readable message indicating details about the transition. 130 | This may be an empty string. 131 | maxLength: 32768 132 | type: string 133 | observedGeneration: 134 | description: |- 135 | observedGeneration represents the .metadata.generation that the condition was set based upon. 136 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 137 | with respect to the current state of the instance. 138 | format: int64 139 | minimum: 0 140 | type: integer 141 | reason: 142 | description: |- 143 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 144 | Producers of specific condition types may define expected values and meanings for this field, 145 | and whether the values are considered a guaranteed API. 146 | The value should be a CamelCase string. 147 | This field may not be empty. 148 | maxLength: 1024 149 | minLength: 1 150 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 151 | type: string 152 | status: 153 | description: status of the condition, one of True, False, Unknown. 154 | enum: 155 | - "True" 156 | - "False" 157 | - Unknown 158 | type: string 159 | type: 160 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 161 | maxLength: 316 162 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 163 | type: string 164 | required: 165 | - lastTransitionTime 166 | - message 167 | - reason 168 | - status 169 | - type 170 | type: object 171 | type: array 172 | recordID: 173 | description: Cloudflare DNS record ID 174 | type: string 175 | type: object 176 | type: object 177 | served: true 178 | storage: true 179 | subresources: 180 | status: {} 181 | -------------------------------------------------------------------------------- /config/crd/bases/cloudflare-operator.io_ips.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.17.2 7 | name: ips.cloudflare-operator.io 8 | spec: 9 | group: cloudflare-operator.io 10 | names: 11 | kind: IP 12 | listKind: IPList 13 | plural: ips 14 | singular: ip 15 | scope: Cluster 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.address 19 | name: Address 20 | type: string 21 | - jsonPath: .spec.type 22 | name: Type 23 | type: string 24 | - jsonPath: .status.conditions[?(@.type == "Ready")].status 25 | name: Ready 26 | type: string 27 | name: v1 28 | schema: 29 | openAPIV3Schema: 30 | description: IP is the Schema for the ips API 31 | properties: 32 | apiVersion: 33 | description: |- 34 | APIVersion defines the versioned schema of this representation of an object. 35 | Servers should convert recognized schemas to the latest internal value, and 36 | may reject unrecognized values. 37 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 38 | type: string 39 | kind: 40 | description: |- 41 | Kind is a string value representing the REST resource this object represents. 42 | Servers may infer this from the endpoint the client submits requests to. 43 | Cannot be updated. 44 | In CamelCase. 45 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 46 | type: string 47 | metadata: 48 | type: object 49 | spec: 50 | description: IPSpec defines the desired state of IP 51 | properties: 52 | address: 53 | description: IP address (omit if type is dynamic) 54 | type: string 55 | interval: 56 | description: Interval at which a dynamic IP should be checked 57 | type: string 58 | ipSources: 59 | description: IPSources can be configured to get an IP from an external 60 | source (e.g. an API or public IP echo service) 61 | items: 62 | properties: 63 | insecureSkipVerify: 64 | description: InsecureSkipVerify defines whether to skip TLS 65 | certificate verification 66 | type: boolean 67 | postProcessingRegex: 68 | description: PostProcessingRegex defines the regular expression 69 | to be used to extract the IP from the response or a JQ filter 70 | result 71 | type: string 72 | requestBody: 73 | description: RequestBody to be sent to the URL 74 | type: string 75 | requestHeaders: 76 | description: RequestHeaders to be sent to the URL 77 | x-kubernetes-preserve-unknown-fields: true 78 | requestHeadersSecretRef: 79 | description: |- 80 | RequestHeadersSecretRef is a secret reference to the headers to be sent to the URL (e.g. for authentication) 81 | where the key is the header name and the value is the header value 82 | properties: 83 | name: 84 | description: name is unique within a namespace to reference 85 | a secret resource. 86 | type: string 87 | namespace: 88 | description: namespace defines the space within which the 89 | secret name must be unique. 90 | type: string 91 | type: object 92 | x-kubernetes-map-type: atomic 93 | requestMethod: 94 | description: RequestMethod defines the HTTP method to be used 95 | enum: 96 | - GET 97 | - POST 98 | - PUT 99 | - DELETE 100 | type: string 101 | responseJQFilter: 102 | description: ResponseJQFilter applies a JQ filter to the response 103 | to extract the IP 104 | type: string 105 | url: 106 | description: URL of the IP source (e.g. https://checkip.amazonaws.com) 107 | type: string 108 | type: object 109 | type: array 110 | type: 111 | default: static 112 | description: IP address type (static or dynamic) 113 | enum: 114 | - static 115 | - dynamic 116 | type: string 117 | type: object 118 | status: 119 | description: IPStatus defines the observed state of IP 120 | properties: 121 | conditions: 122 | description: Conditions contains the different condition statuses 123 | for the IP object. 124 | items: 125 | description: Condition contains details for one aspect of the current 126 | state of this API Resource. 127 | properties: 128 | lastTransitionTime: 129 | description: |- 130 | lastTransitionTime is the last time the condition transitioned from one status to another. 131 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 132 | format: date-time 133 | type: string 134 | message: 135 | description: |- 136 | message is a human readable message indicating details about the transition. 137 | This may be an empty string. 138 | maxLength: 32768 139 | type: string 140 | observedGeneration: 141 | description: |- 142 | observedGeneration represents the .metadata.generation that the condition was set based upon. 143 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 144 | with respect to the current state of the instance. 145 | format: int64 146 | minimum: 0 147 | type: integer 148 | reason: 149 | description: |- 150 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 151 | Producers of specific condition types may define expected values and meanings for this field, 152 | and whether the values are considered a guaranteed API. 153 | The value should be a CamelCase string. 154 | This field may not be empty. 155 | maxLength: 1024 156 | minLength: 1 157 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 158 | type: string 159 | status: 160 | description: status of the condition, one of True, False, Unknown. 161 | enum: 162 | - "True" 163 | - "False" 164 | - Unknown 165 | type: string 166 | type: 167 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 168 | maxLength: 316 169 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 170 | type: string 171 | required: 172 | - lastTransitionTime 173 | - message 174 | - reason 175 | - status 176 | - type 177 | type: object 178 | type: array 179 | type: object 180 | type: object 181 | served: true 182 | storage: true 183 | subresources: 184 | status: {} 185 | -------------------------------------------------------------------------------- /config/crd/bases/cloudflare-operator.io_zones.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.17.2 7 | name: zones.cloudflare-operator.io 8 | spec: 9 | group: cloudflare-operator.io 10 | names: 11 | kind: Zone 12 | listKind: ZoneList 13 | plural: zones 14 | singular: zone 15 | scope: Cluster 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.name 19 | name: Zone Name 20 | type: string 21 | - jsonPath: .status.id 22 | name: ID 23 | type: string 24 | - jsonPath: .status.conditions[?(@.type == "Ready")].status 25 | name: Ready 26 | type: string 27 | name: v1 28 | schema: 29 | openAPIV3Schema: 30 | description: Zone is the Schema for the zones API 31 | properties: 32 | apiVersion: 33 | description: |- 34 | APIVersion defines the versioned schema of this representation of an object. 35 | Servers should convert recognized schemas to the latest internal value, and 36 | may reject unrecognized values. 37 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 38 | type: string 39 | kind: 40 | description: |- 41 | Kind is a string value representing the REST resource this object represents. 42 | Servers may infer this from the endpoint the client submits requests to. 43 | Cannot be updated. 44 | In CamelCase. 45 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 46 | type: string 47 | metadata: 48 | type: object 49 | spec: 50 | description: ZoneSpec defines the desired state of Zone 51 | properties: 52 | interval: 53 | default: 5m 54 | description: Interval to check zone status 55 | type: string 56 | name: 57 | description: Name of the zone 58 | type: string 59 | prune: 60 | default: false 61 | description: Prune determines whether DNS records in the zone that 62 | are not managed by cloudflare-operator should be automatically removed 63 | type: boolean 64 | required: 65 | - name 66 | type: object 67 | status: 68 | description: ZoneStatus defines the observed state of Zone 69 | properties: 70 | conditions: 71 | description: Conditions contains the different condition statuses 72 | for the Zone object. 73 | items: 74 | description: Condition contains details for one aspect of the current 75 | state of this API Resource. 76 | properties: 77 | lastTransitionTime: 78 | description: |- 79 | lastTransitionTime is the last time the condition transitioned from one status to another. 80 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 81 | format: date-time 82 | type: string 83 | message: 84 | description: |- 85 | message is a human readable message indicating details about the transition. 86 | This may be an empty string. 87 | maxLength: 32768 88 | type: string 89 | observedGeneration: 90 | description: |- 91 | observedGeneration represents the .metadata.generation that the condition was set based upon. 92 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 93 | with respect to the current state of the instance. 94 | format: int64 95 | minimum: 0 96 | type: integer 97 | reason: 98 | description: |- 99 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 100 | Producers of specific condition types may define expected values and meanings for this field, 101 | and whether the values are considered a guaranteed API. 102 | The value should be a CamelCase string. 103 | This field may not be empty. 104 | maxLength: 1024 105 | minLength: 1 106 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 107 | type: string 108 | status: 109 | description: status of the condition, one of True, False, Unknown. 110 | enum: 111 | - "True" 112 | - "False" 113 | - Unknown 114 | type: string 115 | type: 116 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 117 | maxLength: 316 118 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 119 | type: string 120 | required: 121 | - lastTransitionTime 122 | - message 123 | - reason 124 | - status 125 | - type 126 | type: object 127 | type: array 128 | id: 129 | description: ID of the zone 130 | type: string 131 | type: object 132 | type: object 133 | served: true 134 | storage: true 135 | subresources: 136 | status: {} 137 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/cloudflare-operator.io_dnsrecords.yaml 6 | - bases/cloudflare-operator.io_ips.yaml 7 | - bases/cloudflare-operator.io_accounts.yaml 8 | - bases/cloudflare-operator.io_zones.yaml 9 | #+kubebuilder:scaffold:crdkustomizeresource 10 | 11 | patches: 12 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 13 | # patches here are for enabling the conversion webhook for each CRD 14 | #- path: patches/webhook_in_dnsrecords.yaml 15 | #- path: patches/webhook_in_ips.yaml 16 | #- path: patches/webhook_in_accounts.yaml 17 | #- path: patches/webhook_in_zones.yaml 18 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 19 | 20 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 21 | # patches here are for enabling the CA injection for each CRD 22 | #- path: patches/cainjection_in_dnsrecords.yaml 23 | #- path: patches/cainjection_in_ips.yaml 24 | #- path: patches/cainjection_in_accounts.yaml 25 | #- path: patches/cainjection_in_zones.yaml 26 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 27 | 28 | # [WEBHOOK] To enable webhook, uncomment the following section 29 | # the following config is for teaching kustomize how to do kustomization for CRDs. 30 | 31 | #configurations: 32 | #- kustomizeconfig.yaml 33 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: cloudflare-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: cloudflare-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #labels: 13 | #- includeSelectors: true 14 | # pairs: 15 | # someName: someValue 16 | 17 | resources: 18 | - ../crd 19 | - ../rbac 20 | - ../manager 21 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 22 | # crd/kustomization.yaml 23 | #- ../webhook 24 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 25 | #- ../certmanager 26 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 27 | - ../prometheus 28 | 29 | patches: 30 | # Protect the /metrics endpoint by putting it behind auth. 31 | # If you want your controller-manager to expose the /metrics 32 | # endpoint w/o any authn/z, please comment the following line. 33 | - path: manager_auth_proxy_patch.yaml 34 | 35 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 36 | # crd/kustomization.yaml 37 | #- path: manager_webhook_patch.yaml 38 | 39 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 40 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 41 | # 'CERTMANAGER' needs to be enabled to use ca injection 42 | #- path: webhookcainjection_patch.yaml 43 | 44 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 45 | # Uncomment the following replacements to add the cert-manager CA injection annotations 46 | #replacements: 47 | # - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs 48 | # kind: Certificate 49 | # group: cert-manager.io 50 | # version: v1 51 | # name: serving-cert # this name should match the one in certificate.yaml 52 | # fieldPath: .metadata.namespace # namespace of the certificate CR 53 | # targets: 54 | # - select: 55 | # kind: ValidatingWebhookConfiguration 56 | # fieldPaths: 57 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 58 | # options: 59 | # delimiter: '/' 60 | # index: 0 61 | # create: true 62 | # - select: 63 | # kind: MutatingWebhookConfiguration 64 | # fieldPaths: 65 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 66 | # options: 67 | # delimiter: '/' 68 | # index: 0 69 | # create: true 70 | # - select: 71 | # kind: CustomResourceDefinition 72 | # fieldPaths: 73 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 74 | # options: 75 | # delimiter: '/' 76 | # index: 0 77 | # create: true 78 | # - source: 79 | # kind: Certificate 80 | # group: cert-manager.io 81 | # version: v1 82 | # name: serving-cert # this name should match the one in certificate.yaml 83 | # fieldPath: .metadata.name 84 | # targets: 85 | # - select: 86 | # kind: ValidatingWebhookConfiguration 87 | # fieldPaths: 88 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 89 | # options: 90 | # delimiter: '/' 91 | # index: 1 92 | # create: true 93 | # - select: 94 | # kind: MutatingWebhookConfiguration 95 | # fieldPaths: 96 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 97 | # options: 98 | # delimiter: '/' 99 | # index: 1 100 | # create: true 101 | # - select: 102 | # kind: CustomResourceDefinition 103 | # fieldPaths: 104 | # - .metadata.annotations.[cert-manager.io/inject-ca-from] 105 | # options: 106 | # delimiter: '/' 107 | # index: 1 108 | # create: true 109 | # - source: # Add cert-manager annotation to the webhook Service 110 | # kind: Service 111 | # version: v1 112 | # name: webhook-service 113 | # fieldPath: .metadata.name # namespace of the service 114 | # targets: 115 | # - select: 116 | # kind: Certificate 117 | # group: cert-manager.io 118 | # version: v1 119 | # fieldPaths: 120 | # - .spec.dnsNames.0 121 | # - .spec.dnsNames.1 122 | # options: 123 | # delimiter: '.' 124 | # index: 0 125 | # create: true 126 | # - source: 127 | # kind: Service 128 | # version: v1 129 | # name: webhook-service 130 | # fieldPath: .metadata.namespace # namespace of the service 131 | # targets: 132 | # - select: 133 | # kind: Certificate 134 | # group: cert-manager.io 135 | # version: v1 136 | # fieldPaths: 137 | # - .spec.dnsNames.0 138 | # - .spec.dnsNames.1 139 | # options: 140 | # delimiter: '.' 141 | # index: 1 142 | # create: true 143 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | securityContext: 14 | allowPrivilegeEscalation: false 15 | capabilities: 16 | drop: 17 | - "ALL" 18 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0 19 | args: 20 | - "--secure-listen-address=0.0.0.0:8443" 21 | - "--upstream=http://127.0.0.1:8080/" 22 | - "--logtostderr=true" 23 | - "--v=0" 24 | ports: 25 | - containerPort: 8443 26 | protocol: TCP 27 | name: https 28 | resources: 29 | limits: 30 | cpu: 500m 31 | memory: 128Mi 32 | requests: 33 | cpu: 5m 34 | memory: 64Mi 35 | - name: manager 36 | args: 37 | - "--health-probe-bind-address=:8081" 38 | - "--metrics-bind-address=127.0.0.1:8080" 39 | - "--leader-elect" 40 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: containeroo/cloudflare-operator 8 | newTag: test 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: manager 24 | app.kubernetes.io/created-by: cloudflare-operator 25 | app.kubernetes.io/part-of: cloudflare-operator 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 40 | # according to the platforms which are supported by your solution. 41 | # It is considered best practice to support multiple architectures. You can 42 | # build your manager image using the makefile target docker-buildx. 43 | # affinity: 44 | # nodeAffinity: 45 | # requiredDuringSchedulingIgnoredDuringExecution: 46 | # nodeSelectorTerms: 47 | # - matchExpressions: 48 | # - key: kubernetes.io/arch 49 | # operator: In 50 | # values: 51 | # - amd64 52 | # - arm64 53 | # - ppc64le 54 | # - s390x 55 | # - key: kubernetes.io/os 56 | # operator: In 57 | # values: 58 | # - linux 59 | securityContext: 60 | runAsNonRoot: true 61 | # TODO(user): For common cases that do not require escalating privileges 62 | # it is recommended to ensure that all your Pods/Containers are restrictive. 63 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 64 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 65 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 66 | # seccompProfile: 67 | # type: RuntimeDefault 68 | containers: 69 | - command: 70 | - /manager 71 | args: 72 | - --leader-elect 73 | image: controller:latest 74 | name: manager 75 | securityContext: 76 | allowPrivilegeEscalation: false 77 | capabilities: 78 | drop: 79 | - "ALL" 80 | livenessProbe: 81 | httpGet: 82 | path: /healthz 83 | port: 8081 84 | initialDelaySeconds: 15 85 | periodSeconds: 20 86 | readinessProbe: 87 | httpGet: 88 | path: /readyz 89 | port: 8081 90 | initialDelaySeconds: 5 91 | periodSeconds: 10 92 | # TODO(user): Configure the resources accordingly based on the project requirements. 93 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 94 | resources: 95 | limits: 96 | cpu: 500m 97 | memory: 128Mi 98 | requests: 99 | cpu: 10m 100 | memory: 64Mi 101 | serviceAccountName: controller-manager 102 | terminationGracePeriodSeconds: 10 103 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/cloudflare-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | 24 | # path: /spec/template/spec/containers/0/volumeMounts/0 25 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 26 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 27 | # - op: remove 28 | # path: /spec/template/spec/volumes/0 29 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | app.kubernetes.io/name: servicemonitor 8 | app.kubernetes.io/instance: controller-manager-metrics-monitor 9 | app.kubernetes.io/component: metrics 10 | app.kubernetes.io/created-by: cloudflare-operator 11 | app.kubernetes.io/part-of: cloudflare-operator 12 | app.kubernetes.io/managed-by: kustomize 13 | name: controller-manager-metrics-monitor 14 | namespace: system 15 | spec: 16 | endpoints: 17 | - path: /metrics 18 | port: https 19 | scheme: https 20 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 21 | tlsConfig: 22 | insecureSkipVerify: true 23 | selector: 24 | matchLabels: 25 | control-plane: controller-manager 26 | -------------------------------------------------------------------------------- /config/rbac/account_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit accounts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: account-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: account-editor-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - accounts 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - cloudflare-operator.io 28 | resources: 29 | - accounts/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/account_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view accounts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: account-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: account-viewer-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - accounts 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - cloudflare-operator.io 24 | resources: 25 | - accounts/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /config/rbac/dnsrecord_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dnsrecords. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: dnsrecord-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: dnsrecord-editor-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - dnsrecords 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - cloudflare-operator.io 28 | resources: 29 | - dnsrecords/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/dnsrecord_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dnsrecords. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: dnsrecord-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: dnsrecord-viewer-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - dnsrecords 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - cloudflare-operator.io 24 | resources: 25 | - dnsrecords/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/ip_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ips. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ip-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ip-editor-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - ips 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - cloudflare-operator.io 28 | resources: 29 | - ips/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/ip_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ips. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ip-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ip-viewer-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - ips 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - cloudflare-operator.io 24 | resources: 25 | - ips/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - secrets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - cloudflare-operator.io 17 | resources: 18 | - accounts 19 | - dnsrecords 20 | - ips 21 | - zones 22 | verbs: 23 | - create 24 | - delete 25 | - get 26 | - list 27 | - patch 28 | - update 29 | - watch 30 | - apiGroups: 31 | - cloudflare-operator.io 32 | resources: 33 | - accounts/finalizers 34 | - dnsrecords/finalizers 35 | - ips/finalizers 36 | - zones/finalizers 37 | verbs: 38 | - update 39 | - apiGroups: 40 | - cloudflare-operator.io 41 | resources: 42 | - accounts/status 43 | - dnsrecords/status 44 | - ips/status 45 | - zones/status 46 | verbs: 47 | - get 48 | - patch 49 | - update 50 | - apiGroups: 51 | - networking.k8s.io 52 | resources: 53 | - ingresses 54 | verbs: 55 | - get 56 | - list 57 | - watch 58 | - apiGroups: 59 | - networking.k8s.io 60 | resources: 61 | - ingresses/finalizers 62 | verbs: 63 | - update 64 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: cloudflare-operator 9 | app.kubernetes.io/part-of: cloudflare-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/zone_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit zones. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: zone-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: zone-editor-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - zones 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - cloudflare-operator.io 28 | resources: 29 | - zones/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/zone_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view zones. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: zone-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: cloudflare-operator 10 | app.kubernetes.io/part-of: cloudflare-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: zone-viewer-role 13 | rules: 14 | - apiGroups: 15 | - cloudflare-operator.io 16 | resources: 17 | - zones 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - cloudflare-operator.io 24 | resources: 25 | - zones/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/samples/cloudflareoperatorio_v1_account.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: api-token-sample 6 | namespace: cloudflare-operator-system 7 | type: Opaque 8 | stringData: 9 | apiToken: ${CF_API_TOKEN} 10 | --- 11 | apiVersion: cloudflare-operator.io/v1 12 | kind: Account 13 | metadata: 14 | name: account-sample 15 | spec: 16 | apiToken: 17 | secretRef: 18 | name: api-token-sample 19 | namespace: cloudflare-operator-system 20 | -------------------------------------------------------------------------------- /config/samples/cloudflareoperatorio_v1_dnsrecord.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cloudflare-operator.io/v1 3 | kind: DNSRecord 4 | metadata: 5 | name: dnsrecord-sample 6 | namespace: cloudflare-operator-system 7 | spec: 8 | name: containeroo-test.org 9 | content: 9.9.9.9 10 | type: A 11 | proxied: false 12 | ttl: 1 13 | --- 14 | apiVersion: cloudflare-operator.io/v1 15 | kind: DNSRecord 16 | metadata: 17 | name: dnsrecord-ip-ref-sample 18 | namespace: cloudflare-operator-system 19 | spec: 20 | name: containeroo-test.org 21 | type: A 22 | ipRef: 23 | name: ip-sample 24 | proxied: false 25 | ttl: 1 26 | -------------------------------------------------------------------------------- /config/samples/cloudflareoperatorio_v1_ip.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cloudflare-operator.io/v1 3 | kind: IP 4 | metadata: 5 | name: ip-sample 6 | spec: 7 | type: static # or dynamic 8 | address: 1.1.1.1 # will be automatically generated if type is dynamic 9 | # list of services that return your public IP 10 | # ipSources: 11 | # - url: https://ifconfig.me/ip 12 | # - url: https://ipecho.net/plain 13 | # - url: https://myip.is/ip/ 14 | # - url: https://checkip.amazonaws.com 15 | # - url: https://api.ipify.org 16 | interval: 1m # only used if type is dynamic, interval to check if IP has changed 17 | -------------------------------------------------------------------------------- /config/samples/cloudflareoperatorio_v1_zone.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cloudflare-operator.io/v1 3 | kind: Zone 4 | metadata: 5 | name: zone-sample 6 | spec: 7 | name: containeroo-test.org 8 | prune: false 9 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - cloudflareoperatorio_v1_dnsrecord.yaml 4 | - cloudflareoperatorio_v1_ip.yaml 5 | - cloudflareoperatorio_v1_account.yaml 6 | - cloudflareoperatorio_v1_zone.yaml 7 | #+kubebuilder:scaffold:manifestskustomizesamples 8 | -------------------------------------------------------------------------------- /config/samples/networking_v1_ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: ingress-sample 6 | namespace: cloudflare-operator-system 7 | annotations: 8 | cloudflare-operator.io/content: "144.144.144.144" 9 | spec: 10 | rules: 11 | - host: ingress.containeroo-test.org 12 | http: 13 | paths: 14 | - path: / 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: service1 19 | port: 20 | number: 80 21 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.34.2 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.34.2 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.34.2 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.34.2 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.34.2 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.34.2 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containeroo/cloudflare-operator 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/cloudflare/cloudflare-go v0.115.0 7 | github.com/fluxcd/pkg/runtime v0.58.0 8 | github.com/itchyny/gojq v0.12.17 9 | github.com/onsi/ginkgo/v2 v2.23.4 10 | github.com/onsi/gomega v1.37.0 11 | github.com/prometheus/client_golang v1.22.0 12 | golang.org/x/net v0.39.0 13 | k8s.io/api v0.32.3 14 | k8s.io/apiextensions-apiserver v0.32.3 15 | k8s.io/apimachinery v0.32.3 16 | k8s.io/client-go v0.32.3 17 | sigs.k8s.io/controller-runtime v0.20.4 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 25 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 26 | github.com/fluxcd/pkg/apis/meta v1.10.0 // indirect 27 | github.com/fsnotify/fsnotify v1.8.0 // indirect 28 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 29 | github.com/go-logr/logr v1.4.2 // indirect 30 | github.com/go-logr/zapr v1.3.0 // indirect 31 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 32 | github.com/go-openapi/jsonreference v0.21.0 // indirect 33 | github.com/go-openapi/swag v0.23.0 // indirect 34 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 35 | github.com/goccy/go-json v0.10.5 // indirect 36 | github.com/gogo/protobuf v1.3.2 // indirect 37 | github.com/golang/protobuf v1.5.4 // indirect 38 | github.com/google/btree v1.1.3 // indirect 39 | github.com/google/gnostic-models v0.6.9 // indirect 40 | github.com/google/go-cmp v0.7.0 // indirect 41 | github.com/google/go-querystring v1.1.0 // indirect 42 | github.com/google/gofuzz v1.2.0 // indirect 43 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect 44 | github.com/google/uuid v1.6.0 // indirect 45 | github.com/itchyny/timefmt-go v0.1.6 // indirect 46 | github.com/josharian/intern v1.0.0 // indirect 47 | github.com/json-iterator/go v1.1.12 // indirect 48 | github.com/mailru/easyjson v0.9.0 // indirect 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 50 | github.com/modern-go/reflect2 v1.0.2 // indirect 51 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 52 | github.com/pkg/errors v0.9.1 // indirect 53 | github.com/prometheus/client_model v0.6.1 // indirect 54 | github.com/prometheus/common v0.62.0 // indirect 55 | github.com/prometheus/procfs v0.15.1 // indirect 56 | github.com/spf13/pflag v1.0.6 // indirect 57 | github.com/x448/float16 v0.8.4 // indirect 58 | go.uber.org/automaxprocs v1.6.0 // indirect 59 | go.uber.org/multierr v1.11.0 // indirect 60 | go.uber.org/zap v1.27.0 // indirect 61 | golang.org/x/oauth2 v0.27.0 // indirect 62 | golang.org/x/sync v0.13.0 // indirect 63 | golang.org/x/sys v0.32.0 // indirect 64 | golang.org/x/term v0.31.0 // indirect 65 | golang.org/x/text v0.24.0 // indirect 66 | golang.org/x/time v0.10.0 // indirect 67 | golang.org/x/tools v0.31.0 // indirect 68 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 69 | google.golang.org/protobuf v1.36.5 // indirect 70 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 71 | gopkg.in/inf.v0 v0.9.1 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | k8s.io/klog/v2 v2.130.1 // indirect 74 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect 75 | k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect 76 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 77 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect 78 | sigs.k8s.io/yaml v1.4.0 // indirect 79 | ) 80 | -------------------------------------------------------------------------------- /hack/api-docs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hideMemberFields": ["TypeMeta"], 3 | "hideTypePatterns": ["ParseError$", "List$"], 4 | "externalPackages": [ 5 | { 6 | "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", 7 | "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" 8 | }, 9 | { 10 | "typeMatchPrefix": "^k8s\\.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\\.JSON$", 11 | "docsURLTemplate": "https://pkg.go.dev/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1?tab=doc#JSON" 12 | }, 13 | { 14 | "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", 15 | "docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" 16 | }, 17 | { 18 | "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Condition$", 19 | "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Condition" 20 | } 21 | ], 22 | "typeDisplayNamePrefixOverrides": { 23 | "k8s.io/api/": "Kubernetes ", 24 | "k8s.io/apimachinery/pkg/apis/": "Kubernetes ", 25 | "k8s.io/apiextensions-apiserver/": "Kubernetes " 26 | }, 27 | "markdownDisabled": false 28 | } 29 | -------------------------------------------------------------------------------- /hack/api-docs/template/members.tpl: -------------------------------------------------------------------------------- 1 | {{ define "members" }} 2 | {{ range .Members }} 3 | {{ if not (hiddenMember .)}} 4 |
{{ fieldName . }}
20 | (Members of {{ fieldName . }}
are embedded into this type.)
21 |
metadata
field.
33 | {{ end }}
34 |
35 | {{ if or (eq (fieldName .) "spec") }}
36 | Packages:
10 |This page was automatically generated with gen-crd-api-reference-docs
{{.Underlying}}
alias){{ end -}}
5 | 9 | (Appears on: 10 | {{- $prev := "" -}} 11 | {{- range . -}} 12 | {{- if $prev -}}, {{ end -}} 13 | {{ $prev = . }} 14 | {{ typeDisplayName . }} 15 | {{- end -}} 16 | ) 17 |
18 | {{ end }} 19 | 20 | {{ with .CommentLines }} 21 | {{ safe (renderComments .) }} 22 | {{ end }} 23 | 24 | {{ if .Members }} 25 |Field | 31 |Description | 32 |
---|---|
38 | apiVersion 39 | string |
40 |
41 | {{ apiGroup . }}
42 | |
43 |
46 | kind 47 | string 48 | |
49 |
50 | {{ .Name.Name }}
51 | |
52 |