├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature-request.yml ├── dependabot.yaml ├── env ├── stale.yaml └── workflows │ ├── build-image.yaml │ ├── codeql.yml │ ├── e2e-tests.yaml │ ├── govuln.yaml │ ├── integration-test.yaml │ ├── lint.yaml │ └── unit-test.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── ParentPrefixSelectorGuide.md ├── README.md ├── api └── v1 │ ├── groupversion_info.go │ ├── ipaddress_types.go │ ├── ipaddressclaim_types.go │ ├── iprange_types.go │ ├── iprangeclaim_types.go │ ├── prefix_types.go │ ├── prefixclaim_types.go │ ├── shared_conditions.go │ └── zz_generated.deepcopy.go ├── cmd └── main.go ├── config ├── crd │ ├── bases │ │ ├── netbox.dev_ipaddressclaims.yaml │ │ ├── netbox.dev_ipaddresses.yaml │ │ ├── netbox.dev_iprangeclaims.yaml │ │ ├── netbox.dev_ipranges.yaml │ │ ├── netbox.dev_prefixclaims.yaml │ │ └── netbox.dev_prefixes.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ ├── kustomization.yaml │ ├── manager_metrics_patch.yaml │ └── metrics_service.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── network-policy │ ├── allow-metrics-traffic.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── ipaddress_editor_role.yaml │ ├── ipaddress_viewer_role.yaml │ ├── ipaddressclaim_editor_role.yaml │ ├── ipaddressclaim_viewer_role.yaml │ ├── iprange_editor_role.yaml │ ├── iprange_viewer_role.yaml │ ├── iprangeclaim_editor_role.yaml │ ├── iprangeclaim_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── metrics_auth_role.yaml │ ├── metrics_auth_role_binding.yaml │ ├── metrics_reader_role.yaml │ ├── prefix_editor_role.yaml │ ├── prefix_viewer_role.yaml │ ├── prefixclaim_editor_role.yaml │ ├── prefixclaim_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── kustomization.yaml │ ├── netbox_v1_ipaddress.yaml │ ├── netbox_v1_ipaddressclaim.yaml │ ├── netbox_v1_iprange.yaml │ ├── netbox_v1_iprangeclaim.yaml │ ├── netbox_v1_prefix.yaml │ ├── netbox_v1_prefixclaim.yaml │ ├── netbox_v1_prefixclaim_parentprefixselector.yaml │ └── netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml ├── docs ├── examples │ ├── README.md │ ├── example1-getting-started │ │ ├── README.md │ │ ├── dynamic_prefixclaim-large-font.drawio.svg │ │ ├── prefixclaim-dynamic.drawio.svg │ │ ├── prefixclaim-dynamic.yaml │ │ ├── prefixclaim-simple.drawio.svg │ │ ├── prefixclaim-simple.yaml │ │ └── simple_prefixclaim-large-font.drawio.svg │ ├── example2-load-balancer-ip │ │ ├── README.md │ │ ├── load-balancer-ip-pool-netbox-large-font.drawio.svg │ │ ├── load-balancer-ip-pool-netbox.yaml │ │ ├── metallb-ipaddresspool-netbox.drawio.svg │ │ ├── prepare-demo-env.sh │ │ ├── sample-deployment.yaml │ │ └── zurich-pool.yaml │ ├── example3-restoration │ │ ├── README.md │ │ ├── prefixclaim-restore1.yaml │ │ ├── prefixclaim-restore2.yaml │ │ ├── prefixclaim-restore3.yaml │ │ ├── restoration-large-font.drawio.png │ │ ├── restoration-large-font.drawio.svg │ │ └── restoration.drawio.svg │ ├── example4-exhaustion │ │ ├── README.md │ │ ├── exhaustion-1-starting-point.drawio.svg │ │ ├── exhaustion-2-prefix-exhausted.drawio.svg │ │ ├── exhaustion-3-after-fix.drawio.svg │ │ └── prefixclaim-exhaustion.yaml │ └── example5-multicluster │ │ ├── README.md │ │ ├── cluster-cfg.yaml │ │ ├── create-kind-clusters.sh │ │ ├── demo-setup.drawio.svg │ │ ├── kustomization.yaml │ │ ├── london-pools.yaml │ │ ├── multicluster.drawio.svg │ │ ├── netbox-l2advertisement.yaml │ │ ├── netbox-svc.yaml │ │ ├── prepare-demo-env.sh │ │ └── zurich-pools.yaml ├── netbox-operator-high-level-architecture.drawio.svg ├── operational-manual.md └── prefixclaim-sample-with-netbox-running-in-cluster.drawio.svg ├── gen └── mock_interfaces │ └── netbox_mocks.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal └── controller │ ├── expected_netboxmock_calls_test.go │ ├── interfaces.go │ ├── ipaddress_controller.go │ ├── ipaddress_controller_test.go │ ├── ipaddressclaim_controller.go │ ├── ipaddressclaim_controller_test.go │ ├── ipaddressclaim_helpers.go │ ├── iprange_controller.go │ ├── iprange_controller_test.go │ ├── iprangeclaim_controller.go │ ├── iprangeclaim_controller_test.go │ ├── iprangeclaim_helpers.go │ ├── netbox_testdata_test.go │ ├── prefix_controller.go │ ├── prefix_controller_test.go │ ├── prefixclaim_controller.go │ ├── prefixclaim_controller_test.go │ ├── prefixclaim_helpers.go │ ├── prefixclaim_helpers_test.go │ ├── suite_test.go │ └── utils.go ├── kind ├── deploy-netbox.sh ├── kustomization.yaml ├── load-data-job.yaml ├── load-data-job │ ├── README.md │ ├── dockerfile.orig │ ├── load-data.orig.sh │ ├── local-data-setup.sql │ └── main.py ├── local-env.sh ├── namespace.yaml ├── netbox-db.yaml └── secret.yaml ├── kustomization.yaml ├── pkg ├── config │ ├── config.go │ └── config_test.go └── netbox │ ├── api │ ├── assertion.go │ ├── client.go │ ├── constant.go │ ├── errors.go │ ├── helper.go │ ├── ip_address.go │ ├── ip_address_claim.go │ ├── ip_address_claim_test.go │ ├── ip_address_test.go │ ├── ip_range.go │ ├── ip_range_claim.go │ ├── ip_range_claim_test.go │ ├── ip_range_test.go │ ├── prefix.go │ ├── prefix_claim.go │ ├── prefix_claim_test.go │ ├── prefix_test.go │ ├── site.go │ ├── site_test.go │ ├── tenancy.go │ └── tenancy_test.go │ ├── interfaces │ └── netbox.go │ ├── models │ └── ipam.go │ └── utils │ └── error.go ├── tests └── e2e │ ├── Prefix │ ├── IPv4 │ │ ├── prefixclaim-ipv4-invalid-parentprefixselector │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_prefixclaim.yaml │ │ ├── prefixclaim-ipv4-parentprefix-apply-update │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_prefixclaim-update.yaml │ │ │ └── netbox_v1_prefixclaim.yaml │ │ ├── prefixclaim-ipv4-parentprefix-restore │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_prefixclaim_1.yaml │ │ │ └── netbox_v1_prefixclaim_2.yaml │ │ ├── prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_prefixclaim.yaml │ │ ├── prefixclaim-ipv4-parentprefixselector-restore │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_prefixclaim_1.yaml │ │ │ └── netbox_v1_prefixclaim_2.yaml │ │ ├── prefixclaim-ipv4-parentprefixselector │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_prefixclaim.yaml │ │ └── prefixclaim-ipv4-prefixexhausted │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_prefixclaim_1.yaml │ │ │ ├── netbox_v1_prefixclaim_2.yaml │ │ │ └── netbox_v1_prefixclaim_3.yaml │ └── IPv6 │ │ └── prefixclaim-ipv6-apply-update │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_prefixclaim-update.yaml │ │ └── netbox_v1_prefixclaim.yaml │ ├── README.md │ ├── ipaddress │ ├── ipv4 │ │ ├── ipaddressclaim-ipv4-apply-update │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_ipaddressclaim-update.yaml │ │ │ └── netbox_v1_ipaddressclaim.yaml │ │ ├── ipaddressclaim-ipv4-prefixexhausted │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_ipaddressclaim_1.yaml │ │ │ ├── netbox_v1_ipaddressclaim_2.yaml │ │ │ └── netbox_v1_ipaddressclaim_3.yaml │ │ └── ipaddressclaim-ipv4-restore │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_ipaddressclaim_1.yaml │ │ │ └── netbox_v1_ipaddressclaim_2.yaml │ └── ipv6 │ │ ├── ipaddressclaim-ipv6-apply-update │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_ipaddressclaim-update.yaml │ │ └── netbox_v1_ipaddressclaim.yaml │ │ ├── ipaddressclaim-ipv6-prefixexhausted │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_ipaddressclaim_1.yaml │ │ ├── netbox_v1_ipaddressclaim_2.yaml │ │ └── netbox_v1_ipaddressclaim_3.yaml │ │ └── ipaddressclaim-ipv6-restore │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_ipaddressclaim_1.yaml │ │ └── netbox_v1_ipaddressclaim_2.yaml │ ├── iprange │ ├── ipv4 │ │ ├── iprangeclaim-ipv4-apply-update │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_iprangeclaim-update.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-cidr │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-customfieldnotexisting │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-customfieldwrongdatatype │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-parentprefix │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-size │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-invalid-tenant │ │ │ ├── chainsaw-test.yaml │ │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv4-prefixexhausted │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_iprangeclaim_1.yaml │ │ │ ├── netbox_v1_iprangeclaim_2.yaml │ │ │ └── netbox_v1_iprangeclaim_3.yaml │ │ └── iprangeclaim-ipv4-restore │ │ │ ├── chainsaw-test.yaml │ │ │ ├── netbox_v1_iprangeclaim_1.yaml │ │ │ └── netbox_v1_iprangeclaim_2.yaml │ └── ipv6 │ │ ├── iprangeclaim-ipv6-apply-update │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_iprangeclaim-update.yaml │ │ └── netbox_v1_iprangeclaim.yaml │ │ ├── iprangeclaim-ipv6-prefixexhausted │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_iprangeclaim_1.yaml │ │ ├── netbox_v1_iprangeclaim_2.yaml │ │ └── netbox_v1_iprangeclaim_3.yaml │ │ └── iprangeclaim-ipv6-restore │ │ ├── chainsaw-test.yaml │ │ ├── netbox_v1_iprangeclaim_1.yaml │ │ └── netbox_v1_iprangeclaim_2.yaml │ └── kind-config.yaml └── tools └── .golangci.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | description: Report a bug encountered while operating netbox operator 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | id: confirmations 8 | attributes: 9 | label: Bug report criteria 10 | description: Please confirm this bug report meets the following criteria. 11 | options: 12 | - label: This bug report is not security related, security issues should be disclosed privately via netbox operator maintainers. 13 | - label: Existing open issues have been checked and this is not a duplicate. 14 | 15 | - type: markdown 16 | attributes: 17 | value: | 18 | Please fill the form below and provide as much information as possible. 19 | Not doing so may result in your bug not being addressed in a timely manner. 20 | 21 | - type: textarea 22 | id: problem 23 | attributes: 24 | label: What happened? 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: expected 30 | attributes: 31 | label: What did you expect to happen? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: repro 37 | attributes: 38 | label: How can we reproduce it (as minimally and precisely as possible)? 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | id: netboxOperatorVersion 44 | attributes: 45 | label: Netbox operator version 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: config 51 | attributes: 52 | label: Netbox operator configuration (command line flags or environment variables) 53 | description: Please copy and paste your configuration here. 54 | render: Shell 55 | 56 | - type: textarea 57 | id: logs 58 | attributes: 59 | label: Relevant log output 60 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 61 | render: Shell 62 | 63 | - type: textarea 64 | id: additional 65 | attributes: 66 | label: Anything else we need to know? 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Question 5 | url: https://github.com/netbox-community/netbox-operator/discussions 6 | about: Questions related to Netbox Operator 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | description: Provide ideas for a new feature 4 | labels: ["feature"] 5 | body: 6 | - type: textarea 7 | id: feature 8 | attributes: 9 | label: What would you like to be added? 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | id: rationale 15 | attributes: 16 | label: Why is this needed? 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | 9 | - package-ecosystem: gomod 10 | directory: / 11 | schedule: 12 | interval: weekly 13 | allow: 14 | - dependency-type: all 15 | 16 | - package-ecosystem: docker 17 | directory: / 18 | schedule: 19 | interval: weekly 20 | -------------------------------------------------------------------------------- /.github/env: -------------------------------------------------------------------------------- 1 | golang-version=1.24 2 | kind-version=v0.25.0 3 | kind-image=kindest/node:v1.32.0 4 | -------------------------------------------------------------------------------- /.github/stale.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configuration for probot-stale - https://github.com/probot/stale 3 | 4 | # Number of days of inactivity before an Issue or Pull Request becomes stale 5 | daysUntilStale: 90 6 | 7 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 8 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 9 | daysUntilClose: 21 10 | 11 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 12 | onlyLabels: [] 13 | 14 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 15 | exemptLabels: 16 | - "bug" 17 | 18 | # Set to true to ignore issues in a project (defaults to false) 19 | exemptProjects: false 20 | 21 | # Set to true to ignore issues in a milestone (defaults to false) 22 | exemptMilestones: false 23 | 24 | # Set to true to ignore issues with an assignee (defaults to false) 25 | exemptAssignees: false 26 | 27 | # Label to use when marking as stale 28 | staleLabel: stale 29 | 30 | # Comment to post when marking as stale. Set to `false` to disable 31 | markComment: This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions. 32 | # Comment to post when removing the stale label. 33 | # unmarkComment: > 34 | # Your comment here. 35 | 36 | # Comment to post when closing a stale Issue or Pull Request. 37 | # closeComment: > 38 | # Your comment here. 39 | 40 | # Limit the number of actions per hour, from 1-30. Default is 30 41 | limitPerRun: 30 42 | 43 | # Limit to only `issues` or `pulls` 44 | # only: issues 45 | 46 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 47 | # pulls: 48 | # daysUntilStale: 30 49 | # markComment: > 50 | # This pull request has been automatically marked as stale because it has not had 51 | # recent activity. It will be closed if no further activity occurs. Thank you 52 | # for your contributions. 53 | 54 | # issues: 55 | # exemptLabels: 56 | # - confirmed 57 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build docker image 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | env: 11 | IMAGE_NAME: netbox-operator 12 | DOCKER_METADATA_PR_HEAD_SHA: true 13 | jobs: 14 | push: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | packages: write 18 | contents: read 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v3 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@v3 25 | - name: Login to GitHub Container Registry 26 | if: github.event_name != 'pull_request' 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Generate docker image tag 33 | id: meta 34 | uses: docker/metadata-action@v5.7.0 35 | with: 36 | images: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} 37 | tags: | 38 | # (for commits on the main branch only) generate a tag named `latest` 39 | type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} 40 | # (for all commits) generate a tag named sha-[short sha value] 41 | type=sha,enable=true 42 | # (for tagged commits only) generate tags identical to the git tag version, with and without the leading v 43 | type=semver,pattern={{raw}},enable=${{startsWith(github.ref, 'refs/tags/v')}} 44 | type=semver,pattern={{version}},enable=${{startsWith(github.ref, 'refs/tags/v')}} 45 | - name: Build and push 46 | uses: docker/build-push-action@v6 47 | with: 48 | platforms: linux/amd64,linux/arm64 49 | # we push only if the pipeline is run against the commits on main branch or a tag 50 | push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} 51 | tags: ${{ steps.meta.outputs.tags }} 52 | labels: ${{ steps.meta.outputs.labels }} 53 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yaml: -------------------------------------------------------------------------------- 1 | # Modified from https://github.com/prometheus-operator/prometheus-operator/blob/main/.github/workflows/e2e-feature-gated.yaml 2 | name: e2e-tests 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | env: 11 | NETBOX_HOST: demo.netbox.dev 12 | AUTH_TOKEN: 0123456789abcdef0123456789abcdef01234567 13 | POD_NAMESPACE: default 14 | HTTPS_ENABLE: true 15 | NETBOX_RESTORATION_HASH_FIELD_NAME: netboxOperatorRestorationHash 16 | jobs: 17 | e2e-tests-3-7-8: 18 | name: Against netbox version 3.7.8 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 22 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 23 | with: 24 | go-version: 1.24.3 25 | - name: Import environment variables from file 26 | run: | 27 | cat ".github/env" >> "$GITHUB_ENV" 28 | - name: Run e2e tests 29 | run: | 30 | make test-e2e-3.7.8 31 | e2e-tests-4-0-11: 32 | name: Against netbox version 4.0.11 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 36 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 37 | with: 38 | go-version: 1.24.3 39 | - name: Import environment variables from file 40 | run: | 41 | cat ".github/env" >> "$GITHUB_ENV" 42 | - name: Run e2e tests 43 | run: | 44 | make test-e2e-4.0.11 45 | e2e-tests-4-1-8: 46 | name: Against netbox version 4.1.8 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 50 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 51 | with: 52 | go-version: 1.24.3 53 | - name: Import environment variables from file 54 | run: | 55 | cat ".github/env" >> "$GITHUB_ENV" 56 | - name: Run e2e tests 57 | run: | 58 | make test-e2e-4.1.8 59 | -------------------------------------------------------------------------------- /.github/workflows/govuln.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Go Vulnerability Checker 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | permissions: read-all 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 16 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 17 | with: 18 | go-version: 1.24.3 19 | - run: | 20 | set -euo pipefail 21 | 22 | make vulncheck 23 | -------------------------------------------------------------------------------- /.github/workflows/integration-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Integration Tests 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | permissions: 11 | contents: read 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 17 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 18 | with: 19 | go-version: 1.24.3 20 | - name: tests 21 | run: | 22 | go install github.com/onsi/ginkgo/v2/ginkgo 23 | make integration-test 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Static analysis 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | permissions: read-all 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 16 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 17 | with: 18 | go-version: 1.24.3 19 | - name: golangci-lint 20 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 21 | with: 22 | version: v2.1.2 23 | args: --config tools/.golangci.yaml 24 | - run: | 25 | set -euo pipefail 26 | 27 | make vet 28 | - run: | 29 | set -euo pipefail 30 | 31 | make lint 32 | - run: | 33 | set -euo pipefail 34 | 35 | make fmt 36 | 37 | DIFF=$(git status --porcelain) 38 | 39 | if [ -n "$DIFF" ]; then 40 | echo "These files were modified:" 41 | echo 42 | echo "$DIFF" 43 | echo 44 | exit 1 45 | fi 46 | - run: | 47 | set -euo pipefail 48 | 49 | DIFF=$(git status --porcelain) 50 | 51 | make generate manifests 52 | 53 | if [ -n "$DIFF" ]; then 54 | echo "These files were modified:" 55 | echo 56 | echo "$DIFF" 57 | echo 58 | echo "Please run make generate manifests and commit the changes." 59 | exit 1 60 | fi 61 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Unit tests 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | permissions: 11 | contents: read 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 17 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 18 | with: 19 | go-version: 1.24.3 20 | - name: tests 21 | run: | 22 | make test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | Dockerfile.cross 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Test/Intermediate files 14 | kind/load-data-job/load-data.sh 15 | kind/load-data-job/dockerfile 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # Kubernetes Generated files - skip generated files, except for vendored files 24 | !vendor/**/zz_generated.* 25 | 26 | # editor and IDE paraphernalia 27 | .idea 28 | .vscode 29 | *.swp 30 | *.swo 31 | *~ 32 | *.drawio.dtmp 33 | *drawio.svg.bkp 34 | .DS_Store 35 | 36 | # Temporary files 37 | tmp 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.24.3 AS builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY api/ api/ 17 | COPY internal/controller/ internal/controller/ 18 | COPY pkg/ pkg/ 19 | 20 | # Build 21 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 22 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 23 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 24 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go 26 | 27 | # Use distroless as minimal base image to package the manager binary 28 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 29 | FROM gcr.io/distroless/static:nonroot 30 | WORKDIR / 31 | COPY --from=builder /workspace/manager . 32 | USER 65532:65532 33 | 34 | ENTRYPOINT ["/manager"] 35 | -------------------------------------------------------------------------------- /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: netbox.dev 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: netbox-operator 9 | repo: github.com/netbox-community/netbox-operator 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | domain: netbox.dev 16 | kind: IpAddress 17 | path: github.com/netbox-community/netbox-operator/api/v1 18 | version: v1 19 | - api: 20 | crdVersion: v1 21 | namespaced: true 22 | controller: true 23 | domain: netbox.dev 24 | kind: IpAddressClaim 25 | path: github.com/netbox-community/netbox-operator/api/v1 26 | version: v1 27 | - api: 28 | crdVersion: v1 29 | namespaced: true 30 | controller: true 31 | domain: netbox.dev 32 | kind: Prefix 33 | path: github.com/netbox-community/netbox-operator/api/v1 34 | version: v1 35 | - api: 36 | crdVersion: v1 37 | namespaced: true 38 | controller: true 39 | domain: netbox.dev 40 | kind: PrefixClaim 41 | path: github.com/netbox-community/netbox-operator/api/v1 42 | version: v1 43 | - api: 44 | crdVersion: v1 45 | namespaced: true 46 | controller: true 47 | domain: netbox.dev 48 | kind: IpRangeClaim 49 | path: github.com/netbox-community/netbox-operator/api/v1 50 | version: v1 51 | - api: 52 | crdVersion: v1 53 | namespaced: true 54 | controller: true 55 | domain: netbox.dev 56 | kind: IpRange 57 | path: github.com/netbox-community/netbox-operator/api/v1 58 | version: v1 59 | version: "3" 60 | -------------------------------------------------------------------------------- /ParentPrefixSelectorGuide.md: -------------------------------------------------------------------------------- 1 | # A guide of `ParentPrefixSelector` in `PrefixClaim` 2 | 3 | There are 2 ways to make a Prefix claim: 4 | - provide a `parentPrefix` 5 | - provide a `parentPrefixSelector` 6 | 7 | In this documentation, we will focus on the `parentPrefixSelector` only. 8 | 9 | # CRD format 10 | 11 | The following is a sample of utilizing the `parentPrefixSelector`: 12 | 13 | ```bash 14 | apiVersion: netbox.dev/v1 15 | kind: PrefixClaim 16 | metadata: 17 | labels: 18 | app.kubernetes.io/name: netbox-operator 19 | app.kubernetes.io/managed-by: kustomize 20 | name: prefixclaim-customfields-sample 21 | spec: 22 | tenant: "MY_TENANT" 23 | site: "DM-Akron" 24 | description: "some description" 25 | comments: "your comments" 26 | preserveInNetbox: true 27 | prefixLength: "/31" 28 | parentPrefixSelector: 29 | tenant: "MY_TENANT" 30 | site: "DM-Buffalo" 31 | family: "IPv4" 32 | environment: "Production" 33 | poolName: "Pool 1" 34 | ``` 35 | 36 | The usage will be explained in the following sections. 37 | 38 | ## Notes on `Spec.tenant` and `Spec.site` 39 | 40 | Please provide the *name*, not the *slug* value 41 | 42 | ## `parentPrefixSelector` 43 | 44 | The `parentPrefixSelector` is a key-value map, where all the entries are of data type ``. 45 | 46 | The map contains a set of query conditions for selecting a set of prefixes that can be used as the parent prefix. 47 | 48 | The query conditions will be chained by the AND operator, and exact match of the keys and values will be performed. 49 | 50 | The fields that can be used as query conditions in the `parentPrefixSelector` are: 51 | - `tenant`, `site`, and `family` (in lowercase characters) 52 | - these fields are built-in fields from NetBox, so you do *not* need to create custom fields for them 53 | - please provide the *name*, not the *slug* value for `tenant` and `site` 54 | - if the entry for `tenant` and `site` fields is missing, it will *not* inherit from the Spec 55 | - custom fields 56 | - the data types tested and supported so far are `string`, `integer`, and `boolean` 57 | - for `boolean` type, please use `true` and `false` as the value 58 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the netbox v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=netbox.dev 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "netbox.dev", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1/shared_conditions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | 21 | var ConditionReadyFalseNewResource = metav1.Condition{ 22 | Type: "Ready", 23 | Status: "False", 24 | Reason: "NewResource", 25 | Message: "Pending Reconciliation", 26 | } 27 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/netbox.dev_ipaddresses.yaml 6 | - bases/netbox.dev_ipaddressclaims.yaml 7 | - bases/netbox.dev_prefixes.yaml 8 | - bases/netbox.dev_prefixclaims.yaml 9 | - bases/netbox.dev_iprangeclaims.yaml 10 | - bases/netbox.dev_ipranges.yaml 11 | #+kubebuilder:scaffold:crdkustomizeresource 12 | 13 | patches: 14 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 15 | # patches here are for enabling the conversion webhook for each CRD 16 | #- path: patches/webhook_in_ipaddresses.yaml 17 | #- path: patches/webhook_in_ipaddressclaims.yaml 18 | #- path: patches/webhook_in_iprangeclaims.yaml 19 | #- path: patches/webhook_in_ipranges.yaml 20 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 21 | 22 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 23 | # patches here are for enabling the CA injection for each CRD 24 | #- path: patches/cainjection_in_ipaddresses.yaml 25 | #- path: patches/cainjection_in_ipaddressclaims.yaml 26 | #- path: patches/cainjection_in_prefixes.yaml 27 | #- path: patches/cainjection_in_prefixclaims.yaml 28 | #- path: patches/cainjection_in_iprangeclaims.yaml 29 | #- path: patches/cainjection_in_ipranges.yaml 30 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 31 | 32 | # [WEBHOOK] To enable webhook, uncomment the following section 33 | # the following config is for teaching kustomize how to do kustomization for CRDs. 34 | 35 | #configurations: 36 | #- kustomizeconfig.yaml 37 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/default/manager_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch adds the args to allow exposing the metrics endpoint using HTTPS 2 | - op: add 3 | path: /spec/template/spec/containers/0/args/0 4 | value: --metrics-bind-address=:8443 5 | -------------------------------------------------------------------------------- /config/default/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: controller-manager-metrics-service 9 | namespace: system 10 | spec: 11 | ports: 12 | - name: https 13 | port: 8443 14 | protocol: TCP 15 | targetPort: 8443 16 | selector: 17 | control-plane: controller-manager 18 | app.kubernetes.io/part-of: netbox-operator 19 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | labels: 8 | control-plane: controller-manager 9 | app.kubernetes.io/name: deployment 10 | app.kubernetes.io/instance: controller-manager 11 | app.kubernetes.io/component: manager 12 | app.kubernetes.io/created-by: netbox-operator 13 | app.kubernetes.io/part-of: netbox-operator 14 | app.kubernetes.io/managed-by: kustomize 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 | app.kubernetes.io/part-of: netbox-operator 27 | spec: 28 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 29 | # according to the platforms which are supported by your solution. 30 | # It is considered best practice to support multiple architectures. You can 31 | # build your manager image using the makefile target docker-buildx. 32 | # affinity: 33 | # nodeAffinity: 34 | # requiredDuringSchedulingIgnoredDuringExecution: 35 | # nodeSelectorTerms: 36 | # - matchExpressions: 37 | # - key: kubernetes.io/arch 38 | # operator: In 39 | # values: 40 | # - amd64 41 | # - arm64 42 | # - ppc64le 43 | # - s390x 44 | # - key: kubernetes.io/os 45 | # operator: In 46 | # values: 47 | # - linux 48 | securityContext: 49 | runAsNonRoot: true 50 | # TODO(user): For common cases that do not require escalating privileges 51 | # it is recommended to ensure that all your Pods/Containers are restrictive. 52 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 53 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 54 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 55 | # seccompProfile: 56 | # type: RuntimeDefault 57 | containers: 58 | - command: 59 | - /manager 60 | args: 61 | - --leader-elect 62 | image: controller:latest 63 | name: manager 64 | env: 65 | - name: POD_NAMESPACE 66 | valueFrom: 67 | fieldRef: 68 | fieldPath: metadata.namespace 69 | securityContext: 70 | allowPrivilegeEscalation: false 71 | capabilities: 72 | drop: 73 | - "ALL" 74 | livenessProbe: 75 | httpGet: 76 | path: /healthz 77 | port: 8081 78 | initialDelaySeconds: 15 79 | periodSeconds: 20 80 | readinessProbe: 81 | httpGet: 82 | path: /readyz 83 | port: 8081 84 | initialDelaySeconds: 5 85 | periodSeconds: 10 86 | # TODO(user): Configure the resources accordingly based on the project requirements. 87 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 88 | resources: 89 | limits: 90 | cpu: 500m 91 | memory: 128Mi 92 | requests: 93 | cpu: 10m 94 | memory: 64Mi 95 | serviceAccountName: controller-manager 96 | terminationGracePeriodSeconds: 10 97 | -------------------------------------------------------------------------------- /config/network-policy/allow-metrics-traffic.yaml: -------------------------------------------------------------------------------- 1 | # This NetworkPolicy allows ingress traffic 2 | # with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those 3 | # namespaces are able to gathering data from the metrics endpoint. 4 | apiVersion: networking.k8s.io/v1 5 | kind: NetworkPolicy 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: netbox-operator 9 | app.kubernetes.io/managed-by: kustomize 10 | name: allow-metrics-traffic 11 | namespace: system 12 | spec: 13 | podSelector: 14 | matchLabels: 15 | control-plane: controller-manager 16 | policyTypes: 17 | - Ingress 18 | ingress: 19 | # This allows ingress traffic from any namespace with the label metrics: enabled 20 | - from: 21 | - namespaceSelector: 22 | matchLabels: 23 | metrics: enabled # Only from namespaces with this label 24 | ports: 25 | - port: 8443 26 | protocol: TCP 27 | -------------------------------------------------------------------------------- /config/network-policy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - allow-metrics-traffic.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | app.kubernetes.io/name: netbox-operator 8 | app.kubernetes.io/part-of: netbox-operator 9 | app.kubernetes.io/managed-by: kustomize 10 | name: controller-manager-metrics-monitor 11 | namespace: system 12 | spec: 13 | endpoints: 14 | - path: /metrics 15 | port: https # Ensure this is the name of the port that exposes HTTPS metrics 16 | scheme: https 17 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 18 | tlsConfig: 19 | # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables 20 | # certificate verification. This poses a significant security risk by making the system vulnerable to 21 | # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between 22 | # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, 23 | # compromising the integrity and confidentiality of the information. 24 | # Please use the following options for secure configurations: 25 | # caFile: /etc/metrics-certs/ca.crt 26 | # certFile: /etc/metrics-certs/tls.crt 27 | # keyFile: /etc/metrics-certs/tls.key 28 | insecureSkipVerify: true 29 | selector: 30 | matchLabels: 31 | control-plane: controller-manager 32 | app.kubernetes.io/part-of: netbox-operator 33 | -------------------------------------------------------------------------------- /config/rbac/ipaddress_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ipaddresses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ipaddress-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ipaddress-editor-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipaddresses 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - netbox.dev 28 | resources: 29 | - ipaddresses/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/ipaddress_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ipaddresses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ipaddress-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ipaddress-viewer-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipaddresses 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - ipaddresses/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/ipaddressclaim_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ipaddressclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ipaddressclaim-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ipaddressclaim-editor-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipaddressclaims 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - netbox.dev 28 | resources: 29 | - ipaddressclaims/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/ipaddressclaim_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ipaddressclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ipaddressclaim-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ipaddressclaim-viewer-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipaddressclaims 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - ipaddressclaims/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/iprange_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ipranges. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: iprange-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: iprange-editor-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipranges 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - netbox.dev 28 | resources: 29 | - ipranges/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/iprange_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ipranges. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: iprange-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: iprange-viewer-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipranges 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - ipranges/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/iprangeclaim_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit iprangeclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: iprangeclaim-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: iprangeclaim-editor-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - iprangeclaims 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - netbox.dev 28 | resources: 29 | - iprangeclaims/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/iprangeclaim_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view iprangeclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: iprangeclaim-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: iprangeclaim-viewer-role 13 | rules: 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - iprangeclaims 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - iprangeclaims/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # The following RBAC configurations are used to protect 13 | # the metrics endpoint with authn/authz. These configurations 14 | # ensure that only authorized users and service accounts 15 | # can access the metrics endpoint. Comment the following 16 | # permissions if you want to disable this protection. 17 | # More info: https://book.kubebuilder.io/reference/metrics.html 18 | - metrics_auth_role.yaml 19 | - metrics_auth_role_binding.yaml 20 | - metrics_reader_role.yaml 21 | # For each CRD, "Editor" and "Viewer" roles are scaffolded by 22 | # default, aiding admins in cluster management. Those roles are 23 | # not used by the Project itself. You can comment the following lines 24 | # if you do not want those helpers be installed with your Project. 25 | - prefixclaim_editor_role.yaml 26 | - prefixclaim_viewer_role.yaml 27 | - prefix_editor_role.yaml 28 | - prefix_viewer_role.yaml 29 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: netbox-operator 10 | app.kubernetes.io/part-of: netbox-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: netbox-operator 9 | app.kubernetes.io/part-of: netbox-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/metrics_auth_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-auth-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/metrics_auth_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: metrics-auth-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: metrics-auth-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/metrics_reader_role.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/prefix_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit prefixes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefix-editor-role 9 | rules: 10 | - apiGroups: 11 | - netbox.dev 12 | resources: 13 | - prefixes 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - prefixes/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/prefix_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view prefixes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefix-viewer-role 9 | rules: 10 | - apiGroups: 11 | - netbox.dev 12 | resources: 13 | - prefixes 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - netbox.dev 20 | resources: 21 | - prefixes/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/prefixclaim_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit prefixclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-editor-role 9 | rules: 10 | - apiGroups: 11 | - netbox.dev 12 | resources: 13 | - prefixclaims 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - netbox.dev 24 | resources: 25 | - prefixclaims/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/prefixclaim_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view prefixclaims. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-viewer-role 9 | rules: 10 | - apiGroups: 11 | - netbox.dev 12 | resources: 13 | - prefixclaims 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - netbox.dev 20 | resources: 21 | - prefixclaims/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - events 11 | verbs: 12 | - create 13 | - patch 14 | - apiGroups: 15 | - netbox.dev 16 | resources: 17 | - ipaddressclaims 18 | - ipaddresses 19 | - iprangeclaims 20 | - ipranges 21 | - prefixclaims 22 | - prefixes 23 | verbs: 24 | - create 25 | - delete 26 | - get 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - netbox.dev 33 | resources: 34 | - ipaddressclaims/finalizers 35 | - ipaddresses/finalizers 36 | - iprangeclaims/finalizers 37 | - ipranges/finalizers 38 | - prefixclaims/finalizers 39 | - prefixes/finalizers 40 | verbs: 41 | - update 42 | - apiGroups: 43 | - netbox.dev 44 | resources: 45 | - ipaddressclaims/status 46 | - ipaddresses/status 47 | - iprangeclaims/status 48 | - ipranges/status 49 | - prefixclaims/status 50 | - prefixes/status 51 | verbs: 52 | - get 53 | - patch 54 | - update 55 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: netbox-operator 9 | app.kubernetes.io/part-of: netbox-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: controller-manager 8 | namespace: system 9 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - netbox_v1_ipaddress.yaml 4 | - netbox_v1_ipaddressclaim.yaml 5 | - netbox_v1_prefix.yaml 6 | - netbox_v1_prefixclaim.yaml 7 | - netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml 8 | - netbox_v1_prefixclaim_parentprefixselector.yaml 9 | - netbox_v1_iprangeclaim.yaml 10 | - netbox_v1_iprange.yaml 11 | #+kubebuilder:scaffold:manifestskustomizesamples 12 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_ipaddress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: IpAddress 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: ipaddress-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | description: "some description" 11 | comments: "your comments" 12 | preserveInNetbox: true 13 | ipAddress: "2.0.0.100/32" 14 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_ipaddressclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: IpAddressClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: ipaddressclaim-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | description: "some description" 11 | comments: "your comments" 12 | preserveInNetbox: true 13 | parentPrefix: "2.0.0.0/16" 14 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_iprange.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: IpRange 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: iprange-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | description: "some description" 11 | comments: "your comments" 12 | preserveInNetbox: true 13 | startAddress: "2.0.0.200/32" 14 | endAddress: "2.0.0.202/32" 15 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: IpRangeClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: iprangeclaim-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | description: "some description" 11 | comments: "your comments" 12 | preserveInNetbox: true 13 | parentPrefix: "2.0.0.0/16" 14 | size: 3 15 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_prefix.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: Prefix 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefix-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | site: "DM-Akron" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | prefix: "2.0.0.0/24" 15 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefixclaim-sample 8 | spec: 9 | tenant: "Dunder-Mifflin, Inc." 10 | site: "DM-Akron" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | parentPrefix: "2.0.0.0/16" 15 | prefixLength: "/28" 16 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_prefixclaim_parentprefixselector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefixclaim-parentprefixselector-sample 8 | spec: 9 | tenant: "MY_TENANT" 10 | site: "DM-Akron" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | prefixLength: "/31" 15 | parentPrefixSelector: 16 | tenant: "MY_TENANT" 17 | family: "IPv4" 18 | environment: "Production" 19 | poolName: "Pool 1" 20 | -------------------------------------------------------------------------------- /config/samples/netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefixclaim-parentprefixselector-bool-int-sample 8 | spec: 9 | tenant: "MY_TENANT" 10 | site: "DM-Akron" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | prefixLength: "/31" 15 | parentPrefixSelector: 16 | # should return a prefix in 3.0.0.0/24 with the sample data 17 | environment: "Production" 18 | poolName: "Pool 1" 19 | cfDataTypeBool: "true" 20 | cfDataTypeInteger: "1" 21 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | # NetBox Operator Examples 2 | 3 | This folder shows some examples how the NetBox Operator can be used. 4 | 5 | Each example folder contains a README.md which explains how you can set up your local enviroment to step through the examples. 6 | 7 | Prerequisites: 8 | - go version v1.24.0+ 9 | - docker image netbox-operatore:build-local 10 | - kustomize version v5.5.0+ 11 | - kubectl version v1.32.2+ 12 | - kind v0.27.0 13 | - docker cli 14 | -------------------------------------------------------------------------------- /docs/examples/example1-getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Example 1: Getting Started 2 | 3 | # 0.1 Create a local cluster with nebox-installed 4 | 5 | 1. use the 'create-kind' and 'deploy-kind' targets from the Makefile to create a kind cluster and deploy NetBox and NetBox Operator on it 6 | ```bash 7 | make create-kind 8 | make deploy-kind 9 | ``` 10 | 11 | # 0.2 Manually Create a Prefix in NetBox 12 | 13 | Before prefixes and ip addresses can be claimed with the NetBox operator, a prefix has to be created in NetBox. 14 | 15 | 1. Port-forward NetBox: 16 | ```bash 17 | kubectl port-forward deploy/netbox 8080:8080 18 | ``` 19 | 2. Open in your favorite browser and log in with the username `admin` and password `admin` 20 | 3. Create a new prefix '3.0.0.64/26' with custom field 'environment: prod' 21 | 22 | # 0.3 Navigate to the example folder 23 | 24 | Navigate to 'docs/examples/example1-getting-started' to run the examples below 25 | 26 | # 1.1 Claim a Prefix 27 | 28 | In this example, we use a `.spec.parentPrefix` that we know in advance. This is useful if you already know exactly from which prefix you want to claim from. 29 | 30 | 1. Inspect the spec of the sample prefix claim CR 31 | ```bash 32 | cat prefixclaim-simple.yaml 33 | ``` 34 | 2. Apply the manifest defining the prefix claim 35 | ```bash 36 | kubectl apply -f prefixclaim-simple.yaml 37 | ``` 38 | 3. Check that the prefix claim CR got a prefix assigned 39 | ```bash 40 | kubectl get pxc,px 41 | ``` 42 | 43 | ![Example 1.1](prefixclaim-simple.drawio.svg) 44 | 45 | # 1.2 Dynamically Claim a Prefix with a Parent Prefix Selector 46 | 47 | In this example, we use a `.spec.parentPrefixSelector`, which is a list of selectors that tell NetBox Operator from which parent prefixes to claim our Prefix from. 48 | 49 | Navigate to 'docs/examples/example1-getting-started' to run the following commands. 50 | 51 | 1. Inspect the spec of the sample prefix claim CR 52 | ```bash 53 | cat prefixclaim-dynamic.yaml 54 | ``` 55 | 2. Apply the manifest defining the prefix claim 56 | ```bash 57 | kubectl apply -f prefixclaim-dynamic.yaml 58 | ``` 59 | 3. Check that the prefix claim CR got a prefix addigned 60 | ```bash 61 | kubectl get pxc,px 62 | ``` 63 | 64 | ![Example 1.2](prefixclaim-dynamic.drawio.svg) 65 | -------------------------------------------------------------------------------- /docs/examples/example1-getting-started/prefixclaim-dynamic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: dynamic-prefix-claim 8 | spec: 9 | tenant: "MY_TENANT" 10 | parentPrefixSelector: 11 | environment: prod 12 | family: IPv4 13 | prefixLength: "/30" 14 | -------------------------------------------------------------------------------- /docs/examples/example1-getting-started/prefixclaim-simple.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: simple-prefixclaim 8 | spec: 9 | tenant: "MY_TENANT" 10 | parentPrefix: 3.0.0.64/26 11 | prefixLength: "/30" 12 | -------------------------------------------------------------------------------- /docs/examples/example2-load-balancer-ip/README.md: -------------------------------------------------------------------------------- 1 | # Example 2: Glue NetBox CRs to MetalLB CRs 2 | 3 | ## Introduction 4 | 5 | So we have Prefixes represented as Kubernetes Resources. Now what can we do with this? 6 | 7 | We use kro.run to glue this to MetalLB IPAddressPools 8 | 9 | ### 0.1 Create a local cluster with nebox-installed 10 | 11 | 1. use the 'create-kind' and 'deploy-kind' targets from the Makefile to create a kind cluster and deploy NetBox and NetBox Operator on it 12 | ```bash 13 | make create-kind 14 | make deploy-kind 15 | ``` 16 | 17 | ### 0.2 Manually Create a Prefix in NetBox 18 | 19 | Before prefixes and ip addresses can be claimed with the NetBox operator, a prefix has to be created in NetBox. 20 | 21 | 1. Port-forward NetBox: 22 | ```bash 23 | kubectl port-forward deploy/netbox 8080:8080 24 | ``` 25 | 2. Open in your favorite browser and log in with the username `admin` and password `admin` 26 | 3. Create a new prefix '3.0.0.64/26' with custom field 'environment: prod' 27 | 28 | ### 0.3 Navigate to the example folder 29 | 30 | Navigate to 'docs/examples/example2-load-balancer-ip/' to run the examples below 31 | 32 | ## Example Steps 33 | 34 | 0. Install kro and metallb with the installation script `docs/examples/example2-load-balancer-ip/prepare-demo-env.sh` 35 | Then navigate to 'docs/examples/example2-load-balancer-ip' to follow the steps below. 36 | 37 | 1. Inspect the spec of the sample prefix claim CR 38 | ```bash 39 | cat zurich-pool.yaml 40 | ``` 41 | 2. Apply the manifests to create a deployment with a service and a metallb-ip-address-pool-netbox to create a metalLB IPAddressPool from the prefix claimed from NetBox 42 | ```bash 43 | kubectl apply -f zurich-pool.yaml 44 | ``` 45 | 3. Check if the prefixclaim CR and the metalLB ipaddresspool CR got created 46 | ```bash 47 | kubectl get pxc,ipaddresspool -A 48 | ``` 49 | 4. Inspect the spec of the sample prefix claim CR 50 | ```bash 51 | cat sample-deployment.yaml 52 | ``` 53 | 5. Apply the manifests to createa deployment with a service that gets a ip assigned from the metalLB pool created in the prevoius step 54 | ```bash 55 | kubectl apply -f sample-deployment.yaml 56 | ``` 57 | 6. check if the service got an external ip address assigned and that the nginx deployment is ready 58 | ```bash 59 | kubectl get deploy,svc -n nginx 60 | ``` 61 | 7. try to connect to your service with the external ip 62 | ```bash 63 | k exec curl -it -- sh 64 | curl 65 | ``` 66 | 67 | 68 | ![Example 2](metallb-ipaddresspool-netbox.drawio.svg) 69 | -------------------------------------------------------------------------------- /docs/examples/example2-load-balancer-ip/load-balancer-ip-pool-netbox.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kro.run/v1alpha1 2 | kind: ResourceGraphDefinition 3 | metadata: 4 | name: load-balancer-ip-pool-netbox 5 | spec: 6 | schema: 7 | apiVersion: v1alpha1 8 | kind: LoadBalancerIPPoolNetBox 9 | spec: 10 | name: string 11 | tenant: string 12 | prefixLength: string 13 | parentPrefixSelector: 14 | environment: string 15 | family: string 16 | status: 17 | 18 | # Define the resources this API will manage. 19 | resources: 20 | - id: prefixclaim 21 | template: 22 | apiVersion: netbox.dev/v1 23 | kind: PrefixClaim 24 | metadata: 25 | name: ${schema.spec.name} 26 | spec: 27 | prefixLength: ${schema.spec.prefixLength} 28 | parentPrefixSelector: ${schema.spec.parentPrefixSelector} 29 | 30 | - id: ipaddresspool 31 | template: 32 | apiVersion: metallb.io/v1beta1 33 | kind: IPAddressPool 34 | metadata: 35 | name: ${schema.spec.name} 36 | namespace: metallb-system 37 | spec: 38 | addresses: 39 | - ${prefixclaim.status.prefix} 40 | -------------------------------------------------------------------------------- /docs/examples/example2-load-balancer-ip/prepare-demo-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # install netbox in the london cluster and load demo data 5 | make deploy-kind 6 | 7 | # install curl pod to demo access to created service 8 | kind load docker-image curlimages/curl 9 | kind load docker-image curlimages/curl 10 | kubectl run curl --image curlimages/curl --image-pull-policy=Never -- sleep infinity 11 | 12 | # load the nginx image into the kind cluster 13 | kind load docker-image nginx 14 | kind load docker-image nginx 15 | 16 | DEPLOYMENT_NAME=netbox-operator-controller-manager 17 | NAMESPACE=netbox-operator-system 18 | CONTEXT=kind-kind 19 | 20 | # install MetalLB 21 | kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml 22 | 23 | # install kro 24 | helm install kro oci://ghcr.io/kro-run/kro/kro \ 25 | --namespace kro \ 26 | --create-namespace \ 27 | --version=0.2.1 28 | 29 | while true; do 30 | # Check if the deployment is ready 31 | READY_REPLICAS=$(kubectl --context $CONTEXT get deployment $DEPLOYMENT_NAME -n $NAMESPACE -o jsonpath='{.status.readyReplicas}') 32 | DESIRED_REPLICAS=$(kubectl --context $CONTEXT get deployment $DEPLOYMENT_NAME -n $NAMESPACE -o jsonpath='{.status.replicas}') 33 | 34 | if [[ "$READY_REPLICAS" == "$DESIRED_REPLICAS" ]] && [[ "$READY_REPLICAS" -gt 0 ]]; then 35 | echo "Deployment $DEPLOYMENT_NAME in cluster $CONTEXT is ready." 36 | break 37 | else 38 | echo "Waiting... Ready replicas in cluster $CONTEXT: $READY_REPLICAS / $DESIRED_REPLICAS" 39 | sleep 5 40 | fi 41 | done 42 | kubectl apply --context $CONTEXT -f docs/examples/example2-load-balancer-ip/load-balancer-ip-pool-netbox.yaml 43 | -------------------------------------------------------------------------------- /docs/examples/example2-load-balancer-ip/sample-deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: nginx 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: my-nginx 11 | namespace: nginx 12 | spec: 13 | selector: 14 | matchLabels: 15 | run: my-nginx 16 | replicas: 2 17 | template: 18 | metadata: 19 | labels: 20 | run: my-nginx 21 | spec: 22 | containers: 23 | - name: my-nginx 24 | image: nginx 25 | imagePullPolicy: Never 26 | ports: 27 | - containerPort: 80 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: my-nginx 33 | namespace: nginx 34 | labels: 35 | run: my-nginx 36 | annotations: 37 | metallb.universe.tf/address-pool: zurich-pool 38 | spec: 39 | type: LoadBalancer 40 | ports: 41 | - port: 80 42 | protocol: TCP 43 | selector: 44 | run: my-nginx 45 | -------------------------------------------------------------------------------- /docs/examples/example2-load-balancer-ip/zurich-pool.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kro.run/v1alpha1 2 | kind: LoadBalancerIPPoolNetBox 3 | metadata: 4 | name: zurich-pool 5 | spec: 6 | name: zurich-pool 7 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 8 | prefixLength: "/30" 9 | parentPrefixSelector: 10 | environment: prod 11 | family: IPv4 12 | -------------------------------------------------------------------------------- /docs/examples/example3-restoration/README.md: -------------------------------------------------------------------------------- 1 | # Example 3: restoration Feature Restoration 2 | 3 | ## Introduction 4 | 5 | NetBox Operator offers a few restoration features. In this example we showcase how NetBox Operator can restoration prefixes. This is especially useful when e.g. you need sticky IPs or Prefixes when redeploying an entire cluster. 6 | 7 | ## Instructions 8 | 9 | First, let's create some resources we want to restoration later. 10 | 11 | ```bash 12 | kubectl create ns restoration 13 | 14 | kubectl -n restoration apply -f prefixclaim-restore1.yaml 15 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 16 | kubectl -n restoration get prefixclaims 17 | 18 | kubectl -n restoration apply -f prefixclaim-restore2.yaml 19 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 20 | kubectl -n restoration get prefixclaims 21 | 22 | kubectl -n restoration apply -f prefixclaim-restore3.yaml 23 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 24 | kubectl -n restoration get prefixclaims 25 | ``` 26 | 27 | ![Figure 4: Restoration](restoration.drawio.svg) 28 | 29 | Since we set `.spec.preserveInNetbox` to `true`, we can delete and restoration the resources. To delete all reasources, delete the entire namespace: 30 | 31 | ```bash 32 | kubectl delete ns restoration 33 | ``` 34 | 35 | Make sure the resources are gone in Kubernetes: 36 | 37 | ```bash 38 | kubectl -n restoration get prefixclaims 39 | ``` 40 | 41 | Verify in the NetBox UI that the Prefixes still exist. 42 | 43 | Now apply the manifests again and verify they become ready. We apply the manifests in the reverse order to make sure the order does not matter 44 | 45 | ```bash 46 | kubectl create ns restoration 47 | 48 | kubectl -n restoration apply -f prefixclaim-restore3.yaml 49 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 50 | kubectl -n restoration get prefixclaims 51 | 52 | kubectl -n restoration apply -f prefixclaim-restore2.yaml 53 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 54 | kubectl -n restoration get prefixclaims 55 | 56 | kubectl -n restoration apply -f prefixclaim-restore1.yaml 57 | kubectl -n restoration wait --for=condition=Ready prefixclaims --all 58 | kubectl -n restoration get prefixclaims 59 | ``` 60 | 61 | Delete Leases to speed up: 62 | 63 | ```bash 64 | kubectl -n netbox-operator-system get lease -oname | grep -v netbox | xargs -n1 kubectl -n netbox-operator-system delete 65 | ``` 66 | 67 | Note that the assigned Prefixes are the same as before. You can also play around with this by just restoring single prefixes. If you're curious about how this is done, make sure to read [the "Restoration from NetBox" section in the main README.md](https://github.com/netbox-community/netbox-operator/tree/main?tab=readme-ov-file#restoration-from-netbox) and to check out the code. Also have a look at the "Netbox Restoration Hash" custom field in NetBox. 68 | -------------------------------------------------------------------------------- /docs/examples/example3-restoration/prefixclaim-restore1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | name: prefixclaim-restoration-sample-1 6 | spec: 7 | tenant: "Dunder-Mifflin, Inc." 8 | preserveInNetbox: true 9 | parentPrefixSelector: 10 | tenant: "Dunder-Mifflin, Inc." 11 | family: IPv4 12 | prefixLength: "/32" 13 | -------------------------------------------------------------------------------- /docs/examples/example3-restoration/prefixclaim-restore2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | name: prefixclaim-restoration-sample-2 6 | spec: 7 | tenant: "Dunder-Mifflin, Inc." 8 | preserveInNetbox: true 9 | parentPrefixSelector: 10 | tenant: "Dunder-Mifflin, Inc." 11 | family: IPv4 12 | prefixLength: "/32" 13 | -------------------------------------------------------------------------------- /docs/examples/example3-restoration/prefixclaim-restore3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | name: prefixclaim-restoration-sample-3 6 | spec: 7 | tenant: "Dunder-Mifflin, Inc." 8 | preserveInNetbox: true 9 | parentPrefixSelector: 10 | tenant: "Dunder-Mifflin, Inc." 11 | family: IPv4 12 | prefixLength: "/32" 13 | -------------------------------------------------------------------------------- /docs/examples/example3-restoration/restoration-large-font.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-operator/8a0c4d31c6cf1c48f36f4894d3ed5d4a80ffef04/docs/examples/example3-restoration/restoration-large-font.drawio.png -------------------------------------------------------------------------------- /docs/examples/example4-exhaustion/README.md: -------------------------------------------------------------------------------- 1 | # Example 3: Advanced Feature Prefix Exhaustion 2 | 3 | ## Introduction 4 | 5 | NetBox Operator offers a few advanced features. In this example we showcase how NetBox Operator can recover from prefix exhaustion. 6 | 7 | When a Prefix is exhausted and this is fixed in the NetBox backend (e.g. by the Infrastructure team), NetBox Operator will automatically reconcile this. 8 | 9 | ## Instructions 10 | 11 | ![Figure 1: Starting Point](exhaustion-1-starting-point.drawio.svg) 12 | 13 | Create a /24 Prefix (e.g. 1.122.0.0/24) with Custom Field Environment set to "prod" in NetBox UI. 14 | 15 | Apply Resource and show PrefixClaims: 16 | 17 | ```bash 18 | kubectl create ns advanced 19 | kubectl apply -f prefixclaim-exhaustion.yaml 20 | kubectl -n advanced get prefixclaims,prefixes 21 | ``` 22 | 23 | Note that only 2 out of the 3 PrefixClaims will become Ready. This is because the /24 Prefix is exhausted already after two Prefixes. This will look similar to this (note the order is non-deterministic): 24 | 25 | ```bash 26 | NAME PREFIX PREFIXASSIGNED READY AGE 27 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-1 1.122.0.0/25 True True 2m2s 28 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-2 1.122.0.128/25 True True 2m2s 29 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-3 False 2m2s 30 | 31 | NAME PREFIX READY ID URL AGE 32 | prefix.netbox.dev/prefixclaim-exhaustion-sample-1 1.122.0.0/25 True 148 http://172.18.1.2/ipam/prefixes/148 2m2s 33 | prefix.netbox.dev/prefixclaim-exhaustion-sample-2 1.122.0.128/25 True 149 http://172.18.1.2/ipam/prefixes/149 2m2s 34 | ``` 35 | 36 | ![Figure 2: Parent Prefix Exhausted](exhaustion-2-prefix-exhausted.drawio.svg) 37 | 38 | 39 | Create another /24 Prefix (e.g. 1.100.0.0/24) with Custom Field Environment set to "prod" in NetBox UI. 40 | 41 | Wait for the PrefixClaim to be reconciled again or trigger reconciliation by e.g. adding an annotation: 42 | 43 | ```bash 44 | kubectl -n advanced annotate prefixclaim prefixclaim-exhaustion-sample-3 reconcile="$(date)" --overwrite 45 | ``` 46 | 47 | Confirm that the third Prefix is now also assigned: 48 | 49 | ```bash 50 | kubectl -n advanced get prefixclaims,prefixes 51 | ``` 52 | 53 | Which should look as follows: 54 | 55 | ```bash 56 | NAME PREFIX PREFIXASSIGNED READY AGE 57 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-1 1.122.0.0/25 True True 4s 58 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-2 1.122.0.128/25 True True 4s 59 | prefixclaim.netbox.dev/prefixclaim-exhaustion-sample-3 1.100.0.0/25 True True 4s 60 | 61 | NAME PREFIX READY ID URL AGE 62 | prefix.netbox.dev/prefixclaim-exhaustion-sample-1 1.122.0.0/25 True 148 http://172.18.1.2/ipam/prefixes/148 4s 63 | prefix.netbox.dev/prefixclaim-exhaustion-sample-2 1.122.0.128/25 True 149 http://172.18.1.2/ipam/prefixes/149 4s 64 | prefix.netbox.dev/prefixclaim-exhaustion-sample-3 1.100.0.0/25 True 151 http://172.18.1.2/ipam/prefixes/151 3s``` 65 | ``` 66 | 67 | ![Figure 3: Parent Prefix Exhaustion fixed](exhaustion-3-after-fix.drawio.svg) 68 | -------------------------------------------------------------------------------- /docs/examples/example4-exhaustion/prefixclaim-exhaustion.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | name: prefixclaim-exhaustion-sample-1 6 | spec: 7 | tenant: "MY_TENANT" 8 | preserveInNetbox: true 9 | parentPrefixSelector: 10 | environment: prod 11 | family: IPv4 12 | prefixLength: "/25" 13 | --- 14 | apiVersion: netbox.dev/v1 15 | kind: PrefixClaim 16 | metadata: 17 | name: prefixclaim-exhaustion-sample-2 18 | spec: 19 | tenant: "MY_TENANT" 20 | preserveInNetbox: true 21 | parentPrefixSelector: 22 | environment: prod 23 | family: IPv4 24 | prefixLength: "/25" 25 | --- 26 | apiVersion: netbox.dev/v1 27 | kind: PrefixClaim 28 | metadata: 29 | name: prefixclaim-exhaustion-sample-3 30 | spec: 31 | tenant: "MY_TENANT" 32 | preserveInNetbox: true 33 | parentPrefixSelector: 34 | environment: prod 35 | family: IPv4 36 | prefixLength: "/25" 37 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/README.md: -------------------------------------------------------------------------------- 1 | # Example 5: Advanced Feature Multi Cluster Support 2 | 3 | ## Introduction 4 | 5 | NetBox Operator uses NetBox to avoid IP overlaps. This means that we can use NetBox Operator on multiple clusters. You can try this out using the example in this directory. 6 | 7 | This example shows how to claim multiple prefixes from different clusters and make them available as metalLB ip address pools. 8 | 9 | ### 0.1 Create a local cluster with nebox-installed 10 | 11 | 1. set up your local environment to run the following examples with the set up script 'docs/examples/example5-multicluster/prepare-demo-env.sh' 12 | 13 | ### 0.2 Manually Create a Prefix in NetBox 14 | 15 | Before prefixes and ip addresses can be claimed with the NetBox operator, a prefix has to be created in NetBox. 16 | 17 | 1. Port-forward NetBox: 18 | ```bash 19 | kubectl port-forward deploy/netbox 8080:8080 20 | ``` 21 | 2. Open in your favorite browser and log in with the username `admin` and password `admin` 22 | 3. Create a new prefix '3.0.0.64/26' with custom field 'environment: prod' 23 | 24 | ### 0.3 Navigate to the example folder 25 | 26 | Navigate to 'docs/examples/example5-multicluster/' to run the examples below 27 | 28 | ## Example Steps 29 | 30 | 1. Create ip address pools on the london cluster 31 | ```bash 32 | kubectl apply --context kind-london -f docs/examples/example5-multicluster/london-pools.yaml 33 | ``` 34 | 2. Create ip address pool on the zurich cluster 35 | ```bash 36 | kubectl create --context kind-zurich -f docs/examples/example5-multicluster/zurich-pools.yaml 37 | ``` 38 | 3. Look up the created prefix claims 39 | ```bash 40 | kubectl get --context kind-london pxc -A 41 | ``` 42 | and 43 | ```bash 44 | kubectl get --context kind-zurich pxc -A 45 | ``` 46 | 47 | ![Example 2](multicluster.drawio.svg) -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/cluster-cfg.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kind.x-k8s.io/v1alpha4 2 | kind: Cluster 3 | networking: 4 | serviceSubnet: "10.96.0.0/20" # until 10.96.15.255 5 | apiServerAddress: "127.0.0.1" 6 | apiServerPort: 6443 7 | nodes: 8 | - role: control-plane 9 | kubeadmConfigPatches: 10 | - | 11 | kind: InitConfiguration 12 | nodeRegistration: 13 | kubeletExtraArgs: 14 | node-labels: "ingress-ready=true" 15 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/create-kind-clusters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script creates the specified number of kind clusters with MetalLB 4 | set -e 5 | # Define colors 6 | RED='\033[0;31m' 7 | NC='\033[0m' # No Color 8 | 9 | # Check if cluster names are provided 10 | if [ $# -eq 0 ]; then 11 | echo -e "${RED}Error: No cluster names provided${NC}" 12 | echo "Usage: $0 ... " 13 | exit 1 14 | fi 15 | 16 | number_of_clusters=$1 17 | clustername=${2:-dns} # Set default cluster name to 'dns' if not provided 18 | mkdir -p tmp 19 | i=0 20 | 21 | # Loop to create the specified number of clusters 22 | for clustername in "$@"; do 23 | config_file="docs/examples/example5-multicluster/cluster-cfg.yaml" 24 | temp_config="tmp/cluster-$clustername-cfg.yaml" 25 | i=$((i + 1)) 26 | 27 | if [ -f "$config_file" ]; then 28 | # Make a temporary copy of the configuration file 29 | cp "$config_file" "$temp_config" 30 | 31 | # Modify apiServerPort in the copied config file 32 | sed -i'' -e "s/apiServerPort: 6443/apiServerPort: $((6443 + i))/g" "$temp_config" 33 | rm "$temp_config"-e 34 | 35 | # check if cluster exists 36 | if kind get clusters | grep -q "^${clustername}$"; then 37 | echo "Cluster ${clustername} already exists. Skipping creation." 38 | continue 39 | fi 40 | kind create cluster --name $clustername --config $temp_config || { echo -e "${RED}Error: Failed to create cluster ${clustername}${NC}"; rm -f "$temp_config"; exit 1; } 41 | 42 | # Install MetalLB 43 | kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml 44 | 45 | else 46 | echo -e "${RED}Error: Configuration file $config_file not found${NC}" 47 | exit 1 48 | fi 49 | done 50 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../../kind 3 | 4 | patches: 5 | - patch: |- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: controller-manager 10 | namespace: system 11 | spec: 12 | template: 13 | spec: 14 | containers: 15 | - name: manager 16 | env: 17 | - name: NETBOX_HOST 18 | value: "172.18.1.2" 19 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/london-pools.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | name: london-prefix-1 5 | spec: 6 | tenant: "MY_TENANT" 7 | prefixLength: "/30" 8 | parentPrefixSelector: 9 | environment: prod 10 | familily: IPv4 11 | --- 12 | apiVersion: netbox.dev/v1 13 | kind: PrefixClaim 14 | metadata: 15 | name: london-prefix-2 16 | spec: 17 | tenant: "MY_TENANT" 18 | prefixLength: "/30" 19 | parentPrefixSelector: 20 | environment: prod 21 | familily: IPv4 22 | --- 23 | apiVersion: netbox.dev/v1 24 | kind: PrefixClaim 25 | metadata: 26 | name: london-prefix-3 27 | spec: 28 | tenant: "MY_TENANT" 29 | prefixLength: "/30" 30 | parentPrefixSelector: 31 | environment: prod 32 | familily: IPv4 33 | --- 34 | apiVersion: netbox.dev/v1 35 | kind: PrefixClaim 36 | metadata: 37 | name: london-prefix-4 38 | spec: 39 | tenant: "MY_TENANT" 40 | prefixLength: "/30" 41 | parentPrefixSelector: 42 | environment: prod 43 | familily: IPv4 44 | --- 45 | apiVersion: netbox.dev/v1 46 | kind: PrefixClaim 47 | metadata: 48 | name: london-prefix-5 49 | spec: 50 | tenant: "MY_TENANT" 51 | prefixLength: "/30" 52 | parentPrefixSelector: 53 | environment: prod 54 | familily: IPv4 55 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/netbox-l2advertisement.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: metallb.io/v1beta1 3 | kind: L2Advertisement 4 | metadata: 5 | namespace: metallb-system 6 | name: output-l2-advertisement 7 | spec: 8 | --- 9 | apiVersion: metallb.io/v1beta1 10 | kind: IPAddressPool 11 | metadata: 12 | name: db-ipaddresspool 13 | namespace: metallb-system 14 | spec: 15 | addresses: 16 | - 172.18.1.1/32 17 | --- 18 | apiVersion: metallb.io/v1beta1 19 | kind: IPAddressPool 20 | metadata: 21 | name: netbox-ipaddresspool 22 | namespace: metallb-system 23 | spec: 24 | addresses: 25 | - 172.18.1.2/32 26 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/netbox-svc.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: netbox 6 | namespace: default 7 | annotations: 8 | metallb.universe.tf/address-pool: netbox-ipaddresspool 9 | spec: 10 | allocateLoadBalancerNodePorts: true 11 | externalTrafficPolicy: Cluster 12 | type: LoadBalancer 13 | internalTrafficPolicy: Cluster 14 | ipFamilies: 15 | - IPv4 16 | ipFamilyPolicy: SingleStack 17 | ports: 18 | - name: http 19 | port: 80 20 | protocol: TCP 21 | targetPort: http 22 | selector: 23 | app.kubernetes.io/component: netbox 24 | app.kubernetes.io/instance: netbox 25 | app.kubernetes.io/name: netbox 26 | sessionAffinity: None -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/prepare-demo-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | #create the kind clusters zurich and london 4 | ./docs/examples/example5-multicluster/create-kind-clusters.sh zurich london 5 | 6 | # install netbox in the london cluster and load demo data 7 | kubectl config use-context kind-london 8 | ./kind/deploy-netbox.sh london "4.1.8" default 9 | 10 | # install NetBox Operator 11 | kubectl config use-context kind-london 12 | kind load docker-image netbox-operator:build-local --name london 13 | kind load docker-image netbox-operator:build-local --name london # fixes an issue with podman where the image is not correctly tagged after the first kind load docker-image 14 | kustomize build docs/examples/example5-multicluster/ | kubectl apply -f - 15 | 16 | kubectl config use-context kind-zurich 17 | kind load docker-image netbox-operator:build-local --name zurich 18 | kind load docker-image netbox-operator:build-local --name zurich # fixes an issue with podman where the image is not correctly tagged after the first kind load docker-image 19 | kustomize build docs/examples/example5-multicluster/ | kubectl apply -f - 20 | kind load docker-image curlimages/curl --name zurich 21 | kind load docker-image curlimages/curl --name zurich 22 | kubectl run curl --image curlimages/curl --image-pull-policy=Never -- sleep infinity 23 | 24 | # expose netbox service 25 | kubectl config use-context kind-london 26 | kubectl apply -f docs/examples/example5-multicluster/netbox-svc.yaml 27 | kubectl apply -f docs/examples/example5-multicluster/netbox-l2advertisement.yaml 28 | -------------------------------------------------------------------------------- /docs/examples/example5-multicluster/zurich-pools.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | name: zurich-prefix-1 5 | spec: 6 | tenant: "MY_TENANT" 7 | prefixLength: "/30" 8 | parentPrefixSelector: 9 | environment: prod 10 | familiy: IPv4 11 | --- 12 | apiVersion: netbox.dev/v1 13 | kind: PrefixClaim 14 | metadata: 15 | name: zurich-prefix-2 16 | spec: 17 | tenant: "MY_TENANT" 18 | prefixLength: "/30" 19 | parentPrefixSelector: 20 | environment: prod 21 | --- 22 | apiVersion: netbox.dev/v1 23 | kind: PrefixClaim 24 | metadata: 25 | name: zurich-prefix-3 26 | spec: 27 | tenant: "MY_TENANT" 28 | prefixLength: "/30" 29 | parentPrefixSelector: 30 | environment: prod 31 | --- 32 | apiVersion: netbox.dev/v1 33 | kind: PrefixClaim 34 | metadata: 35 | name: zurich-prefix-4 36 | spec: 37 | tenant: "MY_TENANT" 38 | prefixLength: "/30" 39 | parentPrefixSelector: 40 | environment: prod 41 | --- 42 | apiVersion: netbox.dev/v1 43 | kind: PrefixClaim 44 | metadata: 45 | name: zurich-prefix-5 46 | spec: 47 | tenant: "MY_TENANT" 48 | prefixLength: "/30" 49 | parentPrefixSelector: 50 | environment: prod 51 | -------------------------------------------------------------------------------- /docs/operational-manual.md: -------------------------------------------------------------------------------- 1 | # Operational Manual 2 | 3 | This document describes how to troubleshoot errors in the NetBox Operator. 4 | Known issues are collected on the [GitHub issues](https://github.com/netbox-community/netbox-operator/issues) page. 5 | 6 | ## Troubleshooting 7 | 8 | ### Check Logs 9 | Use the following command to get logs from the operator: 10 | 11 | ```bash 12 | kubectl logs -n deployment/netbox-operator-controller-manager 13 | ``` 14 | 15 | ### Check CR Status 16 | Inspect the CRs status: 17 | 18 | ```bash 19 | kubectl describe -n 20 | ``` 21 | 22 | E.g.: 23 | ```bash 24 | kubectl describe prefixclaim prefixclaim-sample -n 25 | kubectl describe ipaddressclaim ipaddressclaim-sample -n 26 | kubectl describe prefix prefix-sample -n 27 | kubectl describe ipaddress ipaddress-sample -n 28 | ``` 29 | This will show you the status of the operator and any errors it may have encountered. 30 | 31 | ### Verify Operator Version 32 | 33 | ```bash 34 | kubectl get deployment netbox-operator-controller-manager -n -o=jsonpath="{.spec.template.spec.containers[*].image}" 35 | ``` 36 | 37 | ### Look at Related Pods 38 | If netbox-oeprator is not running correctly, inspect the related pods: 39 | 40 | ```bash 41 | kubectl logs deployment/netbox-operator-controller-manager -n -c manager 42 | kubectl describe pod -n 43 | ``` 44 | 45 | ### Check Events 46 | Events might give hints about what’s going wrong: 47 | 48 | ```bash 49 | kubectl get events -n --sort-by='.lastTimestamp' 50 | ``` -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 | */ -------------------------------------------------------------------------------- /internal/controller/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | ) 23 | 24 | type ObjectWithConditions interface { 25 | client.Object 26 | Conditions() *[]metav1.Condition 27 | } 28 | -------------------------------------------------------------------------------- /internal/controller/ipaddressclaim_helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | "crypto/sha1" 21 | "fmt" 22 | 23 | "github.com/go-logr/logr" 24 | netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 25 | "github.com/netbox-community/netbox-operator/pkg/config" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | ) 28 | 29 | func generateIpAddressFromIpAddressClaim(claim *netboxv1.IpAddressClaim, ip string, logger logr.Logger) *netboxv1.IpAddress { 30 | ipAddressResource := &netboxv1.IpAddress{ 31 | ObjectMeta: metav1.ObjectMeta{ 32 | Name: claim.Name, 33 | Namespace: claim.ObjectMeta.Namespace, 34 | }, 35 | Spec: generateIpAddressSpec(claim, ip, logger), 36 | } 37 | return ipAddressResource 38 | } 39 | 40 | func generateIpAddressSpec(claim *netboxv1.IpAddressClaim, ip string, logger logr.Logger) netboxv1.IpAddressSpec { 41 | // log a warning if the netboxOperatorRestorationHash name is a key in the customFields map of the IpAddressClaim 42 | _, ok := claim.Spec.CustomFields[config.GetOperatorConfig().NetboxRestorationHashFieldName] 43 | if ok { 44 | logger.Info(fmt.Sprintf("Warning: restoration hash is calculated from spec, custom field with key %s will be ignored", config.GetOperatorConfig().NetboxRestorationHashFieldName)) 45 | } 46 | 47 | // Copy customFields from claim and add restoration hash 48 | customFields := make(map[string]string, len(claim.Spec.CustomFields)+1) 49 | for k, v := range claim.Spec.CustomFields { 50 | customFields[k] = v 51 | } 52 | 53 | customFields[config.GetOperatorConfig().NetboxRestorationHashFieldName] = generateIpAddressRestorationHash(claim) 54 | 55 | return netboxv1.IpAddressSpec{ 56 | IpAddress: ip, 57 | Tenant: claim.Spec.Tenant, 58 | CustomFields: customFields, 59 | Description: claim.Spec.Description, 60 | Comments: claim.Spec.Comments, 61 | PreserveInNetbox: claim.Spec.PreserveInNetbox, 62 | } 63 | } 64 | 65 | func generateIpAddressRestorationHash(claim *netboxv1.IpAddressClaim) string { 66 | rd := IpAddressClaimRestorationData{ 67 | Namespace: claim.Namespace, 68 | Name: claim.Name, 69 | ParentPrefix: claim.Spec.ParentPrefix, 70 | Tenant: claim.Spec.Tenant, 71 | } 72 | return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant))) 73 | } 74 | 75 | type IpAddressClaimRestorationData struct { 76 | // only use immutable fields 77 | Namespace string 78 | Name string 79 | ParentPrefix string 80 | Tenant string 81 | } 82 | -------------------------------------------------------------------------------- /internal/controller/iprange_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo/v2" 21 | . "github.com/onsi/gomega" 22 | 23 | "github.com/netbox-community/netbox-operator/pkg/netbox/api" 24 | "github.com/netbox-community/netbox-operator/pkg/netbox/models" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 | 29 | netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 30 | ) 31 | 32 | var ipRangeRecondiler *IpRangeReconciler 33 | 34 | var _ = Describe("IpRange Controller", func() { 35 | Context("When generating NetBox IpRange Model form IpRangeSpec", func() { 36 | // dummy reconciler 37 | ipRangeRecondiler = &IpRangeReconciler{ 38 | NetboxClient: &api.NetboxClient{ 39 | Ipam: ipamMockIpAddress, 40 | Tenancy: tenancyMock, 41 | Dcim: dcimMock, 42 | }, 43 | } 44 | 45 | // default IpRange 46 | ipRange := &netboxv1.IpRange{ 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Namespace: "default", 49 | }, 50 | Spec: netboxv1.IpRangeSpec{ 51 | StartAddress: "1.0.0.1/32", 52 | EndAddress: "1.0.0.5/32", 53 | Comments: "a comment", 54 | Description: "a description", 55 | Tenant: "a tenant", 56 | CustomFields: map[string]string{"custom_field_2": "valueToBeSet"}, 57 | }} 58 | ipRange.Name = "test-claim" 59 | 60 | // default managedCustomFieldsAnnotation 61 | managedCustomFieldsAnnotation := "{\"custom_field_1\":\"valueToBeRemoved\"}" 62 | 63 | // default request 64 | req := reconcile.Request{ 65 | NamespacedName: client.ObjectKey{ 66 | Name: "test-claim", 67 | Namespace: "default", 68 | }, 69 | } 70 | 71 | It("should create the correct ip range model", func() { 72 | ipRangeModel, err := ipRangeRecondiler.generateNetboxIpRangeModelFromIpRangeSpec(ipRange, req, managedCustomFieldsAnnotation) 73 | 74 | Expect(ipRangeModel).To(Equal(&models.IpRange{ 75 | Metadata: &models.NetboxMetadata{ 76 | Comments: "a comment", 77 | Description: "default/test-claim // a description // managed by netbox-operator, please don't edit it in Netbox unless you know what you're doing", 78 | Custom: map[string]string{"custom_field_2": "valueToBeSet", "custom_field_1": ""}, 79 | Tenant: "a tenant", 80 | }, 81 | StartAddress: "1.0.0.1/32", 82 | EndAddress: "1.0.0.5/32", 83 | })) 84 | 85 | Expect(err).To(BeNil()) 86 | }) 87 | 88 | It("should return error if parsing of annotation fails", func() { 89 | invalidManagedCustomFieldsAnnotation := "{:\"valueToBeRemoved\"}" 90 | ipRangeModel, err := ipRangeRecondiler.generateNetboxIpRangeModelFromIpRangeSpec(ipRange, req, invalidManagedCustomFieldsAnnotation) 91 | 92 | Expect(ipRangeModel).To(BeNil()) 93 | 94 | Expect(err).To(HaveOccurred()) 95 | }) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /internal/controller/iprangeclaim_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | 25 | netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 26 | "github.com/netbox-community/netbox-operator/pkg/config" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | ) 29 | 30 | var _ = Describe("IpRangeClaim Controller", func() { 31 | Context("When generating the ip range spec", func() { 32 | It("should create the correct spec", func() { 33 | ctx := context.TODO() 34 | 35 | claim := &netboxv1.IpRangeClaim{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Namespace: "default", 38 | }, 39 | Spec: netboxv1.IpRangeClaimSpec{ 40 | ParentPrefix: "1.0.0.1/28", 41 | Comments: "test", 42 | }} 43 | 44 | claim.Name = "test-claim" 45 | 46 | ipRange := generateIpRangeFromIpRangeClaim(ctx, claim, "1.0.0.1/32", "1.0.0.3/32") 47 | Expect(ipRange).To(Equal(&netboxv1.IpRange{ 48 | ObjectMeta: metav1.ObjectMeta{ 49 | Name: claim.Name, 50 | Namespace: claim.ObjectMeta.Namespace, 51 | }, 52 | Spec: netboxv1.IpRangeSpec{ 53 | Comments: "test", 54 | StartAddress: "1.0.0.1/32", 55 | EndAddress: "1.0.0.3/32", 56 | CustomFields: map[string]string{config.GetOperatorConfig().NetboxRestorationHashFieldName: "331f244f24c08ea3fc6fb7f16cbef20ef2bf02de"}, 57 | }, 58 | })) 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /internal/controller/iprangeclaim_helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | "context" 21 | "crypto/sha1" 22 | "fmt" 23 | 24 | "github.com/go-logr/logr" 25 | netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 26 | "github.com/netbox-community/netbox-operator/pkg/config" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "sigs.k8s.io/controller-runtime/pkg/log" 29 | ) 30 | 31 | func generateIpRangeFromIpRangeClaim(ctx context.Context, claim *netboxv1.IpRangeClaim, startIp string, endIp string) *netboxv1.IpRange { 32 | logger := log.FromContext(ctx) 33 | ipRangeResource := &netboxv1.IpRange{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | Name: claim.Name, 36 | Namespace: claim.ObjectMeta.Namespace, 37 | }, 38 | Spec: generateIpRangeSpec(claim, startIp, endIp, logger), 39 | } 40 | return ipRangeResource 41 | } 42 | 43 | func generateIpRangeSpec(claim *netboxv1.IpRangeClaim, startIp string, endIp string, logger logr.Logger) netboxv1.IpRangeSpec { 44 | // log a warning if the netboxOperatorRestorationHash name is a key in the customFields map of the IpRangeClaim 45 | _, ok := claim.Spec.CustomFields[config.GetOperatorConfig().NetboxRestorationHashFieldName] 46 | if ok { 47 | logger.Info(fmt.Sprintf("Warning: restoration hash is calculated from spec, custom field with key %s will be ignored", config.GetOperatorConfig().NetboxRestorationHashFieldName)) 48 | } 49 | 50 | // Copy customFields from claim and add restoration hash 51 | customFields := make(map[string]string, len(claim.Spec.CustomFields)+1) 52 | for k, v := range claim.Spec.CustomFields { 53 | customFields[k] = v 54 | } 55 | 56 | customFields[config.GetOperatorConfig().NetboxRestorationHashFieldName] = generateIpRangeRestorationHash(claim) 57 | 58 | return netboxv1.IpRangeSpec{ 59 | StartAddress: startIp, 60 | EndAddress: endIp, 61 | Tenant: claim.Spec.Tenant, 62 | CustomFields: customFields, 63 | Description: claim.Spec.Description, 64 | Comments: claim.Spec.Comments, 65 | PreserveInNetbox: claim.Spec.PreserveInNetbox, 66 | } 67 | } 68 | 69 | func generateIpRangeRestorationHash(claim *netboxv1.IpRangeClaim) string { 70 | rd := IpRangeClaimRestorationData{ 71 | Namespace: claim.Namespace, 72 | Name: claim.Name, 73 | ParentPrefix: claim.Spec.ParentPrefix, 74 | Tenant: claim.Spec.Tenant, 75 | Size: fmt.Sprintf("%d", claim.Spec.Size), 76 | } 77 | return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant+rd.Size))) 78 | } 79 | 80 | type IpRangeClaimRestorationData struct { 81 | // only use immutable fields 82 | Namespace string 83 | Name string 84 | ParentPrefix string 85 | Tenant string 86 | Size string 87 | } 88 | -------------------------------------------------------------------------------- /internal/controller/prefix_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | // import ( 20 | // "context" 21 | 22 | // . "github.com/onsi/ginkgo/v2" 23 | // . "github.com/onsi/gomega" 24 | // "k8s.io/apimachinery/pkg/api/errors" 25 | // "k8s.io/apimachinery/pkg/types" 26 | // "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | // netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 31 | // ) 32 | 33 | // var _ = Describe("Prefix Controller", func() { 34 | // Context("When reconciling a resource", func() { 35 | // const resourceName = "test-resource" 36 | 37 | // ctx := context.Background() 38 | 39 | // typeNamespacedName := types.NamespacedName{ 40 | // Name: resourceName, 41 | // Namespace: "default", // TODO(user):Modify as needed 42 | // } 43 | // prefix := &netboxv1.Prefix{} 44 | 45 | // BeforeEach(func() { 46 | // By("creating the custom resource for the Kind Prefix") 47 | // err := k8sClient.Get(ctx, typeNamespacedName, prefix) 48 | // if err != nil && errors.IsNotFound(err) { 49 | // resource := &netboxv1.Prefix{ 50 | // ObjectMeta: metav1.ObjectMeta{ 51 | // Name: resourceName, 52 | // Namespace: "default", 53 | // }, 54 | // // TODO(user): Specify other spec details if needed. 55 | // } 56 | // Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 57 | // } 58 | // }) 59 | 60 | // AfterEach(func() { 61 | // // TODO(user): Cleanup logic after each test, like removing the resource instance. 62 | // resource := &netboxv1.Prefix{} 63 | // err := k8sClient.Get(ctx, typeNamespacedName, resource) 64 | // Expect(err).NotTo(HaveOccurred()) 65 | 66 | // By("Cleanup the specific resource instance Prefix") 67 | // Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 68 | // }) 69 | // It("should successfully reconcile the resource", func() { 70 | // By("Reconciling the created resource") 71 | // controllerReconciler := &PrefixReconciler{ 72 | // Client: k8sClient, 73 | // Scheme: k8sClient.Scheme(), 74 | // } 75 | 76 | // _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 77 | // NamespacedName: typeNamespacedName, 78 | // }) 79 | // Expect(err).NotTo(HaveOccurred()) 80 | // // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 81 | // // Example: If you expect a certain status condition after reconciliation, verify it here. 82 | // }) 83 | // }) 84 | // }) 85 | -------------------------------------------------------------------------------- /internal/controller/prefixclaim_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | // import ( 20 | // "context" 21 | 22 | // . "github.com/onsi/ginkgo/v2" 23 | // . "github.com/onsi/gomega" 24 | // "k8s.io/apimachinery/pkg/api/errors" 25 | // "k8s.io/apimachinery/pkg/types" 26 | // "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | // metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | // netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 31 | // ) 32 | 33 | // var _ = Describe("PrefixClaim Controller", func() { 34 | // Context("When reconciling a resource", func() { 35 | // const resourceName = "test-resource" 36 | 37 | // ctx := context.Background() 38 | 39 | // typeNamespacedName := types.NamespacedName{ 40 | // Name: resourceName, 41 | // Namespace: "default", // TODO(user):Modify as needed 42 | // } 43 | // prefixclaim := &netboxv1.PrefixClaim{} 44 | 45 | // BeforeEach(func() { 46 | // By("creating the custom resource for the Kind PrefixClaim") 47 | // err := k8sClient.Get(ctx, typeNamespacedName, prefixclaim) 48 | // if err != nil && errors.IsNotFound(err) { 49 | // resource := &netboxv1.PrefixClaim{ 50 | // ObjectMeta: metav1.ObjectMeta{ 51 | // Name: resourceName, 52 | // Namespace: "default", 53 | // }, 54 | // // TODO(user): Specify other spec details if needed. 55 | // } 56 | // Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 57 | // } 58 | // }) 59 | 60 | // AfterEach(func() { 61 | // // TODO(user): Cleanup logic after each test, like removing the resource instance. 62 | // resource := &netboxv1.PrefixClaim{} 63 | // err := k8sClient.Get(ctx, typeNamespacedName, resource) 64 | // Expect(err).NotTo(HaveOccurred()) 65 | 66 | // By("Cleanup the specific resource instance PrefixClaim") 67 | // Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 68 | // }) 69 | // It("should successfully reconcile the resource", func() { 70 | // By("Reconciling the created resource") 71 | // controllerReconciler := &PrefixClaimReconciler{ 72 | // Client: k8sClient, 73 | // Scheme: k8sClient.Scheme(), 74 | // } 75 | 76 | // _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 77 | // NamespacedName: typeNamespacedName, 78 | // }) 79 | // Expect(err).NotTo(HaveOccurred()) 80 | // // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 81 | // // Example: If you expect a certain status condition after reconciliation, verify it here. 82 | // }) 83 | // }) 84 | // }) 85 | -------------------------------------------------------------------------------- /internal/controller/prefixclaim_helpers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | "testing" 21 | 22 | netboxv1 "github.com/netbox-community/netbox-operator/api/v1" 23 | ) 24 | 25 | func testPrefixClaimHash(t *testing.T, prefixClaim *netboxv1.PrefixClaim, expectedHash string) { 26 | generatedHash := generatePrefixRestorationHash(prefixClaim) 27 | 28 | if generatedHash != expectedHash { 29 | t.Errorf("hash mistatch: expected %#v, got %#v from %#v", expectedHash, generatedHash, prefixClaim) 30 | } 31 | } 32 | 33 | func TestBackwardCompatibilityOfGeneratePrefixRestorationHash(t *testing.T) { 34 | { 35 | // output observed when applied config/samples/netbox_v1_prefixclaim.yaml on commit 064e6b 36 | // concatenated string = "defaultprefixclaim-sample2.0.0.0/16/28Dunder-Mifflin, Inc." 37 | // sha1 = "a0601ac7e6d196a82c0e61f9be17313113c3043f" 38 | prefixClaim := &netboxv1.PrefixClaim{ 39 | Spec: netboxv1.PrefixClaimSpec{ 40 | ParentPrefix: "2.0.0.0/16", // not used, as we read from the ParentPrefix in Status 41 | PrefixLength: "/28", 42 | Tenant: "Dunder-Mifflin, Inc.", 43 | ParentPrefixSelector: nil, // TODO(henrybear327): check the default value of this 44 | }, 45 | Status: netboxv1.PrefixClaimStatus{ 46 | SelectedParentPrefix: "2.0.0.0/16", 47 | }, 48 | } 49 | prefixClaim.Namespace = "default" 50 | prefixClaim.Name = "prefixclaim-sample" 51 | 52 | testPrefixClaimHash(t, prefixClaim, "a0601ac7e6d196a82c0e61f9be17313113c3043f") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/controller/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 controller 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "strings" 24 | 25 | apismeta "k8s.io/apimachinery/pkg/api/meta" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/client-go/tools/record" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 30 | "sigs.k8s.io/controller-runtime/pkg/log" 31 | ) 32 | 33 | func convertCIDRToLeaseLockName(cidr string) string { 34 | return strings.ReplaceAll(strings.ReplaceAll(cidr, "/", "-"), ":", "-") 35 | } 36 | 37 | func generateManagedCustomFieldsAnnotation(customFields map[string]string) (string, error) { 38 | if customFields == nil { 39 | customFields = make(map[string]string) 40 | } 41 | 42 | metadataJSON, err := json.Marshal(customFields) 43 | if err != nil { 44 | return "", fmt.Errorf("failed to marshal custom fields to JSON: %w", err) 45 | } 46 | 47 | return string(metadataJSON), nil 48 | } 49 | 50 | func removeFinalizer(ctx context.Context, c client.Client, o client.Object, finalizerName string) error { 51 | logger := log.FromContext(ctx) 52 | if controllerutil.ContainsFinalizer(o, finalizerName) { 53 | logger.V(4).Info("removing the finalizer") 54 | controllerutil.RemoveFinalizer(o, finalizerName) 55 | if err := c.Update(ctx, o); err != nil { 56 | return err 57 | } 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func addFinalizer(ctx context.Context, c client.Client, o client.Object, finalizerName string) error { 64 | logger := log.FromContext(ctx) 65 | if !controllerutil.ContainsFinalizer(o, finalizerName) { 66 | logger.V(4).Info("add the finalizer") 67 | controllerutil.AddFinalizer(o, finalizerName) 68 | if err := c.Update(ctx, o); err != nil { 69 | return err 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | 76 | type EventStatusRecorder struct { 77 | client client.Client 78 | rec record.EventRecorder 79 | } 80 | 81 | func NewEventStatusRecorder(client client.Client, rec record.EventRecorder) *EventStatusRecorder { 82 | return &EventStatusRecorder{ 83 | client: client, 84 | rec: rec, 85 | } 86 | } 87 | 88 | func (esr *EventStatusRecorder) Report(ctx context.Context, o ObjectWithConditions, condition metav1.Condition, eventType string, errExt error, additionalMessages ...string) error { 89 | logger := log.FromContext(ctx) 90 | 91 | if errExt != nil { 92 | condition.Message = condition.Message + ": " + errExt.Error() 93 | logger.Error(errExt, condition.Message) 94 | } 95 | 96 | condition.Message = strings.Join(append([]string{condition.Message}, additionalMessages...), ", ") 97 | condition.ObservedGeneration = o.GetGeneration() 98 | 99 | conditionChanged := apismeta.SetStatusCondition(o.Conditions(), condition) 100 | if conditionChanged { 101 | esr.rec.Event(o, eventType, condition.Reason, condition.Message) 102 | logger.Info("Condition "+condition.Type+" changed to "+string(condition.Status), "Reason", condition.Reason, "Message", condition.Message) 103 | 104 | err := esr.client.Status().Update(ctx, o) 105 | if err != nil { 106 | return err 107 | } 108 | } 109 | 110 | return nil 111 | } 112 | 113 | func (esr *EventStatusRecorder) Recorder() record.EventRecorder { 114 | return esr.rec 115 | } 116 | -------------------------------------------------------------------------------- /kind/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../config/default 3 | - secret.yaml 4 | - namespace.yaml 5 | 6 | patches: 7 | - patch: |- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | spec: 14 | template: 15 | spec: 16 | containers: 17 | - name: manager 18 | image: netbox-operator:build-local 19 | env: 20 | - name: NETBOX_HOST 21 | value: "netbox.default.svc.cluster.local" 22 | - name: HTTPS_ENABLE 23 | value: "false" 24 | #local netbox instance token, not a real secret 25 | volumeMounts: 26 | - mountPath: "/config.env" 27 | subPath: "config.env" 28 | name: netbox-auth 29 | readOnly: true 30 | volumes: 31 | - name: netbox-auth 32 | secret: 33 | secretName: netbox-auth 34 | -------------------------------------------------------------------------------- /kind/load-data-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: netbox-demo-data-load-job 5 | spec: 6 | template: 7 | spec: 8 | restartPolicy: OnFailure 9 | containers: 10 | - name: netbox-demo-data-load 11 | image: ghcr.io/zalando/spilo-16:3.2-p3 12 | volumeMounts: 13 | - name: netbox-demo-data-load-job-scripts 14 | mountPath: /load-data-job 15 | env: 16 | - name: PGPASSWORD 17 | valueFrom: 18 | secretKeyRef: 19 | key: password 20 | name: netbox.netbox-db.credentials.postgresql.acid.zalan.do 21 | - name: NAMESPACE 22 | valueFrom: 23 | fieldRef: 24 | fieldPath: metadata.namespace 25 | command: ["/bin/sh", "/load-data-job/load-data.sh"] 26 | volumes: 27 | - name: netbox-demo-data-load-job-scripts 28 | configMap: 29 | name: netbox-demo-data-load-job-scripts 30 | -------------------------------------------------------------------------------- /kind/load-data-job/README.md: -------------------------------------------------------------------------------- 1 | # load-data-job 2 | 3 | Due to database schema changes cross major/minor NetBox versions, we have to `patch` the SQL files and demo data link on-the-fly. 4 | 5 | The default values stems from the NetBox 4.1.x version. So the patching will only happen for 3.7.x and 4.0.x versions. 6 | 7 | Please see `../local-env.sh`, that's where all the patching happen. 8 | -------------------------------------------------------------------------------- /kind/load-data-job/dockerfile.orig: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | ADD main.py . 3 | RUN pip install -Iv pynetbox==7.4.1 4 | CMD ["python", "./main.py"] 5 | -------------------------------------------------------------------------------- /kind/load-data-job/load-data.orig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -o errexit 3 | 4 | TMP_SQL_FILE=$(mktemp /tmp/netbox-data-dump.XXXXXXX.sql) || exit 1 5 | curl -k https://raw.githubusercontent.com/netbox-community/netbox-demo-data/master/sql/netbox-demo-v4.1.sql > "${TMP_SQL_FILE}" 6 | psql "user=netbox host=netbox-db.${NAMESPACE}.svc.cluster.local" netbox -q -f "${TMP_SQL_FILE}" 7 | rm "${TMP_SQL_FILE}" 8 | psql "user=netbox host=netbox-db.${NAMESPACE}.svc.cluster.local" netbox -q -f /load-data-job/local-data-setup.sql 9 | -------------------------------------------------------------------------------- /kind/load-data-job/local-data-setup.sql: -------------------------------------------------------------------------------- 1 | -- insert User Token 2 | INSERT INTO public.users_token (id, created, expires, key, write_enabled, description, user_id, allowed_ips, last_used) 3 | VALUES (1, '2024-06-14 12:20:13.317942+00', NULL, '0123456789abcdef0123456789abcdef01234567', true, 'test-token', 1, '{}', NULL); 4 | -------------------------------------------------------------------------------- /kind/local-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -u -o pipefail 3 | 4 | NAMESPACE="" 5 | VERSION="4.1.8" # default value 6 | while [[ $# -gt 0 ]]; do 7 | case $1 in 8 | -n|--namespace) 9 | NAMESPACE="$2" 10 | shift # past argument 11 | shift # past value 12 | ;; 13 | -v|--version) 14 | VERSION="$2" 15 | shift # past argument 16 | shift # past value 17 | ;; 18 | -*|--*) 19 | echo "Unknown option $1" 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | echo "=======Parsed arguments=======" 26 | echo "Namespace = ${NAMESPACE}" 27 | echo "Version = ${VERSION}" 28 | echo "==============================" 29 | 30 | # aurgment check / init 31 | if [ -z "$NAMESPACE" ]; then 32 | echo "Using default namespace" 33 | NAMESPACE="default" 34 | else 35 | echo "Using namespace: $NAMESPACE" 36 | fi 37 | 38 | # create a kind cluster 39 | kind create cluster || echo "cluster already exists, continuing..." 40 | kubectl wait --for=jsonpath='{.status.phase}'=Active --timeout=1s namespace/${NAMESPACE} 41 | 42 | ./kind/deploy-netbox.sh kind $VERSION $NAMESPACE 43 | -------------------------------------------------------------------------------- /kind/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: netbox-operator-system 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: deployment 9 | app.kubernetes.io/instance: controller-manager 10 | app.kubernetes.io/component: manager 11 | app.kubernetes.io/created-by: netbox-operator 12 | app.kubernetes.io/part-of: netbox-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | -------------------------------------------------------------------------------- /kind/netbox-db.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "acid.zalan.do/v1" 2 | kind: postgresql 3 | metadata: 4 | name: netbox-db 5 | spec: 6 | teamId: "netbox" 7 | volume: 8 | size: 100Mi 9 | numberOfInstances: 1 10 | enableMasterLoadBalancer: true 11 | users: 12 | # database owner 13 | netbox: 14 | - superuser 15 | - createdb 16 | 17 | #databases: name->owner 18 | databases: 19 | netbox: netbox 20 | postgresql: 21 | version: "16" 22 | -------------------------------------------------------------------------------- /kind/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: netbox-auth 5 | namespace: netbox-operator-system 6 | data: 7 | #local netbox instance token, not a real secret 8 | config.env: QVVUSF9UT0tFTj0wMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3Cg== 9 | -------------------------------------------------------------------------------- /kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - config/default 6 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 | /* 18 | Package config contains all global configuration parameters. 19 | */ 20 | package config 21 | 22 | import ( 23 | "errors" 24 | "log" 25 | "os" 26 | "sync" 27 | 28 | "github.com/spf13/viper" 29 | ) 30 | 31 | var configuration *OperatorConfig 32 | 33 | type OperatorConfig struct { 34 | viper *viper.Viper 35 | CaCert string `mapstructure:"CA_CERT"` 36 | NetboxHost string `mapstructure:"NETBOX_HOST"` 37 | AuthToken string `mapstructure:"AUTH_TOKEN"` 38 | HttpsEnable bool `mapstructure:"HTTPS_ENABLE"` 39 | DebugEnable bool `mapstructure:"DEBUG_ENABLE"` 40 | NetboxRestorationHashFieldName string `mapstructure:"NETBOX_RESTORATION_HASH_FIELD_NAME"` 41 | } 42 | 43 | func (c *OperatorConfig) setDefaults() { 44 | c.viper.SetDefault("CA_CERT", "") 45 | c.viper.SetDefault("NETBOX_HOST", "") 46 | c.viper.SetDefault("AUTH_TOKEN", "") 47 | c.viper.SetDefault("HTTPS_ENABLE", true) 48 | c.viper.SetDefault("DEBUG_ENABLE", false) 49 | c.viper.SetDefault("NETBOX_RESTORATION_HASH_FIELD_NAME", "netboxOperatorRestorationHash") 50 | } 51 | 52 | func (c *OperatorConfig) LoadCaCert() (cert []byte, err error) { 53 | caCert, err := os.ReadFile(c.CaCert) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return caCert, nil 58 | } 59 | 60 | var once sync.Once 61 | 62 | func GetOperatorConfig() *OperatorConfig { 63 | once.Do(func() { 64 | c := &OperatorConfig{} 65 | c.viper = viper.New() 66 | c.setDefaults() 67 | 68 | c.viper.SetConfigName("config") 69 | c.viper.SetConfigType("env") 70 | c.viper.AddConfigPath("/") 71 | c.viper.AddConfigPath(".") 72 | if err := c.viper.ReadInConfig(); err != nil { 73 | var cfnferr viper.ConfigFileNotFoundError 74 | if !errors.As(err, &cfnferr) { 75 | // Config file was found but another error was produced 76 | log.Fatalf("error reading config file: %s", err) 77 | return 78 | } 79 | } else { 80 | log.Printf("No config file found: %s - continuing...", c.viper.ConfigFileUsed()) 81 | } 82 | c.viper.AutomaticEnv() 83 | 84 | err := c.viper.Unmarshal(c) 85 | if err != nil { 86 | log.Fatalf("error unmarshalling config: %s", err) 87 | return 88 | } 89 | 90 | configuration = c 91 | }) 92 | 93 | return configuration 94 | } 95 | 96 | func GetProtocol() string { 97 | if GetOperatorConfig().HttpsEnable { 98 | return "https" 99 | } 100 | return "http" 101 | } 102 | 103 | func GetBaseUrl() string { 104 | return GetProtocol() + "://" + GetOperatorConfig().NetboxHost 105 | } 106 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | //go:build unit 2 | 3 | package config 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const ( 13 | filePath = "." 14 | fileName = "test" 15 | fileFormat = "env" 16 | ) 17 | 18 | func TestLoadConfigFromEnv(t *testing.T) { 19 | os.Setenv("NETBOX_HOST", "netbox_host") 20 | os.Setenv("AUTH_TOKEN", "auth-token") 21 | 22 | configuration := GetOperatorConfig() 23 | 24 | assert.Equal(t, "netbox_host", configuration.NetboxHost) 25 | assert.Equal(t, "auth-token", configuration.AuthToken) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/netbox/api/assertion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "testing" 21 | 22 | netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func AssertNil(t *testing.T, err error) { 27 | 28 | t.Helper() 29 | assert.Nil(t, err) 30 | } 31 | 32 | func AssertError(t *testing.T, err error, msg string) { 33 | t.Helper() 34 | assert.EqualError(t, err, msg) 35 | } 36 | 37 | func AssertIpAddress(t *testing.T, given *netboxModels.WritableIPAddress, actual *netboxModels.IPAddress) { 38 | 39 | t.Helper() 40 | 41 | assert.Greater(t, actual.ID, int64(0)) 42 | assert.Equal(t, given.Address, actual.Address) 43 | assert.Equal(t, given.Comments, actual.Comments) 44 | assert.Equal(t, given.Description, actual.Description) 45 | assert.Equal(t, int64(2), actual.Tenant.ID) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/netbox/api/constant.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | const ( 20 | warningComment = " // managed by netbox-operator, please don't edit it in Netbox unless you know what you're doing" 21 | maxAllowedDescriptionLength = 200 // max length of description field in Netbox 22 | minWarningCommentLength = 30 // len(" // managed by netbox-operator") 23 | ) 24 | -------------------------------------------------------------------------------- /pkg/netbox/api/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import "errors" 20 | 21 | var ( 22 | ErrParentPrefixExhausted = errors.New("parent prefix exhausted") 23 | ErrParentPrefixNotFound = errors.New("parent prefix not found") 24 | ErrWrongMatchingPrefixSubnetFormat = errors.New("wrong matchingPrefix subnet format") 25 | ErrInvalidIpFamily = errors.New("invalid IP Family") 26 | ErrRestorationHashMismatch = errors.New("restoration hash mismatch") 27 | ) 28 | -------------------------------------------------------------------------------- /pkg/netbox/api/helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "net" 23 | "net/url" 24 | "strings" 25 | "unicode/utf8" 26 | 27 | "github.com/go-openapi/runtime" 28 | "github.com/go-openapi/strfmt" 29 | ) 30 | 31 | type CustomFieldEntry struct { 32 | key string 33 | value string 34 | } 35 | 36 | type QueryFilter struct { 37 | netBoxFields map[string]string 38 | customFields []CustomFieldEntry 39 | } 40 | 41 | func newQueryFilterOperation(netBoxFields map[string]string, customFields []CustomFieldEntry) func(co *runtime.ClientOperation) { 42 | return func(co *runtime.ClientOperation) { 43 | co.Params = &QueryFilter{ 44 | netBoxFields: netBoxFields, 45 | customFields: customFields, 46 | } 47 | } 48 | } 49 | 50 | func (o *QueryFilter) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 51 | // We currently write the request by ANDing all the custom fields 52 | 53 | // The idea is to provide filtering of tenant and site here 54 | // Doing string filtering on tenant and site doesn't really work though, so we will use tenant_id and site_id instead 55 | // The query format is like the following: http://localhost:8080/ipam/prefixes/?q=&site_id=2 56 | for key, value := range o.netBoxFields { 57 | if err := r.SetQueryParam(url.QueryEscape(key), url.QueryEscape(value)); err != nil { 58 | return err 59 | } 60 | } 61 | 62 | // The custom field query format is like the following: http://localhost:8080/ipam/prefixes/?q=&cf_poolName=Pool+2&cf_environment=Production 63 | // The GitHub issue related to supporting multiple custom field in a query: https://github.com/netbox-community/netbox/issues/7163 64 | for _, entry := range o.customFields { 65 | if err := r.SetQueryParam(fmt.Sprintf("cf_%s", url.QueryEscape(entry.key)), entry.value); err != nil { 66 | return err 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func TruncateDescription(description string) string { 74 | 75 | // Calculate the remaining space for the comment 76 | remainingSpace := maxAllowedDescriptionLength - minWarningCommentLength 77 | 78 | // Check if the description length exceeds the maximum allowed length 79 | if utf8.RuneCountInString(description+warningComment) > maxAllowedDescriptionLength { 80 | // Truncate the description to fit the remaining space 81 | if utf8.RuneCountInString(description) > remainingSpace { 82 | description = string([]rune(description)[:remainingSpace]) 83 | warning := string([]rune(warningComment)[:minWarningCommentLength]) 84 | return description + warning 85 | } 86 | // Only truncate the warning 87 | return string([]rune(description + warningComment)[:maxAllowedDescriptionLength]) 88 | } 89 | 90 | return description + warningComment 91 | } 92 | 93 | func SetIpAddressMask(ip string, ipFamily int64) (string, error) { 94 | var ipAddress net.IP 95 | var err error 96 | if strings.Contains(ip, "/") { 97 | ipAddress, _, err = net.ParseCIDR(ip) 98 | if err != nil { 99 | return "", err 100 | } 101 | } else { 102 | ipAddress = net.ParseIP(ip) 103 | if ipAddress == nil { 104 | return "", fmt.Errorf("invalid IP address: %s", ip) 105 | } 106 | } 107 | 108 | switch ipFamily { 109 | case int64(IPv4Family): 110 | return ipAddress.String() + ipMaskIPv4, nil 111 | case int64(IPv6Family): 112 | return ipAddress.String() + ipMaskIPv6, nil 113 | default: 114 | return "", errors.New("unknown IP family") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pkg/netbox/api/ip_address_claim.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | 23 | "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" 24 | "github.com/netbox-community/netbox-operator/pkg/config" 25 | "github.com/netbox-community/netbox-operator/pkg/netbox/models" 26 | "github.com/netbox-community/netbox-operator/pkg/netbox/utils" 27 | ) 28 | 29 | type IPFamily int64 30 | 31 | const ( 32 | IPv4Family IPFamily = iota + 4 33 | _ // Skip 5 34 | IPv6Family 35 | ) 36 | 37 | const ( 38 | ipMaskIPv4 = "/32" 39 | ipMaskIPv6 = "/128" 40 | ) 41 | 42 | func (r *NetboxClient) RestoreExistingIpByHash(hash string) (*models.IPAddress, error) { 43 | customIpSearch := newQueryFilterOperation(nil, []CustomFieldEntry{ 44 | { 45 | key: config.GetOperatorConfig().NetboxRestorationHashFieldName, 46 | value: hash, 47 | }, 48 | }) 49 | list, err := r.Ipam.IpamIPAddressesList(ipam.NewIpamIPAddressesListParams(), nil, customIpSearch) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | // TODO: find a better way? 55 | if list.Payload.Count != nil && *list.Payload.Count == 0 { 56 | return nil, nil 57 | } 58 | 59 | // We should not have more than 1 result... 60 | if len(list.Payload.Results) != 1 { 61 | return nil, fmt.Errorf("incorrect number of restoration results, number of results: %v", len(list.Payload.Results)) 62 | } 63 | res := list.Payload.Results[0] 64 | if res.Address == nil { 65 | return nil, errors.New("ipaddress in netbox is nil") 66 | } 67 | 68 | return &models.IPAddress{ 69 | IpAddress: *res.Address, 70 | }, nil 71 | } 72 | 73 | // GetAvailableIpAddressByClaim searches an available IpAddress in Netbox matching IpAddressClaim requirements 74 | func (r *NetboxClient) GetAvailableIpAddressByClaim(ipAddressClaim *models.IPAddressClaim) (*models.IPAddress, error) { 75 | _, err := r.GetTenantDetails(ipAddressClaim.Metadata.Tenant) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | responseParentPrefix, err := r.GetPrefix(&models.Prefix{ 81 | Prefix: ipAddressClaim.ParentPrefix, 82 | Metadata: ipAddressClaim.Metadata, 83 | }) 84 | if err != nil { 85 | return nil, err 86 | } 87 | if len(responseParentPrefix.Payload.Results) == 0 { 88 | return nil, utils.NetboxNotFoundError("parent prefix") 89 | } 90 | 91 | parentPrefixId := responseParentPrefix.Payload.Results[0].ID 92 | responseAvailableIPs, err := r.GetAvailableIpAddressesByParentPrefix(parentPrefixId) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | ipAddress, err := SetIpAddressMask(responseAvailableIPs.Payload[0].Address, responseAvailableIPs.Payload[0].Family) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | return &models.IPAddress{ 103 | IpAddress: ipAddress, 104 | }, nil 105 | } 106 | 107 | func (r *NetboxClient) GetAvailableIpAddressesByParentPrefix(parentPrefixId int64) (*ipam.IpamPrefixesAvailableIpsListOK, error) { 108 | requestAvailableIPs := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixId) 109 | responseAvailableIPs, err := r.Ipam.IpamPrefixesAvailableIpsList(requestAvailableIPs, nil) 110 | if err != nil { 111 | return nil, err 112 | } 113 | if len(responseAvailableIPs.Payload) == 0 { 114 | return nil, ErrParentPrefixExhausted 115 | } 116 | return responseAvailableIPs, nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/netbox/api/site.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "github.com/netbox-community/go-netbox/v3/netbox/client/dcim" 21 | 22 | "github.com/netbox-community/netbox-operator/pkg/netbox/models" 23 | "github.com/netbox-community/netbox-operator/pkg/netbox/utils" 24 | ) 25 | 26 | func (r *NetboxClient) GetSiteDetails(name string) (*models.Site, error) { 27 | request := dcim.NewDcimSitesListParams().WithName(&name) 28 | response, err := r.Dcim.DcimSitesList(request, nil) 29 | if err != nil { 30 | return nil, utils.NetboxError("failed to fetch Site details", err) 31 | } 32 | if len(response.Payload.Results) == 0 { 33 | return nil, utils.NetboxNotFoundError("site '" + name + "'") 34 | } 35 | 36 | return &models.Site{ 37 | Id: response.Payload.Results[0].ID, 38 | Slug: *response.Payload.Results[0].Slug, 39 | Name: *response.Payload.Results[0].Name, 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/netbox/api/site_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/netbox-community/netbox-operator/gen/mock_interfaces" 24 | 25 | "github.com/netbox-community/go-netbox/v3/netbox/client/dcim" 26 | netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" 27 | "github.com/stretchr/testify/assert" 28 | "go.uber.org/mock/gomock" 29 | ) 30 | 31 | func TestSite_GetSiteDetails(t *testing.T) { 32 | ctrl := gomock.NewController(t) 33 | defer ctrl.Finish() 34 | mockDcim := mock_interfaces.NewMockDcimInterface(ctrl) 35 | 36 | site := "mySite" 37 | 38 | siteListRequestInput := dcim.NewDcimSitesListParams().WithName(&site) 39 | 40 | siteOutputId := int64(1) 41 | siteOutputSlug := "mysite" 42 | siteListOutput := &dcim.DcimSitesListOK{ 43 | Payload: &dcim.DcimSitesListOKBody{ 44 | Results: []*netboxModels.Site{ 45 | { 46 | ID: siteOutputId, 47 | Name: &site, 48 | Slug: &siteOutputSlug, 49 | }, 50 | }, 51 | }, 52 | } 53 | 54 | mockDcim.EXPECT().DcimSitesList(siteListRequestInput, nil).Return(siteListOutput, nil) 55 | netboxClient := &NetboxClient{Dcim: mockDcim} 56 | 57 | actual, err := netboxClient.GetSiteDetails(site) 58 | assert.NoError(t, err) 59 | assert.Equal(t, site, actual.Name) 60 | assert.Equal(t, siteOutputId, actual.Id) 61 | assert.Equal(t, siteOutputSlug, actual.Slug) 62 | } 63 | 64 | func TestSite_GetEmptyResult(t *testing.T) { 65 | ctrl := gomock.NewController(t) 66 | defer ctrl.Finish() 67 | mockDcim := mock_interfaces.NewMockDcimInterface(ctrl) 68 | 69 | site := "mySite" 70 | 71 | siteListRequestInput := dcim.NewDcimSitesListParams().WithName(&site) 72 | 73 | emptyListSiteOutput := &dcim.DcimSitesListOK{ 74 | Payload: &dcim.DcimSitesListOKBody{ 75 | Results: []*netboxModels.Site{}, 76 | }, 77 | } 78 | 79 | mockDcim.EXPECT().DcimSitesList(siteListRequestInput, nil).Return(emptyListSiteOutput, nil) 80 | netboxClient := &NetboxClient{Dcim: mockDcim} 81 | 82 | actual, err := netboxClient.GetSiteDetails(site) 83 | assert.Nil(t, actual) 84 | assert.EqualError(t, err, "failed to fetch site 'mySite': not found") 85 | } 86 | 87 | func TestSite_GetError(t *testing.T) { 88 | ctrl := gomock.NewController(t) 89 | defer ctrl.Finish() 90 | mockDcim := mock_interfaces.NewMockDcimInterface(ctrl) 91 | 92 | site := "mySite" 93 | 94 | siteListRequestInput := dcim.NewDcimSitesListParams().WithName(&site) 95 | 96 | expectedErr := "error geting sites list" 97 | 98 | mockDcim.EXPECT().DcimSitesList(siteListRequestInput, nil).Return(nil, errors.New(expectedErr)) 99 | netboxClient := &NetboxClient{Dcim: mockDcim} 100 | 101 | actual, err := netboxClient.GetSiteDetails(site) 102 | assert.Nil(t, actual) 103 | assert.EqualError(t, err, "failed to fetch Site details: "+expectedErr) 104 | } 105 | -------------------------------------------------------------------------------- /pkg/netbox/api/tenancy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "github.com/netbox-community/go-netbox/v3/netbox/client/tenancy" 21 | 22 | "github.com/netbox-community/netbox-operator/pkg/netbox/models" 23 | "github.com/netbox-community/netbox-operator/pkg/netbox/utils" 24 | ) 25 | 26 | func (r *NetboxClient) GetTenantDetails(name string) (*models.Tenant, error) { 27 | request := tenancy.NewTenancyTenantsListParams().WithName(&name) 28 | response, err := r.Tenancy.TenancyTenantsList(request, nil) 29 | if err != nil { 30 | return nil, utils.NetboxError("failed to fetch Tenant details", err) 31 | } 32 | if len(response.Payload.Results) == 0 { 33 | return nil, utils.NetboxNotFoundError("tenant '" + name + "'") 34 | } 35 | 36 | return &models.Tenant{ 37 | Id: response.Payload.Results[0].ID, 38 | Slug: *response.Payload.Results[0].Slug, 39 | Name: *response.Payload.Results[0].Name, 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/netbox/api/tenancy_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 api 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/netbox-community/netbox-operator/gen/mock_interfaces" 23 | 24 | "github.com/netbox-community/go-netbox/v3/netbox/client/tenancy" 25 | netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" 26 | "github.com/stretchr/testify/assert" 27 | "go.uber.org/mock/gomock" 28 | ) 29 | 30 | func TestTenancy_GetTenantDetails(t *testing.T) { 31 | ctrl := gomock.NewController(t) 32 | defer ctrl.Finish() 33 | mockPrefix := mock_interfaces.NewMockTenancyInterface(ctrl) 34 | 35 | tenant := "myTenant" 36 | 37 | tenantListRequestInput := tenancy.NewTenancyTenantsListParams().WithName(&tenant) 38 | 39 | tenantOutputId := int64(1) 40 | tenantOutputSlug := "mytenant" 41 | tenantListOutput := &tenancy.TenancyTenantsListOK{ 42 | Payload: &tenancy.TenancyTenantsListOKBody{ 43 | Results: []*netboxModels.Tenant{ 44 | { 45 | ID: tenantOutputId, 46 | Name: &tenant, 47 | Slug: &tenantOutputSlug, 48 | }, 49 | }, 50 | }, 51 | } 52 | 53 | mockPrefix.EXPECT().TenancyTenantsList(tenantListRequestInput, nil).Return(tenantListOutput, nil) 54 | netboxClient := &NetboxClient{Tenancy: mockPrefix} 55 | 56 | actual, err := netboxClient.GetTenantDetails(tenant) 57 | assert.NoError(t, err) 58 | assert.Equal(t, tenant, actual.Name) 59 | assert.Equal(t, tenantOutputId, actual.Id) 60 | assert.Equal(t, tenantOutputSlug, actual.Slug) 61 | } 62 | 63 | func TestTenancy_GetWrongTenantDetails(t *testing.T) { 64 | ctrl := gomock.NewController(t) 65 | defer ctrl.Finish() 66 | mockPrefix := mock_interfaces.NewMockTenancyInterface(ctrl) 67 | 68 | wrongTenant := "wrongTenant" 69 | 70 | wrongTenantListRequestInput := tenancy.NewTenancyTenantsListParams().WithName(&wrongTenant) 71 | 72 | emptyListTenantOutput := &tenancy.TenancyTenantsListOK{ 73 | Payload: &tenancy.TenancyTenantsListOKBody{ 74 | Results: []*netboxModels.Tenant{}, 75 | }, 76 | } 77 | 78 | mockPrefix.EXPECT().TenancyTenantsList(wrongTenantListRequestInput, nil).Return(emptyListTenantOutput, nil) 79 | netboxClient := &NetboxClient{Tenancy: mockPrefix} 80 | 81 | actual, err := netboxClient.GetTenantDetails(wrongTenant) 82 | assert.Nil(t, actual) 83 | assert.EqualError(t, err, "failed to fetch tenant 'wrongTenant': not found") 84 | } 85 | -------------------------------------------------------------------------------- /pkg/netbox/interfaces/netbox.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 interfaces 18 | 19 | import ( 20 | "github.com/go-openapi/runtime" 21 | "github.com/netbox-community/go-netbox/v3/netbox/client/dcim" 22 | "github.com/netbox-community/go-netbox/v3/netbox/client/extras" 23 | "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" 24 | "github.com/netbox-community/go-netbox/v3/netbox/client/tenancy" 25 | ) 26 | 27 | type IpamInterface interface { 28 | IpamIPAddressesList(params *ipam.IpamIPAddressesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesListOK, error) 29 | IpamIPAddressesCreate(params *ipam.IpamIPAddressesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesCreateCreated, error) 30 | IpamIPAddressesUpdate(params *ipam.IpamIPAddressesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesUpdateOK, error) 31 | IpamIPAddressesDelete(params *ipam.IpamIPAddressesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesDeleteNoContent, error) 32 | IpamPrefixesAvailableIpsList(params *ipam.IpamPrefixesAvailableIpsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesAvailableIpsListOK, error) 33 | 34 | IpamPrefixesList(params *ipam.IpamPrefixesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesListOK, error) 35 | IpamPrefixesCreate(params *ipam.IpamPrefixesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesCreateCreated, error) 36 | IpamPrefixesUpdate(params *ipam.IpamPrefixesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesUpdateOK, error) 37 | IpamPrefixesDelete(params *ipam.IpamPrefixesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesDeleteNoContent, error) 38 | IpamPrefixesAvailablePrefixesList(params *ipam.IpamPrefixesAvailablePrefixesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesAvailablePrefixesListOK, error) 39 | 40 | IpamIPRangesList(params *ipam.IpamIPRangesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesListOK, error) 41 | IpamIPRangesCreate(params *ipam.IpamIPRangesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesCreateCreated, error) 42 | IpamIPRangesUpdate(params *ipam.IpamIPRangesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesUpdateOK, error) 43 | IpamIPRangesDelete(params *ipam.IpamIPRangesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesDeleteNoContent, error) 44 | IpamIPRangesAvailableIpsList(params *ipam.IpamIPRangesAvailableIpsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesAvailableIpsListOK, error) 45 | } 46 | 47 | type TenancyInterface interface { 48 | TenancyTenantsList(params *tenancy.TenancyTenantsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...tenancy.ClientOption) (*tenancy.TenancyTenantsListOK, error) 49 | } 50 | 51 | type ExtrasInterface interface { 52 | ExtrasCustomFieldsList(params *extras.ExtrasCustomFieldsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...extras.ClientOption) (*extras.ExtrasCustomFieldsListOK, error) 53 | } 54 | 55 | type DcimInterface interface { 56 | DcimSitesList(params *dcim.DcimSitesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...dcim.ClientOption) (*dcim.DcimSitesListOK, error) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/netbox/models/ipam.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 models 18 | 19 | type Tenant struct { 20 | Id int64 `json:"id,omitempty"` 21 | Name string `json:"name,omitempty"` 22 | Slug string `json:"slug,omitempty"` 23 | } 24 | 25 | type Site struct { 26 | Id int64 `json:"id,omitempty"` 27 | Name string `json:"name,omitempty"` 28 | Slug string `json:"slug,omitempty"` 29 | } 30 | 31 | type NetboxMetadata struct { 32 | Comments string `json:"comments,omitempty"` 33 | Custom map[string]string `json:"customFields,omitempty"` 34 | Description string `json:"description,omitempty"` 35 | Region string `json:"region,omitempty"` 36 | Site string `json:"site,omitempty"` 37 | Tenant string `json:"tenant,omitempty"` 38 | } 39 | 40 | type IPAddress struct { 41 | IpAddress string `json:"ipAddress,omitempty"` 42 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 43 | } 44 | 45 | type IPAddressClaim struct { 46 | ParentPrefix string `json:"parentPrefix,omitempty"` 47 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 48 | } 49 | 50 | type Prefix struct { 51 | Prefix string `json:"prefix,omitempty"` 52 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 53 | } 54 | 55 | type PrefixClaim struct { 56 | ParentPrefix string `json:"parentPrefix,omitempty"` 57 | PrefixLength string `json:"prefixLength,omitempty"` 58 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 59 | } 60 | 61 | type IpRange struct { 62 | StartAddress string `json:"startAddress,omitempty"` 63 | EndAddress string `json:"endAddress,omitempty"` 64 | Id int64 `json:"id,omitempty"` 65 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 66 | } 67 | 68 | type IpRangeClaim struct { 69 | ParentPrefix string `json:"prefix,omitempty"` 70 | Size int `json:"size,omitempty"` 71 | Metadata *NetboxMetadata `json:"metadata,omitempty"` 72 | } 73 | -------------------------------------------------------------------------------- /pkg/netbox/utils/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Swisscom (Schweiz) AG. 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 utils 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | var ErrNotFound = errors.New("not found") 26 | 27 | func NetboxError(message string, err error) error { 28 | return fmt.Errorf(message+": %w", err) 29 | } 30 | 31 | func NetboxNotFoundError(name string) error { 32 | return NetboxError("failed to fetch "+name, ErrNotFound) 33 | } 34 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-invalid-parentprefixselector/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: prefixclaim-ipv4-invalid-parentprefixselector 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_prefixclaim.yaml 14 | - name: Check CR spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: PrefixClaim 20 | metadata: 21 | name: prefixclaim-ipv4-invalid-parentprefixselector 22 | spec: 23 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 24 | site: "MY_SITE_2" # Use the `name` value instead of the `slug` value 25 | description: "some description" 26 | comments: "your comments" 27 | preserveInNetbox: true 28 | prefixLength: "/31" 29 | parentPrefixSelector: # The keys and values are case-sensitive 30 | tenant: "niltenant" # Use the `name` value instead of the `slug` value 31 | site: "nilsite" # Use the `name` value instead of the `slug` value 32 | family: "IPv4" # Can only be either IPv4 or IPv6" 33 | # custom fields of your interest 34 | environment: "nilenv" 35 | poolName: "nilpool" 36 | status: 37 | (conditions[?type == 'PrefixAssigned']): 38 | - status: 'False' 39 | - assert: 40 | resource: 41 | apiVersion: v1 42 | kind: Event 43 | type: Warning 44 | reason: PrefixCRNotCreated 45 | source: 46 | component: prefix-claim-controller 47 | message: "Failed to assign prefix, prefix CR creation skipped: failed to fetch tenant 'niltenant': not found" 48 | involvedObject: 49 | apiVersion: netbox.dev/v1 50 | kind: PrefixClaim 51 | name: prefixclaim-ipv4-invalid-parentprefixselector 52 | - name: Cleanup events 53 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource 54 | cleanup: 55 | - script: 56 | content: | 57 | kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-invalid-parentprefixselector -n $NAMESPACE 58 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-invalid-parentprefixselector/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-invalid-parentprefixselector 9 | spec: 10 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 11 | site: "MY_SITE_2" # Use the `name` value instead of the `slug` value 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: true 15 | prefixLength: "/31" 16 | parentPrefixSelector: # The keys and values are case-sensitive 17 | tenant: "niltenant" # Use the `name` value instead of the `slug` value 18 | site: "nilsite" # Use the `name` value instead of the `slug` value 19 | family: "IPv4" # Can only be either IPv4 or IPv6" 20 | # custom fields of your interest 21 | environment: "nilenv" 22 | poolName: "nilpool" 23 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefix-apply-update/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: prefixclaim-ipv4-parentprefix-apply-update 6 | annotations: 7 | description: Tests if creation and update is successful using parentPrefix 8 | # TODO(jstudler): Add update of customFields 9 | # TODO(jstudler): Add update of preserveInNetbox 10 | spec: 11 | steps: 12 | - name: Apply CR 13 | try: 14 | - apply: 15 | file: netbox_v1_prefixclaim.yaml 16 | - name: Check CR spec and status 17 | try: 18 | - assert: 19 | resource: 20 | apiVersion: netbox.dev/v1 21 | kind: PrefixClaim 22 | metadata: 23 | name: prefixclaim-ipv4-parentprefix-apply-update 24 | spec: 25 | comments: your comments 26 | description: some description 27 | parentPrefix: 2.0.2.0/24 28 | prefixLength: /28 29 | site: MY_SITE 30 | tenant: MY_TENANT 31 | status: 32 | parentPrefix: 2.0.2.0/24 33 | prefix: 2.0.2.0/28 34 | prefixName: prefixclaim-ipv4-parentprefix-apply-update 35 | - assert: 36 | resource: 37 | apiVersion: netbox.dev/v1 38 | kind: Prefix 39 | metadata: 40 | name: prefixclaim-ipv4-parentprefix-apply-update 41 | spec: 42 | comments: your comments 43 | description: some description 44 | prefix: 2.0.2.0/28 45 | site: MY_SITE 46 | tenant: MY_TENANT 47 | - name: Update CR 48 | try: 49 | - apply: 50 | file: netbox_v1_prefixclaim-update.yaml 51 | - name: Check CR spec and status 52 | try: 53 | - assert: 54 | resource: 55 | apiVersion: netbox.dev/v1 56 | kind: PrefixClaim 57 | metadata: 58 | name: prefixclaim-ipv4-parentprefix-apply-update 59 | spec: 60 | comments: new comments 61 | description: new description 62 | parentPrefix: 2.0.2.0/24 63 | prefixLength: /28 64 | site: MY_SITE 65 | tenant: MY_TENANT 66 | status: 67 | parentPrefix: 2.0.2.0/24 68 | prefix: 2.0.2.0/28 69 | prefixName: prefixclaim-ipv4-parentprefix-apply-update 70 | - assert: 71 | resource: 72 | apiVersion: netbox.dev/v1 73 | kind: Prefix 74 | metadata: 75 | name: prefixclaim-ipv4-parentprefix-apply-update 76 | spec: 77 | comments: new comments 78 | description: new description 79 | prefix: 2.0.2.0/28 80 | site: MY_SITE 81 | tenant: MY_TENANT 82 | - name: Cleanup events 83 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource 84 | cleanup: 85 | - script: 86 | content: | 87 | kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-parentprefix-apply-update -n $NAMESPACE 88 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefix-apply-update/netbox_v1_prefixclaim-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefix-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "new description" 13 | comments: "new comments" 14 | preserveInNetbox: false 15 | parentPrefix: "2.0.2.0/24" 16 | prefixLength: "/28" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefix-apply-update/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefix-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | parentPrefix: "2.0.2.0/24" 16 | prefixLength: "/28" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefix-restore/netbox_v1_prefixclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefix-restore-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: true 15 | parentPrefix: "2.0.3.0/24" 16 | prefixLength: "/28" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefix-restore/netbox_v1_prefixclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefix-restore-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: true 15 | parentPrefix: "2.0.3.0/24" 16 | prefixLength: "/28" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield 6 | annotations: 7 | description: Tests if creation fails for parentPrefixSelector with non exising custom field 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_prefixclaim.yaml 14 | - name: Check CR spec 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: PrefixClaim 20 | metadata: 21 | name: prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield 22 | spec: 23 | comments: your comments 24 | description: some description 25 | parentPrefixSelector: # The keys and values are case-sensitive 26 | tenant: "MY_TENANT" 27 | site: "MY_SITE" 28 | family: "IPv4" 29 | environment: "Production" 30 | poolName: "Pool 1" 31 | nonexisingfield: "value" 32 | prefixLength: /31 33 | preserveInNetbox: false 34 | site: MY_SITE_2 35 | tenant: MY_TENANT_2 36 | - name: Check CR status 37 | try: 38 | - assert: 39 | resource: 40 | apiVersion: netbox.dev/v1 41 | kind: PrefixClaim 42 | metadata: 43 | name: prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield 44 | status: 45 | (conditions[?type == 'PrefixAssigned']): 46 | - status: 'False' 47 | reason: 'PrefixCRNotCreated' 48 | - assert: 49 | resource: 50 | apiVersion: v1 51 | kind: Event 52 | type: Warning 53 | reason: PrefixCRNotCreated 54 | source: 55 | component: prefix-claim-controller 56 | message: "Failed to assign prefix, prefix CR creation skipped: invalid parentPrefixSelector, netbox custom fields nonexisingfield do not exist" 57 | involvedObject: 58 | apiVersion: netbox.dev/v1 59 | kind: PrefixClaim 60 | name: prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield 61 | - name: Cleanup events 62 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource 63 | cleanup: 64 | - script: 65 | content: | 66 | kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield -n $NAMESPACE 67 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefixselector-nonexisingcustomfield 9 | spec: 10 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 11 | site: "MY_SITE_2" # Use the `name` value instead of the `slug` value 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | prefixLength: "/31" 16 | parentPrefixSelector: # The keys and values are case-sensitive 17 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 18 | site: "MY_SITE" # Use the `name` value instead of the `slug` value 19 | family: "IPv4" # Can only be either IPv4 or IPv6" 20 | # custom fields of your interest 21 | environment: "Production" 22 | poolName: "Pool 1" 23 | nonexisingfield: "value" 24 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector-restore/netbox_v1_prefixclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefixselector-restore-1 9 | spec: 10 | tenant: "MY_TENANT_2" 11 | site: "MY_SITE_2" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: true 15 | prefixLength: "/28" 16 | parentPrefixSelector: # The keys and values are case-sensitive 17 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 18 | site: "MY_SITE" # Use the `name` value instead of the `slug` value 19 | family: "IPv4" # Can only be either IPv4 or IPv6" 20 | # custom fields of your interest 21 | environment: "Production" 22 | poolName: "Pool 2" 23 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector-restore/netbox_v1_prefixclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefixselector-restore-2 9 | spec: 10 | tenant: "MY_TENANT_2" 11 | site: "MY_SITE_2" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | prefixLength: "/28" 16 | parentPrefixSelector: # The keys and values are case-sensitive 17 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 18 | site: "MY_SITE" # Use the `name` value instead of the `slug` value 19 | family: "IPv4" # Can only be either IPv4 or IPv6" 20 | # custom fields of your interest 21 | environment: "Production" 22 | poolName: "Pool 2" 23 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: prefixclaim-ipv4-parentprefixselector-apply 6 | annotations: 7 | description: Tests if creation succeeds when using parentPrefixSelector 8 | spec: 9 | steps: 10 | - name: Apply CR 1 11 | try: 12 | - apply: 13 | file: netbox_v1_prefixclaim.yaml 14 | - name: Check CR 1 spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: PrefixClaim 20 | metadata: 21 | name: prefixclaim-ipv4-parentprefixselector-apply 22 | spec: 23 | comments: your comments 24 | description: some description 25 | parentPrefixSelector: 26 | environment: Production 27 | family: IPv4 28 | poolName: Pool 1 29 | site: MY_SITE 30 | tenant: MY_TENANT 31 | prefixLength: /31 32 | preserveInNetbox: true 33 | site: MY_SITE_2 34 | tenant: MY_TENANT_2 35 | status: 36 | parentPrefix: 3.0.1.0/24 37 | prefix: 3.0.1.0/31 38 | prefixName: prefixclaim-ipv4-parentprefixselector-apply 39 | - assert: 40 | resource: 41 | apiVersion: netbox.dev/v1 42 | kind: Prefix 43 | metadata: 44 | name: prefixclaim-ipv4-parentprefixselector-apply 45 | spec: 46 | comments: your comments 47 | description: some description 48 | prefix: 3.0.1.0/31 49 | preserveInNetbox: true 50 | tenant: MY_TENANT_2 51 | customFields: 52 | netboxOperatorRestorationHash: 46116345cc81820fdb412dc83e7147d4b1dc1afa 53 | - name: Cleanup events 54 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource 55 | cleanup: 56 | - script: 57 | content: | 58 | kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-parentprefixselector-apply -n $NAMESPACE 59 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-parentprefixselector/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-parentprefixselector-apply 9 | spec: 10 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 11 | site: "MY_SITE_2" # Use the `name` value instead of the `slug` value 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: true 15 | prefixLength: "/31" 16 | parentPrefixSelector: # The keys and values are case-sensitive 17 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 18 | site: "MY_SITE" # Use the `name` value instead of the `slug` value 19 | family: "IPv4" # Can only be either IPv4 or IPv6" 20 | # custom fields of your interest 21 | environment: "Production" 22 | poolName: "Pool 1" 23 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-prefixexhausted/netbox_v1_prefixclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-prefixexhausted-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | parentPrefix: "2.0.1.0/24" 16 | prefixLength: "/25" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-prefixexhausted/netbox_v1_prefixclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-prefixexhausted-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | parentPrefix: "2.0.1.0/24" 16 | prefixLength: "/25" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv4/prefixclaim-ipv4-prefixexhausted/netbox_v1_prefixclaim_3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: PrefixClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: prefixclaim-ipv4-prefixexhausted-3 9 | spec: 10 | tenant: "MY_TENANT" 11 | site: "MY_SITE" 12 | description: "some description" 13 | comments: "your comments" 14 | preserveInNetbox: false 15 | parentPrefix: "2.0.1.0/24" 16 | prefixLength: "/25" 17 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv6/prefixclaim-ipv6-apply-update/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chainsaw.kyverno.io/v1alpha1 2 | kind: Test 3 | metadata: 4 | name: prefixclaim-ipv6-apply-update 5 | annotations: 6 | description: Tests if creation and update is successful 7 | # TODO(jstudler): Add update of custom fields 8 | # TODO(jstudler): Add update of preserveInNetbox 9 | spec: 10 | steps: 11 | - name: Apply CR 12 | try: 13 | - apply: 14 | file: netbox_v1_prefixclaim.yaml 15 | - name: Check CR spec and status 16 | try: 17 | - assert: 18 | resource: 19 | apiVersion: netbox.dev/v1 20 | kind: PrefixClaim 21 | metadata: 22 | name: prefixclaim-ipv6-apply-update 23 | spec: 24 | comments: your comments 25 | description: some description 26 | parentPrefixSelector: 27 | environment: production 28 | family: IPv6 29 | poolName: pool 4 30 | tenant: MY_TENANT 31 | prefixLength: /124 32 | preserveInNetbox: true 33 | tenant: MY_TENANT_2 34 | status: 35 | parentPrefix: 2::/64 36 | prefix: 2::/124 37 | prefixName: prefixclaim-ipv6-apply-update 38 | - assert: 39 | resource: 40 | apiVersion: netbox.dev/v1 41 | kind: Prefix 42 | metadata: 43 | name: prefixclaim-ipv6-apply-update 44 | spec: 45 | comments: your comments 46 | description: some description 47 | prefix: 2::/124 48 | preserveInNetbox: true 49 | tenant: MY_TENANT_2 50 | - name: Update CR 51 | try: 52 | - apply: 53 | file: netbox_v1_prefixclaim-update.yaml 54 | - name: Check CR spec and status 55 | try: 56 | - assert: 57 | resource: 58 | apiVersion: netbox.dev/v1 59 | kind: PrefixClaim 60 | metadata: 61 | name: prefixclaim-ipv6-apply-update 62 | spec: 63 | comments: new comments 64 | description: new description 65 | parentPrefixSelector: 66 | environment: production 67 | family: IPv6 68 | poolName: pool 4 69 | tenant: MY_TENANT 70 | prefixLength: /124 71 | preserveInNetbox: true 72 | tenant: MY_TENANT_2 73 | status: 74 | parentPrefix: 2::/64 75 | prefix: 2::/124 76 | prefixName: prefixclaim-ipv6-apply-update 77 | - assert: 78 | resource: 79 | apiVersion: netbox.dev/v1 80 | kind: Prefix 81 | metadata: 82 | name: prefixclaim-ipv6-apply-update 83 | spec: 84 | comments: new comments 85 | description: new description 86 | prefix: 2::/124 87 | preserveInNetbox: true 88 | tenant: MY_TENANT_2 89 | - name: Cleanup events 90 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource 91 | cleanup: 92 | - script: 93 | content: | 94 | kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv6-apply-update -n $NAMESPACE 95 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv6/prefixclaim-ipv6-apply-update/netbox_v1_prefixclaim-update.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefixclaim-ipv6-apply-update 8 | spec: 9 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 10 | description: "new description" 11 | comments: "new comments" 12 | preserveInNetbox: true 13 | prefixLength: "/124" 14 | parentPrefixSelector: # The keys and values are case-sensitive 15 | # if the entry for tenant or site is missing, it will *not* inherit from the tenant and site from the Spec 16 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 17 | family: "IPv6" # Can only be either IPv4 or IPv6" 18 | environment: "production" 19 | poolName: "pool 4" 20 | -------------------------------------------------------------------------------- /tests/e2e/Prefix/IPv6/prefixclaim-ipv6-apply-update/netbox_v1_prefixclaim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: netbox.dev/v1 2 | kind: PrefixClaim 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: netbox-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: prefixclaim-ipv6-apply-update 8 | spec: 9 | tenant: "MY_TENANT_2" # Use the `name` value instead of the `slug` value 10 | description: "some description" 11 | comments: "your comments" 12 | preserveInNetbox: true 13 | prefixLength: "/124" 14 | parentPrefixSelector: # The keys and values are case-sensitive 15 | # if the entry for tenant or site is missing, it will *not* inherit from the tenant and site from the Spec 16 | tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value 17 | family: "IPv6" # Can only be either IPv4 or IPv6" 18 | 19 | environment: "production" 20 | poolName: "pool 4" 21 | -------------------------------------------------------------------------------- /tests/e2e/README.md: -------------------------------------------------------------------------------- 1 | # e2e tests 2 | 3 | # How to run the test locally? 4 | 5 | - [Install](https://kyverno.github.io/chainsaw/latest/quick-start/install/) `chainsaw`: `go install github.com/kyverno/chainsaw@latest` 6 | - Execute `make test-e2e` 7 | 8 | # How to add a new test locally? 9 | 10 | - Create a clean cluster `kind delete cluster --name kind && make create-kind deploy-kind && kubectl port-forward deploy/netbox 8080:8080 -n default` 11 | - During development, we would need to have a clean NetBox instance between runs, which we can do with the following commands as soon as we create a new cluster 12 | - Backup: `kubectl exec pod/netbox-db-0 -- bash -c "pg_dump --clean -U postgres netbox" > database.sql` 13 | - The simplest test case is `tests/e2e/prefix/ipv4/prefixclaim-ipv4-apply-update` 14 | - We always need a `chainsaw-test.yaml` 15 | - Perform a clean run by resetting the database first, then execute the test 16 | - Reset database `cat database.sql | kubectl exec -i pod/netbox-db-0 -- psql -U postgres -d netbox` 17 | - Make sure that in the `e2e` namespace, no leftover CRs are there 18 | - Execute the entire e2e test `make test-e2e` 19 | - Or just perform a specific run, e.g. `chainsaw test --test-dir tests/e2e/prefix/ipv4/prefixclaim-ipv4-apply-update` 20 | 21 | # Some debugging tips 22 | 23 | - `kubectl get prefixclaim,prefix,ipaddressclaim,ipaddress,iprange,iprangeclaim -A` 24 | - For monitoring failures, we can use event (which is a native k8s object) 25 | - e.g. `kubectl events --for prefixclaim.netbox.dev/prefixclaim-apply-prefixexhausted-3 -o yaml` 26 | - I am not entirely sure why is resetting the database not clean enough, maybe the redis is doing some caching that I am not aware of 27 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-apply-update/netbox_v1_ipaddressclaim-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "new description" 12 | comments: "new comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.0.0/24" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-apply-update/netbox_v1_ipaddressclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.0.0/24" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-prefixexhausted/netbox_v1_ipaddressclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-prefixexhausted-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.1.0/30" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-prefixexhausted/netbox_v1_ipaddressclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-prefixexhausted-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.1.0/30" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-prefixexhausted/netbox_v1_ipaddressclaim_3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-prefixexhausted-3 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.1.0/30" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-restore/netbox_v1_ipaddressclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-restore-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | parentPrefix: "3.1.2.0/24" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-restore/netbox_v1_ipaddressclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv4-restore-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.1.2.0/24" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-apply-update/netbox_v1_ipaddressclaim-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "new description" 12 | comments: "new comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:0::/64" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-apply-update/netbox_v1_ipaddressclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:0::/64" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-prefixexhausted/netbox_v1_ipaddressclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-prefixexhausted-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:1::/127" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-prefixexhausted/netbox_v1_ipaddressclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-prefixexhausted-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:1::/127" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-prefixexhausted/netbox_v1_ipaddressclaim_3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-prefixexhausted-3 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:1::/127" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-restore/netbox_v1_ipaddressclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-restore-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | parentPrefix: "3:1:2::/64" 15 | -------------------------------------------------------------------------------- /tests/e2e/ipaddress/ipv6/ipaddressclaim-ipv6-restore/netbox_v1_ipaddressclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpAddressClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: ipaddressclaim-ipv6-restore-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:1:2::/64" 15 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-apply-update/netbox_v1_iprangeclaim-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "new description" 12 | comments: "new comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.0.0/24" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-apply-update/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.0.0/24" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-cidr/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-cidr 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | expect: 15 | - check: 16 | ($error != null): true 17 | - name: Cleanup events and leases 18 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 19 | cleanup: 20 | - script: 21 | content: | 22 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 23 | if [ -n "$LEASES" ]; then 24 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 25 | fi 26 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-cidr -n $NAMESPACE 27 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-cidr/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-cidr 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "300.256.999.446/42" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-customfieldnotexisting/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | - name: Check CR spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: IpRangeClaim 20 | metadata: 21 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 22 | spec: 23 | customFields: 24 | justmade: "thisup" 25 | status: 26 | (conditions[?type == 'IPRangeAssigned']): 27 | - status: 'True' 28 | (conditions[?type == 'Ready']): 29 | - status: 'False' 30 | - assert: 31 | resource: 32 | apiVersion: netbox.dev/v1 33 | kind: IpRange 34 | metadata: 35 | finalizers: 36 | - iprange.netbox.dev/finalizer 37 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 38 | ownerReferences: 39 | - apiVersion: netbox.dev/v1 40 | blockOwnerDeletion: true 41 | controller: true 42 | kind: IpRangeClaim 43 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 44 | spec: 45 | customFields: 46 | justmade: thisup 47 | status: 48 | (conditions[?type == 'Ready']): 49 | - status: 'False' 50 | - assert: 51 | resource: 52 | apiVersion: v1 53 | kind: Event 54 | type: Warning 55 | reason: FailedToReserveIPRangeInNetbox 56 | source: 57 | component: ip-range-controller 58 | message: "Failed to reserve IP Range in NetBox: failed to reserve IP Range: [POST /ipam/ip-ranges/][400] ipam_ip-ranges_create default map[__all__:[Unknown field name 'justmade' in custom field data.]], range: 3.2.3.1/32-3.2.3.30/32" 59 | involvedObject: 60 | apiVersion: netbox.dev/v1 61 | kind: IpRange 62 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 63 | - name: Cleanup events and leases 64 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 65 | cleanup: 66 | - script: 67 | content: | 68 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 69 | if [ -n "$LEASES" ]; then 70 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 71 | fi 72 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-customfieldnotexisting -n $NAMESPACE 73 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-customfieldnotexisting/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-customfieldnotexisting 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.3.0/24" 15 | size: 30 16 | customFields: 17 | justmade: "thisup" -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-customfieldwrongdatatype/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | - name: Check CR spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: IpRangeClaim 20 | metadata: 21 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 22 | spec: 23 | customFields: 24 | cfDataTypeInteger: "butthisisastring" 25 | status: 26 | (conditions[?type == 'IPRangeAssigned']): 27 | - status: 'True' 28 | (conditions[?type == 'Ready']): 29 | - status: 'False' 30 | - assert: 31 | resource: 32 | apiVersion: netbox.dev/v1 33 | kind: IpRange 34 | metadata: 35 | finalizers: 36 | - iprange.netbox.dev/finalizer 37 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 38 | ownerReferences: 39 | - apiVersion: netbox.dev/v1 40 | blockOwnerDeletion: true 41 | controller: true 42 | kind: IpRangeClaim 43 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 44 | spec: 45 | customFields: 46 | cfDataTypeInteger: "butthisisastring" 47 | status: 48 | (conditions[?type == 'Ready']): 49 | - status: 'False' 50 | - assert: 51 | resource: 52 | apiVersion: v1 53 | kind: Event 54 | type: Warning 55 | reason: FailedToReserveIPRangeInNetbox 56 | source: 57 | component: ip-range-controller 58 | message: "Failed to reserve IP Range in NetBox: failed to reserve IP Range: [POST /ipam/ip-ranges/][400] ipam_ip-ranges_create default map[__all__:[Unknown field name 'cfDataTypeInteger' in custom field data.]], range: 3.2.3.1/32-3.2.3.30/32" 59 | involvedObject: 60 | apiVersion: netbox.dev/v1 61 | kind: IpRange 62 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 63 | - name: Cleanup events and leases 64 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 65 | cleanup: 66 | - script: 67 | content: | 68 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 69 | if [ -n "$LEASES" ]; then 70 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 71 | fi 72 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-customfieldwrongdatatype -n $NAMESPACE 73 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-customfieldwrongdatatype/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-customfieldwrongdatatype 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.3.0/24" 15 | size: 30 16 | customFields: 17 | cfDataTypeInteger: "butthisisastring" 18 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-parentprefix/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-parentprefix 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | - name: Check CR spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: IpRangeClaim 20 | metadata: 21 | name: iprangeclaim-ipv4-invalid-parentprefix 22 | spec: 23 | parentPrefix: "123.45.67.89/26" 24 | - assert: 25 | resource: 26 | apiVersion: v1 27 | kind: Event 28 | type: Warning 29 | reason: IPRangeCRNotCreated 30 | source: 31 | component: ip-range-claim-controller 32 | message: "Failed to fetch new IP Range from NetBox: failed to fetch parent prefix: not found" 33 | involvedObject: 34 | apiVersion: netbox.dev/v1 35 | kind: IpRangeClaim 36 | name: iprangeclaim-ipv4-invalid-parentprefix 37 | - name: Cleanup events and leases 38 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 39 | cleanup: 40 | - script: 41 | content: | 42 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 43 | if [ -n "$LEASES" ]; then 44 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 45 | fi 46 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-parentprefix -n $NAMESPACE 47 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-parentprefix/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-parentprefix 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "123.45.67.89/26" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-size/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-size 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | expect: 15 | - check: 16 | ($error != null): true 17 | - name: Cleanup events and leases 18 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 19 | cleanup: 20 | - script: 21 | content: | 22 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 23 | if [ -n "$LEASES" ]; then 24 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 25 | fi 26 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-size -n $NAMESPACE 27 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-size/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-size 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.3.0/24" 15 | size: 260 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-tenant/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: iprangeclaim-ipv4-invalid-tenant 6 | annotations: 7 | description: Tests if creation fails 8 | spec: 9 | steps: 10 | - name: Apply CR 11 | try: 12 | - apply: 13 | file: netbox_v1_iprangeclaim.yaml 14 | - name: Check CR spec and status 15 | try: 16 | - assert: 17 | resource: 18 | apiVersion: netbox.dev/v1 19 | kind: IpRangeClaim 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: netbox-operator 23 | app.kubernetes.io/managed-by: kustomize 24 | name: iprangeclaim-ipv4-invalid-tenant 25 | spec: 26 | tenant: "nonexistingtenant" 27 | status: 28 | (conditions[?type == 'IPRangeAssigned']): 29 | - status: 'False' 30 | - assert: 31 | resource: 32 | apiVersion: v1 33 | kind: Event 34 | type: Warning 35 | reason: IPRangeCRNotCreated 36 | source: 37 | component: ip-range-claim-controller 38 | message: "Failed to fetch new IP Range from NetBox: failed to fetch tenant 'nonexistingtenant': not found" 39 | involvedObject: 40 | apiVersion: netbox.dev/v1 41 | kind: IpRangeClaim 42 | name: iprangeclaim-ipv4-invalid-tenant 43 | - name: Cleanup events and leases 44 | description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) 45 | cleanup: 46 | - script: 47 | content: | 48 | LEASES=$(kubectl -n netbox-operator-system get lease -oname | grep -v netbox) # to be enhanced in usage of leaselocker 49 | if [ -n "$LEASES" ]; then 50 | echo "$LEASES" | xargs -n1 kubectl -n netbox-operator-system delete 51 | fi 52 | kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-tenant -n $NAMESPACE 53 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-tenant/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-invalid-tenant 9 | spec: 10 | tenant: "nonexistingtenant" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.3.0/24" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-prefixexhausted/netbox_v1_iprangeclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-prefixexhausted-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.1.0/26" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-prefixexhausted/netbox_v1_iprangeclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-prefixexhausted-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.1.0/26" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-prefixexhausted/netbox_v1_iprangeclaim_3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-prefixexhausted-3 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.1.0/26" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-restore/netbox_v1_iprangeclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-restore-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | parentPrefix: "3.2.2.0/24" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv4/iprangeclaim-ipv4-restore/netbox_v1_iprangeclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv4-restore-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3.2.2.0/24" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-apply-update/netbox_v1_iprangeclaim-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "new description" 12 | comments: "new comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:0::/64" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-apply-update/netbox_v1_iprangeclaim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-apply-update 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:0::/64" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-prefixexhausted/netbox_v1_iprangeclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-prefixexhausted-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:1::/122" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-prefixexhausted/netbox_v1_iprangeclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-prefixexhausted-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:1::/122" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-prefixexhausted/netbox_v1_iprangeclaim_3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-prefixexhausted-3 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:1::/122" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-restore/netbox_v1_iprangeclaim_1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-restore-1 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: true 14 | parentPrefix: "3:2:2::/64" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/iprange/ipv6/iprangeclaim-ipv6-restore/netbox_v1_iprangeclaim_2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: netbox.dev/v1 3 | kind: IpRangeClaim 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: netbox-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: iprangeclaim-ipv6-restore-2 9 | spec: 10 | tenant: "MY_TENANT" 11 | description: "some description" 12 | comments: "your comments" 13 | preserveInNetbox: false 14 | parentPrefix: "3:2:2::/64" 15 | size: 30 16 | -------------------------------------------------------------------------------- /tests/e2e/kind-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://kind.sigs.k8s.io/docs/user/configuration/ 2 | kind: Cluster 3 | apiVersion: kind.x-k8s.io/v1alpha4 4 | name: kind 5 | 6 | nodes: 7 | - role: control-plane 8 | - role: worker 9 | - role: worker 10 | -------------------------------------------------------------------------------- /tools/.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - goheader 6 | - ineffassign 7 | - nakedret 8 | - revive 9 | - staticcheck 10 | - unconvert 11 | - unparam 12 | - unused 13 | settings: 14 | goheader: 15 | values: 16 | regexp: 17 | AUTHOR: ^Copyright 2024 (The Kubernetes authors|Swisscom \(Schweiz\) AG)\. 18 | template: |- 19 | {{ AUTHOR }} 20 | 21 | Licensed under the Apache License, Version 2.0 (the "License"); 22 | you may not use this file except in compliance with the License. 23 | You may obtain a copy of the License at 24 | 25 | http://www.apache.org/licenses/LICENSE-2.0 26 | 27 | Unless required by applicable law or agreed to in writing, software 28 | distributed under the License is distributed on an "AS IS" BASIS, 29 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | See the License for the specific language governing permissions and 31 | limitations under the License. 32 | revive: 33 | confidence: 0.8 34 | severity: error 35 | enable-all-rules: false 36 | rules: 37 | - name: blank-imports 38 | severity: error 39 | disabled: false 40 | - name: context-as-argument 41 | severity: error 42 | disabled: false 43 | - name: dot-imports 44 | severity: error 45 | disabled: true 46 | - name: error-return 47 | severity: error 48 | disabled: false 49 | - name: error-naming 50 | severity: error 51 | disabled: false 52 | - name: if-return 53 | severity: error 54 | disabled: false 55 | - name: increment-decrement 56 | severity: error 57 | disabled: false 58 | - name: var-declaration 59 | severity: error 60 | disabled: false 61 | - name: package-comments 62 | severity: error 63 | disabled: false 64 | - name: range 65 | severity: error 66 | disabled: false 67 | - name: receiver-naming 68 | severity: error 69 | disabled: false 70 | - name: time-naming 71 | severity: error 72 | disabled: false 73 | - name: indent-error-flow 74 | severity: error 75 | disabled: false 76 | - name: errorf 77 | severity: error 78 | disabled: false 79 | - name: context-keys-type 80 | severity: error 81 | disabled: false 82 | - name: error-strings 83 | severity: error 84 | disabled: false 85 | staticcheck: 86 | # removed: "IP", "HTTPS", "URL" from defaults 87 | initialisms: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] 88 | checks: 89 | - all 90 | - -QF1008 91 | - -QF1003 92 | exclusions: 93 | generated: lax 94 | presets: 95 | - comments 96 | - common-false-positives 97 | - legacy 98 | - std-error-handling 99 | paths: 100 | - ^zz_generated.* 101 | - third_party$ 102 | - builtin$ 103 | - examples$ 104 | issues: 105 | max-same-issues: 0 106 | formatters: 107 | enable: 108 | - goimports 109 | exclusions: 110 | generated: lax 111 | paths: 112 | - ^zz_generated.* 113 | - third_party$ 114 | - builtin$ 115 | - examples$ 116 | --------------------------------------------------------------------------------