├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── release.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ ├── draft_release.yaml │ └── pullrequests.yaml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── OWNERS ├── OWNERS_ALIASES ├── PROJECT ├── README.md ├── SECURITY_CONTACTS ├── TAG ├── api ├── v1alpha1 │ ├── conversion.go │ ├── conversion_test.go │ ├── doc.go │ ├── groupversion_info.go │ ├── inclusterippool_conversion.go │ ├── inclusterippool_types.go │ ├── v1alpha1_suite_test.go │ ├── zz_generated.conversion.go │ └── zz_generated.deepcopy.go └── v1alpha2 │ ├── doc.go │ ├── groupversion_info.go │ ├── inclusterippool_conversion.go │ ├── inclusterippool_types.go │ └── zz_generated.deepcopy.go ├── cloudbuild.yaml ├── clusterctl-settings.json ├── code-of-conduct.md ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── ipam.cluster.x-k8s.io_globalinclusterippools.yaml │ │ └── ipam.cluster.x-k8s.io_inclusterippools.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── patches │ │ ├── cainjection_in_globalinclusterippools.yaml │ │ ├── cainjection_in_inclusterippools.yaml │ │ ├── clusterctl-label_in_globalinclusterippools.yaml │ │ ├── clusterctl-label_in_inclusterippools.yaml │ │ ├── webhook_in_globalinclusterippools.yaml │ │ └── webhook_in_inclusterippools.yaml │ └── test │ │ ├── cluster.x-k8s.io_clusters.yaml │ │ ├── ipam.cluster.x-k8s.io_ipaddressclaims.yaml │ │ └── ipam.cluster.x-k8s.io_ipaddresses.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_image_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── globalinclusterippool_editor_role.yaml │ ├── globalinclusterippool_viewer_role.yaml │ ├── inclusterippool_editor_role.yaml │ ├── inclusterippool_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── claim.yaml │ ├── ipam_v1alpha2_globalinclusterippool.yaml │ └── ipam_v1alpha2_inclusterippool.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── boilerplate │ ├── BUILD │ ├── boilerplate.Dockerfile.txt │ ├── boilerplate.Makefile.txt │ ├── boilerplate.bzl.txt │ ├── boilerplate.generatebzl.txt │ ├── boilerplate.generatego.txt │ ├── boilerplate.go.txt │ ├── boilerplate.py │ ├── boilerplate.py.txt │ ├── boilerplate.sh.txt │ ├── boilerplate_test.py │ └── test │ │ ├── BUILD │ │ ├── fail.go │ │ ├── fail.py │ │ ├── pass.go │ │ └── pass.py ├── licenses.md.tpl ├── verify-boilerplate.sh └── version.sh ├── internal ├── controllers │ ├── doc.go │ ├── inclusterippool.go │ ├── inclusterippool_test.go │ ├── ipaddressclaim.go │ ├── ipaddressclaim_test.go │ └── suite_test.go ├── index │ └── index.go ├── poolutil │ ├── pool.go │ ├── pool_test.go │ └── poolutil_suite_test.go └── webhooks │ ├── doc.go │ ├── inclusterippool.go │ ├── inclusterippool_test.go │ └── util_test.go ├── main.go ├── metadata.yaml ├── pkg ├── ipamutil │ ├── address.go │ └── reconciler.go ├── predicates │ ├── references.go │ └── references_test.go └── types │ └── types.go ├── tilt-provider.yaml └── version └── version.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Tell us about a problem you are experiencing 4 | labels: kind/bug 5 | 6 | --- 7 | 8 | 9 | **What steps did you take and what happened:** 10 | [A clear and concise description of what the bug is.] 11 | 12 | 13 | **What did you expect to happen:** 14 | 15 | 16 | **Anything else you would like to add:** 17 | [Miscellaneous information that will assist in solving the issue.] 18 | 19 | 20 | **Environment:** 21 | 22 | - Cluster-api-ipam-provider-in-cluster version: 23 | - Cluster-api version: 24 | - Kubernetes version: (use `kubectl version`): 25 | - OS (e.g. from `/etc/os-release`): 26 | 27 | /kind bug 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature enhancement request 3 | about: Suggest an idea for this project 4 | labels: enhancement, kind/feature 5 | 6 | --- 7 | 8 | 9 | **Describe the solution you'd like** 10 | [A clear and concise description of what you want to happen.] 11 | 12 | 13 | **Anything else you would like to add:** 14 | [Miscellaneous information that will assist in solving the issue.] 15 | 16 | 17 | /kind feature 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | about: Release checklist 4 | labels: kind/release 5 | 6 | --- 7 | 8 | ## Release 9 | 10 | Release vX.X.X 11 | 12 | ## Checklist 13 | 14 | - [ ] Update metadata & clusterctl-settings 15 | - [ ] Update docs, (compatibility table, usage etc) . 16 | - [ ] Create tag. 17 | - [ ] Update the created draft release to include (BREAKING Changes, Important Notes). 18 | - [ ] Promote the [image](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-capi-ipam-ic/images.yaml), 19 | - [ ] Publish the release. 20 | 21 | 22 | /assign @cluster-api-ipam-provider-in-cluster-maintainers 23 | /kind release 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | **What this PR does / why we need it**: 5 | 6 | **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: 7 | Fixes # 8 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: ":seedling:" 9 | labels: 10 | - "ok-to-test" 11 | - package-ecosystem: "gomod" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | commit-message: 16 | prefix: ":seedling:" 17 | labels: 18 | - "ok-to-test" 19 | ignore: 20 | # Ignore controller-runtime as its upgraded manually, following cluster-api 21 | - dependency-name: "sigs.k8s.io/controller-runtime" 22 | # Ignore k8s and its transitives modules as they are upgraded manually 23 | # together with controller-runtime. 24 | - dependency-name: "k8s.io/*" 25 | - dependency-name: "go.etcd.io/*" 26 | - dependency-name: "google.golang.org/grpc" 27 | -------------------------------------------------------------------------------- /.github/workflows/draft_release.yaml: -------------------------------------------------------------------------------- 1 | name: Create Draft Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: write # Allow creating a release. 10 | 11 | jobs: 12 | draft_release: 13 | name: Create Draft Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | # - name: Set env 17 | # run: echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV 18 | - name: Checkout the Repository 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - name: Install Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: '^1.23' 26 | # - name: generate release notes 27 | # run: | 28 | # make release-notes 29 | - name: Create Release Artifacts 30 | run: | 31 | make release 32 | - name: Create Release 33 | uses: softprops/action-gh-release@v2 34 | with: 35 | draft: true 36 | files: out/*.* 37 | # body_path: _releasenotes/${{ env.RELEASE_TAG }}.md 38 | -------------------------------------------------------------------------------- /.github/workflows/pullrequests.yaml: -------------------------------------------------------------------------------- 1 | name: pullrequests 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, synchronize, reopened] 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.23" 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@v8.0.0 20 | with: 21 | version: v2.1.6 22 | args: '--timeout 10m' 23 | 24 | test: 25 | name: test 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-go@v5 30 | with: 31 | go-version: "1.23" 32 | - name: Run Tests 33 | run: make test 34 | 35 | validate-generated: 36 | name: validate generated files 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: actions/setup-go@v5 41 | with: 42 | go-version: "1.23" 43 | - name: run code generators 44 | run: make generate 45 | - name: run manifest generators 46 | run: make manifests 47 | - name: ensure no files changed 48 | run: git diff --exit-code || (echo "Generated files are out of sync. Please run 'make generate manifests' and commit the updated files."; exit 1) 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | bin/ 4 | *.exe 5 | *.exe~ 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # IntelliJ 14 | .idea/ 15 | *.iml 16 | .idea 17 | 18 | # VSCode 19 | .vscode/ 20 | *.code-workspace 21 | 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | # Kubernetes Generated files - skip generated files, except for vendored files 27 | !vendor/**/zz_generated.* 28 | 29 | .tiltbuild/ 30 | out/ 31 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | go: "1.23" 4 | build-tags: 5 | - e2e 6 | allow-parallel-runners: true 7 | 8 | linters: 9 | default: none 10 | enable: 11 | - asasalint 12 | - asciicheck 13 | - bidichk 14 | - bodyclose 15 | - containedctx 16 | - copyloopvar 17 | - dogsled 18 | - dupword 19 | - durationcheck 20 | - errcheck 21 | - errchkjson 22 | - ginkgolinter 23 | - goconst 24 | - gocritic 25 | - godot 26 | - goprintffuncname 27 | - gosec 28 | - govet 29 | - importas 30 | - ineffassign 31 | - intrange 32 | - loggercheck 33 | - misspell 34 | - nakedret 35 | - nilerr 36 | - noctx 37 | - nolintlint 38 | - nosprintfhostport 39 | - prealloc 40 | - predeclared 41 | - revive 42 | - rowserrcheck 43 | - staticcheck 44 | - thelper 45 | - unconvert 46 | - unparam 47 | - unused 48 | - usestdlibvars 49 | - usetesting 50 | - whitespace 51 | settings: 52 | ginkgolinter: 53 | forbid-focus-container: true 54 | gocritic: 55 | disabled-checks: 56 | - appendAssign 57 | - dupImport # https://github.com/go-critic/go-critic/issues/845 58 | - evalOrder 59 | - ifElseChain 60 | - octalLiteral 61 | - regexpSimplify 62 | - sloppyReassign 63 | - truncateCmp 64 | - typeDefFirst 65 | - unnamedResult 66 | - unnecessaryDefer 67 | - whyNoLint 68 | - wrapperFunc 69 | enabled-tags: 70 | - experimental 71 | godot: 72 | # declarations - for top level declaration comments (default); 73 | # toplevel - for top level comments; 74 | # all - for all comments. 75 | scope: toplevel 76 | exclude: 77 | - ^ \+.* 78 | - ^ ANCHOR.* 79 | importas: 80 | alias: 81 | # Kubernetes 82 | - pkg: k8s.io/api/core/v1 83 | alias: corev1 84 | - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 85 | alias: apiextensionsv1 86 | - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 87 | alias: metav1 88 | - pkg: k8s.io/apimachinery/pkg/api/errors 89 | alias: apierrors 90 | - pkg: k8s.io/apimachinery/pkg/util/errors 91 | alias: kerrors 92 | # Controller Runtime 93 | - pkg: sigs.k8s.io/controller-runtime 94 | alias: ctrl 95 | # CAPI 96 | - pkg: sigs.k8s.io/cluster-api/api/v1alpha3 97 | alias: clusterv1alpha3 98 | - pkg: sigs.k8s.io/cluster-api/api/v1alpha4 99 | alias: clusterv1alpha4 100 | - pkg: sigs.k8s.io/cluster-api/api/v1beta1 101 | alias: clusterv1 102 | # CAPI exp IPAM 103 | - pkg: sigs.k8s.io/cluster-api/exp/ipamapi/v1alpha1 104 | alias: ipamv1 105 | no-unaliased: true 106 | nolintlint: 107 | require-specific: true 108 | allow-unused: false 109 | revive: 110 | rules: 111 | # The following rules are recommended https://github.com/mgechev/revive#recommended-configuration 112 | - name: blank-imports 113 | - name: context-as-argument 114 | - name: context-keys-type 115 | - name: dot-imports 116 | - name: error-return 117 | - name: error-strings 118 | - name: error-naming 119 | - name: exported 120 | - name: if-return 121 | - name: increment-decrement 122 | - name: var-naming 123 | - name: var-declaration 124 | - name: package-comments 125 | - name: range 126 | - name: receiver-naming 127 | - name: time-naming 128 | - name: unexported-return 129 | - name: indent-error-flow 130 | - name: errorf 131 | - name: empty-block 132 | - name: superfluous-else 133 | - name: unused-parameter 134 | - name: unreachable-code 135 | - name: redefines-builtin-id 136 | # 137 | # Rules in addition to the recommended configuration above. 138 | # 139 | - name: bool-literal-in-expr 140 | - name: constant-logical-expr 141 | 142 | exclusions: 143 | generated: lax 144 | rules: 145 | - linters: 146 | - revive 147 | text: 'exported: exported method .*\.(Reconcile|SetupWithManager|SetupWebhookWithManager) should have comment or be unexported' 148 | - linters: 149 | - errcheck 150 | text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked 151 | # Exclude some packages or code to require comments, for example test code, or fake clients. 152 | - linters: 153 | - revive 154 | text: exported (method|function|type|const) (.+) should have comment or be unexported 155 | source: (func|type).*Fake.* 156 | - linters: 157 | - revive 158 | path: fake_\.go 159 | text: exported (method|function|type|const) (.+) should have comment or be unexported 160 | - linters: 161 | - revive 162 | path: cmd/clusterctl/internal/test/providers.*.go 163 | text: exported (method|function|type|const) (.+) should have comment or be unexported 164 | - linters: 165 | - revive 166 | path: (framework|e2e)/.*.go 167 | text: exported (method|function|type|const) (.+) should have comment or be unexported 168 | # Disable unparam "always receives" which might not be really 169 | # useful when building libraries. 170 | - linters: 171 | - unparam 172 | text: always receives 173 | # Dot imports for gomega and ginkgo are allowed 174 | # within test files and test utils. 175 | - linters: 176 | - revive 177 | - staticcheck 178 | path: _test\.go 179 | text: should not use dot imports 180 | # Append should be able to assign to a different var/slice. 181 | - linters: 182 | - gocritic 183 | text: 'appendAssign: append result not assigned to the same slice' 184 | # Disable linters for conversion 185 | - linters: 186 | - staticcheck 187 | path: .*(api|types)\/.*\/conversion.*\.go$ 188 | text: 'SA1019: in.(.+) is deprecated' 189 | - linters: 190 | - revive 191 | # Ignoring stylistic checks for generated code 192 | path: .*(api|types|test)\/.*\/conversion.*\.go$ 193 | # Checking if an error is nil to just after return the error or nil is redundant 194 | text: 'if-return: redundant if ...; err != nil check, just return error instead' 195 | - linters: 196 | - revive 197 | # Ignoring stylistic checks for generated code 198 | path: .*(api|types|test)\/.*\/conversion.*\.go$ 199 | # Exported function and methods should have comments. This warns on undocumented exported functions and methods. 200 | text: exported (method|function|type|const) (.+) should have comment or be unexported 201 | - linters: 202 | - revive 203 | # Ignoring stylistic checks for generated code 204 | path: .*(api|types|test)\/.*\/conversion.*\.go$ 205 | # This rule warns when initialism, variable or package naming conventions are not followed. 206 | text: 'var-naming: don''t use underscores in Go names;' 207 | - linters: 208 | - revive 209 | # Ignoring stylistic checks for generated code 210 | path: .*(api|types)\/.*\/conversion.*\.go$ 211 | # By convention, receiver names in a method should reflect their identity. 212 | text: 'receiver-naming: receiver name' 213 | - linters: 214 | - staticcheck 215 | path: .*(api|types|test)\/.*\/conversion.*\.go$ 216 | text: 'ST1003: should not use underscores in Go names;' 217 | - linters: 218 | - staticcheck 219 | path: .*(api|types)\/.*\/conversion.*\.go$ 220 | text: 'ST1016: methods on the same type should have the same receiver name' 221 | # We don't care about defer in for loops in test files. 222 | - linters: 223 | - gocritic 224 | path: _test\.go 225 | text: 'deferInLoop: Possible resource leak, ''defer'' is called in the ''for'' loop' 226 | - linters: 227 | - goconst 228 | path: (.+)_test\.go 229 | - linters: 230 | - staticcheck 231 | text: 'QF1003: could use tagged switch on .*' 232 | - linters: 233 | - staticcheck 234 | text: 'QF1008: could remove embedded field' 235 | paths: 236 | - zz_generated.*\.go$ 237 | - vendored_openapi\.go$ 238 | - api/.*/conversion\.go$ 239 | - third_party 240 | - third_party$ 241 | - builtin$ 242 | - examples$ 243 | 244 | issues: 245 | max-issues-per-linter: 0 246 | max-same-issues: 0 247 | 248 | formatters: 249 | enable: 250 | - gci 251 | - gofmt 252 | - goimports 253 | settings: 254 | gci: 255 | sections: 256 | - standard 257 | - default 258 | - prefix(sigs.k8s.io/cluster-api-ipam-provider-in-cluster) 259 | custom-order: true 260 | exclusions: 261 | generated: lax 262 | paths: 263 | - zz_generated.*\.go$ 264 | - vendored_openapi\.go$ 265 | - api/.*/conversion\.go$ 266 | - third_party 267 | - third_party$ 268 | - builtin$ 269 | - examples$ 270 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## Getting Started 8 | 9 | We have full documentation on how to get started contributing here: 10 | 11 | 14 | 15 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) - Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 16 | - [Kubernetes Contributor Guide](https://k8s.dev/guide) - Main contributor documentation, or you can just jump directly to the [contributing page](https://k8s.dev/docs/guide/contributing/) 17 | - [Contributor Cheat Sheet](https://k8s.dev/cheatsheet) - Common resources for existing developers 18 | 19 | 20 | ## Contributing a patch 21 | 22 | 1. Submit an issue describing your proposed change to the repo in question. 23 | 2. The repo owners will respond to your issue promptly. 24 | 3. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 25 | 4. Fork the desired repo, develop and test your code changes. 26 | 5. Submit a pull request. 27 | 28 | 29 | ## Mentorship 30 | 31 | - [Mentoring Initiatives](https://k8s.dev/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! 32 | 33 | 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # global ARGs used in base images 16 | ARG ARCH 17 | 18 | # Build the manager binary 19 | FROM golang:1.23 AS builder 20 | 21 | # builder ARGs. No inheritance from global ARGs, so we need to redeclare them. 22 | ARG ARCH 23 | ARG ldflags 24 | 25 | WORKDIR /workspace 26 | # Copy the Go Modules manifests 27 | COPY go.mod go.mod 28 | COPY go.sum go.sum 29 | # cache deps before building and copying source so that we don't need to re-download as much 30 | # and so that source changes don't invalidate our downloaded layer 31 | RUN --mount=type=cache,target=/go/pkg/mod \ 32 | go mod download 33 | 34 | # Copy the go source 35 | COPY main.go main.go 36 | COPY api/ api/ 37 | COPY internal/ internal/ 38 | COPY pkg/ pkg/ 39 | 40 | # Build 41 | RUN --mount=type=cache,target=/root/.cache/go-build \ 42 | --mount=type=cache,target=/go/pkg/mod \ 43 | CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} \ 44 | go build -trimpath -ldflags "${ldflags} -extldflags '-static'" \ 45 | -o manager main.go 46 | 47 | # Use distroless as minimal base image to package the manager binary 48 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 49 | FROM gcr.io/distroless/static:nonroot-${ARCH} 50 | WORKDIR / 51 | COPY --from=builder /workspace/manager . 52 | USER 65532 53 | 54 | # Add directory containing the licenses of all dependencies 55 | COPY out/licenses /licenses 56 | 57 | ENTRYPOINT ["/manager"] 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Image URL to use all building/pushing image targets 16 | TAG ?= dev 17 | 18 | PROD_REGISTRY ?= registry.k8s.io/capi-ipam-ic 19 | STAGING_REGISTRY ?= gcr.io/k8s-staging-capi-ipam-ic 20 | 21 | IMAGE_NAME ?= cluster-api-ipam-in-cluster-controller 22 | STAGING_IMG ?= $(STAGING_REGISTRY)/$(IMAGE_NAME) 23 | 24 | # PULL_BASE_REF is set by prow and contains the git ref for a build, e.g. branch name or tag 25 | RELEASE_ALIAS_TAG ?= $(PULL_BASE_REF) 26 | 27 | ARCH ?= $(shell go env GOARCH) 28 | ALL_ARCH ?= amd64 arm64 29 | 30 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 31 | ENVTEST_K8S_VERSION = 1.29 32 | 33 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 34 | ifeq (,$(shell go env GOBIN)) 35 | GOBIN=$(shell go env GOPATH)/bin 36 | else 37 | GOBIN=$(shell go env GOBIN) 38 | endif 39 | 40 | HACK_BIN=$(shell pwd)/hack/bin 41 | 42 | # Setting SHELL to bash allows bash commands to be executed by recipes. 43 | # This is a requirement for 'setup-envtest.sh' in the test target. 44 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 45 | SHELL = /usr/bin/env bash -o pipefail 46 | .SHELLFLAGS = -ec 47 | 48 | .PHONY: all 49 | all: build 50 | 51 | ##@ General 52 | 53 | # The help target prints out all targets with their descriptions organized 54 | # beneath their categories. The categories are represented by '##@' and the 55 | # target descriptions by '##'. The awk commands is responsible for reading the 56 | # entire set of makefiles included in this invocation, looking for lines of the 57 | # file as xyz: ## something, and then pretty-format the target and help. Then, 58 | # if there's a line with ##@ something, that gets pretty-printed as a category. 59 | # More info on the usage of ANSI control characters for terminal formatting: 60 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 61 | # More info on the awk command: 62 | # http://linuxcommand.org/lc3_adv_awk.php 63 | 64 | .PHONY: help 65 | help: ## Display this help. 66 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 67 | 68 | ##@ Development 69 | 70 | .PHONY: manifests 71 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 72 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 73 | 74 | .PHONY: generate 75 | generate: controller-gen conversion-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 76 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 77 | $(CONVERSION_GEN) \ 78 | --output-file=zz_generated.conversion.go \ 79 | --go-header-file=./hack/boilerplate.go.txt \ 80 | ./api/v1alpha1 81 | 82 | .PHONY: fmt 83 | fmt: ## Run go fmt against code. 84 | go fmt ./... 85 | 86 | .PHONY: vet 87 | vet: ## Run go vet against code. 88 | go vet ./... 89 | 90 | .PHONY: test 91 | test: manifests generate fmt vet envtest ## Run tests. 92 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out 93 | 94 | ##@ Build 95 | 96 | .PHONY: build 97 | build: generate fmt vet ## Build manager binary. 98 | go build -o bin/manager main.go 99 | 100 | .PHONY: run 101 | run: manifests generate fmt vet ## Run a controller from your host. 102 | go run ./main.go 103 | 104 | .PHONY: docker-build 105 | docker-build: licenses-report ## Build docker image with the manager. 106 | DOCKER_BUILDKIT=1 docker build --build-arg ARCH=$(ARCH) -t $(STAGING_IMG)-$(ARCH):$(TAG) . 107 | 108 | .PHONY: docker-build-all 109 | docker-build-all: $(addprefix docker-build-,$(ALL_ARCH)) 110 | 111 | docker-build-%: 112 | $(MAKE) ARCH=$* docker-build 113 | 114 | .PHONY: docker-push-all 115 | docker-push-all: $(addprefix docker-push-,$(ALL_ARCH)) docker-push-manifest 116 | 117 | docker-push-%: 118 | $(MAKE) ARCH=$* docker-push 119 | 120 | .PHONY: docker-push 121 | docker-push: ## Push docker image with the manager. 122 | docker push $(STAGING_IMG)-$(ARCH):$(TAG) 123 | 124 | docker-push-manifest: 125 | docker manifest create --amend $(STAGING_IMG):$(TAG) $(shell echo $(ALL_ARCH) | sed -e "s~[^ ]*~$(STAGING_IMG)\-&:$(TAG)~g") 126 | @for arch in $(ALL_ARCH); do docker manifest annotate --arch $${arch} $(STAGING_IMG):$(TAG) $(STAGING_IMG)-$${arch}:$(TAG); done 127 | docker manifest push --purge $(STAGING_IMG):$(TAG) 128 | 129 | 130 | ##@ Release 131 | 132 | RELEASE_TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null) 133 | 134 | RELEASE_DIR ?= out 135 | 136 | $(RELEASE_DIR): 137 | mkdir -p $(RELEASE_DIR)/ 138 | 139 | .PHONY: release 140 | release: clean-release 141 | @if [ -z "${RELEASE_TAG}" ]; then echo "RELEASE_TAG is not set"; exit 1; fi 142 | @if ! [ -z "$$(git status --porcelain)" ]; then echo "Your local git repository contains uncommitted changes, use git clean before proceeding."; exit 1; fi 143 | git checkout "${RELEASE_TAG}" 144 | $(MAKE) manifest-modification REGISTRY=$(PROD_REGISTRY) 145 | $(MAKE) release-manifests 146 | $(MAKE) release-metadata 147 | $(MAKE) licenses-report 148 | $(MAKE) clean-release-git 149 | 150 | .PHONY: clean-release 151 | clean-release: 152 | rm -rf $(RELEASE_DIR) 153 | 154 | .PHONY: clean-release-git 155 | clean-release-git: ## Restores the git files usually modified during a release 156 | git restore ./*manager_image_patch.yaml 157 | 158 | .PHONY: manifest-modification 159 | manifest-modification: 160 | $(MAKE) set-manifest-image MANIFEST_IMG=$(REGISTRY)/$(IMAGE_NAME) MANIFEST_TAG=$(RELEASE_TAG) 161 | $(MAKE) set-manifest-pull-policy PULL_POLICY=IfNotPresent 162 | 163 | .PHONY: release-manifests 164 | release-manifests: kustomize $(RELEASE_DIR) 165 | $(KUSTOMIZE) build config/default > $(RELEASE_DIR)/ipam-components.yaml 166 | 167 | .PHONY: release-metadata 168 | release-metadata: 169 | cp metadata.yaml $(RELEASE_DIR)/metadata.yaml 170 | 171 | .PHONY: staging-images-release-alias-tag 172 | staging-images-release-alias-tag: ## Add the release alias tag to the last build tag 173 | gcloud container images add-tag $(STAGING_IMG):$(TAG) $(STAGING_IMG):$(RELEASE_ALIAS_TAG) 174 | 175 | .PHONY: release-staging-images 176 | release-staging-images: docker-build-all docker-push-all staging-images-release-alias-tag 177 | 178 | licenses-report: go-licenses 179 | rm -rf $(RELEASE_DIR)/licenses 180 | $(GO_LICENSES) save --save_path $(RELEASE_DIR)/licenses ./... 181 | $(GO_LICENSES) report --template hack/licenses.md.tpl ./... > $(RELEASE_DIR)/licenses/licenses.md 182 | (cd out/licenses && tar -czf ../licenses.tar.gz *) 183 | 184 | ##@ Release Utils 185 | 186 | .PHONY: set-manifest-pull-policy 187 | set-manifest-pull-policy: 188 | $(info Updating kustomize pull policy file for manager resources) 189 | sed -i'' -e 's@imagePullPolicy: .*@imagePullPolicy: '"$(PULL_POLICY)"'@' ./config/default/manager_image_patch.yaml 190 | 191 | .PHONY: set-manifest-image 192 | set-manifest-image: 193 | $(info Updating kustomize image patch file for manager resource) 194 | sed -i'' -e 's@image: .*@image: '"${MANIFEST_IMG}:$(MANIFEST_TAG)"'@' ./config/default/manager_image_patch.yaml 195 | 196 | ##@ Deployment 197 | 198 | ifndef ignore-not-found 199 | ignore-not-found = false 200 | endif 201 | 202 | .PHONY: install 203 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 204 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 205 | 206 | .PHONY: uninstall 207 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 208 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 209 | 210 | .PHONY: deploy 211 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 212 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 213 | $(KUSTOMIZE) build config/default | kubectl apply -f - 214 | 215 | .PHONY: undeploy 216 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 217 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 218 | 219 | CONTROLLER_GEN = $(HACK_BIN)/controller-gen 220 | .PHONY: controller-gen 221 | controller-gen: ## Download controller-gen locally if necessary. 222 | env GOBIN=$(HACK_BIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.17.2 223 | 224 | KUSTOMIZE = $(HACK_BIN)/kustomize 225 | .PHONY: kustomize 226 | kustomize: ## Download kustomize locally if necessary. 227 | env GOBIN=$(HACK_BIN) go install sigs.k8s.io/kustomize/kustomize/v4@v4.5.7 228 | 229 | ENVTEST = $(HACK_BIN)/setup-envtest 230 | .PHONY: envtest 231 | envtest: ## Download envtest-setup locally if necessary. 232 | env GOBIN=$(HACK_BIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 233 | 234 | GO_LICENSES = $(HACK_BIN)/go-licenses 235 | .PHONY: go-licenses 236 | go-licenses: 237 | env GOBIN=$(HACK_BIN) go install github.com/google/go-licenses@latest 238 | 239 | .PHONY: verify-boilerplate 240 | verify-boilerplate: ## Verifies all sources have appropriate boilerplate 241 | ./hack/verify-boilerplate.sh 242 | 243 | CONVERSION_GEN = $(HACK_BIN)/conversion-gen 244 | .PHONY: conversion-gen 245 | conversion-gen: ## Download conversion-gen locally if necessary. 246 | env GOBIN=$(HACK_BIN) go install k8s.io/code-generator/cmd/conversion-gen@v0.30.1 247 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Deutsche Telekom AG. 2 | Copyright (c) 2023 The Kubernetes Authors. 3 | 4 | This project is licensed under Apache License, Version 2.0; 5 | you may not use them except in compliance with the License. 6 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners for information on OWNERS files. 2 | # See the OWNERS_ALIASES file at https://github.com/kubernetes-sigs/cluster-api/blob/main/OWNERS_ALIASES for a list of members for each alias. 3 | 4 | approvers: 5 | - cluster-api-ipam-provider-in-cluster-maintainers 6 | 7 | reviewers: 8 | - cluster-api-ipam-provider-in-cluster-maintainers 9 | - cluster-api-ipam-provider-in-cluster-reviewers 10 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners#owners_aliases 2 | 3 | aliases: 4 | cluster-api-ipam-provider-in-cluster-maintainers: 5 | - schrej 6 | - srm09 7 | - mcbenjemaa 8 | 9 | cluster-api-ipam-provider-in-cluster-reviewers: 10 | - schrej 11 | - srm09 12 | - rikatz 13 | - mcbenjemaa 14 | -------------------------------------------------------------------------------- /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: cluster.x-k8s.io 6 | layout: 7 | - go.kubebuilder.io/v3 8 | projectName: cluster-api-ipam-provider-in-cluster 9 | repo: sigs.k8s.io/cluster-api-ipam-provider-in-cluster 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | group: ipam 16 | kind: InClusterIPPool 17 | path: sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha1 18 | version: v1alpha1 19 | - api: 20 | crdVersion: v1 21 | namespaced: true 22 | domain: cluster.x-k8s.io 23 | group: ipam 24 | kind: InClusterIPPool 25 | path: sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2 26 | version: v1alpha2 27 | webhooks: 28 | conversion: true 29 | webhookVersion: v1 30 | - api: 31 | crdVersion: v1 32 | namespaced: true 33 | domain: cluster.x-k8s.io 34 | group: ipam 35 | kind: GlobalInClusterIPPool 36 | path: sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha1 37 | version: v1alpha1 38 | webhooks: 39 | conversion: true 40 | webhookVersion: v1 41 | - api: 42 | crdVersion: v1 43 | namespaced: true 44 | domain: cluster.x-k8s.io 45 | group: ipam 46 | kind: GlobalInClusterIPPool 47 | path: sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2 48 | version: v1alpha2 49 | webhooks: 50 | conversion: true 51 | webhookVersion: v1 52 | version: "3" 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cluster API IPAM Provider In Cluster 2 | 3 | This is an [IPAM provider](https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20220125-ipam-integration.md#ipam-provider) for [Cluster API](https://github.com/kubernetes-sigs/cluster-api) that manages pools of IP addresses using Kubernetes resources. It serves as a reference implementation for IPAM providers, but can also be used as a simple replacement for DHCP. 4 | 5 | IPAM providers allow to control how IP addresses are assigned to Cluster API Machines. It is usually only useful for non-cloud deployments. The infrastructure provider in use must support IPAM providers in order to use this provider. 6 | 7 | ## Features 8 | 9 | - Manages IP Addresses in-cluster using custom Kubernetes resources 10 | - Address pools can be cluster-wide or namespaced 11 | - Pools can consist of subnets, arbitrary address ranges and/or individual addresses 12 | - Both IPv4 and IPv6 are supported 13 | - Individual addresses, ranges and subnets can be excluded from a pool 14 | - Well-known reserved addresses are excluded by default, which can be configured per pool 15 | 16 | ## Setup via clusterctl 17 | 18 | This provider comes with clusterctl support and can be installed using `clusterctl init --ipam in-cluster`. 19 | 20 | ## Usage 21 | 22 | This provider comes with two resources to specify pools from which addresses can be allocated: the `InClusterIPPool` and the `GlobalInClusterIPPool`. As the names suggest, the former is namespaced, the latter is cluster-wide. Otherwise they are identical. The following examples will all use the `InClusterIPPool`, but all examples work with the `GlobalInClusterIPPool` as well. 23 | 24 | A simple pool that covers an entire /24 IPv4 network could look like this: 25 | 26 | ```yaml 27 | apiVersion: ipam.cluster.x-k8s.io/v1alpha2 28 | kind: InClusterIPPool 29 | metadata: 30 | name: inclusterippool-sample 31 | spec: 32 | addresses: 33 | - 10.0.0.0/24 34 | prefix: 24 35 | gateway: 10.0.0.1 36 | ``` 37 | 38 | IPv6 is also supported, but a single pool can only consist of v4 **or** v6 addresses, not both. For simplicity we'll stick to IPv4 in the examples. 39 | 40 | The `addresses` field supports CIDR notation, as well as arbitrary ranges and individual addresses. Using the `excludedAddresses` field, addresses, ranges or subnets can be excluded from the pool. 41 | 42 | ```yaml 43 | apiVersion: ipam.cluster.x-k8s.io/v1alpha2 44 | kind: InClusterIPPool 45 | metadata: 46 | name: inclusterippool-sample 47 | spec: 48 | addresses: 49 | - 10.0.0.0/24 50 | - 10.0.1.10-10.0.1.100 51 | - 10.0.2.1 52 | - 10.0.2.2 53 | excludeAddresses: 54 | - 10.10.0.16/28 55 | - 10.10.0.242 56 | - 10.0.1.25-10.0.1.30 57 | prefix: 22 58 | gateway: 10.0.0.1 59 | ``` 60 | 61 | Be aware that the prefix needs to cover all addresses that are part of the pool. The first network in the `addresses` list and the `prefix` field, which specifies the length of the prefix, is used to determine the prefix. In this case, `10.1.0.0/24` in the `addresses` list would lead to a validation error. 62 | 63 | The `gateway` will never be allocated. By default, addresses that are usually reserved will not be allocated either. For v4 networks this is the first (network) and last (broadcast) address within the prefix. In the example above that would be `10.0.0.0` and `10.0.3.255` (the latter not being in the network anyway). For v6 networks the first address is excluded. 64 | 65 | If you want to use all networks that are part of the prefix, you can set `allocateReservedIPAddresses` to `true`. In the example below, both `10.0.0.0` and `10.0.0.255` will be allocated. The gateway will still be excluded. 66 | 67 | ```yaml 68 | apiVersion: ipam.cluster.x-k8s.io/v1alpha2 69 | kind: InClusterIPPool 70 | metadata: 71 | name: inclusterippool-sample 72 | spec: 73 | addresses: 74 | - 10.0.0.0/24 75 | prefix: 24 76 | gateway: 10.0.0.1 77 | allocateReservedIPAddresses: true 78 | ``` 79 | 80 | 81 | ### IPv6 Status Limitations 82 | 83 | This provider does fully support IPv6 pools, but they come with a small limitation related to the pools' status. Since the IPv6 address space is very large it exceeds the int64 range, making it cumbersome to calculate the total amount of available addresses for large pools. The status will therefore be limited to the maximum int64 value (2^64), even when more addresses are available. Since the Kubernetes api server will probably not allow storing enough resources to exhaust the pool anyway, we've decided to keep this limitation in favour of simpler implementation. 84 | 85 | ## Community, discussion, contribution, and support 86 | 87 | The in-cluster IPAM provider is part of the cluster-api project. Please refer to it's [readme](https://github.com/kubernetes-sigs/cluster-api?tab=readme-ov-file#-community-discussion-contribution-and-support) for information on how to connect with the project. 88 | 89 | The best way to reach the maintainers of this sub-project is the [#cluster-api](https://kubernetes.slack.com/archives/C8TSNPY4T) channel on the [Kubernetes Slack](https://slack.k8s.io). 90 | 91 | Pull Requests and feedback on issues are very welcome! See the [issue tracker](https://github.com/kubernetes-sigs/cluster-api-ipam-provider-in-cluster/issues) if you're unsure where to start, especially the [Good first issue](https://github.com/kubernetes-sigs/cluster-api-ipam-provider-in-cluster/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) and [Help wanted](https://github.com/kubernetes-sigs/cluster-api-ipam-provider-in-cluster/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+) tags, and also feel free to reach out to discuss. 92 | 93 | ## Code of conduct 94 | 95 | Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](https://github.com/kubernetes-sigs/cluster-api-ipam-provider-in-cluster/blob/main/code-of-conduct.md). 96 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Security Response Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | schrej 14 | CecileRobertMichon 15 | fabriziopandini 16 | -------------------------------------------------------------------------------- /TAG: -------------------------------------------------------------------------------- 1 | v0.1.0-alpha.2 2 | -------------------------------------------------------------------------------- /api/v1alpha1/conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | "net/netip" 22 | 23 | "go4.org/netipx" 24 | "k8s.io/apimachinery/pkg/conversion" 25 | "k8s.io/apimachinery/pkg/util/validation/field" 26 | 27 | v1alpha2 "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 28 | ) 29 | 30 | func Convert_v1alpha1_InClusterIPPoolSpec_To_v1alpha2_InClusterIPPoolSpec(in *InClusterIPPoolSpec, out *v1alpha2.InClusterIPPoolSpec, _ conversion.Scope) error { 31 | out.Gateway = in.Gateway 32 | out.Prefix = in.Prefix 33 | out.Addresses = in.Addresses 34 | 35 | if in.Subnet != "" { 36 | prefix, err := netip.ParsePrefix(in.Subnet) 37 | if err != nil { 38 | return field.Invalid(field.NewPath("spec", "subnet"), in.Subnet, err.Error()) 39 | } 40 | 41 | prefixRange := netipx.RangeOfPrefix(prefix) 42 | if in.First == "" { 43 | in.First = prefixRange.From().Next().String() // omits the first address, the assumed network address 44 | } 45 | if in.Last == "" { 46 | in.Last = prefixRange.To().Prev().String() // omits the last address, the assumed broadcast 47 | } 48 | if in.Prefix == 0 { 49 | in.Prefix = prefix.Bits() 50 | out.Prefix = prefix.Bits() 51 | } 52 | } 53 | 54 | if in.First != "" && in.Last != "" { 55 | out.Addresses = append(out.Addresses, fmt.Sprintf("%s-%s", in.First, in.Last)) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func Convert_v1alpha2_InClusterIPPoolSpec_To_v1alpha1_InClusterIPPoolSpec(in *v1alpha2.InClusterIPPoolSpec, out *InClusterIPPoolSpec, s conversion.Scope) error { 62 | return autoConvert_v1alpha2_InClusterIPPoolSpec_To_v1alpha1_InClusterIPPoolSpec(in, out, s) 63 | } 64 | -------------------------------------------------------------------------------- /api/v1alpha1/conversion_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by conversion-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | . "github.com/onsi/ginkgo/v2" 22 | . "github.com/onsi/gomega" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | v1alpha2 "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 25 | utilconversion "sigs.k8s.io/cluster-api/util/conversion" 26 | "sigs.k8s.io/controller-runtime/pkg/conversion" 27 | . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" 28 | ) 29 | 30 | // IgnoreAnnotations will skip comparing annotations. 31 | // This is useful as the old version data will be loaded into an annotation 32 | // when upconverting to allow downconverting later. 33 | var IgnoreAnnotations = IgnorePaths{"ObjectMeta.Annotations"} 34 | 35 | var _ = Describe("v1alpha1 to v1alpha2 conversions", func() { 36 | DescribeTable("InClusterIPPoolSpec", 37 | func(specIn *InClusterIPPoolSpec, expectedSpecOut *v1alpha2.InClusterIPPoolSpec) { 38 | in := &InClusterIPPool{Spec: *specIn} 39 | spokeBefore := in.DeepCopyObject().(conversion.Convertible) 40 | 41 | dst := &v1alpha2.InClusterIPPool{} 42 | dstHub := dst.DeepCopyObject().(conversion.Hub) 43 | Expect(spokeBefore.ConvertTo(dstHub)).To(Succeed()) 44 | expectedOut := &v1alpha2.InClusterIPPool{Spec: *expectedSpecOut} 45 | Expect(dstHub).To(EqualObject(expectedOut, IgnoreAnnotations)) 46 | 47 | gin := &GlobalInClusterIPPool{Spec: *specIn} 48 | spokeBefore = gin.DeepCopyObject().(conversion.Convertible) 49 | 50 | gdst := &v1alpha2.GlobalInClusterIPPool{} 51 | dstHub = gdst.DeepCopyObject().(conversion.Hub) 52 | Expect(spokeBefore.ConvertTo(dstHub)).To(Succeed()) 53 | gexpectedOut := &v1alpha2.GlobalInClusterIPPool{Spec: *expectedSpecOut} 54 | Expect(dstHub).To(EqualObject(gexpectedOut, IgnoreAnnotations)) 55 | }, 56 | Entry("converts with First/Last/Subnet/Prefix", 57 | &InClusterIPPoolSpec{ 58 | Gateway: "1.1.1.1", 59 | Subnet: "1.1.1.1/28", 60 | First: "1.1.1.1", 61 | Last: "1.1.1.10", 62 | Prefix: 28, 63 | }, 64 | &v1alpha2.InClusterIPPoolSpec{ 65 | Gateway: "1.1.1.1", 66 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 67 | Prefix: 28, 68 | }, 69 | ), 70 | Entry("converts with only Address", 71 | &InClusterIPPoolSpec{ 72 | Gateway: "1.1.1.1", 73 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 74 | Prefix: 28, 75 | }, 76 | &v1alpha2.InClusterIPPoolSpec{ 77 | Gateway: "1.1.1.1", 78 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 79 | Prefix: 28, 80 | }, 81 | ), 82 | Entry("defaults Prefix from Subnet", 83 | &InClusterIPPoolSpec{ 84 | Subnet: "1.1.1.0/24", 85 | First: "1.1.1.10", 86 | Last: "1.1.1.15", 87 | Gateway: "1.1.1.1", 88 | }, 89 | &v1alpha2.InClusterIPPoolSpec{ 90 | Gateway: "1.1.1.1", 91 | Addresses: []string{"1.1.1.10-1.1.1.15"}, 92 | Prefix: 24, 93 | }, 94 | ), 95 | Entry("defaults First and Last from Subnet", 96 | &InClusterIPPoolSpec{ 97 | Subnet: "1.1.1.0/24", 98 | Gateway: "1.1.1.1", 99 | }, 100 | &v1alpha2.InClusterIPPoolSpec{ 101 | Gateway: "1.1.1.1", 102 | Addresses: []string{"1.1.1.1-1.1.1.254"}, 103 | Prefix: 24, 104 | }, 105 | ), 106 | ) 107 | }) 108 | 109 | var _ = Describe("v1alpha2 to v1alpha1 conversions", func() { 110 | DescribeTable("InClusterIPPool", 111 | func(specIn *v1alpha2.InClusterIPPoolSpec, expectedSpecOut *InClusterIPPoolSpec) { 112 | in := &v1alpha2.InClusterIPPool{Spec: *specIn} 113 | hub := in.DeepCopyObject().(conversion.Hub) 114 | 115 | dst := &InClusterIPPool{} 116 | spoke := dst.DeepCopyObject().(conversion.Convertible) 117 | Expect(spoke.ConvertFrom(hub)).To(Succeed()) 118 | expectedOut := &InClusterIPPool{Spec: *expectedSpecOut} 119 | Expect(spoke).To(EqualObject(expectedOut, IgnoreAnnotations)) 120 | 121 | gin := &v1alpha2.GlobalInClusterIPPool{Spec: *specIn} 122 | hub = gin.DeepCopyObject().(conversion.Hub) 123 | 124 | gdst := &GlobalInClusterIPPool{} 125 | spoke = gdst.DeepCopyObject().(conversion.Convertible) 126 | Expect(spoke.ConvertFrom(hub)).To(Succeed()) 127 | gexpectedOut := &GlobalInClusterIPPool{Spec: *expectedSpecOut} 128 | Expect(spoke).To(EqualObject(gexpectedOut, IgnoreAnnotations)) 129 | }, 130 | Entry("Addresses are unmodified", 131 | &v1alpha2.InClusterIPPoolSpec{ 132 | Gateway: "1.2.3.5", 133 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 134 | Prefix: 10, 135 | }, 136 | &InClusterIPPoolSpec{ 137 | Gateway: "1.2.3.5", 138 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 139 | Prefix: 10, 140 | }, 141 | ), 142 | ) 143 | }) 144 | 145 | var _ = Describe("v1alpha1 -> v1alpha2 -> v1alpha1 conversions", func() { 146 | DescribeTable("InClusterIPPool", 147 | func(specIn *InClusterIPPoolSpec, expectedSpecOut *InClusterIPPoolSpec) { 148 | in := &InClusterIPPool{Spec: *specIn} 149 | expectedOut := &InClusterIPPool{Spec: *expectedSpecOut} 150 | 151 | spokeBefore := in.DeepCopyObject().(conversion.Convertible) 152 | 153 | dst := &v1alpha2.InClusterIPPool{} 154 | dstHub := dst.DeepCopyObject().(conversion.Hub) 155 | Expect(spokeBefore.ConvertTo(dstHub)).To(Succeed()) 156 | 157 | alpha1 := &InClusterIPPool{} 158 | spokeAfter := alpha1.DeepCopyObject().(conversion.Convertible) 159 | Expect(spokeAfter.ConvertFrom(dstHub)).To(Succeed()) 160 | 161 | Expect(spokeAfter).To(EqualObject(expectedOut, IgnoreAnnotations)) 162 | 163 | gin := &GlobalInClusterIPPool{Spec: *specIn} 164 | spokeBefore = gin.DeepCopyObject().(conversion.Convertible) 165 | 166 | gdst := &v1alpha2.GlobalInClusterIPPool{} 167 | dstHub = gdst.DeepCopyObject().(conversion.Hub) 168 | Expect(spokeBefore.ConvertTo(dstHub)).To(Succeed()) 169 | 170 | galpha1 := &GlobalInClusterIPPool{} 171 | spokeAfter = galpha1.DeepCopyObject().(conversion.Convertible) 172 | Expect(spokeAfter.ConvertFrom(dstHub)).To(Succeed()) 173 | 174 | gexpectedOut := &GlobalInClusterIPPool{Spec: *expectedSpecOut} 175 | Expect(spokeAfter).To(EqualObject(gexpectedOut, IgnoreAnnotations)) 176 | }, 177 | Entry("Fields are set back as expected when specs have Addresses, Gateway, Prefix", 178 | &InClusterIPPoolSpec{ 179 | Gateway: "1.1.1.1", 180 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 181 | Prefix: 16, 182 | }, 183 | &InClusterIPPoolSpec{ 184 | Gateway: "1.1.1.1", 185 | Addresses: []string{"1.1.1.1-1.1.1.10"}, 186 | Prefix: 16, 187 | }, 188 | ), 189 | Entry("Fields are set back as expected when specs have First, Last, Subnet", 190 | &InClusterIPPoolSpec{ 191 | Gateway: "1.1.1.1", 192 | Subnet: "1.1.1.1/16", 193 | First: "1.1.1.2", 194 | Last: "1.1.1.10", 195 | Prefix: 16, 196 | }, 197 | &InClusterIPPoolSpec{ 198 | Gateway: "1.1.1.1", 199 | Subnet: "1.1.1.1/16", 200 | First: "1.1.1.2", 201 | Last: "1.1.1.10", 202 | Prefix: 16, 203 | }, 204 | ), 205 | Entry("Fields are defaulted when applied as v1alpha1", 206 | &InClusterIPPoolSpec{ 207 | Gateway: "1.1.1.1", 208 | Subnet: "1.1.1.1/24", 209 | }, 210 | &InClusterIPPoolSpec{ 211 | Gateway: "1.1.1.1", 212 | Subnet: "1.1.1.1/24", 213 | First: "1.1.1.1", 214 | Last: "1.1.1.254", 215 | Prefix: 24, 216 | }, 217 | ), 218 | ) 219 | }) 220 | 221 | var _ = Describe("fuzzy conversion", func() { 222 | Describe("InClusterIPPool", func() { 223 | It("passes capi fuzzer tests", func() { 224 | scheme := runtime.NewScheme() 225 | Expect(AddToScheme(scheme)).To(Succeed()) 226 | Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) 227 | 228 | utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ 229 | Scheme: scheme, 230 | Hub: &v1alpha2.InClusterIPPool{}, 231 | Spoke: &InClusterIPPool{}, 232 | }) 233 | }) 234 | }) 235 | 236 | Describe("GlobalInClusterIPPool", func() { 237 | It("passes capi fuzzer tests", func() { 238 | scheme := runtime.NewScheme() 239 | Expect(AddToScheme(scheme)).To(Succeed()) 240 | Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) 241 | 242 | utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ 243 | Scheme: scheme, 244 | Hub: &v1alpha2.GlobalInClusterIPPool{}, 245 | Spoke: &GlobalInClusterIPPool{}, 246 | }) 247 | }) 248 | }) 249 | }) 250 | -------------------------------------------------------------------------------- /api/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the infrastructure v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=ipam.cluster.x-k8s.io 20 | // +k8s:conversion-gen=sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2 21 | package v1alpha1 22 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the ipam.cluster.x-k8s.io v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=ipam.cluster.x-k8s.io 20 | package v1alpha1 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: "ipam.cluster.x-k8s.io", Version: "v1alpha1"} 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 | // localSchemeBuilder is used by the generated conversion code to manage the scheme. 38 | localSchemeBuilder = &SchemeBuilder.SchemeBuilder 39 | ) 40 | -------------------------------------------------------------------------------- /api/v1alpha1/inclusterippool_conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //nolint:forcetypeassert,golint,revive,stylecheck 18 | package v1alpha1 19 | 20 | import ( 21 | utilconversion "sigs.k8s.io/cluster-api/util/conversion" 22 | "sigs.k8s.io/controller-runtime/pkg/conversion" 23 | 24 | v1alpha2 "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 25 | ) 26 | 27 | // ConvertTo converts v1alpha1.InClusterIPPool to v1alpha2.InClusterIPPool. 28 | func (src *InClusterIPPool) ConvertTo(dstRaw conversion.Hub) error { 29 | dst := dstRaw.(*v1alpha2.InClusterIPPool) 30 | if err := Convert_v1alpha1_InClusterIPPool_To_v1alpha2_InClusterIPPool(src, dst, nil); err != nil { 31 | return err 32 | } 33 | 34 | if err := utilconversion.MarshalData(src, dst); err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | 41 | // ConvertTo converts v1alpha2.InClusterIPPool to v1alpha1.InClusterIPPool. 42 | func (dst *InClusterIPPool) ConvertFrom(srcRaw conversion.Hub) error { 43 | src := srcRaw.(*v1alpha2.InClusterIPPool) 44 | if err := Convert_v1alpha2_InClusterIPPool_To_v1alpha1_InClusterIPPool(src, dst, nil); err != nil { 45 | return err 46 | } 47 | 48 | restored := &InClusterIPPool{} 49 | if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { 50 | return err 51 | } 52 | 53 | restoredV2 := &v1alpha2.InClusterIPPool{} 54 | if err := Convert_v1alpha1_InClusterIPPool_To_v1alpha2_InClusterIPPool(restored, restoredV2, nil); err != nil { 55 | return err 56 | } 57 | 58 | addressesAreEqual := true 59 | if len(restoredV2.Spec.Addresses) == len(src.Spec.Addresses) { 60 | for i := range restoredV2.Spec.Addresses { 61 | if restoredV2.Spec.Addresses[i] != src.Spec.Addresses[i] { 62 | addressesAreEqual = false 63 | break 64 | } 65 | } 66 | } else { 67 | addressesAreEqual = false 68 | } 69 | 70 | if addressesAreEqual && 71 | restoredV2.Spec.Prefix == src.Spec.Prefix && 72 | restoredV2.Spec.Gateway == src.Spec.Gateway { 73 | dst.Spec = restored.Spec 74 | } 75 | 76 | return nil 77 | } 78 | 79 | // ConvertTo converts v1alpha1.GlobalInClusterIPPool to v1alpha2.GlobalInClusterIPPool. 80 | func (src *GlobalInClusterIPPool) ConvertTo(dstRaw conversion.Hub) error { 81 | dst := dstRaw.(*v1alpha2.GlobalInClusterIPPool) 82 | if err := Convert_v1alpha1_GlobalInClusterIPPool_To_v1alpha2_GlobalInClusterIPPool(src, dst, nil); err != nil { 83 | return err 84 | } 85 | 86 | if err := utilconversion.MarshalData(src, dst); err != nil { 87 | return err 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // ConvertTo converts v1alpha2.GlobalInClusterIPPool to v1alpha1.GlobalInClusterIPPool. 94 | func (dst *GlobalInClusterIPPool) ConvertFrom(srcRaw conversion.Hub) error { 95 | src := srcRaw.(*v1alpha2.GlobalInClusterIPPool) 96 | if err := Convert_v1alpha2_GlobalInClusterIPPool_To_v1alpha1_GlobalInClusterIPPool(src, dst, nil); err != nil { 97 | return err 98 | } 99 | 100 | restored := &GlobalInClusterIPPool{} 101 | if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { 102 | return err 103 | } 104 | 105 | dst.Spec = restored.Spec 106 | return nil 107 | } 108 | 109 | // ConvertTo converts v1alpha1.InClusterIPPoolList to v1alpha2.InClusterIPPoolList. 110 | func (src *InClusterIPPoolList) ConvertTo(dstRaw conversion.Hub) error { 111 | dst := dstRaw.(*v1alpha2.InClusterIPPoolList) 112 | return Convert_v1alpha1_InClusterIPPoolList_To_v1alpha2_InClusterIPPoolList(src, dst, nil) 113 | } 114 | 115 | // ConvertTo converts v1alpha2.InClusterIPPoolList to v1alpha1.InClusterIPPoolList. 116 | func (dst *InClusterIPPoolList) ConvertFrom(srcRaw conversion.Hub) error { 117 | src := srcRaw.(*v1alpha2.InClusterIPPoolList) 118 | return Convert_v1alpha2_InClusterIPPoolList_To_v1alpha1_InClusterIPPoolList(src, dst, nil) 119 | } 120 | 121 | // ConvertTo converts v1alpha1.GlobalInClusterIPPoolList to v1alpha2.GlobalInClusterIPPoolList. 122 | func (src *GlobalInClusterIPPoolList) ConvertTo(dstRaw conversion.Hub) error { 123 | dst := dstRaw.(*v1alpha2.GlobalInClusterIPPoolList) 124 | return Convert_v1alpha1_GlobalInClusterIPPoolList_To_v1alpha2_GlobalInClusterIPPoolList(src, dst, nil) 125 | } 126 | 127 | // ConvertTo converts v1alpha2.GlobalInClusterIPPoolList to v1alpha1.GlobalInClusterIPPoolList. 128 | func (dst *GlobalInClusterIPPoolList) ConvertFrom(srcRaw conversion.Hub) error { 129 | src := srcRaw.(*v1alpha2.GlobalInClusterIPPoolList) 130 | return Convert_v1alpha2_GlobalInClusterIPPoolList_To_v1alpha1_GlobalInClusterIPPoolList(src, dst, nil) 131 | } 132 | -------------------------------------------------------------------------------- /api/v1alpha1/inclusterippool_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // InClusterIPPoolSpec defines the desired state of InClusterIPPool. 24 | type InClusterIPPoolSpec struct { 25 | // Addresses is a list of IP addresses that can be assigned. This set of 26 | // addresses can be non-contiguous. Can be omitted if subnet, or first and 27 | // last is set. 28 | // +optional 29 | Addresses []string `json:"addresses,omitempty"` 30 | 31 | // Subnet is the subnet to assign IP addresses from. 32 | // Can be omitted if addresses or first, last and prefix are set. 33 | // +optional 34 | Subnet string `json:"subnet,omitempty"` 35 | 36 | // First is the first address that can be assigned. 37 | // If unset, the second address of subnet will be used. 38 | // +optional 39 | First string `json:"start,omitempty"` 40 | 41 | // Last is the last address that can be assigned. 42 | // Must come after first and needs to fit into a common subnet. 43 | // If unset, the second last address of subnet will be used. 44 | // +optional 45 | Last string `json:"end,omitempty"` 46 | 47 | // Prefix is the network prefix to use. 48 | // If unset the prefix from the subnet will be used. 49 | // +optional 50 | // +kubebuilder:validation:Maximum=128 51 | Prefix int `json:"prefix,omitempty"` 52 | 53 | // Gateway 54 | // +optional 55 | Gateway string `json:"gateway,omitempty"` 56 | } 57 | 58 | // InClusterIPPoolStatus defines the observed state of InClusterIPPool. 59 | type InClusterIPPoolStatus struct { 60 | // Addresses reports the count of total, free, and used IPs in the pool. 61 | // +optional 62 | Addresses *InClusterIPPoolStatusIPAddresses `json:"ipAddresses,omitempty"` 63 | } 64 | 65 | // InClusterIPPoolStatusIPAddresses contains the count of total, free, and used IPs in a pool. 66 | type InClusterIPPoolStatusIPAddresses struct { 67 | // Total is the total number of IPs configured for the pool. 68 | // Counts greater than int can contain will report as math.MaxInt. 69 | Total int `json:"total"` 70 | 71 | // Free is the count of unallocated IPs in the pool. 72 | // Counts greater than int can contain will report as math.MaxInt. 73 | Free int `json:"free"` 74 | 75 | // Used is the count of allocated IPs in the pool. 76 | // Counts greater than int can contain will report as math.MaxInt. 77 | Used int `json:"used"` 78 | 79 | // Out of Range is the count of allocated IPs in the pool that is not 80 | // contained within spec.Addresses. 81 | // Counts greater than int can contain will report as math.MaxInt. 82 | OutOfRange int `json:"outOfRange"` 83 | } 84 | 85 | // +kubebuilder:object:root=true 86 | // +kubebuilder:subresource:status 87 | // +kubebuilder:deprecatedversion 88 | // +kubebuilder:resource:categories=cluster-api 89 | // +kubebuilder:printcolumn:name="Subnet",type="string",JSONPath=".spec.subnet",description="Subnet to allocate IPs from" 90 | // +kubebuilder:printcolumn:name="First",type="string",JSONPath=".spec.first",description="First address of the range to allocate from" 91 | // +kubebuilder:printcolumn:name="Last",type="string",JSONPath=".spec.last",description="Last address of the range to allocate from" 92 | // +kubebuilder:printcolumn:name="Addresses",type="string",JSONPath=".spec.addresses",description="List of addresses, within the subnet, to allocate from" 93 | // +kubebuilder:printcolumn:name="Total",type="integer",JSONPath=".status.ipAddresses.total",description="Count of IPs configured for the pool" 94 | // +kubebuilder:printcolumn:name="Free",type="integer",JSONPath=".status.ipAddresses.free",description="Count of unallocated IPs in the pool" 95 | // +kubebuilder:printcolumn:name="Used",type="integer",JSONPath=".status.ipAddresses.used",description="Count of allocated IPs in the pool" 96 | 97 | // InClusterIPPool is the Schema for the inclusterippools API. 98 | type InClusterIPPool struct { 99 | metav1.TypeMeta `json:",inline"` 100 | metav1.ObjectMeta `json:"metadata,omitempty"` 101 | 102 | Spec InClusterIPPoolSpec `json:"spec,omitempty"` 103 | Status InClusterIPPoolStatus `json:"status,omitempty"` 104 | } 105 | 106 | //+kubebuilder:object:root=true 107 | 108 | // InClusterIPPoolList contains a list of InClusterIPPool. 109 | type InClusterIPPoolList struct { 110 | metav1.TypeMeta `json:",inline"` 111 | metav1.ListMeta `json:"metadata,omitempty"` 112 | Items []InClusterIPPool `json:"items"` 113 | } 114 | 115 | // +kubebuilder:object:root=true 116 | // +kubebuilder:subresource:status 117 | // +kubebuilder:deprecatedversion 118 | // +kubebuilder:resource:scope=Cluster,categories=cluster-api 119 | // +kubebuilder:printcolumn:name="Subnet",type="string",JSONPath=".spec.subnet",description="Subnet to allocate IPs from" 120 | // +kubebuilder:printcolumn:name="First",type="string",JSONPath=".spec.first",description="First address of the range to allocate from" 121 | // +kubebuilder:printcolumn:name="Last",type="string",JSONPath=".spec.last",description="Last address of the range to allocate from" 122 | // +kubebuilder:printcolumn:name="Total",type="integer",JSONPath=".status.ipAddresses.total",description="Count of IPs configured for the pool" 123 | // +kubebuilder:printcolumn:name="Free",type="integer",JSONPath=".status.ipAddresses.free",description="Count of unallocated IPs in the pool" 124 | // +kubebuilder:printcolumn:name="Used",type="integer",JSONPath=".status.ipAddresses.used",description="Count of allocated IPs in the pool" 125 | 126 | // GlobalInClusterIPPool is the Schema for the global inclusterippools API. 127 | // This pool type is cluster scoped. IPAddressClaims can reference 128 | // pools of this type from any namespace. 129 | type GlobalInClusterIPPool struct { 130 | metav1.TypeMeta `json:",inline"` 131 | metav1.ObjectMeta `json:"metadata,omitempty"` 132 | 133 | Spec InClusterIPPoolSpec `json:"spec,omitempty"` 134 | Status InClusterIPPoolStatus `json:"status,omitempty"` 135 | } 136 | 137 | //+kubebuilder:object:root=true 138 | 139 | // GlobalInClusterIPPoolList contains a list of GlobalInClusterIPPool. 140 | type GlobalInClusterIPPoolList struct { 141 | metav1.TypeMeta `json:",inline"` 142 | metav1.ListMeta `json:"metadata,omitempty"` 143 | Items []GlobalInClusterIPPool `json:"items"` 144 | } 145 | 146 | func init() { 147 | SchemeBuilder.Register( 148 | &InClusterIPPool{}, 149 | &InClusterIPPoolList{}, 150 | &GlobalInClusterIPPool{}, 151 | &GlobalInClusterIPPoolList{}, 152 | ) 153 | } 154 | 155 | // PoolSpec implements the genericInClusterPool interface. 156 | func (p *InClusterIPPool) PoolSpec() *InClusterIPPoolSpec { 157 | return &p.Spec 158 | } 159 | 160 | // PoolStatus implements the genericInClusterPool interface. 161 | func (p *InClusterIPPool) PoolStatus() *InClusterIPPoolStatus { 162 | return &p.Status 163 | } 164 | 165 | // PoolSpec implements the genericInClusterPool interface. 166 | func (p *GlobalInClusterIPPool) PoolSpec() *InClusterIPPoolSpec { 167 | return &p.Spec 168 | } 169 | 170 | // PoolStatus implements the genericInClusterPool interface. 171 | func (p *GlobalInClusterIPPool) PoolStatus() *InClusterIPPoolStatus { 172 | return &p.Status 173 | } 174 | -------------------------------------------------------------------------------- /api/v1alpha1/v1alpha1_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestV1alpha1(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "V1alpha1 Suite") 29 | } 30 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *GlobalInClusterIPPool) DeepCopyInto(out *GlobalInClusterIPPool) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | in.Status.DeepCopyInto(&out.Status) 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalInClusterIPPool. 37 | func (in *GlobalInClusterIPPool) DeepCopy() *GlobalInClusterIPPool { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(GlobalInClusterIPPool) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *GlobalInClusterIPPool) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *GlobalInClusterIPPoolList) DeepCopyInto(out *GlobalInClusterIPPoolList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]GlobalInClusterIPPool, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalInClusterIPPoolList. 69 | func (in *GlobalInClusterIPPoolList) DeepCopy() *GlobalInClusterIPPoolList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(GlobalInClusterIPPoolList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *GlobalInClusterIPPoolList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *InClusterIPPool) DeepCopyInto(out *InClusterIPPool) { 88 | *out = *in 89 | out.TypeMeta = in.TypeMeta 90 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 91 | in.Spec.DeepCopyInto(&out.Spec) 92 | in.Status.DeepCopyInto(&out.Status) 93 | } 94 | 95 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPool. 96 | func (in *InClusterIPPool) DeepCopy() *InClusterIPPool { 97 | if in == nil { 98 | return nil 99 | } 100 | out := new(InClusterIPPool) 101 | in.DeepCopyInto(out) 102 | return out 103 | } 104 | 105 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 106 | func (in *InClusterIPPool) DeepCopyObject() runtime.Object { 107 | if c := in.DeepCopy(); c != nil { 108 | return c 109 | } 110 | return nil 111 | } 112 | 113 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 114 | func (in *InClusterIPPoolList) DeepCopyInto(out *InClusterIPPoolList) { 115 | *out = *in 116 | out.TypeMeta = in.TypeMeta 117 | in.ListMeta.DeepCopyInto(&out.ListMeta) 118 | if in.Items != nil { 119 | in, out := &in.Items, &out.Items 120 | *out = make([]InClusterIPPool, len(*in)) 121 | for i := range *in { 122 | (*in)[i].DeepCopyInto(&(*out)[i]) 123 | } 124 | } 125 | } 126 | 127 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolList. 128 | func (in *InClusterIPPoolList) DeepCopy() *InClusterIPPoolList { 129 | if in == nil { 130 | return nil 131 | } 132 | out := new(InClusterIPPoolList) 133 | in.DeepCopyInto(out) 134 | return out 135 | } 136 | 137 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 138 | func (in *InClusterIPPoolList) DeepCopyObject() runtime.Object { 139 | if c := in.DeepCopy(); c != nil { 140 | return c 141 | } 142 | return nil 143 | } 144 | 145 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 146 | func (in *InClusterIPPoolSpec) DeepCopyInto(out *InClusterIPPoolSpec) { 147 | *out = *in 148 | if in.Addresses != nil { 149 | in, out := &in.Addresses, &out.Addresses 150 | *out = make([]string, len(*in)) 151 | copy(*out, *in) 152 | } 153 | } 154 | 155 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolSpec. 156 | func (in *InClusterIPPoolSpec) DeepCopy() *InClusterIPPoolSpec { 157 | if in == nil { 158 | return nil 159 | } 160 | out := new(InClusterIPPoolSpec) 161 | in.DeepCopyInto(out) 162 | return out 163 | } 164 | 165 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 166 | func (in *InClusterIPPoolStatus) DeepCopyInto(out *InClusterIPPoolStatus) { 167 | *out = *in 168 | if in.Addresses != nil { 169 | in, out := &in.Addresses, &out.Addresses 170 | *out = new(InClusterIPPoolStatusIPAddresses) 171 | **out = **in 172 | } 173 | } 174 | 175 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolStatus. 176 | func (in *InClusterIPPoolStatus) DeepCopy() *InClusterIPPoolStatus { 177 | if in == nil { 178 | return nil 179 | } 180 | out := new(InClusterIPPoolStatus) 181 | in.DeepCopyInto(out) 182 | return out 183 | } 184 | 185 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 186 | func (in *InClusterIPPoolStatusIPAddresses) DeepCopyInto(out *InClusterIPPoolStatusIPAddresses) { 187 | *out = *in 188 | } 189 | 190 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolStatusIPAddresses. 191 | func (in *InClusterIPPoolStatusIPAddresses) DeepCopy() *InClusterIPPoolStatusIPAddresses { 192 | if in == nil { 193 | return nil 194 | } 195 | out := new(InClusterIPPoolStatusIPAddresses) 196 | in.DeepCopyInto(out) 197 | return out 198 | } 199 | -------------------------------------------------------------------------------- /api/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha2 contains API Schema definitions for the infrastructure v1alpha2 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=ipam.cluster.x-k8s.io 20 | package v1alpha2 21 | -------------------------------------------------------------------------------- /api/v1alpha2/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime/schema" 21 | "sigs.k8s.io/controller-runtime/pkg/scheme" 22 | ) 23 | 24 | var ( 25 | // GroupVersion is group version used to register these objects. 26 | GroupVersion = schema.GroupVersion{Group: "ipam.cluster.x-k8s.io", Version: "v1alpha2"} 27 | 28 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 29 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 30 | 31 | // AddToScheme adds the types in this group-version to the given scheme. 32 | AddToScheme = SchemeBuilder.AddToScheme 33 | ) 34 | -------------------------------------------------------------------------------- /api/v1alpha2/inclusterippool_conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | // Hub marks InClusterIPPool as a conversion hub. 20 | func (*InClusterIPPool) Hub() {} 21 | 22 | // Hub marks GlobalInClusterIPPool as a conversion hub. 23 | func (*GlobalInClusterIPPool) Hub() {} 24 | 25 | // Hub marks InClusterIPPoolList as a conversion hub. 26 | func (*InClusterIPPoolList) Hub() {} 27 | 28 | // Hub marks GlobalInClusterIPPoolList as a conversion hub. 29 | func (*GlobalInClusterIPPoolList) Hub() {} 30 | -------------------------------------------------------------------------------- /api/v1alpha2/inclusterippool_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // InClusterIPPoolSpec defines the desired state of InClusterIPPool. 24 | type InClusterIPPoolSpec struct { 25 | // Addresses is a list of IP addresses that can be assigned. This set of 26 | // addresses can be non-contiguous. 27 | Addresses []string `json:"addresses"` 28 | 29 | // Prefix is the network prefix to use. 30 | // +kubebuilder:validation:Maximum=128 31 | // +kubebuilder:validation:Minimum=0 32 | Prefix int `json:"prefix"` 33 | 34 | // Gateway 35 | // +optional 36 | Gateway string `json:"gateway,omitempty"` 37 | 38 | // AllocateReservedIPAddresses causes the provider to allocate the network 39 | // address (the first address in the inferred subnet) and broadcast address 40 | // (the last address in the inferred subnet) when IPv4. The provider will 41 | // allocate the anycast address (the first address in the inferred 42 | // subnet) when IPv6. 43 | // +optional 44 | AllocateReservedIPAddresses bool `json:"allocateReservedIPAddresses,omitempty"` 45 | 46 | // ExcludedAddresses is a list of IP addresses, which will be excluded from 47 | // the set of assignable IP addresses. 48 | // +optional 49 | ExcludedAddresses []string `json:"excludedAddresses,omitempty"` 50 | } 51 | 52 | // InClusterIPPoolStatus defines the observed state of InClusterIPPool. 53 | type InClusterIPPoolStatus struct { 54 | // Addresses reports the count of total, free, and used IPs in the pool. 55 | // +optional 56 | Addresses *InClusterIPPoolStatusIPAddresses `json:"ipAddresses,omitempty"` 57 | } 58 | 59 | // InClusterIPPoolStatusIPAddresses contains the count of total, free, and used IPs in a pool. 60 | type InClusterIPPoolStatusIPAddresses struct { 61 | // Total is the total number of IPs configured for the pool. 62 | // Counts greater than int can contain will report as math.MaxInt. 63 | Total int `json:"total"` 64 | 65 | // Free is the count of unallocated IPs in the pool. 66 | // Counts greater than int can contain will report as math.MaxInt. 67 | Free int `json:"free"` 68 | 69 | // Used is the count of allocated IPs in the pool. 70 | // Counts greater than int can contain will report as math.MaxInt. 71 | Used int `json:"used"` 72 | 73 | // Out of Range is the count of allocated IPs in the pool that is not 74 | // contained within spec.Addresses. 75 | // Counts greater than int can contain will report as math.MaxInt. 76 | OutOfRange int `json:"outOfRange"` 77 | } 78 | 79 | // +kubebuilder:object:root=true 80 | // +kubebuilder:subresource:status 81 | // +kubebuilder:storageversion 82 | // +kubebuilder:resource:categories=cluster-api 83 | // +kubebuilder:printcolumn:name="Addresses",type="string",JSONPath=".spec.addresses",description="List of addresses, to allocate from" 84 | // +kubebuilder:printcolumn:name="Total",type="integer",JSONPath=".status.ipAddresses.total",description="Count of IPs configured for the pool" 85 | // +kubebuilder:printcolumn:name="Free",type="integer",JSONPath=".status.ipAddresses.free",description="Count of unallocated IPs in the pool" 86 | // +kubebuilder:printcolumn:name="Used",type="integer",JSONPath=".status.ipAddresses.used",description="Count of allocated IPs in the pool" 87 | 88 | // InClusterIPPool is the Schema for the inclusterippools API. 89 | type InClusterIPPool struct { 90 | metav1.TypeMeta `json:",inline"` 91 | metav1.ObjectMeta `json:"metadata,omitempty"` 92 | 93 | Spec InClusterIPPoolSpec `json:"spec,omitempty"` 94 | Status InClusterIPPoolStatus `json:"status,omitempty"` 95 | } 96 | 97 | //+kubebuilder:object:root=true 98 | 99 | // InClusterIPPoolList contains a list of InClusterIPPool. 100 | type InClusterIPPoolList struct { 101 | metav1.TypeMeta `json:",inline"` 102 | metav1.ListMeta `json:"metadata,omitempty"` 103 | Items []InClusterIPPool `json:"items"` 104 | } 105 | 106 | // +kubebuilder:object:root=true 107 | // +kubebuilder:subresource:status 108 | // +kubebuilder:storageversion 109 | // +kubebuilder:resource:scope=Cluster,categories=cluster-api 110 | // +kubebuilder:printcolumn:name="Addresses",type="string",JSONPath=".spec.addresses",description="List of addresses, to allocate from" 111 | // +kubebuilder:printcolumn:name="Total",type="integer",JSONPath=".status.ipAddresses.total",description="Count of IPs configured for the pool" 112 | // +kubebuilder:printcolumn:name="Free",type="integer",JSONPath=".status.ipAddresses.free",description="Count of unallocated IPs in the pool" 113 | // +kubebuilder:printcolumn:name="Used",type="integer",JSONPath=".status.ipAddresses.used",description="Count of allocated IPs in the pool" 114 | 115 | // GlobalInClusterIPPool is the Schema for the global inclusterippools API. 116 | // This pool type is cluster scoped. IPAddressClaims can reference 117 | // pools of this type from any namespace. 118 | type GlobalInClusterIPPool struct { 119 | metav1.TypeMeta `json:",inline"` 120 | metav1.ObjectMeta `json:"metadata,omitempty"` 121 | 122 | Spec InClusterIPPoolSpec `json:"spec,omitempty"` 123 | Status InClusterIPPoolStatus `json:"status,omitempty"` 124 | } 125 | 126 | //+kubebuilder:object:root=true 127 | 128 | // GlobalInClusterIPPoolList contains a list of GlobalInClusterIPPool. 129 | type GlobalInClusterIPPoolList struct { 130 | metav1.TypeMeta `json:",inline"` 131 | metav1.ListMeta `json:"metadata,omitempty"` 132 | Items []GlobalInClusterIPPool `json:"items"` 133 | } 134 | 135 | func init() { 136 | SchemeBuilder.Register( 137 | &InClusterIPPool{}, 138 | &InClusterIPPoolList{}, 139 | &GlobalInClusterIPPool{}, 140 | &GlobalInClusterIPPoolList{}, 141 | ) 142 | } 143 | 144 | // PoolSpec implements the genericInClusterPool interface. 145 | func (p *InClusterIPPool) PoolSpec() *InClusterIPPoolSpec { 146 | return &p.Spec 147 | } 148 | 149 | // PoolStatus implements the genericInClusterPool interface. 150 | func (p *InClusterIPPool) PoolStatus() *InClusterIPPoolStatus { 151 | return &p.Status 152 | } 153 | 154 | // PoolSpec implements the genericInClusterPool interface. 155 | func (p *GlobalInClusterIPPool) PoolSpec() *InClusterIPPoolSpec { 156 | return &p.Spec 157 | } 158 | 159 | // PoolStatus implements the genericInClusterPool interface. 160 | func (p *GlobalInClusterIPPool) PoolStatus() *InClusterIPPoolStatus { 161 | return &p.Status 162 | } 163 | -------------------------------------------------------------------------------- /api/v1alpha2/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha2 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *GlobalInClusterIPPool) DeepCopyInto(out *GlobalInClusterIPPool) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | in.Spec.DeepCopyInto(&out.Spec) 33 | in.Status.DeepCopyInto(&out.Status) 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalInClusterIPPool. 37 | func (in *GlobalInClusterIPPool) DeepCopy() *GlobalInClusterIPPool { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(GlobalInClusterIPPool) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *GlobalInClusterIPPool) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *GlobalInClusterIPPoolList) DeepCopyInto(out *GlobalInClusterIPPoolList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]GlobalInClusterIPPool, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalInClusterIPPoolList. 69 | func (in *GlobalInClusterIPPoolList) DeepCopy() *GlobalInClusterIPPoolList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(GlobalInClusterIPPoolList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *GlobalInClusterIPPoolList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *InClusterIPPool) DeepCopyInto(out *InClusterIPPool) { 88 | *out = *in 89 | out.TypeMeta = in.TypeMeta 90 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 91 | in.Spec.DeepCopyInto(&out.Spec) 92 | in.Status.DeepCopyInto(&out.Status) 93 | } 94 | 95 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPool. 96 | func (in *InClusterIPPool) DeepCopy() *InClusterIPPool { 97 | if in == nil { 98 | return nil 99 | } 100 | out := new(InClusterIPPool) 101 | in.DeepCopyInto(out) 102 | return out 103 | } 104 | 105 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 106 | func (in *InClusterIPPool) DeepCopyObject() runtime.Object { 107 | if c := in.DeepCopy(); c != nil { 108 | return c 109 | } 110 | return nil 111 | } 112 | 113 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 114 | func (in *InClusterIPPoolList) DeepCopyInto(out *InClusterIPPoolList) { 115 | *out = *in 116 | out.TypeMeta = in.TypeMeta 117 | in.ListMeta.DeepCopyInto(&out.ListMeta) 118 | if in.Items != nil { 119 | in, out := &in.Items, &out.Items 120 | *out = make([]InClusterIPPool, len(*in)) 121 | for i := range *in { 122 | (*in)[i].DeepCopyInto(&(*out)[i]) 123 | } 124 | } 125 | } 126 | 127 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolList. 128 | func (in *InClusterIPPoolList) DeepCopy() *InClusterIPPoolList { 129 | if in == nil { 130 | return nil 131 | } 132 | out := new(InClusterIPPoolList) 133 | in.DeepCopyInto(out) 134 | return out 135 | } 136 | 137 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 138 | func (in *InClusterIPPoolList) DeepCopyObject() runtime.Object { 139 | if c := in.DeepCopy(); c != nil { 140 | return c 141 | } 142 | return nil 143 | } 144 | 145 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 146 | func (in *InClusterIPPoolSpec) DeepCopyInto(out *InClusterIPPoolSpec) { 147 | *out = *in 148 | if in.Addresses != nil { 149 | in, out := &in.Addresses, &out.Addresses 150 | *out = make([]string, len(*in)) 151 | copy(*out, *in) 152 | } 153 | if in.ExcludedAddresses != nil { 154 | in, out := &in.ExcludedAddresses, &out.ExcludedAddresses 155 | *out = make([]string, len(*in)) 156 | copy(*out, *in) 157 | } 158 | } 159 | 160 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolSpec. 161 | func (in *InClusterIPPoolSpec) DeepCopy() *InClusterIPPoolSpec { 162 | if in == nil { 163 | return nil 164 | } 165 | out := new(InClusterIPPoolSpec) 166 | in.DeepCopyInto(out) 167 | return out 168 | } 169 | 170 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 171 | func (in *InClusterIPPoolStatus) DeepCopyInto(out *InClusterIPPoolStatus) { 172 | *out = *in 173 | if in.Addresses != nil { 174 | in, out := &in.Addresses, &out.Addresses 175 | *out = new(InClusterIPPoolStatusIPAddresses) 176 | **out = **in 177 | } 178 | } 179 | 180 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolStatus. 181 | func (in *InClusterIPPoolStatus) DeepCopy() *InClusterIPPoolStatus { 182 | if in == nil { 183 | return nil 184 | } 185 | out := new(InClusterIPPoolStatus) 186 | in.DeepCopyInto(out) 187 | return out 188 | } 189 | 190 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 191 | func (in *InClusterIPPoolStatusIPAddresses) DeepCopyInto(out *InClusterIPPoolStatusIPAddresses) { 192 | *out = *in 193 | } 194 | 195 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InClusterIPPoolStatusIPAddresses. 196 | func (in *InClusterIPPoolStatusIPAddresses) DeepCopy() *InClusterIPPoolStatusIPAddresses { 197 | if in == nil { 198 | return nil 199 | } 200 | out := new(InClusterIPPoolStatusIPAddresses) 201 | in.DeepCopyInto(out) 202 | return out 203 | } 204 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # See https://cloud.google.com/cloud-build/docs/build-config 2 | 3 | timeout: 1800s 4 | options: 5 | substitution_option: ALLOW_LOOSE 6 | steps: 7 | - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20250116-2a05ea7e3d' 8 | entrypoint: make 9 | env: 10 | - DOCKER_CLI_EXPERIMENTAL=enabled 11 | - TAG=$_GIT_TAG 12 | - PULL_BASE_REF=$_PULL_BASE_REF 13 | - DOCKER_BUILDKIT=1 14 | args: 15 | - release-staging-images 16 | substitutions: 17 | # _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and 18 | # can be used as a substitution 19 | _GIT_TAG: 'dev' 20 | # _PULL_BASE_REF will contain the ref that was pushed to to trigger this build - 21 | # a branch like 'main' or 'release-0.2', or a tag like 'v0.2'. 22 | _PULL_BASE_REF: 'dev' 23 | -------------------------------------------------------------------------------- /clusterctl-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipam-in-cluster", 3 | "config": { 4 | "componentsFile": "ipam-components.yaml", 5 | "nextVersion": "v1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: cert-manager.io/v1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 18 | dnsNames: 19 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 21 | issuerRef: 22 | kind: Issuer 23 | name: selfsigned-issuer 24 | secretName: $(SERVICE_NAME)-cert # this secret will not be prefixed, since it's not managed by kustomize 25 | subject: 26 | organizations: 27 | - k8s-sig-cluster-lifecycle -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - certificate.yaml 5 | 6 | configurations: 7 | - kustomizeconfig.yaml 8 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | - kind: Certificate 18 | group: cert-manager.io 19 | path: spec/secretName 20 | -------------------------------------------------------------------------------- /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 | --- 5 | apiVersion: kustomize.config.k8s.io/v1beta1 6 | kind: Kustomization 7 | resources: 8 | - bases/ipam.cluster.x-k8s.io_inclusterippools.yaml 9 | - bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml 10 | #+kubebuilder:scaffold:crdkustomizeresource 11 | 12 | patches: 13 | - path: patches/clusterctl-label_in_inclusterippools.yaml 14 | - path: patches/clusterctl-label_in_globalinclusterippools.yaml 15 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 16 | # patches here are for enabling the conversion webhook for each CRD 17 | - path: patches/webhook_in_inclusterippools.yaml 18 | - path: patches/webhook_in_globalinclusterippools.yaml 19 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 20 | 21 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 22 | # patches here are for enabling the CA injection for each CRD 23 | #- patches/cainjection_in_inclusterippools.yaml 24 | #- patches/cainjection_in_globalinclusterippools.yaml 25 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 26 | 27 | # the following config is for teaching kustomize how to do kustomization for CRDs. 28 | configurations: 29 | - kustomizeconfig.yaml 30 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_globalinclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: globalinclusterippools.ipam.cluster.x-k8s.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_inclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: inclusterippools.ipam.cluster.x-k8s.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/clusterctl-label_in_globalinclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | labels: 6 | clusterctl.cluster.x-k8s.io/move-hierarchy: "" 7 | name: globalinclusterippools.ipam.cluster.x-k8s.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/clusterctl-label_in_inclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | labels: 6 | clusterctl.cluster.x-k8s.io/move-hierarchy: "" 7 | name: inclusterippools.ipam.cluster.x-k8s.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_globalinclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: globalinclusterippools.ipam.cluster.x-k8s.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_inclusterippools.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: inclusterippools.ipam.cluster.x-k8s.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/test/ipam.cluster.x-k8s.io_ipaddresses.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.1 7 | name: ipaddresses.ipam.cluster.x-k8s.io 8 | spec: 9 | group: ipam.cluster.x-k8s.io 10 | names: 11 | categories: 12 | - cluster-api 13 | kind: IPAddress 14 | listKind: IPAddressList 15 | plural: ipaddresses 16 | singular: ipaddress 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - description: Address 21 | jsonPath: .spec.address 22 | name: Address 23 | type: string 24 | - description: Name of the pool the address is from 25 | jsonPath: .spec.poolRef.name 26 | name: Pool Name 27 | type: string 28 | - description: Kind of the pool the address is from 29 | jsonPath: .spec.poolRef.kind 30 | name: Pool Kind 31 | type: string 32 | - description: Time duration since creation of IPAdress 33 | jsonPath: .metadata.creationTimestamp 34 | name: Age 35 | type: date 36 | name: v1alpha1 37 | schema: 38 | openAPIV3Schema: 39 | description: IPAddress is the Schema for the ipaddress API. 40 | properties: 41 | apiVersion: 42 | description: |- 43 | APIVersion defines the versioned schema of this representation of an object. 44 | Servers should convert recognized schemas to the latest internal value, and 45 | may reject unrecognized values. 46 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 47 | type: string 48 | kind: 49 | description: |- 50 | Kind is a string value representing the REST resource this object represents. 51 | Servers may infer this from the endpoint the client submits requests to. 52 | Cannot be updated. 53 | In CamelCase. 54 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 55 | type: string 56 | metadata: 57 | type: object 58 | spec: 59 | description: IPAddressSpec is the desired state of an IPAddress. 60 | properties: 61 | address: 62 | description: Address is the IP address. 63 | type: string 64 | claimRef: 65 | description: ClaimRef is a reference to the claim this IPAddress was 66 | created for. 67 | properties: 68 | name: 69 | default: "" 70 | description: |- 71 | Name of the referent. 72 | This field is effectively required, but due to backwards compatibility is 73 | allowed to be empty. Instances of this type with an empty value here are 74 | almost certainly wrong. 75 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 76 | type: string 77 | type: object 78 | x-kubernetes-map-type: atomic 79 | gateway: 80 | description: Gateway is the network gateway of the network the address 81 | is from. 82 | type: string 83 | poolRef: 84 | description: PoolRef is a reference to the pool that this IPAddress 85 | was created from. 86 | properties: 87 | apiGroup: 88 | description: |- 89 | APIGroup is the group for the resource being referenced. 90 | If APIGroup is not specified, the specified Kind must be in the core API group. 91 | For any other third-party types, APIGroup is required. 92 | type: string 93 | kind: 94 | description: Kind is the type of resource being referenced 95 | type: string 96 | name: 97 | description: Name is the name of resource being referenced 98 | type: string 99 | required: 100 | - kind 101 | - name 102 | type: object 103 | x-kubernetes-map-type: atomic 104 | prefix: 105 | description: Prefix is the prefix of the address. 106 | type: integer 107 | required: 108 | - address 109 | - claimRef 110 | - poolRef 111 | - prefix 112 | type: object 113 | type: object 114 | served: true 115 | storage: false 116 | subresources: {} 117 | - additionalPrinterColumns: 118 | - description: Address 119 | jsonPath: .spec.address 120 | name: Address 121 | type: string 122 | - description: Name of the pool the address is from 123 | jsonPath: .spec.poolRef.name 124 | name: Pool Name 125 | type: string 126 | - description: Kind of the pool the address is from 127 | jsonPath: .spec.poolRef.kind 128 | name: Pool Kind 129 | type: string 130 | - description: Time duration since creation of IPAdress 131 | jsonPath: .metadata.creationTimestamp 132 | name: Age 133 | type: date 134 | name: v1beta1 135 | schema: 136 | openAPIV3Schema: 137 | description: IPAddress is the Schema for the ipaddress API. 138 | properties: 139 | apiVersion: 140 | description: |- 141 | APIVersion defines the versioned schema of this representation of an object. 142 | Servers should convert recognized schemas to the latest internal value, and 143 | may reject unrecognized values. 144 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 145 | type: string 146 | kind: 147 | description: |- 148 | Kind is a string value representing the REST resource this object represents. 149 | Servers may infer this from the endpoint the client submits requests to. 150 | Cannot be updated. 151 | In CamelCase. 152 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 153 | type: string 154 | metadata: 155 | type: object 156 | spec: 157 | description: IPAddressSpec is the desired state of an IPAddress. 158 | properties: 159 | address: 160 | description: Address is the IP address. 161 | type: string 162 | claimRef: 163 | description: ClaimRef is a reference to the claim this IPAddress was 164 | created for. 165 | properties: 166 | name: 167 | default: "" 168 | description: |- 169 | Name of the referent. 170 | This field is effectively required, but due to backwards compatibility is 171 | allowed to be empty. Instances of this type with an empty value here are 172 | almost certainly wrong. 173 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 174 | type: string 175 | type: object 176 | x-kubernetes-map-type: atomic 177 | gateway: 178 | description: Gateway is the network gateway of the network the address 179 | is from. 180 | type: string 181 | poolRef: 182 | description: PoolRef is a reference to the pool that this IPAddress 183 | was created from. 184 | properties: 185 | apiGroup: 186 | description: |- 187 | APIGroup is the group for the resource being referenced. 188 | If APIGroup is not specified, the specified Kind must be in the core API group. 189 | For any other third-party types, APIGroup is required. 190 | type: string 191 | kind: 192 | description: Kind is the type of resource being referenced 193 | type: string 194 | name: 195 | description: Name is the name of resource being referenced 196 | type: string 197 | required: 198 | - kind 199 | - name 200 | type: object 201 | x-kubernetes-map-type: atomic 202 | prefix: 203 | description: Prefix is the prefix of the address. 204 | type: integer 205 | required: 206 | - address 207 | - claimRef 208 | - poolRef 209 | - prefix 210 | type: object 211 | type: object 212 | served: true 213 | storage: true 214 | subresources: {} -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: capi-ipam-in-cluster-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: capi-ipam-in-cluster- 10 | 11 | # Labels to add to all resources and selectors. 12 | commonLabels: 13 | cluster.x-k8s.io/provider: "ipam-in-cluster" 14 | 15 | resources: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | - manager_image_patch.yaml 29 | # Protect the /metrics endpoint by putting it behind auth. 30 | # If you want your controller-manager to expose the /metrics 31 | # endpoint w/o any authn/z, please comment the following line. 32 | # - manager_auth_proxy_patch.yaml 33 | 34 | # Mount the controller config file for loading manager configurations 35 | # through a ComponentConfig type 36 | #- manager_config_patch.yaml 37 | 38 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 39 | # crd/kustomization.yaml 40 | - manager_webhook_patch.yaml 41 | 42 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 43 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 44 | # 'CERTMANAGER' needs to be enabled to use ca injection 45 | - webhookcainjection_patch.yaml 46 | 47 | # the following config is for teaching kustomize how to do var substitution 48 | vars: 49 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 50 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 51 | objref: 52 | kind: Certificate 53 | group: cert-manager.io 54 | version: v1 55 | name: serving-cert # this name should match the one in certificate.yaml 56 | fieldref: 57 | fieldpath: metadata.namespace 58 | - name: CERTIFICATE_NAME 59 | objref: 60 | kind: Certificate 61 | group: cert-manager.io 62 | version: v1 63 | name: serving-cert # this name should match the one in certificate.yaml 64 | - name: SERVICE_NAMESPACE # namespace of the service 65 | objref: 66 | kind: Service 67 | version: v1 68 | name: webhook-service 69 | fieldref: 70 | fieldpath: metadata.namespace 71 | - name: SERVICE_NAME 72 | objref: 73 | kind: Service 74 | version: v1 75 | name: webhook-service 76 | -------------------------------------------------------------------------------- /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 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | - name: manager 24 | args: 25 | - "--health-probe-bind-address=:8081" 26 | - "--metrics-bind-address=127.0.0.1:8080" 27 | - "--leader-elect" 28 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/default/manager_image_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 | image: gcr.io/k8s-staging-capi-ipam-ic/cluster-api-ipam-in-cluster-controller:dev 12 | imagePullPolicy: Always 13 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | secretName: capi-ipam-in-cluster-webhook-service-cert 23 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(NAMESPACE) and $(CERTIFICATENAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | name: validating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1 11 | kind: MutatingWebhookConfiguration 12 | metadata: 13 | name: mutating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | --- 17 | apiVersion: apiextensions.k8s.io/v1 18 | kind: CustomResourceDefinition 19 | metadata: 20 | annotations: 21 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 22 | name: inclusterippools.ipam.cluster.x-k8s.io 23 | --- 24 | apiVersion: apiextensions.k8s.io/v1 25 | kind: CustomResourceDefinition 26 | metadata: 27 | annotations: 28 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 29 | name: globalinclusterippools.ipam.cluster.x-k8s.io 30 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 7bb7acb4.ipam.cluster.x-k8s.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - name: manager-config 9 | files: 10 | - controller_manager_config.yaml 11 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | containers: 28 | - command: 29 | - /manager 30 | args: 31 | - --leader-elect 32 | image: controller:latest 33 | name: manager 34 | livenessProbe: 35 | httpGet: 36 | path: /healthz 37 | port: 8081 38 | initialDelaySeconds: 15 39 | periodSeconds: 20 40 | readinessProbe: 41 | httpGet: 42 | path: /readyz 43 | port: 8081 44 | initialDelaySeconds: 5 45 | periodSeconds: 10 46 | # TODO(user): Configure the resources accordingly based on the project requirements. 47 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 48 | resources: 49 | limits: 50 | cpu: 500m 51 | memory: 128Mi 52 | requests: 53 | cpu: 10m 54 | memory: 64Mi 55 | securityContext: 56 | allowPrivilegeEscalation: false 57 | capabilities: 58 | drop: 59 | - ALL 60 | runAsUser: 65532 61 | runAsGroup: 65532 62 | securityContext: 63 | runAsNonRoot: true 64 | seccompProfile: 65 | type: RuntimeDefault 66 | serviceAccountName: controller-manager 67 | terminationGracePeriodSeconds: 10 68 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/globalinclusterippool_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit globalinclusterippools. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: globalinclusterippool-editor-role 6 | rules: 7 | - apiGroups: 8 | - ipam.cluster.x-k8s.io 9 | resources: 10 | - globalinclusterippools 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ipam.cluster.x-k8s.io 21 | resources: 22 | - globalinclusterippools/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/globalinclusterippool_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view globalinclusterippools. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: globalinclusterippool-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ipam.cluster.x-k8s.io 9 | resources: 10 | - globalinclusterippools 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ipam.cluster.x-k8s.io 17 | resources: 18 | - globalinclusterippools/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/inclusterippool_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit inclusterippools. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: inclusterippool-editor-role 6 | rules: 7 | - apiGroups: 8 | - ipam.cluster.x-k8s.io 9 | resources: 10 | - inclusterippools 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ipam.cluster.x-k8s.io 21 | resources: 22 | - inclusterippools/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/inclusterippool_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view inclusterippools. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: inclusterippool-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ipam.cluster.x-k8s.io 9 | resources: 10 | - inclusterippools 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ipam.cluster.x-k8s.io 17 | resources: 18 | - inclusterippools/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - cluster.x-k8s.io 9 | resources: 10 | - clusters 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ipam.cluster.x-k8s.io 17 | resources: 18 | - globalinclusterippools 19 | - inclusterippools 20 | - ipaddresses 21 | verbs: 22 | - create 23 | - delete 24 | - get 25 | - list 26 | - patch 27 | - update 28 | - watch 29 | - apiGroups: 30 | - ipam.cluster.x-k8s.io 31 | resources: 32 | - globalinclusterippools/finalizers 33 | - inclusterippools/finalizers 34 | - ipaddressclaims/finalizers 35 | - ipaddresses/finalizers 36 | verbs: 37 | - update 38 | - apiGroups: 39 | - ipam.cluster.x-k8s.io 40 | resources: 41 | - globalinclusterippools/status 42 | - inclusterippools/status 43 | - ipaddressclaims/status 44 | - ipaddresses/status 45 | verbs: 46 | - get 47 | - patch 48 | - update 49 | - apiGroups: 50 | - ipam.cluster.x-k8s.io 51 | resources: 52 | - ipaddressclaims 53 | verbs: 54 | - get 55 | - list 56 | - patch 57 | - update 58 | - watch 59 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ipam.cluster.x-k8s.io/v1beta1 2 | kind: IPAddressClaim 3 | metadata: 4 | name: my-claim 5 | spec: 6 | poolRef: 7 | apiGroup: ipam.cluster.x-k8s.io 8 | kind: InClusterIPPool 9 | name: inclusterippool-sample 10 | -------------------------------------------------------------------------------- /config/samples/ipam_v1alpha2_globalinclusterippool.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ipam.cluster.x-k8s.io/v1alpha2 2 | kind: GlobalInClusterIPPool 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: globalinclusterippool 6 | app.kubernetes.io/instance: globalinclusterippool-sample 7 | app.kubernetes.io/part-of: cluster-api-ipam-provider-in-cluster 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: cluster-api-ipam-provider-in-cluster 10 | name: globalinclusterippool-sample 11 | spec: 12 | addresses: 13 | - 10.0.0.2-10.0.0.254 14 | prefix: 24 15 | gateway: 10.0.0.1 16 | excludedAddresses: 17 | - 10.0.0.3-10.0.1.6 18 | - 10.0.0.10 19 | - 10.0.0.192/26 20 | 21 | -------------------------------------------------------------------------------- /config/samples/ipam_v1alpha2_inclusterippool.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ipam.cluster.x-k8s.io/v1alpha2 2 | kind: InClusterIPPool 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: inclusterippool 6 | app.kubernetes.io/instance: inclusterippool-sample 7 | app.kubernetes.io/part-of: cluster-api-ipam-provider-in-cluster 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: cluster-api-ipam-provider-in-cluster 10 | name: inclusterippool-sample 11 | spec: 12 | addresses: 13 | - 10.0.0.2-10.0.0.254 14 | prefix: 24 15 | gateway: 10.0.0.1 16 | excludedAddresses: 17 | - 10.0.0.3-10.0.1.6 18 | - 10.0.0.10 19 | - 10.0.0.192/26 20 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | - v1beta1 10 | clientConfig: 11 | service: 12 | name: webhook-service 13 | namespace: system 14 | path: /mutate-ipam-cluster-x-k8s-io-v1alpha2-globalinclusterippool 15 | failurePolicy: Fail 16 | matchPolicy: Equivalent 17 | name: default.globalinclusterippool.ipam.cluster.x-k8s.io 18 | rules: 19 | - apiGroups: 20 | - ipam.cluster.x-k8s.io 21 | apiVersions: 22 | - v1alpha2 23 | operations: 24 | - CREATE 25 | - UPDATE 26 | resources: 27 | - globalinclusterippools 28 | sideEffects: None 29 | - admissionReviewVersions: 30 | - v1 31 | - v1beta1 32 | clientConfig: 33 | service: 34 | name: webhook-service 35 | namespace: system 36 | path: /mutate-ipam-cluster-x-k8s-io-v1alpha2-inclusterippool 37 | failurePolicy: Fail 38 | matchPolicy: Equivalent 39 | name: default.inclusterippool.ipam.cluster.x-k8s.io 40 | rules: 41 | - apiGroups: 42 | - ipam.cluster.x-k8s.io 43 | apiVersions: 44 | - v1alpha2 45 | operations: 46 | - CREATE 47 | - UPDATE 48 | resources: 49 | - inclusterippools 50 | sideEffects: None 51 | --- 52 | apiVersion: admissionregistration.k8s.io/v1 53 | kind: ValidatingWebhookConfiguration 54 | metadata: 55 | name: validating-webhook-configuration 56 | webhooks: 57 | - admissionReviewVersions: 58 | - v1 59 | - v1beta1 60 | clientConfig: 61 | service: 62 | name: webhook-service 63 | namespace: system 64 | path: /validate-ipam-cluster-x-k8s-io-v1alpha2-globalinclusterippool 65 | failurePolicy: Fail 66 | matchPolicy: Equivalent 67 | name: validation.globalinclusterippool.ipam.cluster.x-k8s.io 68 | rules: 69 | - apiGroups: 70 | - ipam.cluster.x-k8s.io 71 | apiVersions: 72 | - v1alpha2 73 | operations: 74 | - CREATE 75 | - UPDATE 76 | - DELETE 77 | resources: 78 | - globalinclusterippools 79 | sideEffects: None 80 | - admissionReviewVersions: 81 | - v1 82 | - v1beta1 83 | clientConfig: 84 | service: 85 | name: webhook-service 86 | namespace: system 87 | path: /validate-ipam-cluster-x-k8s-io-v1alpha2-inclusterippool 88 | failurePolicy: Fail 89 | matchPolicy: Equivalent 90 | name: validation.inclusterippool.ipam.cluster.x-k8s.io 91 | rules: 92 | - apiGroups: 93 | - ipam.cluster.x-k8s.io 94 | apiVersions: 95 | - v1alpha2 96 | operations: 97 | - CREATE 98 | - UPDATE 99 | - DELETE 100 | resources: 101 | - inclusterippools 102 | sideEffects: None 103 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: webhook-service 5 | namespace: system 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: webhook-server 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sigs.k8s.io/cluster-api-ipam-provider-in-cluster 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.23.0 7 | github.com/onsi/gomega v1.36.2 8 | github.com/pkg/errors v0.9.1 9 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba 10 | k8s.io/api v0.32.2 11 | k8s.io/apimachinery v0.32.2 12 | k8s.io/client-go v0.32.2 13 | k8s.io/klog/v2 v2.130.1 14 | k8s.io/utils v0.0.0-20241210054802-24370beab758 15 | sigs.k8s.io/cluster-api v1.9.5 16 | sigs.k8s.io/controller-runtime v0.19.6 17 | ) 18 | 19 | require ( 20 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/blang/semver/v4 v4.0.0 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 25 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 26 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 27 | github.com/fsnotify/fsnotify v1.7.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-openapi/jsonpointer v0.21.0 // indirect 31 | github.com/go-openapi/jsonreference v0.20.4 // indirect 32 | github.com/go-openapi/swag v0.23.0 // indirect 33 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 34 | github.com/gobuffalo/flect v1.0.3 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/golang/protobuf v1.5.4 // indirect 37 | github.com/google/gnostic-models v0.6.8 // indirect 38 | github.com/google/go-cmp v0.6.0 // indirect 39 | github.com/google/gofuzz v1.2.0 // indirect 40 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/josharian/intern v1.0.0 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/mailru/easyjson v0.7.7 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 48 | github.com/prometheus/client_golang v1.19.1 // indirect 49 | github.com/prometheus/client_model v0.6.1 // indirect 50 | github.com/prometheus/common v0.55.0 // indirect 51 | github.com/prometheus/procfs v0.15.1 // indirect 52 | github.com/spf13/pflag v1.0.5 // indirect 53 | github.com/x448/float16 v0.8.4 // indirect 54 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 55 | golang.org/x/net v0.36.0 // indirect 56 | golang.org/x/oauth2 v0.24.0 // indirect 57 | golang.org/x/sys v0.30.0 // indirect 58 | golang.org/x/term v0.29.0 // indirect 59 | golang.org/x/text v0.22.0 // indirect 60 | golang.org/x/time v0.7.0 // indirect 61 | golang.org/x/tools v0.30.0 // indirect 62 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 63 | google.golang.org/protobuf v1.36.1 // indirect 64 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 65 | gopkg.in/inf.v0 v0.9.1 // indirect 66 | gopkg.in/yaml.v3 v3.0.1 // indirect 67 | k8s.io/apiextensions-apiserver v0.32.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 69 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 70 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 71 | sigs.k8s.io/yaml v1.4.0 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/boilerplate/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | exports_files(glob(["*.txt"])) 4 | 5 | py_test( 6 | name = "boilerplate_test", 7 | srcs = [ 8 | "boilerplate.py", 9 | "boilerplate_test.py", 10 | ], 11 | data = glob([ 12 | "*.txt", 13 | "test/*", 14 | ]), 15 | ) 16 | 17 | filegroup( 18 | name = "package-srcs", 19 | srcs = glob(["**"]), 20 | tags = ["automanaged"], 21 | visibility = ["//visibility:private"], 22 | ) 23 | 24 | filegroup( 25 | name = "all-srcs", 26 | srcs = [ 27 | ":package-srcs", 28 | "//hack/boilerplate/test:all-srcs", 29 | ], 30 | tags = ["automanaged"], 31 | ) 32 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Dockerfile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.Makefile.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.bzl.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.generatebzl.txt: -------------------------------------------------------------------------------- 1 | # Copyright The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.generatego.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright YEAR The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2015 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from __future__ import print_function 18 | 19 | import argparse 20 | import datetime 21 | import difflib 22 | import glob 23 | import os 24 | import re 25 | import sys 26 | 27 | parser = argparse.ArgumentParser() 28 | parser.add_argument( 29 | "filenames", 30 | help="list of files to check, all files if unspecified", 31 | nargs='*') 32 | 33 | rootdir = os.path.dirname(__file__) + "/../../" 34 | rootdir = os.path.abspath(rootdir) 35 | parser.add_argument( 36 | "--rootdir", default=rootdir, help="root directory to examine") 37 | 38 | default_boilerplate_dir = os.path.join(rootdir, "hack/boilerplate") 39 | parser.add_argument( 40 | "--boilerplate-dir", default=default_boilerplate_dir) 41 | 42 | parser.add_argument( 43 | "-v", "--verbose", 44 | help="give verbose output regarding why a file does not pass", 45 | action="store_true") 46 | 47 | args = parser.parse_args() 48 | 49 | verbose_out = sys.stderr if args.verbose else open("/dev/null", "w") 50 | 51 | def get_refs(): 52 | refs = {} 53 | 54 | for path in glob.glob(os.path.join(args.boilerplate_dir, "boilerplate.*.txt")): 55 | extension = os.path.basename(path).split(".")[1] 56 | 57 | ref_file = open(path, 'r') 58 | ref = ref_file.read().splitlines() 59 | ref_file.close() 60 | refs[extension] = ref 61 | 62 | return refs 63 | 64 | def is_generated_file(filename, data, regexs): 65 | for d in skipped_ungenerated_files: 66 | if d in filename: 67 | return False 68 | 69 | p = regexs["generated"] 70 | return p.search(data) 71 | 72 | def file_passes(filename, refs, regexs): 73 | try: 74 | f = open(filename, 'r') 75 | except Exception as exc: 76 | print("Unable to open %s: %s" % (filename, exc), file=verbose_out) 77 | return False 78 | 79 | data = f.read() 80 | f.close() 81 | 82 | # determine if the file is automatically generated 83 | generated = is_generated_file(filename, data, regexs) 84 | 85 | basename = os.path.basename(filename) 86 | extension = file_extension(filename) 87 | if generated: 88 | if extension == "go": 89 | extension = "generatego" 90 | elif extension == "bzl": 91 | extension = "generatebzl" 92 | 93 | if extension != "": 94 | ref = refs[extension] 95 | else: 96 | ref = refs[basename] 97 | 98 | # remove extra content from the top of files 99 | if extension == "go" or extension == "generatego": 100 | p = regexs["go_build_constraints"] 101 | (data, found) = p.subn("", data, 1) 102 | elif extension in ["sh", "py"]: 103 | p = regexs["shebang"] 104 | (data, found) = p.subn("", data, 1) 105 | 106 | data = data.splitlines() 107 | 108 | # if our test file is smaller than the reference it surely fails! 109 | if len(ref) > len(data): 110 | print('File %s smaller than reference (%d < %d)' % 111 | (filename, len(data), len(ref)), 112 | file=verbose_out) 113 | return False 114 | 115 | # trim our file to the same number of lines as the reference file 116 | data = data[:len(ref)] 117 | 118 | p = regexs["year"] 119 | for d in data: 120 | if p.search(d): 121 | if generated: 122 | print('File %s has the YEAR field, but it should not be in generated file' % 123 | filename, file=verbose_out) 124 | else: 125 | print('File %s has the YEAR field, but missing the year of date' % 126 | filename, file=verbose_out) 127 | return False 128 | 129 | if not generated: 130 | # Replace all occurrences of the regex "2014|2015|2016|2017|2018" with "YEAR" 131 | p = regexs["date"] 132 | for i, d in enumerate(data): 133 | (data[i], found) = p.subn('YEAR', d) 134 | if found != 0: 135 | break 136 | 137 | # if we don't match the reference at this point, fail 138 | if ref != data: 139 | print("Header in %s does not match reference, diff:" % 140 | filename, file=verbose_out) 141 | if args.verbose: 142 | print(file=verbose_out) 143 | for line in difflib.unified_diff(ref, data, 'reference', filename, lineterm=''): 144 | print(line, file=verbose_out) 145 | print(file=verbose_out) 146 | return False 147 | 148 | return True 149 | 150 | def file_extension(filename): 151 | return os.path.splitext(filename)[1].split(".")[-1].lower() 152 | 153 | skipped_dirs = ['_output', '.git', "hack/boilerplate/test"] 154 | 155 | # list all the files contain 'DO NOT EDIT', but are not generated 156 | skipped_ungenerated_files = [ 157 | 'hack/lib/swagger.sh', 'hack/boilerplate/boilerplate.py'] 158 | 159 | def normalize_files(files): 160 | newfiles = [] 161 | for pathname in files: 162 | if any(x in pathname for x in skipped_dirs): 163 | continue 164 | newfiles.append(pathname) 165 | for i, pathname in enumerate(newfiles): 166 | if not os.path.isabs(pathname): 167 | newfiles[i] = os.path.join(args.rootdir, pathname) 168 | return newfiles 169 | 170 | def get_files(extensions): 171 | files = [] 172 | if len(args.filenames) > 0: 173 | files = args.filenames 174 | else: 175 | for root, dirs, walkfiles in os.walk(args.rootdir): 176 | # don't visit certain dirs. This is just a performance improvement 177 | # as we would prune these later in normalize_files(). But doing it 178 | # cuts down the amount of filesystem walking we do and cuts down 179 | # the size of the file list 180 | for d in skipped_dirs: 181 | if d in dirs: 182 | dirs.remove(d) 183 | 184 | for name in walkfiles: 185 | pathname = os.path.join(root, name) 186 | files.append(pathname) 187 | 188 | files = normalize_files(files) 189 | outfiles = [] 190 | for pathname in files: 191 | basename = os.path.basename(pathname) 192 | extension = file_extension(pathname) 193 | if extension in extensions or basename in extensions: 194 | outfiles.append(pathname) 195 | return outfiles 196 | 197 | def get_dates(): 198 | years = datetime.datetime.now().year 199 | return '(%s)' % '|'.join((str(year) for year in range(2014, years+1))) 200 | 201 | def get_regexs(): 202 | regexs = {} 203 | # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing 204 | regexs["year"] = re.compile('YEAR') 205 | # get_dates return 2014, 2015, 2016, 2017, or 2018 until the current year as a regex like: "(2014|2015|2016|2017|2018)"; 206 | # company holder names can be anything 207 | regexs["date"] = re.compile(get_dates()) 208 | # strip the following build constraints/tags: 209 | # //go:build 210 | # // +build \n\n 211 | regexs["go_build_constraints"] = re.compile( 212 | r"^(//(go:build| \+build).*\n)+\n", re.MULTILINE) 213 | # strip #!.* from scripts 214 | regexs["shebang"] = re.compile(r"^(#!.*\n)\n*", re.MULTILINE) 215 | # Search for generated files 216 | regexs["generated"] = re.compile('DO NOT EDIT') 217 | return regexs 218 | 219 | def main(): 220 | regexs = get_regexs() 221 | refs = get_refs() 222 | filenames = get_files(refs.keys()) 223 | 224 | for filename in filenames: 225 | if not file_passes(filename, refs, regexs): 226 | print(filename, file=sys.stdout) 227 | 228 | return 0 229 | 230 | if __name__ == "__main__": 231 | sys.exit(main()) -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.py.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | # Copyright YEAR The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /hack/boilerplate/boilerplate_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2016 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import boilerplate 18 | import unittest 19 | import StringIO 20 | import os 21 | import sys 22 | 23 | class TestBoilerplate(unittest.TestCase): 24 | """ 25 | Note: run this test from the hack/boilerplate directory. 26 | 27 | $ python -m unittest boilerplate_test 28 | """ 29 | 30 | def test_boilerplate(self): 31 | os.chdir("test/") 32 | 33 | class Args(object): 34 | def __init__(self): 35 | self.filenames = [] 36 | self.rootdir = "." 37 | self.boilerplate_dir = "../" 38 | self.verbose = True 39 | 40 | # capture stdout 41 | old_stdout = sys.stdout 42 | sys.stdout = StringIO.StringIO() 43 | 44 | boilerplate.args = Args() 45 | ret = boilerplate.main() 46 | 47 | output = sorted(sys.stdout.getvalue().split()) 48 | 49 | sys.stdout = old_stdout 50 | 51 | self.assertEquals( 52 | output, ['././fail.go', '././fail.py']) -------------------------------------------------------------------------------- /hack/boilerplate/test/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load( 4 | "@io_bazel_rules_go//go:def.bzl", 5 | "go_library", 6 | ) 7 | 8 | go_library( 9 | name = "go_default_library", 10 | srcs = [ 11 | "fail.go", 12 | "pass.go", 13 | ], 14 | importpath = "sigs.k8s.io/cluster-api-provider-aws/hack/boilerplate/test", 15 | ) 16 | 17 | filegroup( 18 | name = "package-srcs", 19 | srcs = glob(["**"]), 20 | tags = ["automanaged"], 21 | visibility = ["//visibility:private"], 22 | ) 23 | 24 | filegroup( 25 | name = "all-srcs", 26 | srcs = [":package-srcs"], 27 | tags = ["automanaged"], 28 | ) 29 | -------------------------------------------------------------------------------- /hack/boilerplate/test/fail.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 The Kubernetes Authors. 3 | 4 | fail 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Package test contains test boilerplate. 20 | package test 21 | -------------------------------------------------------------------------------- /hack/boilerplate/test/fail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2015 The Kubernetes Authors. 4 | # 5 | # failed 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | -------------------------------------------------------------------------------- /hack/boilerplate/test/pass.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package test 18 | -------------------------------------------------------------------------------- /hack/boilerplate/test/pass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2015 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | True 18 | -------------------------------------------------------------------------------- /hack/licenses.md.tpl: -------------------------------------------------------------------------------- 1 | # Dependency Licenses 2 | 3 | {{ range . }} 4 | - {{.Name}}@{{.Version}} ([{{.LicenseName}}]({{.LicenseURL}})) 5 | {{- end }} 6 | -------------------------------------------------------------------------------- /hack/verify-boilerplate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2014 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | set -o verbose 21 | 22 | KUBE_ROOT="$(dirname "${BASH_SOURCE[0]}")/.." 23 | 24 | boilerDir="${KUBE_ROOT}/hack/boilerplate" 25 | boiler="${boilerDir}/boilerplate.py" 26 | 27 | files_need_boilerplate=() 28 | while IFS=$'\n' read -r line; do 29 | files_need_boilerplate+=("$line") 30 | done < <("${boiler}" "$@") 31 | 32 | # Run boilerplate check 33 | if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then 34 | for file in "${files_need_boilerplate[@]}"; do 35 | echo "Boilerplate header is wrong for: ${file}" >&2 36 | done 37 | 38 | exit 1 39 | fi 40 | -------------------------------------------------------------------------------- /hack/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 The Kubernetes Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -o errexit 17 | set -o nounset 18 | set -o pipefail 19 | 20 | version::get_version_vars() { 21 | # shellcheck disable=SC1083 22 | GIT_COMMIT="$(git rev-parse HEAD^{commit})" 23 | 24 | if git_status=$(git status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then 25 | GIT_TREE_STATE="clean" 26 | else 27 | GIT_TREE_STATE="dirty" 28 | fi 29 | 30 | # borrowed from k8s.io/hack/lib/version.sh 31 | # Use git describe to find the version based on tags. 32 | if GIT_VERSION=$(git describe --tags --abbrev=14 2>/dev/null); then 33 | # This translates the "git describe" to an actual semver.org 34 | # compatible semantic version that looks something like this: 35 | # v1.1.0-alpha.0.6+84c76d1142ea4d 36 | if [[ "${GIT_TREE_STATE}" == "dirty" ]]; then 37 | # git describe --dirty only considers changes to existing files, but 38 | # that is problematic since new untracked .go files affect the build, 39 | # so use our idea of "dirty" from git status instead. 40 | GIT_VERSION+="-dirty" 41 | fi 42 | 43 | 44 | # Try to match the "git describe" output to a regex to try to extract 45 | # the "major" and "minor" versions and whether this is the exact tagged 46 | # version or whether the tree is between two tagged versions. 47 | if [[ "${GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?([-].*)?([+].*)?$ ]]; then 48 | GIT_MAJOR=${BASH_REMATCH[1]} 49 | GIT_MINOR=${BASH_REMATCH[2]} 50 | fi 51 | 52 | # If GIT_VERSION is not a valid Semantic Version, then refuse to build. 53 | if ! [[ "${GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then 54 | echo "GIT_VERSION should be a valid Semantic Version. Current value: ${GIT_VERSION}" 55 | echo "Please see more details here: https://semver.org" 56 | exit 1 57 | fi 58 | fi 59 | } 60 | 61 | # borrowed from k8s.io/hack/lib/version.sh and modified 62 | # Prints the value that needs to be passed to the -ldflags parameter of go build 63 | version::ldflags() { 64 | version::get_version_vars 65 | 66 | local -a ldflags 67 | function add_ldflag() { 68 | local key=${1} 69 | local val=${2} 70 | ldflags+=( 71 | "-X 'sigs.k8s.io/cluster-api-ipam-provider-in-cluster/version.${key}=${val}'" 72 | ) 73 | } 74 | 75 | add_ldflag "buildDate" "$(date ${SOURCE_DATE_EPOCH:+"--date=@${SOURCE_DATE_EPOCH}"} -u +'%Y-%m-%dT%H:%M:%SZ')" 76 | add_ldflag "gitCommit" "${GIT_COMMIT}" 77 | add_ldflag "gitTreeState" "${GIT_TREE_STATE}" 78 | add_ldflag "gitMajor" "${GIT_MAJOR}" 79 | add_ldflag "gitMinor" "${GIT_MINOR}" 80 | add_ldflag "gitVersion" "${GIT_VERSION}" 81 | 82 | # The -ldflags parameter takes a single string, so join the output. 83 | echo "${ldflags[*]-}" 84 | } 85 | 86 | version::ldflags -------------------------------------------------------------------------------- /internal/controllers/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package controllers implements controllers to handle allocations using in-cluster resources. 18 | package controllers 19 | -------------------------------------------------------------------------------- /internal/controllers/inclusterippool.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "net/netip" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | apierrors "k8s.io/apimachinery/pkg/api/errors" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "k8s.io/apimachinery/pkg/types" 28 | kerrors "k8s.io/apimachinery/pkg/util/errors" 29 | "k8s.io/utils/ptr" 30 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 31 | "sigs.k8s.io/cluster-api/util/patch" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 35 | "sigs.k8s.io/controller-runtime/pkg/handler" 36 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 37 | 38 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 39 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/poolutil" 40 | pooltypes "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/pkg/types" 41 | ) 42 | 43 | const ( 44 | inClusterIPPoolKind = "InClusterIPPool" 45 | globalInClusterIPPoolKind = "GlobalInClusterIPPool" 46 | 47 | // ProtectPoolFinalizer is used to prevent deletion of a Pool object while its addresses have not been deleted. 48 | ProtectPoolFinalizer = "ipam.cluster.x-k8s.io/ProtectPool" 49 | ) 50 | 51 | // InClusterIPPoolReconciler reconciles a InClusterIPPool object. 52 | type InClusterIPPoolReconciler struct { 53 | client.Client 54 | Scheme *runtime.Scheme 55 | } 56 | 57 | // SetupWithManager sets up the controller with the Manager. 58 | func (r *InClusterIPPoolReconciler) SetupWithManager(_ context.Context, mgr ctrl.Manager) error { 59 | return ctrl.NewControllerManagedBy(mgr). 60 | For(&v1alpha2.InClusterIPPool{}). 61 | Watches( 62 | &ipamv1.IPAddress{}, 63 | handler.EnqueueRequestsFromMapFunc(r.ipAddressToInClusterIPPool)). 64 | Complete(r) 65 | } 66 | 67 | func (r *InClusterIPPoolReconciler) ipAddressToInClusterIPPool(_ context.Context, clientObj client.Object) []reconcile.Request { 68 | ipAddress, ok := clientObj.(*ipamv1.IPAddress) 69 | if !ok { 70 | return nil 71 | } 72 | 73 | if ipAddress.Spec.PoolRef.APIGroup != nil && 74 | *ipAddress.Spec.PoolRef.APIGroup == v1alpha2.GroupVersion.Group && 75 | ipAddress.Spec.PoolRef.Kind == inClusterIPPoolKind { 76 | return []reconcile.Request{{ 77 | NamespacedName: types.NamespacedName{ 78 | Namespace: ipAddress.Namespace, 79 | Name: ipAddress.Spec.PoolRef.Name, 80 | }, 81 | }} 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // GlobalInClusterIPPoolReconciler reconciles a GlobalInClusterIPPool object. 88 | type GlobalInClusterIPPoolReconciler struct { 89 | client.Client 90 | Scheme *runtime.Scheme 91 | } 92 | 93 | // SetupWithManager sets up the controller with the Manager. 94 | func (r *GlobalInClusterIPPoolReconciler) SetupWithManager(_ context.Context, mgr ctrl.Manager) error { 95 | return ctrl.NewControllerManagedBy(mgr). 96 | For(&v1alpha2.GlobalInClusterIPPool{}). 97 | Watches( 98 | &ipamv1.IPAddress{}, 99 | handler.EnqueueRequestsFromMapFunc(r.ipAddressToGlobalInClusterIPPool)). 100 | Complete(r) 101 | } 102 | 103 | func (r *GlobalInClusterIPPoolReconciler) ipAddressToGlobalInClusterIPPool(_ context.Context, clientObj client.Object) []reconcile.Request { 104 | ipAddress, ok := clientObj.(*ipamv1.IPAddress) 105 | if !ok { 106 | return nil 107 | } 108 | 109 | if ipAddress.Spec.PoolRef.APIGroup != nil && 110 | *ipAddress.Spec.PoolRef.APIGroup == v1alpha2.GroupVersion.Group && 111 | ipAddress.Spec.PoolRef.Kind == globalInClusterIPPoolKind { 112 | return []reconcile.Request{{ 113 | NamespacedName: types.NamespacedName{ 114 | Namespace: ipAddress.Namespace, 115 | Name: ipAddress.Spec.PoolRef.Name, 116 | }, 117 | }} 118 | } 119 | 120 | return nil 121 | } 122 | 123 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=inclusterippools,verbs=get;list;watch;create;update;patch;delete 124 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=inclusterippools/status,verbs=get;update;patch 125 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=inclusterippools/finalizers,verbs=update 126 | 127 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 128 | // move the current state of the cluster closer to the desired state. 129 | func (r *InClusterIPPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 130 | log := ctrl.LoggerFrom(ctx) 131 | log.Info("Reconciling pool") 132 | 133 | pool := &v1alpha2.InClusterIPPool{} 134 | if err := r.Get(ctx, req.NamespacedName, pool); err != nil { 135 | if !apierrors.IsNotFound(err) { 136 | return ctrl.Result{}, errors.Wrap(err, "failed to fetch InClusterIPPool") 137 | } 138 | return ctrl.Result{}, nil 139 | } 140 | return genericReconcile(ctx, r.Client, pool) 141 | } 142 | 143 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=globalinclusterippools,verbs=get;list;watch;create;update;patch;delete 144 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=globalinclusterippools/status,verbs=get;update;patch 145 | //+kubebuilder:rbac:groups=ipam.cluster.x-k8s.io,resources=globalinclusterippools/finalizers,verbs=update 146 | 147 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 148 | // move the current state of the cluster closer to the desired state. 149 | func (r *GlobalInClusterIPPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 150 | log := ctrl.LoggerFrom(ctx) 151 | log.Info("Reconciling pool") 152 | 153 | pool := &v1alpha2.GlobalInClusterIPPool{} 154 | if err := r.Get(ctx, req.NamespacedName, pool); err != nil { 155 | if !apierrors.IsNotFound(err) { 156 | return ctrl.Result{}, errors.Wrap(err, "failed to fetch GlobalInClusterIPPool") 157 | } 158 | return ctrl.Result{}, nil 159 | } 160 | return genericReconcile(ctx, r.Client, pool) 161 | } 162 | 163 | func genericReconcile(ctx context.Context, c client.Client, pool pooltypes.GenericInClusterPool) (_ ctrl.Result, reterr error) { 164 | log := ctrl.LoggerFrom(ctx) 165 | 166 | patchHelper, err := patch.NewHelper(pool, c) 167 | if err != nil { 168 | return ctrl.Result{}, err 169 | } 170 | 171 | defer func() { 172 | if err := patchHelper.Patch(ctx, pool); err != nil { 173 | reterr = kerrors.NewAggregate([]error{reterr, err}) 174 | } 175 | }() 176 | 177 | poolTypeRef := corev1.TypedLocalObjectReference{ 178 | APIGroup: ptr.To(v1alpha2.GroupVersion.Group), 179 | Kind: pool.GetObjectKind().GroupVersionKind().Kind, 180 | Name: pool.GetName(), 181 | } 182 | 183 | addressesInUse, err := poolutil.ListAddressesInUse(ctx, c, pool.GetNamespace(), poolTypeRef) 184 | if err != nil { 185 | return ctrl.Result{}, errors.Wrap(err, "failed to list addresses") 186 | } 187 | 188 | inUseCount := len(addressesInUse) 189 | 190 | if !controllerutil.ContainsFinalizer(pool, ProtectPoolFinalizer) { 191 | controllerutil.AddFinalizer(pool, ProtectPoolFinalizer) 192 | } 193 | 194 | if !pool.GetDeletionTimestamp().IsZero() { 195 | if inUseCount == 0 { 196 | controllerutil.RemoveFinalizer(pool, ProtectPoolFinalizer) 197 | } 198 | return ctrl.Result{}, nil 199 | } 200 | 201 | poolIPSet, err := poolutil.PoolSpecToIPSet(pool.PoolSpec()) 202 | if err != nil { 203 | return ctrl.Result{}, errors.Wrap(err, "failed to build ip set from pool spec") 204 | } 205 | 206 | poolCount := poolutil.IPSetCount(poolIPSet) 207 | if pool.PoolSpec().Gateway != "" { 208 | gatewayAddr, err := netip.ParseAddr(pool.PoolSpec().Gateway) 209 | if err != nil { 210 | return ctrl.Result{}, errors.Wrap(err, "failed to parse pool gateway") 211 | } 212 | 213 | if poolIPSet.Contains(gatewayAddr) { 214 | poolCount-- 215 | } 216 | } 217 | 218 | free := poolCount - inUseCount 219 | outOfRangeIPSet, err := poolutil.AddressesOutOfRangeIPSet(addressesInUse, poolIPSet) 220 | if err != nil { 221 | return ctrl.Result{}, errors.Wrap(err, "failed to build out of range ip set") 222 | } 223 | 224 | pool.PoolStatus().Addresses = &v1alpha2.InClusterIPPoolStatusIPAddresses{ 225 | Total: poolCount, 226 | Used: inUseCount, 227 | Free: free, 228 | OutOfRange: poolutil.IPSetCount(outOfRangeIPSet), 229 | } 230 | 231 | log.Info("Updating pool with usage info", "statusAddresses", pool.PoolStatus().Addresses) 232 | 233 | return ctrl.Result{}, nil 234 | } 235 | -------------------------------------------------------------------------------- /internal/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "path/filepath" 22 | "testing" 23 | "time" 24 | 25 | . "github.com/onsi/ginkgo/v2" 26 | . "github.com/onsi/gomega" 27 | corev1 "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | "k8s.io/client-go/rest" 31 | "k8s.io/klog/v2" 32 | "k8s.io/utils/ptr" 33 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/client" 37 | "sigs.k8s.io/controller-runtime/pkg/envtest" 38 | "sigs.k8s.io/controller-runtime/pkg/envtest/komega" 39 | 40 | //+kubebuilder:scaffold:imports 41 | v1alpha2 "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 42 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/index" 43 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/pkg/ipamutil" 44 | ) 45 | 46 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 47 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 48 | 49 | var ( 50 | cfg *rest.Config 51 | k8sClient client.Client 52 | testEnv *envtest.Environment 53 | ctx context.Context 54 | cancelCtx func() 55 | ) 56 | 57 | func TestAPIs(t *testing.T) { 58 | RegisterFailHandler(Fail) 59 | RunSpecs(t, "Controller Suite") 60 | } 61 | 62 | var _ = BeforeSuite(func() { 63 | klog.SetOutput(GinkgoWriter) 64 | ctrl.SetLogger(klog.Background()) 65 | 66 | ctx, cancelCtx = context.WithCancel(ctrl.SetupSignalHandler()) 67 | 68 | By("bootstrapping test environment") 69 | testEnv = &envtest.Environment{ 70 | CRDDirectoryPaths: []string{ 71 | filepath.Join("..", "..", "config", "crd", "bases"), 72 | filepath.Join("..", "..", "config", "crd", "test"), 73 | }, 74 | ErrorIfCRDPathMissing: true, 75 | ControlPlaneStopTimeout: 60 * time.Second, 76 | AttachControlPlaneOutput: true, 77 | } 78 | 79 | var err error 80 | cfg, err = testEnv.Start() 81 | Expect(err).NotTo(HaveOccurred()) 82 | Expect(cfg).NotTo(BeNil()) 83 | 84 | Expect(v1alpha2.AddToScheme(scheme.Scheme)).To(Succeed()) 85 | Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) 86 | Expect(ipamv1.AddToScheme(scheme.Scheme)).To(Succeed()) 87 | 88 | //+kubebuilder:scaffold:scheme 89 | 90 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 91 | Scheme: scheme.Scheme, 92 | }) 93 | Expect(err).ToNot(HaveOccurred()) 94 | 95 | k8sClient = mgr.GetClient() 96 | komega.SetClient(mgr.GetClient()) 97 | 98 | Expect(index.SetupIndexes(ctx, mgr)).To(Succeed()) 99 | 100 | Expect( 101 | (&ipamutil.ClaimReconciler{ 102 | Client: mgr.GetClient(), 103 | Scheme: mgr.GetScheme(), 104 | Adapter: &InClusterProviderAdapter{Client: mgr.GetClient()}, 105 | }).SetupWithManager(ctx, mgr), 106 | ).To(Succeed()) 107 | 108 | Expect( 109 | (&InClusterIPPoolReconciler{ 110 | Client: mgr.GetClient(), 111 | Scheme: mgr.GetScheme(), 112 | }).SetupWithManager(ctx, mgr), 113 | ).To(Succeed()) 114 | 115 | Expect( 116 | (&GlobalInClusterIPPoolReconciler{ 117 | Client: mgr.GetClient(), 118 | Scheme: mgr.GetScheme(), 119 | }).SetupWithManager(ctx, mgr), 120 | ).To(Succeed()) 121 | 122 | go func() { 123 | defer GinkgoRecover() 124 | err = mgr.Start(ctx) 125 | Expect(err).ToNot(HaveOccurred(), "failed to run manager") 126 | }() 127 | }) 128 | 129 | var _ = AfterSuite(func() { 130 | cancelCtx() 131 | By("tearing down the test environment") 132 | err := testEnv.Stop() 133 | Expect(err).NotTo(HaveOccurred()) 134 | }) 135 | 136 | func newClaim(name, namespace, poolKind, poolName string) ipamv1.IPAddressClaim { 137 | return ipamv1.IPAddressClaim{ 138 | ObjectMeta: metav1.ObjectMeta{ 139 | Name: name, 140 | Namespace: namespace, 141 | }, 142 | Spec: ipamv1.IPAddressClaimSpec{ 143 | PoolRef: corev1.TypedLocalObjectReference{ 144 | APIGroup: ptr.To("ipam.cluster.x-k8s.io"), 145 | Kind: poolKind, 146 | Name: poolName, 147 | }, 148 | }, 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /internal/index/index.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package index implements several indexes for the controller-runtime Managers cache. 18 | package index 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/manager" 28 | ) 29 | 30 | const ( 31 | // IPAddressPoolRefCombinedField is an index for the poolRef of an IPAddress. 32 | IPAddressPoolRefCombinedField = "index.poolRef" 33 | 34 | // IPAddressClaimPoolRefCombinedField is an index for the poolRef of an IPAddressClaim. 35 | IPAddressClaimPoolRefCombinedField = "index.poolRef" 36 | ) 37 | 38 | // SetupIndexes adds indexes to the cache of a Manager. 39 | func SetupIndexes(ctx context.Context, mgr manager.Manager) error { 40 | err := mgr.GetCache().IndexField(ctx, &ipamv1.IPAddress{}, 41 | IPAddressPoolRefCombinedField, 42 | IPAddressByCombinedPoolRef, 43 | ) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return mgr.GetCache().IndexField(ctx, &ipamv1.IPAddressClaim{}, 49 | IPAddressClaimPoolRefCombinedField, 50 | ipAddressClaimByCombinedPoolRef, 51 | ) 52 | } 53 | 54 | // IPAddressByCombinedPoolRef fulfills the IndexerFunc for IPAddress poolRefs. 55 | func IPAddressByCombinedPoolRef(o client.Object) []string { 56 | ip, ok := o.(*ipamv1.IPAddress) 57 | if !ok { 58 | panic(fmt.Sprintf("Expected an IPAddress but got a %T", o)) 59 | } 60 | return []string{IPPoolRefValue(ip.Spec.PoolRef)} 61 | } 62 | 63 | func ipAddressClaimByCombinedPoolRef(o client.Object) []string { 64 | ip, ok := o.(*ipamv1.IPAddressClaim) 65 | if !ok { 66 | panic(fmt.Sprintf("Expected an IPAddressClaim but got a %T", o)) 67 | } 68 | return []string{IPPoolRefValue(ip.Spec.PoolRef)} 69 | } 70 | 71 | // IPPoolRefValue turns a corev1.TypedLocalObjectReference to an indexable value. 72 | func IPPoolRefValue(ref corev1.TypedLocalObjectReference) string { 73 | return fmt.Sprintf("%s%s", ref.Kind, ref.Name) 74 | } 75 | -------------------------------------------------------------------------------- /internal/poolutil/pool.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package poolutil implements utility functions to manage a pool of IP addresses. 18 | package poolutil 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "math" 24 | "math/big" 25 | "net/netip" 26 | "strings" 27 | 28 | "go4.org/netipx" 29 | corev1 "k8s.io/api/core/v1" 30 | "k8s.io/apimachinery/pkg/runtime/schema" 31 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 32 | "sigs.k8s.io/controller-runtime/pkg/client" 33 | 34 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 35 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/index" 36 | ) 37 | 38 | // AddressesOutOfRangeIPSet returns an IPSet of the inUseAddresses IPs that are 39 | // not in the poolIPSet. 40 | func AddressesOutOfRangeIPSet(inUseAddresses []ipamv1.IPAddress, poolIPSet *netipx.IPSet) (*netipx.IPSet, error) { 41 | outOfRangeBuilder := &netipx.IPSetBuilder{} 42 | for _, address := range inUseAddresses { 43 | ip, err := netip.ParseAddr(address.Spec.Address) 44 | if err != nil { 45 | // if an address we fetch for the pool is unparsable then it isn't in the pool ranges 46 | continue 47 | } 48 | outOfRangeBuilder.Add(ip) 49 | } 50 | outOfRangeBuilder.RemoveSet(poolIPSet) 51 | return outOfRangeBuilder.IPSet() 52 | } 53 | 54 | // ListAddressesInUse fetches all IPAddresses belonging to the specified pool. 55 | // Note: requires `index.ipAddressByCombinedPoolRef` to be set up. 56 | func ListAddressesInUse(ctx context.Context, c client.Reader, namespace string, poolRef corev1.TypedLocalObjectReference) ([]ipamv1.IPAddress, error) { 57 | addresses := &ipamv1.IPAddressList{} 58 | err := c.List(ctx, addresses, 59 | client.MatchingFields{ 60 | index.IPAddressPoolRefCombinedField: index.IPPoolRefValue(poolRef), 61 | }, 62 | client.InNamespace(namespace), 63 | ) 64 | addr := []ipamv1.IPAddress{} 65 | for _, a := range addresses.Items { 66 | gv, _ := schema.ParseGroupVersion(a.APIVersion) 67 | if gv.Group != "ipam.cluster.x-k8s.io" { 68 | continue 69 | } 70 | addr = append(addr, a) 71 | } 72 | return addr, err 73 | } 74 | 75 | // AddressByNamespacedName finds a specific ip address by namespace and name in a slice of addresses. 76 | func AddressByNamespacedName(addresses []ipamv1.IPAddress, namespace, name string) *ipamv1.IPAddress { 77 | for _, a := range addresses { 78 | if a.Namespace == namespace && a.Name == name { 79 | return &a 80 | } 81 | } 82 | return nil 83 | } 84 | 85 | // FindFreeAddress returns the next free IP Address in a range based on a set of existing addresses. 86 | func FindFreeAddress(poolIPSet *netipx.IPSet, inUseIPSet *netipx.IPSet) (netip.Addr, error) { 87 | for _, iprange := range poolIPSet.Ranges() { 88 | ip := iprange.From() 89 | for { 90 | if !inUseIPSet.Contains(ip) { 91 | return ip, nil 92 | } 93 | if ip == iprange.To() { 94 | break 95 | } 96 | ip = ip.Next() 97 | } 98 | } 99 | return netip.Addr{}, errors.New("no address available") 100 | } 101 | 102 | // PoolSpecToIPSet converts a pool spec to an IPSet. Reserved addresses will be 103 | // omitted from the set depending on whether the 104 | // `spec.AllocateReservedIPAddresses` flag is set. 105 | func PoolSpecToIPSet(poolSpec *v1alpha2.InClusterIPPoolSpec) (*netipx.IPSet, error) { 106 | addressesIPSet, err := AddressesToIPSet(poolSpec.Addresses) 107 | if err != nil { 108 | return nil, err // should not happen, webhook validates pools for correctness. 109 | } 110 | 111 | builder := &netipx.IPSetBuilder{} 112 | builder.AddSet(addressesIPSet) 113 | 114 | if len(poolSpec.ExcludedAddresses) > 0 { 115 | excludedAddressesIPSet, err := AddressesToIPSet(poolSpec.ExcludedAddresses) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | builder.RemoveSet(excludedAddressesIPSet) 121 | } 122 | 123 | if !poolSpec.AllocateReservedIPAddresses { 124 | subnet := netip.PrefixFrom(addressesIPSet.Ranges()[0].From(), poolSpec.Prefix) // safe because of webhook validation 125 | subnetRange := netipx.RangeOfPrefix(subnet) 126 | builder.Remove(subnetRange.From()) // network addr in IPv4, anycast addr in IPv6 127 | if subnet.Addr().Is4() { 128 | builder.Remove(subnetRange.To()) // broadcast addr 129 | } 130 | } 131 | 132 | if poolSpec.Gateway != "" { 133 | gateway, err := netip.ParseAddr(poolSpec.Gateway) 134 | if err != nil { 135 | return nil, err // should not happen, webhook validates pools for correctness. 136 | } 137 | builder.Remove(gateway) 138 | } 139 | 140 | return builder.IPSet() 141 | } 142 | 143 | // AddressesToIPSet converts an array of addresses to an AddressesToIPSet 144 | // addresses may be specified as individual IPs, CIDR ranges, or hyphenated IP 145 | // ranges. 146 | func AddressesToIPSet(addresses []string) (*netipx.IPSet, error) { 147 | builder := &netipx.IPSetBuilder{} 148 | for _, addressStr := range addresses { 149 | ipSet, err := AddressToIPSet(addressStr) 150 | if err != nil { 151 | return nil, err 152 | } 153 | builder.AddSet(ipSet) 154 | } 155 | return builder.IPSet() 156 | } 157 | 158 | // AddressToIPSet converts an addresses to an AddressesToIPSet addresses may be 159 | // specified as individual IPs, CIDR ranges, or hyphenated IP ranges. 160 | func AddressToIPSet(addressStr string) (*netipx.IPSet, error) { 161 | builder := &netipx.IPSetBuilder{} 162 | 163 | if strings.Contains(addressStr, "-") { 164 | addrRange, err := netipx.ParseIPRange(addressStr) 165 | if err != nil { 166 | return nil, err 167 | } 168 | builder.AddRange(addrRange) 169 | } else if strings.Contains(addressStr, "/") { 170 | prefix, err := netip.ParsePrefix(addressStr) 171 | if err != nil { 172 | return nil, err 173 | } 174 | builder.AddPrefix(prefix) 175 | } else { 176 | addr, err := netip.ParseAddr(addressStr) 177 | if err != nil { 178 | return nil, err 179 | } 180 | builder.Add(addr) 181 | } 182 | 183 | return builder.IPSet() 184 | } 185 | 186 | // IPSetCount returns the number of IPs contained in the given IPSet. 187 | // This function returns type int, which is much smaller than 188 | // the possible size of a range, which could be 2^128 IPs. 189 | // When an IPSet's count is would be larger than an int, math.MaxInt 190 | // is returned instead. 191 | func IPSetCount(ipSet *netipx.IPSet) int { 192 | if ipSet == nil { 193 | return 0 194 | } 195 | 196 | total := big.NewInt(0) 197 | for _, iprange := range ipSet.Ranges() { 198 | total.Add( 199 | total, 200 | big.NewInt(0).Sub( 201 | big.NewInt(0).SetBytes(iprange.To().AsSlice()), 202 | big.NewInt(0).SetBytes(iprange.From().AsSlice()), 203 | ), 204 | ) 205 | // Subtracting To and From misses that one of those is a valid IP 206 | total.Add(total, big.NewInt(1)) 207 | } 208 | 209 | // If total is greater than Uint64, Uint64() will return 0 210 | // We want to display MaxInt if the value overflows what int can contain 211 | if total.IsInt64() && total.Uint64() <= uint64(math.MaxInt) { 212 | return int(total.Uint64()) //nolint:gosec // [G115] conversion is safe with check beforehand 213 | } 214 | return math.MaxInt 215 | } 216 | 217 | // AddressStrParses checks to see that the addresss string is one of 218 | // a valid single IP address, a hyphonated IP range, or a Prefix. 219 | func AddressStrParses(addressStr string) bool { 220 | _, err := AddressToIPSet(addressStr) 221 | return err == nil 222 | } 223 | -------------------------------------------------------------------------------- /internal/poolutil/poolutil_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package poolutil_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestPoolutil(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Poolutil Suite") 29 | } 30 | -------------------------------------------------------------------------------- /internal/webhooks/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package webhooks implements webhooks. 18 | package webhooks 19 | -------------------------------------------------------------------------------- /internal/webhooks/util_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhooks 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | ctrl "sigs.k8s.io/controller-runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/webhook" 27 | ) 28 | 29 | var ctx = ctrl.SetupSignalHandler() 30 | 31 | // customDefaulterValidator interface is for objects that define both custom defaulting 32 | // and custom validating webhooks. 33 | type customDefaulterValidator interface { 34 | webhook.CustomDefaulter 35 | webhook.CustomValidator 36 | } 37 | 38 | // customDefaultValidateTest returns a new testing function to be used in tests to 39 | // make sure custom defaulting webhooks also pass validation tests on create, 40 | // update and delete. 41 | func customDefaultValidateTest(ctx context.Context, obj runtime.Object, webhook customDefaulterValidator) func(*testing.T) { 42 | return func(t *testing.T) { 43 | t.Helper() 44 | 45 | t.Run("validate-on-create", func(t *testing.T) { 46 | g := gomega.NewWithT(t) 47 | createCopy := obj.DeepCopyObject() 48 | g.Expect(webhook.Default(ctx, createCopy)).To(gomega.Succeed()) 49 | g.Expect(webhook.ValidateCreate(ctx, createCopy)).Error().To(gomega.Succeed(), "should pass validation") 50 | }) 51 | t.Run("validate-on-update", func(t *testing.T) { 52 | g := gomega.NewWithT(t) 53 | updateCopy := obj.DeepCopyObject() 54 | updatedCopy := obj.DeepCopyObject() 55 | g.Expect(webhook.Default(ctx, updatedCopy)).To(gomega.Succeed()) 56 | g.Expect(webhook.Default(ctx, updateCopy)).To(gomega.Succeed()) 57 | g.Expect(webhook.ValidateUpdate(ctx, updateCopy, updatedCopy)).Error().To(gomega.Succeed(), "should pass validation") 58 | }) 59 | t.Run("validate-on-delete", func(t *testing.T) { 60 | g := gomega.NewWithT(t) 61 | deleteCopy := obj.DeepCopyObject() 62 | g.Expect(webhook.Default(ctx, deleteCopy)).To(gomega.Succeed()) 63 | g.Expect(webhook.ValidateDelete(ctx, deleteCopy)).Error().To(gomega.Succeed(), "should pass validation") 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package main is the main package of the Cluster API In-Cluster IPAM Provider. 18 | package main 19 | 20 | import ( 21 | "flag" 22 | "os" 23 | 24 | //+kubebuilder:scaffold:imports 25 | "k8s.io/apimachinery/pkg/runtime" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/pkg/version" 29 | _ "k8s.io/client-go/plugin/pkg/client/auth" 30 | "k8s.io/klog/v2" 31 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 32 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 33 | ctrl "sigs.k8s.io/controller-runtime" 34 | "sigs.k8s.io/controller-runtime/pkg/cache" 35 | "sigs.k8s.io/controller-runtime/pkg/healthz" 36 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 37 | "sigs.k8s.io/controller-runtime/pkg/webhook" 38 | 39 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha1" 40 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 41 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/controllers" 42 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/index" 43 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/internal/webhooks" 44 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/pkg/ipamutil" 45 | ) 46 | 47 | var ( 48 | scheme = runtime.NewScheme() 49 | setupLog = ctrl.Log.WithName("setup") 50 | ) 51 | 52 | func init() { 53 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 54 | utilruntime.Must(ipamv1.AddToScheme(scheme)) 55 | utilruntime.Must(clusterv1.AddToScheme(scheme)) 56 | 57 | utilruntime.Must(v1alpha1.AddToScheme(scheme)) 58 | utilruntime.Must(v1alpha2.AddToScheme(scheme)) 59 | //+kubebuilder:scaffold:scheme 60 | } 61 | 62 | func main() { 63 | var ( 64 | metricsAddr string 65 | enableLeaderElection bool 66 | probeAddr string 67 | watchNamespace string 68 | watchFilter string 69 | webhookPort int 70 | webhookCertDir string 71 | webhookCertName string 72 | webhookKeyName string 73 | ) 74 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 75 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 76 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 77 | "Enable leader election for controller manager. "+ 78 | "Enabling this will ensure there is only one active controller manager.") 79 | flag.StringVar(&watchNamespace, "namespace", "", 80 | "Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.") 81 | flag.StringVar(&watchFilter, "watch-filter", "", "") 82 | flag.IntVar(&webhookPort, "webhook-port", 9443, "Webhook Server port.") 83 | flag.StringVar(&webhookCertDir, "webhook-cert-dir", "/tmp/k8s-webhook-server/serving-certs/", "Webhook cert dir.") 84 | flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "Webhook cert name.") 85 | flag.StringVar(&webhookKeyName, "webhook-key-name", "tls.key", "Webhook key name.") 86 | flag.Parse() 87 | 88 | // klog.Background will automatically use the right logger. 89 | ctrl.SetLogger(klog.Background()) 90 | 91 | ctx := ctrl.SetupSignalHandler() 92 | 93 | opts := ctrl.Options{ 94 | Metrics: metricsserver.Options{ 95 | BindAddress: metricsAddr, 96 | }, 97 | Scheme: scheme, 98 | HealthProbeBindAddress: probeAddr, 99 | LeaderElection: enableLeaderElection, 100 | LeaderElectionID: "7bb7acb4.ipam.cluster.x-k8s.io", 101 | WebhookServer: webhook.NewServer( 102 | webhook.Options{ 103 | Port: webhookPort, 104 | CertDir: webhookCertDir, 105 | CertName: webhookCertName, 106 | KeyName: webhookKeyName, 107 | }, 108 | ), 109 | } 110 | 111 | if watchNamespace != "" { 112 | opts.Cache = cache.Options{ 113 | DefaultNamespaces: map[string]cache.Config{ 114 | watchNamespace: {}, 115 | }, 116 | } 117 | } 118 | 119 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts) 120 | if err != nil { 121 | setupLog.Error(err, "unable to start manager") 122 | os.Exit(1) 123 | } 124 | 125 | if err = index.SetupIndexes(ctx, mgr); err != nil { 126 | setupLog.Error(err, "failed to setup indexes") 127 | os.Exit(1) 128 | } 129 | 130 | if err = (&ipamutil.ClaimReconciler{ 131 | Client: mgr.GetClient(), 132 | Scheme: mgr.GetScheme(), 133 | WatchFilterValue: watchFilter, 134 | Adapter: &controllers.InClusterProviderAdapter{ 135 | Client: mgr.GetClient(), 136 | WatchFilterValue: watchFilter, 137 | }, 138 | }).SetupWithManager(ctx, mgr); err != nil { 139 | setupLog.Error(err, "unable to create controller", "controller", "IPAddressClaim") 140 | os.Exit(1) 141 | } 142 | if err = (&controllers.InClusterIPPoolReconciler{ 143 | Client: mgr.GetClient(), 144 | Scheme: mgr.GetScheme(), 145 | }).SetupWithManager(ctx, mgr); err != nil { 146 | setupLog.Error(err, "unable to create controller", "controller", "InClusterIPPoolReconciler") 147 | os.Exit(1) 148 | } 149 | if err = (&controllers.GlobalInClusterIPPoolReconciler{ 150 | Client: mgr.GetClient(), 151 | Scheme: mgr.GetScheme(), 152 | }).SetupWithManager(ctx, mgr); err != nil { 153 | setupLog.Error(err, "unable to create controller", "controller", "GlobalInClusterIPPoolReconciler") 154 | os.Exit(1) 155 | } 156 | 157 | if webhookPort != 0 { 158 | if err := (&webhooks.InClusterIPPool{Client: mgr.GetClient()}).SetupWebhookWithManager(mgr); err != nil { 159 | setupLog.Error(err, "unable to create webhook", "webhook", "InClusterIPPool") 160 | os.Exit(1) 161 | } 162 | } 163 | //+kubebuilder:scaffold:builder 164 | 165 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 166 | setupLog.Error(err, "unable to set up health check") 167 | os.Exit(1) 168 | } 169 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 170 | setupLog.Error(err, "unable to set up ready check") 171 | os.Exit(1) 172 | } 173 | 174 | setupLog.Info("starting manager", "version", version.Get().String()) 175 | if err := mgr.Start(ctx); err != nil { 176 | setupLog.Error(err, "problem running manager") 177 | os.Exit(1) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3 2 | kind: Metadata 3 | releaseSeries: 4 | - major: 0 5 | minor: 1 6 | contract: v1beta1 7 | - major: 1 8 | minor: 0 9 | contract: v1beta1 10 | -------------------------------------------------------------------------------- /pkg/ipamutil/address.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package ipamutil implements various utility functions to assist with CAPI IPAM implementation. 18 | package ipamutil 19 | 20 | import ( 21 | "github.com/pkg/errors" 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "k8s.io/utils/ptr" 26 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 29 | ) 30 | 31 | // NewIPAddress creates a new ipamv1.IPAddress with references to a pool and claim. 32 | func NewIPAddress(claim *ipamv1.IPAddressClaim, pool client.Object) ipamv1.IPAddress { 33 | poolGVK := pool.GetObjectKind().GroupVersionKind() 34 | 35 | return ipamv1.IPAddress{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Name: claim.Name, 38 | Namespace: claim.Namespace, 39 | }, 40 | Spec: ipamv1.IPAddressSpec{ 41 | ClaimRef: corev1.LocalObjectReference{ 42 | Name: claim.Name, 43 | }, 44 | PoolRef: corev1.TypedLocalObjectReference{ 45 | APIGroup: &poolGVK.Group, 46 | Kind: poolGVK.Kind, 47 | Name: pool.GetName(), 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | // ensureIPAddressOwnerReferences ensures that an IPAddress has the 54 | // IPAddressClaim and IPPool as an OwnerReference. 55 | func ensureIPAddressOwnerReferences(scheme *runtime.Scheme, address *ipamv1.IPAddress, claim *ipamv1.IPAddressClaim, pool client.Object) error { 56 | if err := controllerutil.SetControllerReference(claim, address, scheme); err != nil { 57 | if _, ok := err.(*controllerutil.AlreadyOwnedError); !ok { 58 | return errors.Wrap(err, "Failed to update address's claim owner reference") 59 | } 60 | } 61 | 62 | if err := controllerutil.SetOwnerReference(pool, address, scheme); err != nil { 63 | return errors.Wrap(err, "Failed to update address's pool owner reference") 64 | } 65 | 66 | var poolRefIdx int 67 | poolGVK := pool.GetObjectKind().GroupVersionKind() 68 | for i, ownerRef := range address.GetOwnerReferences() { 69 | if ownerRef.APIVersion == poolGVK.GroupVersion().String() && 70 | ownerRef.Kind == poolGVK.Kind && 71 | ownerRef.Name == pool.GetName() { 72 | poolRefIdx = i 73 | } 74 | } 75 | 76 | address.OwnerReferences[poolRefIdx].Controller = ptr.To(false) 77 | address.OwnerReferences[poolRefIdx].BlockOwnerDeletion = ptr.To(true) 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/predicates/references.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package predicates implements predicates to filter events during ipamv1.IPAddressClaim processing. 18 | package predicates 19 | 20 | import ( 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | "sigs.k8s.io/controller-runtime/pkg/predicate" 26 | ) 27 | 28 | func processIfClaimReferencesPoolKind(gk metav1.GroupKind, obj client.Object) bool { 29 | var claim *ipamv1.IPAddressClaim 30 | var ok bool 31 | if claim, ok = obj.(*ipamv1.IPAddressClaim); !ok { 32 | return false 33 | } 34 | 35 | if claim.Spec.PoolRef.Kind != gk.Kind || claim.Spec.PoolRef.APIGroup == nil || *claim.Spec.PoolRef.APIGroup != gk.Group { 36 | return false 37 | } 38 | 39 | return true 40 | } 41 | 42 | // ClaimReferencesPoolKind is a predicate that ensures an ipamv1.IPAddressClaim references a specified pool kind. 43 | func ClaimReferencesPoolKind(gk metav1.GroupKind) predicate.Funcs { 44 | return predicate.Funcs{ 45 | CreateFunc: func(e event.CreateEvent) bool { 46 | return processIfClaimReferencesPoolKind(gk, e.Object) 47 | }, 48 | DeleteFunc: func(e event.DeleteEvent) bool { 49 | return processIfClaimReferencesPoolKind(gk, e.Object) 50 | }, 51 | UpdateFunc: func(e event.UpdateEvent) bool { 52 | return processIfClaimReferencesPoolKind(gk, e.ObjectNew) 53 | }, 54 | GenericFunc: func(e event.GenericEvent) bool { 55 | return processIfClaimReferencesPoolKind(gk, e.Object) 56 | }, 57 | } 58 | } 59 | 60 | func processIfAddressReferencesPoolKind(gk metav1.GroupKind, obj client.Object) bool { 61 | var addr *ipamv1.IPAddress 62 | var ok bool 63 | if addr, ok = obj.(*ipamv1.IPAddress); !ok { 64 | return false 65 | } 66 | 67 | if addr.Spec.PoolRef.Kind != gk.Kind || addr.Spec.PoolRef.APIGroup == nil || *addr.Spec.PoolRef.APIGroup != gk.Group { 68 | return false 69 | } 70 | 71 | return true 72 | } 73 | 74 | // AddressReferencesPoolKind is a predicate that ensures an ipamv1.IPAddress references a specified pool kind. 75 | func AddressReferencesPoolKind(gk metav1.GroupKind) predicate.Funcs { 76 | return predicate.Funcs{ 77 | CreateFunc: func(e event.CreateEvent) bool { 78 | return processIfAddressReferencesPoolKind(gk, e.Object) 79 | }, 80 | DeleteFunc: func(e event.DeleteEvent) bool { 81 | return processIfAddressReferencesPoolKind(gk, e.Object) 82 | }, 83 | UpdateFunc: func(e event.UpdateEvent) bool { 84 | return processIfAddressReferencesPoolKind(gk, e.ObjectNew) 85 | }, 86 | GenericFunc: func(e event.GenericEvent) bool { 87 | return processIfAddressReferencesPoolKind(gk, e.Object) 88 | }, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pkg/predicates/references_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package predicates 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/gomega" 23 | corev1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/utils/ptr" 26 | ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 27 | "sigs.k8s.io/controller-runtime/pkg/event" 28 | ) 29 | 30 | func TestClaimReferencesPoolKind(t *testing.T) { 31 | tests := []struct { 32 | name string 33 | ref corev1.TypedLocalObjectReference 34 | result bool 35 | }{ 36 | { 37 | name: "true for valid reference", 38 | ref: corev1.TypedLocalObjectReference{ 39 | APIGroup: ptr.To("ipam.cluster.x-k8s.io"), 40 | Kind: "InClusterIPPool", 41 | }, 42 | result: true, 43 | }, 44 | { 45 | name: "false when kind does not match", 46 | ref: corev1.TypedLocalObjectReference{ 47 | APIGroup: ptr.To("ipam.cluster.x-k8s.io"), 48 | Kind: "OutOfClusterIPPool", 49 | }, 50 | result: false, 51 | }, 52 | { 53 | name: "false when no group is set", 54 | ref: corev1.TypedLocalObjectReference{ 55 | Kind: "InClusterIPPool", 56 | }, 57 | result: false, 58 | }, 59 | { 60 | name: "false when group does not match", 61 | ref: corev1.TypedLocalObjectReference{ 62 | APIGroup: ptr.To("cluster.x-k8s.io"), 63 | Kind: "InClusterIPPool", 64 | }, 65 | result: false, 66 | }, 67 | } 68 | 69 | gk := metav1.GroupKind{ 70 | Group: "ipam.cluster.x-k8s.io", 71 | Kind: "InClusterIPPool", 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | g := NewWithT(t) 77 | claim := &ipamv1.IPAddressClaim{ 78 | Spec: ipamv1.IPAddressClaimSpec{ 79 | PoolRef: tt.ref, 80 | }, 81 | } 82 | funcs := ClaimReferencesPoolKind(gk) 83 | g.Expect(funcs.CreateFunc(event.CreateEvent{Object: claim})).To(Equal(tt.result)) 84 | g.Expect(funcs.DeleteFunc(event.DeleteEvent{Object: claim})).To(Equal(tt.result)) 85 | g.Expect(funcs.GenericFunc(event.GenericEvent{Object: claim})).To(Equal(tt.result)) 86 | g.Expect(funcs.UpdateFunc(event.UpdateEvent{ObjectNew: claim})).To(Equal(tt.result)) 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package types contains shared types that lack a better home. 18 | package types 19 | 20 | import ( 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | 23 | "sigs.k8s.io/cluster-api-ipam-provider-in-cluster/api/v1alpha2" 24 | ) 25 | 26 | // GenericInClusterPool is a common interface for InClusterIPPool and GlobalInClusterIPPool. 27 | type GenericInClusterPool interface { 28 | client.Object 29 | PoolSpec() *v1alpha2.InClusterIPPoolSpec 30 | PoolStatus() *v1alpha2.InClusterIPPoolStatus 31 | } 32 | -------------------------------------------------------------------------------- /tilt-provider.yaml: -------------------------------------------------------------------------------- 1 | name: "ipam-in-cluster" 2 | config: 3 | image: "gcr.io/k8s-staging-capi-ipam-ic/cluster-api-ipam-in-cluster-controller" 4 | live_reload_deps: 5 | - main.go 6 | - go.mod 7 | - go.sum 8 | - api 9 | - controllers 10 | - internal 11 | - pkg 12 | label: "IPAM-in-cluster" 13 | manager-name: "capi-ipam-in-cluster-controller-manager" 14 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package version provides version information. 18 | package version 19 | 20 | import ( 21 | "fmt" 22 | "runtime" 23 | ) 24 | 25 | var ( 26 | gitMajor string // major version, always numeric 27 | gitMinor string // minor version, numeric possibly followed by "+" 28 | gitVersion string // semantic version, derived by build scripts 29 | gitCommit string // sha1 from git, output of $(git rev-parse HEAD) 30 | gitTreeState string // state of git tree, either "clean" or "dirty" 31 | buildDate string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') 32 | ) 33 | 34 | // Info exposes information about the version used for the current running code. 35 | type Info struct { 36 | Major string `json:"major,omitempty"` 37 | Minor string `json:"minor,omitempty"` 38 | GitVersion string `json:"gitVersion,omitempty"` 39 | GitCommit string `json:"gitCommit,omitempty"` 40 | GitTreeState string `json:"gitTreeState,omitempty"` 41 | BuildDate string `json:"buildDate,omitempty"` 42 | GoVersion string `json:"goVersion,omitempty"` 43 | Compiler string `json:"compiler,omitempty"` 44 | Platform string `json:"platform,omitempty"` 45 | } 46 | 47 | // Get returns an Info object with all the information about the current running code. 48 | func Get() Info { 49 | return Info{ 50 | Major: gitMajor, 51 | Minor: gitMinor, 52 | GitVersion: gitVersion, 53 | GitCommit: gitCommit, 54 | GitTreeState: gitTreeState, 55 | BuildDate: buildDate, 56 | GoVersion: runtime.Version(), 57 | Compiler: runtime.Compiler, 58 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 59 | } 60 | } 61 | 62 | // String returns info as a human-friendly version string. 63 | func (info Info) String() string { 64 | return info.GitVersion 65 | } 66 | --------------------------------------------------------------------------------