├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── dockerhub-description.yml │ ├── fossa.yml │ ├── lint.yml │ ├── release-drafter.yml │ └── stale.yml ├── .gitignore ├── .gitlab-ci.yml ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── nginxingresscontroller_types.go │ └── zz_generated.deepcopy.go ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── k8s.nginx.org_nginxingresscontrollers.yaml │ ├── nginx-ingress-operator-controller-manager-metrics-service_v1_service.yaml │ ├── nginx-ingress-operator-manager-config_v1_configmap.yaml │ ├── nginx-ingress-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ └── nginx-ingress-operator.clusterserviceversion.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── config ├── crd │ ├── bases │ │ └── k8s.nginx.org_nginxingresscontrollers.yaml │ ├── kic │ │ ├── appprotect.f5.com_aplogconfs.yaml │ │ ├── appprotect.f5.com_appolicies.yaml │ │ ├── appprotect.f5.com_apusersigs.yaml │ │ ├── k8s.nginx.org_globalconfigurations.yaml │ │ ├── k8s.nginx.org_policies.yaml │ │ ├── k8s.nginx.org_transportservers.yaml │ │ ├── k8s.nginx.org_virtualserverroutes.yaml │ │ └── k8s.nginx.org_virtualservers.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_nginxingresscontrollers.yaml │ │ └── webhook_in_nginxingresscontrollers.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── nginx-ingress-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── nginxingresscontroller_editor_role.yaml │ ├── nginxingresscontroller_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── k8s_v1alpha1_nginxingresscontroller.yaml │ └── kustomization.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── configmap.go ├── crds.go ├── daemonset.go ├── daemonset_test.go ├── deployment.go ├── deployment_test.go ├── ingressclass.go ├── ingressclass_test.go ├── nginxingresscontroller_controller.go ├── prerequisites.go ├── rbac.go ├── rbac_test.go ├── scc │ ├── scc.go │ └── scc_test.go ├── secret.go ├── secret_test.go ├── service.go ├── service_test.go ├── serviceaccount.go ├── serviceaccount_test.go ├── suite_test.go ├── utils.go └── utils_test.go ├── docs ├── images │ ├── openshift1.png │ ├── openshift2.png │ ├── openshift3.png │ └── openshift4.png ├── installation.md ├── manual-installation.md ├── nginx-ingress-controller.md ├── openshift-installation.md └── upgrades.md ├── examples ├── deployment-oss-min │ ├── README.md │ ├── global-configuration.yaml │ ├── nginx-ingress-controller.yaml │ └── ns.yaml └── deployment-plus-min │ ├── README.md │ ├── global-configuration.yaml │ ├── nginx-ingress-controller.yaml │ └── ns.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── get_image_info.sh └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Deploy x to '...' using some.yaml 13 | 2. View logs on '....' 14 | 3. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Your environment** 20 | * Version of the NGINX Ingress Operator - release version or a specific commit 21 | * Version of the Ingress Controller - release version or a specific commit 22 | * Version of Kubernetes 23 | * Kubernetes platform (e.g. Mini-kube or GCP) 24 | * Using NGINX or NGINX Plus 25 | 26 | **Additional context** 27 | Add any other context about the problem here. Any log files you want to share. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Proposed changes 2 | Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to that issue here in this description (not in the title of the PR). 3 | 4 | ### Checklist 5 | Before creating a PR, run through this checklist and mark each as complete. 6 | 7 | - [ ] I have read the [CONTRIBUTING](https://github.com/nginxinc/nginx-ingress-operator/blob/main/CONTRIBUTING.md) doc 8 | - [ ] I have added tests that prove my fix is effective or that my feature works 9 | - [ ] I have checked that all unit tests pass after adding my changes 10 | - [ ] I have updated necessary documentation 11 | - [ ] I have rebased my branch onto main 12 | - [ ] I will ensure my PR is targeting the main branch and pulling from my branch from my own fork 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | reviewers: 8 | - "nginxinc/kic" 9 | - "ciarams87" 10 | - package-ecosystem: "gomod" 11 | directory: "/" 12 | schedule: 13 | interval: weekly 14 | reviewers: 15 | - "nginxinc/kic" 16 | - package-ecosystem: "docker" 17 | directory: "/" 18 | schedule: 19 | interval: weekly 20 | reviewers: 21 | - "nginxinc/kic" 22 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - "enhancement" 7 | - title: '💣 Breaking Change' 8 | labels: 9 | - "change" 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - "bug" 13 | - title: '📝 Documentation' 14 | labels: 15 | - "documentation" 16 | - title: '🔨 Maintenance' 17 | labels: 18 | - "chore" 19 | - title: '⬆️ Dependencies' 20 | labels: 21 | - "dependencies" 22 | version-resolver: 23 | major: 24 | labels: 25 | - 'change' 26 | minor: 27 | labels: 28 | - 'enhancement' 29 | patch: 30 | labels: 31 | - 'bug' 32 | - 'chore' 33 | - 'dependencies' 34 | - 'documentation' 35 | default: patch 36 | exclude-labels: 37 | - 'skip-changelog' 38 | autolabeler: 39 | - label: 'documentation' 40 | files: 41 | - '*.md' 42 | branch: 43 | - '/docs{0,1}\/.+/' 44 | - label: 'chore' 45 | branch: 46 | - '/chore\/.+/' 47 | - label: 'bug' 48 | branch: 49 | - '/fix\/.+/' 50 | title: 51 | - '/fix/i' 52 | - label: 'enhancement' 53 | branch: 54 | - '/enh\/.+/' 55 | - '/enhancement\/.+/' 56 | - '/feat\/.+/' 57 | - '/feature\/.+/' 58 | - label: 'dependencies' 59 | files: 60 | - 'go.mod' 61 | - 'go.sum' 62 | branch: 63 | - '/deps\/.+/' 64 | template: | 65 | ## New in NGINX Ingress Operator v$RESOLVED_VERSION 66 | 67 | $CHANGES 68 | 69 | ## Compatibility 70 | 71 | - NGINX Ingress Controller NIC_VERSION_REPLACE_ME! 72 | - Openshift 4.6 or newer. 73 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'examples/**' 10 | - '**.md' 11 | tags: 12 | - 'v[0-9]+.[0-9]+.[0-9]+' 13 | pull_request: 14 | branches: 15 | - main 16 | types: 17 | - opened 18 | - reopened 19 | - synchronize 20 | paths-ignore: 21 | - 'docs/**' 22 | - 'examples/**' 23 | - '**.md' 24 | 25 | env: 26 | platforms: "linux/amd64,linux/arm64,linux/ppc64le,linux/s390x" 27 | 28 | concurrency: 29 | group: ${{ github.ref_name }}-ci 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | 34 | vars: 35 | name: Get variables 36 | runs-on: ubuntu-20.04 37 | outputs: 38 | sha_short: ${{ steps.vars.outputs.sha }} 39 | go_version: ${{ steps.vars.outputs.go_version }} 40 | repo_name: ${{ steps.vars.outputs.repo }} 41 | steps: 42 | - name: Checkout Repository 43 | uses: actions/checkout@v3 44 | - name: Output Variables 45 | id: vars 46 | run: | 47 | echo "::set-output name=sha::$(echo ${GITHUB_SHA} | cut -c1-7)" 48 | echo "::set-output name=go_version::$(grep "go 1." go.mod | cut -d " " -f 2)" 49 | echo "::set-output name=repo::$(echo ${GITHUB_REPOSITORY} | cut -d '/' -f 2)" 50 | 51 | binary: 52 | name: Build Binary 53 | runs-on: ubuntu-20.04 54 | needs: vars 55 | steps: 56 | - name: Checkout Repository 57 | uses: actions/checkout@v3 58 | - name: Cache Go build 59 | uses: actions/cache@v3 60 | with: 61 | path: | 62 | ~/.cache/go-build 63 | ~/go/pkg/mod 64 | key: ${{ runner.os }}-go-local-build-${{ hashFiles('**/go.sum') }} 65 | restore-keys: | 66 | ${{ runner.os }}-go-local-build- 67 | - name: Setup Golang Environment 68 | uses: actions/setup-go@v3 69 | with: 70 | go-version: ${{ needs.vars.outputs.go_version }} 71 | - name: Build Binary 72 | run: make build 73 | 74 | unit-tests: 75 | name: Unit Tests 76 | runs-on: ubuntu-20.04 77 | needs: vars 78 | steps: 79 | - name: Checkout Repository 80 | uses: actions/checkout@v3 81 | - name: Setup Golang Environment 82 | uses: actions/setup-go@v3 83 | with: 84 | go-version: ${{ needs.vars.outputs.go_version }} 85 | - name: Cache Go tests 86 | uses: actions/cache@v3 87 | with: 88 | path: | 89 | ~/.cache/go-build 90 | ~/go/pkg/mod 91 | key: ${{ runner.os }}-go-tests-${{ hashFiles('**/go.sum') }} 92 | restore-keys: | 93 | ${{ runner.os }}-go-tests- 94 | - name: Run Tests 95 | run: make test 96 | 97 | build: 98 | name: Build Image 99 | runs-on: ubuntu-20.04 100 | needs: [binary, vars, unit-tests] 101 | steps: 102 | - name: Checkout Repository 103 | uses: actions/checkout@v3 104 | with: 105 | fetch-depth: 0 106 | - name: Cache Go build 107 | uses: actions/cache@v3 108 | with: 109 | path: | 110 | ~/.cache/go-build 111 | ~/go/pkg/mod 112 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} 113 | restore-keys: | 114 | ${{ runner.os }}-go-build- 115 | - name: Setup Golang Environment 116 | uses: actions/setup-go@v3 117 | with: 118 | go-version: ${{ needs.vars.outputs.go_version }} 119 | - name: Build binaries 120 | uses: goreleaser/goreleaser-action@v2 121 | with: 122 | version: latest 123 | args: ${{ !startsWith(github.ref, 'refs/tags/') && 'build --snapshot' || 'release' }} ${{ github.event_name == 'pull_request' && '--single-target' || '' }} --rm-dist 124 | env: 125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | GOPATH: ${{ needs.check.outputs.go_path }} 127 | - name: DockerHub Login 128 | uses: docker/login-action@v1 129 | with: 130 | username: ${{ secrets.DOCKER_USERNAME }} 131 | password: ${{ secrets.DOCKER_PASSWORD }} 132 | if: github.event_name != 'pull_request' 133 | - name: Login to GitHub Container Registry 134 | uses: docker/login-action@v1 135 | with: 136 | registry: ghcr.io 137 | username: ${{ github.repository_owner }} 138 | password: ${{ secrets.GITHUB_TOKEN }} 139 | if: github.event_name != 'pull_request' 140 | - name: Setup QEMU 141 | uses: docker/setup-qemu-action@v1 142 | with: 143 | platforms: arm64,ppc64le,s390x 144 | if: github.event_name != 'pull_request' 145 | - name: Docker Buildx 146 | uses: docker/setup-buildx-action@v1 147 | - name: Docker meta 148 | id: meta 149 | uses: docker/metadata-action@v3 150 | with: 151 | images: | 152 | nginx/nginx-ingress-operator 153 | ghcr.io/nginxinc/nginx-ingress-operator 154 | tags: | 155 | type=edge 156 | type=ref,event=pr 157 | type=semver,pattern={{version}} 158 | labels: | 159 | org.opencontainers.image.documentation=https://docs.nginx.com/nginx-ingress-controller 160 | org.opencontainers.image.vendor=NGINX Inc 161 | - name: Output Variables 162 | id: var 163 | run: | 164 | version=${{ steps.meta.outputs.version }} 165 | if ${{ startsWith(github.ref, 'refs/tags/') }}; then 166 | operator_version=v$version 167 | else 168 | tag=$(git describe --tags --abbrev=0) 169 | operator_version=$tag-$version-${{ needs.vars.outputs.sha_short }} 170 | fi 171 | echo "::set-output name=version::$operator_version" 172 | - name: Build Image 173 | uses: docker/build-push-action@v2 174 | with: 175 | context: '.' 176 | cache-from: type=gha 177 | cache-to: type=gha,mode=max 178 | target: goreleaser 179 | tags: ${{ steps.meta.outputs.tags }} 180 | labels: ${{ steps.meta.outputs.labels }} 181 | platforms: ${{ github.event_name != 'pull_request' && env.platforms || '' }} 182 | load: ${{ github.event_name == 'pull_request' }} 183 | push: ${{ github.event_name != 'pull_request' }} 184 | pull: true 185 | build-args: | 186 | VERSION=${{ steps.var.outputs.version }} 187 | - name: Run Trivy vulnerability scanner 188 | uses: aquasecurity/trivy-action@0.2.3 189 | continue-on-error: true 190 | with: 191 | image-ref: nginx/nginx-ingress-operator:${{ steps.meta.outputs.version }} 192 | format: 'sarif' 193 | output: 'trivy-results.sarif' 194 | ignore-unfixed: 'true' 195 | - name: Upload Trivy scan results to GitHub Security tab 196 | uses: github/codeql-action/upload-sarif@v1 197 | continue-on-error: true 198 | with: 199 | sarif_file: 'trivy-results.sarif' 200 | - name: Upload Scan Results 201 | uses: actions/upload-artifact@v3 202 | continue-on-error: true 203 | with: 204 | name: 'trivy-results.sarif' 205 | path: 'trivy-results.sarif' 206 | if: always() 207 | 208 | 209 | notify: 210 | name: Notify 211 | runs-on: ubuntu-20.04 212 | needs: [vars, build] 213 | if: always() && github.ref == 'refs/heads/main' 214 | steps: 215 | - name: Workflow Status 216 | id: check 217 | uses: martialonline/workflow-status@v2 218 | - name: Send Notification 219 | uses: 8398a7/action-slack@v3 220 | if: steps.check.outputs.status == 'failure' 221 | with: 222 | status: custom 223 | custom_payload: | 224 | { 225 | username: 'Github', 226 | icon_emoji: ':octocat:', 227 | mention: 'channel', 228 | attachments: [{ 229 | title: '${{ needs.vars.outputs.repo_name }} ${{ github.workflow }} pipeline has failed', 230 | color: '${{ steps.check.outputs.status }}' == 'failure' ? 'danger' : 'warning', 231 | fields: [{ 232 | title: 'Commit Hash', 233 | value: '${{ needs.vars.outputs.sha_short }}', 234 | short: true 235 | }, 236 | { 237 | title: 'Author', 238 | value: '${{ github.actor }}', 239 | short: true 240 | }, 241 | { 242 | title: 'Commit Message', 243 | value: `${{ github.event.head_commit.message }}`, 244 | short: false 245 | }, 246 | { 247 | title: 'Pipeline URL', 248 | value: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}', 249 | short: false 250 | }] 251 | }] 252 | } 253 | env: 254 | GITHUB_TOKEN: ${{ github.token }} 255 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} 256 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | schedule: 10 | - cron: '45 2 * * 4' 11 | 12 | concurrency: 13 | group: ${{ github.ref_name }}-codeql 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'go' ] 25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 26 | # Learn more: 27 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v1 36 | with: 37 | languages: ${{ matrix.language }} 38 | # If you wish to specify custom queries, you can do so here or in a config file. 39 | # By default, queries listed here will override any specified in a config file. 40 | # Prefix the list here with "+" to use these queries and those in the config file. 41 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 42 | 43 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 44 | # If this step fails, then you should remove it and run the build manually (see below) 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@v1 47 | 48 | # ℹ️ Command-line programs to run using the OS shell. 49 | # 📚 https://git.io/JvXDl 50 | 51 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 52 | # and modify them (or add more) to build your code if your project 53 | # uses a compiled language 54 | 55 | #- run: | 56 | # make bootstrap 57 | # make release 58 | 59 | - name: Perform CodeQL Analysis 60 | uses: github/codeql-action/analyze@v1 61 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-description.yml: -------------------------------------------------------------------------------- 1 | name: Update Docker Hub Description 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - README.md 8 | - .github/workflows/dockerhub-description.yml 9 | 10 | concurrency: 11 | group: ${{ github.ref_name }}-dockerhub-description 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | dockerHubDescription: 16 | runs-on: ubuntu-20.04 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Modify readme for DockerHub 21 | run: | 22 | sed -i '1,2d' README.md 23 | 24 | - name: Docker Hub Description 25 | uses: peter-evans/dockerhub-description@v3 26 | with: 27 | username: ${{ secrets.DOCKER_USERNAME }} 28 | password: ${{ secrets.DOCKER_PASSWORD }} 29 | repository: nginx/nginx-ingress-operator 30 | short-description: ${{ github.event.repository.description }} 31 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | name: Fossa 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**.md' 9 | - 'LICENSE' 10 | 11 | concurrency: 12 | group: ${{ github.ref_name }}-fossa 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | 17 | scan: 18 | name: Fossa 19 | runs-on: ubuntu-20.04 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v3 23 | - name: Scan 24 | uses: fossas/fossa-action@v1 25 | with: 26 | api-key: ${{ secrets.FOSSA_TOKEN }} 27 | 28 | notify: 29 | name: Notify 30 | runs-on: ubuntu-20.04 31 | needs: scan 32 | if: always() 33 | steps: 34 | - name: Workflow Status 35 | id: check 36 | uses: martialonline/workflow-status@v2 37 | - name: Output Variables 38 | id: commit 39 | run: | 40 | echo "::set-output name=sha::$(echo ${GITHUB_SHA} | cut -c1-7)" 41 | echo "::set-output name=repo::${GITHUB_REPOSITORY#*/}" 42 | - name: Send Notification 43 | uses: 8398a7/action-slack@v3 44 | if: steps.check.outputs.status == 'failure' 45 | with: 46 | status: custom 47 | custom_payload: | 48 | { 49 | username: 'Fossa Scan', 50 | icon_emoji: ':fossa:', 51 | mention: 'channel', 52 | attachments: [{ 53 | title: '[${{ steps.commit.outputs.repo }}] ${{ github.workflow }} license scan has failed', 54 | color: 'danger', 55 | fields: [{ 56 | title: 'Commit Hash', 57 | value: '${{ steps.commit.outputs.sha }}', 58 | short: true 59 | }, 60 | { 61 | title: 'Author', 62 | value: '${{ github.actor }}', 63 | short: true 64 | }, 65 | { 66 | title: 'Job URL', 67 | value: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}', 68 | short: false 69 | }] 70 | }] 71 | } 72 | env: 73 | GITHUB_TOKEN: ${{ github.token }} 74 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} 75 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | types: 8 | - opened 9 | - reopened 10 | - synchronize 11 | paths-ignore: 12 | - 'docs/**' 13 | - 'examples/**' 14 | - '**.md' 15 | 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | concurrency: 21 | group: ${{ github.ref_name }}-lint 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | 26 | lint: 27 | name: Lint 28 | runs-on: ubuntu-20.04 29 | steps: 30 | - name: Checkout Repository 31 | uses: actions/checkout@v3 32 | - name: Output Variables 33 | id: vars 34 | run: echo "::set-output name=go_version::$(grep "go 1." go.mod | cut -d " " -f 2)" 35 | - name: Setup Golang Environment 36 | uses: actions/setup-go@v3 37 | with: 38 | go-version: ${{ steps.vars.outputs.go_version }} 39 | - name: Lint Code 40 | uses: golangci/golangci-lint-action@v3 41 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/stale@v5 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days.' 14 | stale-pr-message: 'This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days.' 15 | close-issue-message: 'This issue was closed because it has been stalled for 10 days with no activity.' 16 | close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' 17 | stale-issue-label: 'stale' 18 | stale-pr-label: 'stale' 19 | exempt-all-assignees: true 20 | exempt-issue-labels: 'proposal' 21 | operations-per-run: 100 22 | days-before-stale: 90 23 | days-before-close: 10 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | testbin/* 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | .vscode/ 26 | 27 | dist 28 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - project: "f5/nginx/kic/kic-pipelines" 3 | file: "/include/ingress-operator.yml" 4 | ref: "master" 5 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | misspell: 3 | locale: US 4 | revive: 5 | ignore-generated-header: true 6 | rules: 7 | - name: blank-imports 8 | - name: context-as-argument 9 | - name: context-keys-type 10 | - name: dot-imports 11 | - name: empty-block 12 | - name: error-naming 13 | - name: error-return 14 | - name: error-strings 15 | - name: errorf 16 | - name: exported 17 | - name: if-return 18 | - name: increment-decrement 19 | - name: indent-error-flow 20 | - name: package-comments 21 | - name: range 22 | - name: receiver-naming 23 | - name: redefines-builtin-id 24 | - name: superfluous-else 25 | - name: time-naming 26 | - name: unexported-return 27 | - name: unreachable-code 28 | - name: unused-parameter 29 | - name: var-declaration 30 | - name: var-naming 31 | 32 | linters: 33 | enable: 34 | - asciicheck 35 | - deadcode 36 | - errcheck 37 | - errorlint 38 | - gofmt 39 | - gofumpt 40 | - goimports 41 | - gosec 42 | - gosimple 43 | - govet 44 | - ineffassign 45 | - makezero 46 | - misspell 47 | - nilerr 48 | - noctx 49 | - predeclared 50 | - revive 51 | - staticcheck 52 | - structcheck 53 | - typecheck 54 | - unconvert 55 | - unparam 56 | - unused 57 | - varcheck 58 | - wastedassign 59 | disable-all: true 60 | issues: 61 | max-issues-per-linter: 0 62 | max-same-issues: 0 63 | run: 64 | timeout: 5m 65 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - CGO_ENABLED=0 3 | builds: 4 | - id: nginx-ingress-operator 5 | goos: 6 | - linux 7 | goarch: 8 | - amd64 9 | - arm64 10 | - ppc64le 11 | - s390x 12 | flags: 13 | - -trimpath 14 | gcflags: 15 | - all=-trimpath={{.Env.GOPATH}} 16 | asmflags: 17 | - all=-trimpath={{.Env.GOPATH}} 18 | binary: manager 19 | 20 | archives: 21 | - format: binary 22 | changelog: 23 | skip: true 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 0.5.1 4 | 5 | An automatically generated list of changes can be found on Github at: [0.5.1 Release](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.5.1) 6 | 7 | ### 0.5.0 8 | 9 | An automatically generated list of changes can be found on Github at: [0.5.0 Release](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.5.0) 10 | 11 | ### 0.4.0 12 | 13 | An automatically generated list of changes can be found on Github at: [0.4.0 Release](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.4.0) 14 | 15 | ### 0.3.0 16 | 17 | An automatically generated list of changes can be found on Github at: [0.3.0 Release](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.3.0) 18 | 19 | ### 0.2.0 20 | 21 | An automatically generated list of changes can be found on Github at: [0.2.0 Release](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.2.0) 22 | 23 | ### 0.1.0 24 | 25 | FEATURES: 26 | 27 | * [56](https://github.com/nginxinc/nginx-ingress-operator/pull/56) Graduate Policies. Add enablePreviewPolicy flag support. 28 | * [55](https://github.com/nginxinc/nginx-ingress-operator/pull/55) Add AppProtect User Defined Signatures support. 29 | * [39](https://github.com/nginxinc/nginx-ingress-operator/pull/39) Update secret type of default secret to TLS. 30 | 31 | FIXES: 32 | 33 | * [71](https://github.com/nginxinc/nginx-ingress-operator/pull/71) Fix replicas and service to be optional fields. 34 | * [70](https://github.com/nginxinc/nginx-ingress-operator/pull/70) Make enableCRDs optional. 35 | * [66](https://github.com/nginxinc/nginx-ingress-operator/pull/66) Fix Service to be an optional field. Add support for updating ExtraLabels. 36 | * [65](https://github.com/nginxinc/nginx-ingress-operator/pull/65) Fix SCC resource to only affect KIC pods. 37 | 38 | DOCUMENTATION: 39 | 40 | * [54](https://github.com/nginxinc/nginx-ingress-operator/pull/54) Update IC compatibility in changelog. 41 | 42 | KNOWN ISSUES: 43 | 44 | * The Operator doesn't automatically remove IngressClasses created by [29](https://github.com/nginxinc/nginx-ingress-operator/pull/29). 45 | 46 | COMPATIBILITY: 47 | 48 | - NGINX Ingress Controller 1.10.x 49 | - Openshift 4.5 or newer. 50 | 51 | UPGRADE INSTRUCTIONS: 52 | 53 | 1. Remove the existing Policy CRD: `kubectl delete crd policies.k8s.nginx.org` 54 | **Please note that deletion of the `policies.k8s.nginx.org` CRD will result in all instances of that CRD being deleted too. Ensure to back up any important Custom Resource instances first!** 55 | 1. Delete the existing SCC: `kubectl delete scc nginx-ingress-scc` 56 | 1. Upgrade the operator to version 0.1.0. 57 | 1. If the defaultSecret field is not set in your `nginxingresscontrollers.k8s.nginx.org` resource (or resources): 58 | 1. Remove the generated default secret. For example: `kubectl delete secret -n my-nginx-ingress my-nginx-ingress-controller` 59 | 1. Wait until the operator regenerates the secret. The old secret was of the type `Opaque`. The new secret will be of the type `kubernetes.io/tls`. 60 | 1. Alternatively, if the defaultSecret is set to some secret, make sure it is of the type `kubernetes.io/tls`. If not, recreate the secret with the type `kubernetes.io/tls`. 61 | 1. If the wildcardTLS is set to some secret, make sure it is of the type `kubernetes.io/tls`. If not, recreate the secret with the type `kubernetes.io/tls`. 62 | 1. Ensure that the TLS secrets referenced by Ingress, VirtualServer and Policy resources are of the type `kubernetes.io/tls`, JWT secrets are of the type `nginx.org/jwt` and CA secrets are of the type `nginx.org/ca`. To avoid potential disruption of client traffic, instead of recreating the secrets, create new secrets with the correct type and update the Ingress, VirtualServer and Policy resources to use the new secrets. 63 | 1. Update any existing instances of the `nginxingresscontrollers.k8s.nginx.org` Custom Resource to use an NGINX Ingress Controller 1.10.x image. 64 | 65 | **Note**: Steps 4-8 are required because Version 1.10.0 of the Ingress Controller added a requirement for secrets to be one of the following types: `kubernetes.io/tls` for TLS secrets; `nginx.org/jwk` for JWK secrets; or `nginx.org/ca` for CA secrets. Please see the section UPDATING SECRETS in https://docs.nginx.com/nginx-ingress-controller/releases/#nginx-ingress-controller-1-10-0 for more details. 66 | 67 | ### 0.0.7 68 | 69 | FEATURES: 70 | 71 | * [29](https://github.com/nginxinc/nginx-ingress-operator/pull/29) Add IngressClass support. 72 | * [26](https://github.com/nginxinc/nginx-ingress-operator/pull/26) Add mTLS policy support. 73 | * [25](https://github.com/nginxinc/nginx-ingress-operator/pull/25) Add JWT policy support. 74 | * [21](https://github.com/nginxinc/nginx-ingress-operator/pull/21) Add latency metrics support. 75 | * [18](https://github.com/nginxinc/nginx-ingress-operator/pull/18) Add support for policies in VS routes and VSR subroutes. Add RateLimit policy support 76 | 77 | FIXES: 78 | 79 | * [31](https://github.com/nginxinc/nginx-ingress-operator/pull/31) Add Status update for VS/VSR to RBAC. 80 | 81 | KNOWN ISSUES: 82 | * The Operator doesn't automatically remove IngressClasses created by [29](https://github.com/nginxinc/nginx-ingress-operator/pull/29) 83 | 84 | COMPATIBILITY: 85 | 86 | * NGINX Ingress Controller 1.9.x. 87 | * Openshift 4.5 or newer. 88 | 89 | ### 0.0.6 90 | 91 | FEATURES: 92 | 93 | * [13](https://github.com/nginxinc/nginx-ingress-operator/pull/13) Add support for App Protect. 94 | * [11](https://github.com/nginxinc/nginx-ingress-operator/pull/11) Add enableSnippets cli argument support. 95 | 96 | IMPROVEMENTS: 97 | * [15](https://github.com/nginxinc/nginx-ingress-operator/pull/15) Downgrade operator-sdk to 0.17. 98 | * [14](https://github.com/nginxinc/nginx-ingress-operator/pull/14) Add KIC supported versions to README. 99 | * [12](https://github.com/nginxinc/nginx-ingress-operator/pull/12) Make operator install KIC CRDs from manifests. 100 | * [10](https://github.com/nginxinc/nginx-ingress-operator/pull/10) Update operator-sdk to 0.18. 101 | * [8](https://github.com/nginxinc/nginx-ingress-operator/pull/8) Update go to 1.14. 102 | * [7](https://github.com/nginxinc/nginx-ingress-operator/pull/7) Update makefile to include all manifests. 103 | 104 | COMPATIBILITY: 105 | 106 | * NGINX Ingress Controller 1.8.x. 107 | * Openshift 4.3 or newer. 108 | 109 | ### 0.0.4 110 | 111 | FEATURES: 112 | 113 | * [4](https://github.com/nginxinc/nginx-ingress-operator/pull/4) Add new CRDs for NGINX Ingress Controller 1.7.0 114 | * [5](https://github.com/nginxinc/nginx-ingress-operator/pull/5) Make NGINX Ingress Operator RedHat certified. Learn more about certified operators for Openshift [here](https://connect.redhat.com/en/partner-with-us/red-hat-openshift-operator-certification). 115 | 116 | COMPATIBILITY: 117 | 118 | * NGINX Ingress Controller 1.7.x. 119 | * Openshift 4.3 or newer. 120 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project and everyone participating in it is governed by this code. 4 | 5 | ## Our Pledge 6 | 7 | In the interest of fostering an open and welcoming environment, we as 8 | contributors and maintainers pledge to making participation in our project and 9 | our community a harassment-free experience for everyone, regardless of age, body 10 | size, disability, ethnicity, sex characteristics, gender identity and expression, 11 | level of experience, education, socio-economic status, nationality, personal 12 | appearance, race, religion, or sexual identity and orientation. 13 | 14 | ## Our Standards 15 | 16 | Examples of behavior that contributes to creating a positive environment 17 | include: 18 | 19 | * Using welcoming and inclusive language 20 | * Being respectful of differing viewpoints and experiences 21 | * Gracefully accepting constructive criticism 22 | * Focusing on what is best for the community 23 | * Showing empathy towards other community members 24 | 25 | Examples of unacceptable behavior by participants include: 26 | 27 | * The use of sexualized language or imagery and unwelcome sexual attention or 28 | advances 29 | * Trolling, insulting/derogatory comments, and personal or political attacks 30 | * Public or private harassment 31 | * Publishing others' private information, such as a physical or electronic 32 | address, without explicit permission 33 | * Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | ## Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying the standards of acceptable 39 | behavior and are expected to take appropriate and fair corrective action in 40 | response to any instances of unacceptable behavior. 41 | 42 | Project maintainers have the right and responsibility to remove, edit, or 43 | reject comments, commits, code, wiki edits, issues, and other contributions 44 | that are not aligned to this Code of Conduct, or to ban temporarily or 45 | permanently any contributor for other behaviors that they deem inappropriate, 46 | threatening, offensive, or harmful. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies both within project spaces and in public spaces 51 | when an individual is representing the project or its community. Examples of 52 | representing a project or community include using an official project e-mail 53 | address, posting via an official social media account, or acting as an appointed 54 | representative at an online or offline event. Representation of a project may be 55 | further defined and clarified by project maintainers. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported by contacting the project team at [mailto:nginx@nginx.org]. All 61 | complaints will be reviewed and investigated and will result in a response that 62 | is deemed necessary and appropriate to the circumstances. The project team is 63 | obligated to maintain confidentiality with regard to the reporter of an incident. 64 | Further details of specific enforcement policies may be posted separately. 65 | 66 | Project maintainers who do not follow or enforce the Code of Conduct in good 67 | faith may face temporary or permanent repercussions as determined by other 68 | members of the project's leadership. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | The following is a set of guidelines for contributing to the NGINX Ingress Operator. We really appreciate that you are considering contributing! 4 | 5 | #### Table Of Contents 6 | 7 | [Ask a Question](#ask-a-question) 8 | 9 | [Getting Started](#getting-started) 10 | 11 | [Contributing](#contributing) 12 | 13 | [Style Guides](#style-guides) 14 | * [Git Style Guide](#git-style-guide) 15 | * [Go Style Guide](#go-style-guide) 16 | 17 | [Code of Conduct](https://github.com/nginxinc/nginx-ingress-operator/blob/main/CODE_OF_CONDUCT.md) 18 | 19 | ## Ask a Question 20 | 21 | Please open an Issue on GitHub with the label `question`. 22 | 23 | ## Getting Started 24 | 25 | Follow our [Installation Guide](https://github.com/nginxinc/nginx-ingress-operator/blob/main/docs/installation.md) to get the NGINX Ingress Operator up and running. 26 | 27 | Read the [documentation](https://github.com/nginxinc/nginx-ingress-operator/tree/main/docs) and [examples](https://github.com/nginxinc/nginx-ingress-operator/tree/main/examples). 28 | 29 | ### Project Structure 30 | 31 | * This Operator is written in Go using the operator-framework and supports both the open source NGINX Ingress Controller and NGINX Plus Ingress Controller. 32 | * The project follows a standard Go project layout 33 | * The main code is found at `cmd/manager/` 34 | * The operator code is found at `pkg/` 35 | * Build files for Docker and CI are found under `build/` 36 | * We use [Go Modules](https://github.com/golang/go/wiki/Modules) for managing dependencies. 37 | 38 | ## Contributing 39 | 40 | ### Report a Bug 41 | 42 | To report a bug, open an issue on GitHub with the label `bug` using the available bug report issue template. Please ensure the issue has not already been reported. 43 | 44 | ### Suggest an Enhancement 45 | 46 | To suggest an enhancement, please create an issue on GitHub with the label `enhancement` using the available feature issue template. 47 | 48 | ### Open a Pull Request 49 | 50 | * Fork the repo, create a branch, submit a PR when your changes are tested and ready for review 51 | * Fill in [our pull request template](https://github.com/nginxinc/nginx-ingress-operator/blob/main/.github/PULL_REQUEST_TEMPLATE.md) 52 | 53 | Note: if you’d like to implement a new feature, please consider creating a feature request issue first to start a discussion about the feature. 54 | 55 | ## Style Guides 56 | 57 | ### Git Style Guide 58 | 59 | * Keep a clean, concise and meaningful git commit history on your branch, rebasing locally and squashing before submitting a PR 60 | * Follow the guidelines of writing a good commit message as described here https://chris.beams.io/posts/git-commit/ and summarised in the next few points 61 | * In the subject line, use the present tense ("Add feature" not "Added feature") 62 | * In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to...") 63 | * Limit the subject line to 72 characters or less 64 | * Reference issues and pull requests liberally after the subject line 65 | * Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in your text editor to write a good message instead of `git commit -am`) 66 | 67 | ### Go Style Guide 68 | 69 | * Run `gofmt` over your code to automatically resolve a lot of style issues. Most editors support this running automatically when saving a code file. 70 | * Follow this guide on some good practice and idioms for Go - https://github.com/golang/go/wiki/CodeReviewComments 71 | * To check for extra issues, install [golangci-lint](https://github.com/golangci/golangci-lint) and run `make lint` or `golangci-lint run` 72 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.17 as builder 3 | ARG VERSION 4 | 5 | WORKDIR /workspace 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | # cache deps before building and copying source so that we don't need to re-download as much 10 | # and so that source changes don't invalidate our downloaded layer 11 | RUN go mod download 12 | 13 | # Copy the go source 14 | COPY main.go main.go 15 | COPY api/ api/ 16 | COPY controllers/ controllers/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.version=${VERSION}" -a -o manager main.go 20 | 21 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest as base 22 | # temporary fix for CVE-2022-24407 23 | RUN microdnf --nodocs upgrade -y cyrus-sasl-lib 24 | ARG VERSION 25 | WORKDIR / 26 | COPY config/crd/kic ./config/crd/kic 27 | COPY LICENSE /licenses/ 28 | 29 | LABEL name="NGINX Ingress Operator" \ 30 | maintainer="kubernetes@nginx.com" \ 31 | vendor="NGINX Inc" \ 32 | version="${VERSION}" \ 33 | release="1" \ 34 | summary="The NGINX Ingress Operator is a Kubernetes/OpenShift component which deploys and manages one or more NGINX/NGINX Plus Ingress Controllers" \ 35 | description="The NGINX Ingress Operator is a Kubernetes/OpenShift component which deploys and manages one or more NGINX/NGINX Plus Ingress Controllers" 36 | 37 | ENTRYPOINT ["/manager"] 38 | 39 | USER 1001 40 | 41 | FROM base as goreleaser 42 | ARG TARGETARCH 43 | ARG TARGETVARIANT 44 | 45 | LABEL org.nginx.kic.image.build.version="goreleaser" 46 | 47 | COPY ./dist/nginx-ingress-operator_linux_$TARGETARCH/manager / 48 | 49 | FROM base as local 50 | 51 | LABEL org.nginx.kic.image.build.version="local" 52 | 53 | COPY --from=builder /workspace/manager . 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 F5 Networks, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: nginx.org 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: nginx-ingress-operator 8 | repo: github.com/nginxinc/nginx-ingress-operator 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: nginx.org 15 | group: k8s 16 | kind: NginxIngressController 17 | path: github.com/nginxinc/nginx-ingress-operator/api/v1alpha1 18 | version: v1alpha1 19 | version: "3" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Project Status: Moved to http://example.com – The project has been moved to a new location, and the version at that location should be considered authoritative.](https://www.repostatus.org/badges/latest/moved.svg)](https://www.repostatus.org/#moved) to [nginx-ingress-helm-operator]([http://example.com](https://github.com/nginxinc/nginx-ingress-helm-operator)) 2 | 3 | [![Project Status: Abandoned – Initial development has started, but there has not yet been a stable, usable release; the project has been abandoned and the author(s) do not intend on continuing development.](https://www.repostatus.org/badges/latest/abandoned.svg)](https://www.repostatus.org/#abandoned) 4 | 5 | [![Continuous Integration](https://github.com/nginxinc/nginx-ingress-operator/workflows/Continuous%20Integration/badge.svg)](https://github.com/nginxinc/nginx-ingress-operator/actions) [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fnginx-ingress-operator.svg?type=shield)](https://app.fossa.com/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fnginx-ingress-operator?ref=badge_shield) 6 | 7 | # NGINX Ingress Operator 8 | 9 | **WARNING - DEPRECATION NOTICE: The NGINX Ingress Operator has been updated to be a Helm based operator. This repo has been deprecated and will soon be archived - the new NGINX Ingress Operator repo can be found at https://github.com/nginxinc/nginx-ingress-helm-operator.** 10 | 11 | The NGINX Ingress Operator is a Kubernetes/OpenShift component which deploys and manages one or more [NGINX/NGINX Plus Ingress Controllers](https://github.com/nginxinc/kubernetes-ingress) which in turn handle Ingress traffic for applications running in a cluster. 12 | 13 | Learn more about operators in the [Kubernetes Documentation](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/). 14 | 15 | To install a specific version of the NGINX Ingress Controller with the operator, a specific version of the NGINX Ingress Operator is required. 16 | 17 | The following table shows the relation between the versions of the two projects: 18 | 19 | | NGINX Ingress Controller | NGINX Ingress Operator | 20 | | --- | --- | 21 | | 2.1.x | 0.5.1 | 22 | | 2.0.x | 0.4.0 | 23 | | 1.12.x | 0.3.0 | 24 | | 1.11.x | 0.2.0 | 25 | | 1.10.x | 0.1.0 | 26 | | 1.9.x | 0.0.7 | 27 | | 1.8.x | 0.0.6 | 28 | | 1.7.x | 0.0.4 | 29 | | < 1.7.0 | N/A | 30 | 31 | Note: The NGINX Ingress Operator works only for NGINX Ingress Controller versions after `1.7.0`. 32 | 33 | ## Getting Started 34 | 35 | 1. Install the NGINX Ingress Operator. See [docs](./docs/installation.md). 36 |
NOTE: To use TransportServers as part of your NGINX Ingress Controller configuration, a GlobalConfiguration resource must be created *before* starting the Operator - [see the notes](./examples/deployment-oss-min/README.md#TransportServers) 37 | 1. Deploy a new NGINX Ingress Controller using the [NginxIngressController](docs/nginx-ingress-controller.md) Custom Resource: 38 | * For an NGINX installation see the [NGINX example](./examples/deployment-oss-min). 39 | * For an NGINX Plus installation see the [NGINX Plus example](./examples/deployment-plus-min). 40 | 41 | ## Upgrades 42 | 43 | See [upgrade docs](./docs/upgrades) 44 | 45 | ## NGINX Ingress Operator Releases 46 | We publish NGINX Ingress Operator releases on GitHub. See our [releases page](https://github.com/nginxinc/nginx-ingress-operator/releases). 47 | 48 | The latest stable release is [0.5.1](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.5.1). For production use, we recommend that you choose the latest stable release. 49 | 50 | ## Development 51 | 52 | It is possible to run the operator in your local machine. This is useful for testing or during development. 53 | 54 | ### Run Operator locally 55 | 56 | 1. Have access to a Kubernetes/Openshift cluster. 57 | 1. Apply the latest CRDs: 58 | ``` 59 | make install 60 | kubectl apply -f config/crd/kic/ 61 | ``` 62 | 2. Run `make run`. 63 | 64 | The operator will run in your local machine but will be communicating with the cluster. 65 | 66 | ### Update CRD 67 | 68 | If any change is made in the CRD in the go code, run the following commands to update the changes in the CRD yaml: 69 | 70 | 1. `make manifests` 71 | 2. Apply the new CRD definition again in your cluster `make install`. 72 | 73 | ### Run tests 74 | 75 | Run `make test` to run the full test suite including envtest, or `make unit-test` to run just the unit tests locally. 76 | 77 | ## Contributing 78 | 79 | If you'd like to contribute to the project, please read our [Contributing](./CONTRIBUTING.md) guide. 80 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the k8s v1alpha1 API group 18 | //+kubebuilder:object:generate=true 19 | //+groupName=k8s.nginx.org 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "k8s.nginx.org", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2021. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *AppProtect) DeepCopyInto(out *AppProtect) { 30 | *out = *in 31 | } 32 | 33 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProtect. 34 | func (in *AppProtect) DeepCopy() *AppProtect { 35 | if in == nil { 36 | return nil 37 | } 38 | out := new(AppProtect) 39 | in.DeepCopyInto(out) 40 | return out 41 | } 42 | 43 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 44 | func (in *AppProtectDos) DeepCopyInto(out *AppProtectDos) { 45 | *out = *in 46 | } 47 | 48 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProtectDos. 49 | func (in *AppProtectDos) DeepCopy() *AppProtectDos { 50 | if in == nil { 51 | return nil 52 | } 53 | out := new(AppProtectDos) 54 | in.DeepCopyInto(out) 55 | return out 56 | } 57 | 58 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 59 | func (in *HealthStatus) DeepCopyInto(out *HealthStatus) { 60 | *out = *in 61 | } 62 | 63 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthStatus. 64 | func (in *HealthStatus) DeepCopy() *HealthStatus { 65 | if in == nil { 66 | return nil 67 | } 68 | out := new(HealthStatus) 69 | in.DeepCopyInto(out) 70 | return out 71 | } 72 | 73 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 74 | func (in *Image) DeepCopyInto(out *Image) { 75 | *out = *in 76 | } 77 | 78 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. 79 | func (in *Image) DeepCopy() *Image { 80 | if in == nil { 81 | return nil 82 | } 83 | out := new(Image) 84 | in.DeepCopyInto(out) 85 | return out 86 | } 87 | 88 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 89 | func (in *NginxIngressController) DeepCopyInto(out *NginxIngressController) { 90 | *out = *in 91 | out.TypeMeta = in.TypeMeta 92 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 93 | in.Spec.DeepCopyInto(&out.Spec) 94 | out.Status = in.Status 95 | } 96 | 97 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxIngressController. 98 | func (in *NginxIngressController) DeepCopy() *NginxIngressController { 99 | if in == nil { 100 | return nil 101 | } 102 | out := new(NginxIngressController) 103 | in.DeepCopyInto(out) 104 | return out 105 | } 106 | 107 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 108 | func (in *NginxIngressController) DeepCopyObject() runtime.Object { 109 | if c := in.DeepCopy(); c != nil { 110 | return c 111 | } 112 | return nil 113 | } 114 | 115 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 116 | func (in *NginxIngressControllerList) DeepCopyInto(out *NginxIngressControllerList) { 117 | *out = *in 118 | out.TypeMeta = in.TypeMeta 119 | in.ListMeta.DeepCopyInto(&out.ListMeta) 120 | if in.Items != nil { 121 | in, out := &in.Items, &out.Items 122 | *out = make([]NginxIngressController, len(*in)) 123 | for i := range *in { 124 | (*in)[i].DeepCopyInto(&(*out)[i]) 125 | } 126 | } 127 | } 128 | 129 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxIngressControllerList. 130 | func (in *NginxIngressControllerList) DeepCopy() *NginxIngressControllerList { 131 | if in == nil { 132 | return nil 133 | } 134 | out := new(NginxIngressControllerList) 135 | in.DeepCopyInto(out) 136 | return out 137 | } 138 | 139 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 140 | func (in *NginxIngressControllerList) DeepCopyObject() runtime.Object { 141 | if c := in.DeepCopy(); c != nil { 142 | return c 143 | } 144 | return nil 145 | } 146 | 147 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 148 | func (in *NginxIngressControllerSpec) DeepCopyInto(out *NginxIngressControllerSpec) { 149 | *out = *in 150 | out.Image = in.Image 151 | if in.Replicas != nil { 152 | in, out := &in.Replicas, &out.Replicas 153 | *out = new(int32) 154 | **out = **in 155 | } 156 | if in.EnableCRDs != nil { 157 | in, out := &in.EnableCRDs, &out.EnableCRDs 158 | *out = new(bool) 159 | **out = **in 160 | } 161 | if in.Service != nil { 162 | in, out := &in.Service, &out.Service 163 | *out = new(Service) 164 | (*in).DeepCopyInto(*out) 165 | } 166 | if in.HealthStatus != nil { 167 | in, out := &in.HealthStatus, &out.HealthStatus 168 | *out = new(HealthStatus) 169 | **out = **in 170 | } 171 | if in.NginxStatus != nil { 172 | in, out := &in.NginxStatus, &out.NginxStatus 173 | *out = new(NginxStatus) 174 | (*in).DeepCopyInto(*out) 175 | } 176 | if in.ReportIngressStatus != nil { 177 | in, out := &in.ReportIngressStatus, &out.ReportIngressStatus 178 | *out = new(ReportIngressStatus) 179 | **out = **in 180 | } 181 | if in.EnableLeaderElection != nil { 182 | in, out := &in.EnableLeaderElection, &out.EnableLeaderElection 183 | *out = new(bool) 184 | **out = **in 185 | } 186 | if in.Prometheus != nil { 187 | in, out := &in.Prometheus, &out.Prometheus 188 | *out = new(Prometheus) 189 | (*in).DeepCopyInto(*out) 190 | } 191 | if in.ConfigMapData != nil { 192 | in, out := &in.ConfigMapData, &out.ConfigMapData 193 | *out = make(map[string]string, len(*in)) 194 | for key, val := range *in { 195 | (*out)[key] = val 196 | } 197 | } 198 | if in.AppProtect != nil { 199 | in, out := &in.AppProtect, &out.AppProtect 200 | *out = new(AppProtect) 201 | **out = **in 202 | } 203 | if in.AppProtectDos != nil { 204 | in, out := &in.AppProtectDos, &out.AppProtectDos 205 | *out = new(AppProtectDos) 206 | **out = **in 207 | } 208 | } 209 | 210 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxIngressControllerSpec. 211 | func (in *NginxIngressControllerSpec) DeepCopy() *NginxIngressControllerSpec { 212 | if in == nil { 213 | return nil 214 | } 215 | out := new(NginxIngressControllerSpec) 216 | in.DeepCopyInto(out) 217 | return out 218 | } 219 | 220 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 221 | func (in *NginxIngressControllerStatus) DeepCopyInto(out *NginxIngressControllerStatus) { 222 | *out = *in 223 | } 224 | 225 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxIngressControllerStatus. 226 | func (in *NginxIngressControllerStatus) DeepCopy() *NginxIngressControllerStatus { 227 | if in == nil { 228 | return nil 229 | } 230 | out := new(NginxIngressControllerStatus) 231 | in.DeepCopyInto(out) 232 | return out 233 | } 234 | 235 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 236 | func (in *NginxStatus) DeepCopyInto(out *NginxStatus) { 237 | *out = *in 238 | if in.Port != nil { 239 | in, out := &in.Port, &out.Port 240 | *out = new(uint16) 241 | **out = **in 242 | } 243 | } 244 | 245 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxStatus. 246 | func (in *NginxStatus) DeepCopy() *NginxStatus { 247 | if in == nil { 248 | return nil 249 | } 250 | out := new(NginxStatus) 251 | in.DeepCopyInto(out) 252 | return out 253 | } 254 | 255 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 256 | func (in *Prometheus) DeepCopyInto(out *Prometheus) { 257 | *out = *in 258 | if in.Port != nil { 259 | in, out := &in.Port, &out.Port 260 | *out = new(uint16) 261 | **out = **in 262 | } 263 | } 264 | 265 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Prometheus. 266 | func (in *Prometheus) DeepCopy() *Prometheus { 267 | if in == nil { 268 | return nil 269 | } 270 | out := new(Prometheus) 271 | in.DeepCopyInto(out) 272 | return out 273 | } 274 | 275 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 276 | func (in *ReportIngressStatus) DeepCopyInto(out *ReportIngressStatus) { 277 | *out = *in 278 | } 279 | 280 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReportIngressStatus. 281 | func (in *ReportIngressStatus) DeepCopy() *ReportIngressStatus { 282 | if in == nil { 283 | return nil 284 | } 285 | out := new(ReportIngressStatus) 286 | in.DeepCopyInto(out) 287 | return out 288 | } 289 | 290 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 291 | func (in *Service) DeepCopyInto(out *Service) { 292 | *out = *in 293 | if in.ExtraLabels != nil { 294 | in, out := &in.ExtraLabels, &out.ExtraLabels 295 | *out = make(map[string]string, len(*in)) 296 | for key, val := range *in { 297 | (*out)[key] = val 298 | } 299 | } 300 | if in.ExtraAnnotations != nil { 301 | in, out := &in.ExtraAnnotations, &out.ExtraAnnotations 302 | *out = make(map[string]string, len(*in)) 303 | for key, val := range *in { 304 | (*out)[key] = val 305 | } 306 | } 307 | } 308 | 309 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service. 310 | func (in *Service) DeepCopy() *Service { 311 | if in == nil { 312 | return nil 313 | } 314 | out := new(Service) 315 | in.DeepCopyInto(out) 316 | return out 317 | } 318 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=nginx-ingress-operator 8 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 9 | LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha 10 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.18.0 11 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 12 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 13 | 14 | # Labels for testing. 15 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 16 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 17 | 18 | # Copy files to locations specified by labels. 19 | COPY bundle/manifests /manifests/ 20 | COPY bundle/metadata /metadata/ 21 | COPY bundle/tests/scorecard /tests/scorecard/ 22 | 23 | LABEL com.redhat.openshift.versions="v4.6" 24 | LABEL com.redhat.delivery.operator.bundle=true 25 | LABEL com.redhat.delivery.backport=true 26 | -------------------------------------------------------------------------------- /bundle/manifests/nginx-ingress-operator-controller-manager-metrics-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | control-plane: controller-manager 7 | name: nginx-ingress-operator-controller-manager-metrics-service 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | status: 17 | loadBalancer: {} 18 | -------------------------------------------------------------------------------- /bundle/manifests/nginx-ingress-operator-manager-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | controller_manager_config.yaml: | 4 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 5 | kind: ControllerManagerConfig 6 | health: 7 | healthProbeBindAddress: :8081 8 | metrics: 9 | bindAddress: 127.0.0.1:8080 10 | webhook: 11 | port: 9443 12 | leaderElection: 13 | leaderElect: true 14 | resourceName: ca5c10a7.nginx.org 15 | kind: ConfigMap 16 | metadata: 17 | name: nginx-ingress-operator-manager-config 18 | -------------------------------------------------------------------------------- /bundle/manifests/nginx-ingress-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | name: nginx-ingress-operator-metrics-reader 6 | rules: 7 | - nonResourceURLs: 8 | - /metrics 9 | verbs: 10 | - get 11 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: nginx-ingress-operator 7 | operators.operatorframework.io.bundle.channels.v1: alpha 8 | operators.operatorframework.io.bundle.channel.default.v1: alpha 9 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.18.0 10 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 11 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 12 | 13 | # Annotations for testing. 14 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 15 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 16 | 17 | # OpenShift annotations. 18 | com.redhat.openshift.versions: v4.6 19 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:v1.18.0 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | storage: 16 | spec: 17 | mountPath: {} 18 | - entrypoint: 19 | - scorecard-test 20 | - olm-bundle-validation 21 | image: quay.io/operator-framework/scorecard-test:v1.18.0 22 | labels: 23 | suite: olm 24 | test: olm-bundle-validation-test 25 | storage: 26 | spec: 27 | mountPath: {} 28 | - entrypoint: 29 | - scorecard-test 30 | - olm-crds-have-validation 31 | image: quay.io/operator-framework/scorecard-test:v1.18.0 32 | labels: 33 | suite: olm 34 | test: olm-crds-have-validation-test 35 | storage: 36 | spec: 37 | mountPath: {} 38 | - entrypoint: 39 | - scorecard-test 40 | - olm-crds-have-resources 41 | image: quay.io/operator-framework/scorecard-test:v1.18.0 42 | labels: 43 | suite: olm 44 | test: olm-crds-have-resources-test 45 | storage: 46 | spec: 47 | mountPath: {} 48 | - entrypoint: 49 | - scorecard-test 50 | - olm-spec-descriptors 51 | image: quay.io/operator-framework/scorecard-test:v1.18.0 52 | labels: 53 | suite: olm 54 | test: olm-spec-descriptors-test 55 | storage: 56 | spec: 57 | mountPath: {} 58 | - entrypoint: 59 | - scorecard-test 60 | - olm-status-descriptors 61 | image: quay.io/operator-framework/scorecard-test:v1.18.0 62 | labels: 63 | suite: olm 64 | test: olm-status-descriptors-test 65 | storage: 66 | spec: 67 | mountPath: {} 68 | storage: 69 | spec: 70 | mountPath: {} 71 | -------------------------------------------------------------------------------- /config/crd/kic/appprotect.f5.com_aplogconfs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.4.0 6 | creationTimestamp: null 7 | name: aplogconfs.appprotect.f5.com 8 | spec: 9 | group: appprotect.f5.com 10 | names: 11 | kind: APLogConf 12 | listKind: APLogConfList 13 | plural: aplogconfs 14 | singular: aplogconf 15 | preserveUnknownFields: false 16 | scope: Namespaced 17 | versions: 18 | - name: v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | description: APLogConf is the Schema for the APLogConfs API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 25 | type: string 26 | kind: 27 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 28 | type: string 29 | metadata: 30 | type: object 31 | spec: 32 | description: APLogConfSpec defines the desired state of APLogConf 33 | properties: 34 | content: 35 | properties: 36 | format: 37 | enum: 38 | - splunk 39 | - arcsight 40 | - default 41 | - user-defined 42 | - grpc 43 | type: string 44 | format_string: 45 | type: string 46 | max_message_size: 47 | pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ 48 | type: string 49 | max_request_size: 50 | pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|1[0-9]{3}|20[1-3][0-9]|204[1-8]|any)$ 51 | type: string 52 | type: object 53 | filter: 54 | properties: 55 | request_type: 56 | enum: 57 | - all 58 | - illegal 59 | - blocked 60 | type: string 61 | type: object 62 | type: object 63 | type: object 64 | served: true 65 | storage: true 66 | -------------------------------------------------------------------------------- /config/crd/kic/appprotect.f5.com_apusersigs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.4.0 6 | creationTimestamp: null 7 | name: apusersigs.appprotect.f5.com 8 | spec: 9 | group: appprotect.f5.com 10 | names: 11 | kind: APUserSig 12 | listKind: APUserSigList 13 | plural: apusersigs 14 | singular: apusersig 15 | preserveUnknownFields: false 16 | scope: Namespaced 17 | versions: 18 | - name: v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | description: APUserSig is the Schema for the apusersigs API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 25 | type: string 26 | kind: 27 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 28 | type: string 29 | metadata: 30 | type: object 31 | spec: 32 | description: APUserSigSpec defines the desired state of APUserSig 33 | properties: 34 | properties: 35 | type: string 36 | signatures: 37 | items: 38 | properties: 39 | accuracy: 40 | enum: 41 | - high 42 | - medium 43 | - low 44 | type: string 45 | attackType: 46 | properties: 47 | name: 48 | type: string 49 | type: object 50 | description: 51 | type: string 52 | name: 53 | type: string 54 | references: 55 | properties: 56 | type: 57 | enum: 58 | - bugtraq 59 | - cve 60 | - nessus 61 | - url 62 | type: string 63 | value: 64 | type: string 65 | type: object 66 | risk: 67 | enum: 68 | - high 69 | - medium 70 | - low 71 | type: string 72 | rule: 73 | type: string 74 | signatureType: 75 | enum: 76 | - request 77 | - response 78 | type: string 79 | systems: 80 | items: 81 | properties: 82 | name: 83 | type: string 84 | type: object 85 | type: array 86 | type: object 87 | type: array 88 | tag: 89 | type: string 90 | type: object 91 | type: object 92 | served: true 93 | storage: true 94 | -------------------------------------------------------------------------------- /config/crd/kic/k8s.nginx.org_globalconfigurations.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.8.0 6 | creationTimestamp: null 7 | name: globalconfigurations.k8s.nginx.org 8 | spec: 9 | group: k8s.nginx.org 10 | names: 11 | kind: GlobalConfiguration 12 | listKind: GlobalConfigurationList 13 | plural: globalconfigurations 14 | shortNames: 15 | - gc 16 | singular: globalconfiguration 17 | scope: Namespaced 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: GlobalConfiguration defines the GlobalConfiguration resource. 23 | type: object 24 | properties: 25 | apiVersion: 26 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 30 | type: string 31 | metadata: 32 | type: object 33 | spec: 34 | description: GlobalConfigurationSpec is the spec of the GlobalConfiguration resource. 35 | type: object 36 | properties: 37 | listeners: 38 | type: array 39 | items: 40 | description: Listener defines a listener. 41 | type: object 42 | properties: 43 | name: 44 | type: string 45 | port: 46 | type: integer 47 | protocol: 48 | type: string 49 | served: true 50 | storage: true 51 | status: 52 | acceptedNames: 53 | kind: "" 54 | plural: "" 55 | conditions: [] 56 | storedVersions: [] 57 | -------------------------------------------------------------------------------- /config/crd/kic/k8s.nginx.org_transportservers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.8.0 6 | creationTimestamp: null 7 | name: transportservers.k8s.nginx.org 8 | spec: 9 | group: k8s.nginx.org 10 | names: 11 | kind: TransportServer 12 | listKind: TransportServerList 13 | plural: transportservers 14 | shortNames: 15 | - ts 16 | singular: transportserver 17 | scope: Namespaced 18 | versions: 19 | - additionalPrinterColumns: 20 | - description: Current state of the TransportServer. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. 21 | jsonPath: .status.state 22 | name: State 23 | type: string 24 | - jsonPath: .status.reason 25 | name: Reason 26 | type: string 27 | - jsonPath: .metadata.creationTimestamp 28 | name: Age 29 | type: date 30 | name: v1alpha1 31 | schema: 32 | openAPIV3Schema: 33 | description: TransportServer defines the TransportServer resource. 34 | type: object 35 | properties: 36 | apiVersion: 37 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 38 | type: string 39 | kind: 40 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 41 | type: string 42 | metadata: 43 | type: object 44 | spec: 45 | description: TransportServerSpec is the spec of the TransportServer resource. 46 | type: object 47 | properties: 48 | action: 49 | description: Action defines an action. 50 | type: object 51 | properties: 52 | pass: 53 | type: string 54 | host: 55 | type: string 56 | ingressClassName: 57 | type: string 58 | listener: 59 | description: TransportServerListener defines a listener for a TransportServer. 60 | type: object 61 | properties: 62 | name: 63 | type: string 64 | protocol: 65 | type: string 66 | serverSnippets: 67 | type: string 68 | sessionParameters: 69 | description: SessionParameters defines session parameters. 70 | type: object 71 | properties: 72 | timeout: 73 | type: string 74 | streamSnippets: 75 | type: string 76 | upstreamParameters: 77 | description: UpstreamParameters defines parameters for an upstream. 78 | type: object 79 | properties: 80 | connectTimeout: 81 | type: string 82 | nextUpstream: 83 | type: boolean 84 | nextUpstreamTimeout: 85 | type: string 86 | nextUpstreamTries: 87 | type: integer 88 | udpRequests: 89 | type: integer 90 | udpResponses: 91 | type: integer 92 | upstreams: 93 | type: array 94 | items: 95 | description: Upstream defines an upstream. 96 | type: object 97 | properties: 98 | failTimeout: 99 | type: string 100 | healthCheck: 101 | description: HealthCheck defines the parameters for active Upstream HealthChecks. 102 | type: object 103 | properties: 104 | enable: 105 | type: boolean 106 | fails: 107 | type: integer 108 | interval: 109 | type: string 110 | jitter: 111 | type: string 112 | match: 113 | description: Match defines the parameters of a custom health check. 114 | type: object 115 | properties: 116 | expect: 117 | type: string 118 | send: 119 | type: string 120 | passes: 121 | type: integer 122 | port: 123 | type: integer 124 | timeout: 125 | type: string 126 | loadBalancingMethod: 127 | type: string 128 | maxConns: 129 | type: integer 130 | maxFails: 131 | type: integer 132 | name: 133 | type: string 134 | port: 135 | type: integer 136 | service: 137 | type: string 138 | status: 139 | description: TransportServerStatus defines the status for the TransportServer resource. 140 | type: object 141 | properties: 142 | message: 143 | type: string 144 | reason: 145 | type: string 146 | state: 147 | type: string 148 | served: true 149 | storage: true 150 | subresources: 151 | status: {} 152 | status: 153 | acceptedNames: 154 | kind: "" 155 | plural: "" 156 | conditions: [] 157 | storedVersions: [] 158 | -------------------------------------------------------------------------------- /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/k8s.nginx.org_nginxingresscontrollers.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_nginxingresscontrollers.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_nginxingresscontrollers.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_nginxingresscontrollers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: nginxingresscontrollers.k8s.nginx.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_nginxingresscontrollers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: nginxingresscontrollers.k8s.nginx.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: nginx-ingress-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: nginx-ingress-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=0" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | resources: 24 | limits: 25 | cpu: 500m 26 | memory: 128Mi 27 | requests: 28 | cpu: 5m 29 | memory: 64Mi 30 | - name: manager 31 | args: 32 | - "--health-probe-bind-address=:8081" 33 | - "--metrics-bind-address=127.0.0.1:8080" 34 | - "--leader-elect" 35 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: ca5c10a7.nginx.org 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: nginx/nginx-ingress-operator 16 | newTag: 0.5.1 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | securityContext: 28 | runAsNonRoot: true 29 | containers: 30 | - command: 31 | - /manager 32 | args: 33 | - --leader-elect 34 | image: controller:latest 35 | name: manager 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | livenessProbe: 39 | httpGet: 40 | path: /healthz 41 | port: 8081 42 | initialDelaySeconds: 15 43 | periodSeconds: 20 44 | readinessProbe: 45 | httpGet: 46 | path: /readyz 47 | port: 8081 48 | initialDelaySeconds: 5 49 | periodSeconds: 10 50 | resources: 51 | limits: 52 | cpu: 500m 53 | memory: 256Mi 54 | requests: 55 | cpu: 250m 56 | memory: 128Mi 57 | env: 58 | - name: WATCH_NAMESPACE 59 | value: "" 60 | serviceAccountName: controller-manager 61 | terminationGracePeriodSeconds: 10 62 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/nginx-ingress-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/nginxingresscontroller_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit nginxingresscontrollers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: nginxingresscontroller-editor-role 6 | rules: 7 | - apiGroups: 8 | - k8s.nginx.org 9 | resources: 10 | - nginxingresscontrollers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - k8s.nginx.org 21 | resources: 22 | - nginxingresscontrollers/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/nginxingresscontroller_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view nginxingresscontrollers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: nginxingresscontroller-viewer-role 6 | rules: 7 | - apiGroups: 8 | - k8s.nginx.org 9 | resources: 10 | - nginxingresscontrollers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - k8s.nginx.org 17 | resources: 18 | - nginxingresscontrollers/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - configmaps 13 | - endpoints 14 | - events 15 | - namespaces 16 | - persistentvolumeclaims 17 | - pods 18 | - secrets 19 | - serviceaccounts 20 | - services 21 | - services/finalizers 22 | verbs: 23 | - create 24 | - delete 25 | - get 26 | - list 27 | - patch 28 | - update 29 | - watch 30 | - apiGroups: 31 | - apiextensions.k8s.io 32 | resources: 33 | - customresourcedefinitions 34 | verbs: 35 | - create 36 | - delete 37 | - get 38 | - update 39 | - apiGroups: 40 | - appprotect.f5.com 41 | - appprotectdos.f5.com 42 | - k8s.nginx.org 43 | resources: 44 | - '*' 45 | verbs: 46 | - create 47 | - delete 48 | - get 49 | - list 50 | - patch 51 | - update 52 | - watch 53 | - apiGroups: 54 | - apps 55 | resources: 56 | - daemonsets 57 | - deployments 58 | - replicasets 59 | - statefulsets 60 | verbs: 61 | - create 62 | - delete 63 | - get 64 | - list 65 | - patch 66 | - update 67 | - watch 68 | - apiGroups: 69 | - k8s.nginx.org 70 | resources: 71 | - nginxingresscontrollers 72 | verbs: 73 | - create 74 | - delete 75 | - get 76 | - list 77 | - patch 78 | - update 79 | - watch 80 | - apiGroups: 81 | - k8s.nginx.org 82 | resources: 83 | - nginxingresscontrollers/finalizers 84 | verbs: 85 | - update 86 | - apiGroups: 87 | - k8s.nginx.org 88 | resources: 89 | - nginxingresscontrollers/status 90 | verbs: 91 | - get 92 | - patch 93 | - update 94 | - apiGroups: 95 | - networking.k8s.io 96 | resources: 97 | - ingressclasses 98 | verbs: 99 | - create 100 | - delete 101 | - get 102 | - apiGroups: 103 | - networking.k8s.io 104 | resources: 105 | - ingresses 106 | verbs: 107 | - get 108 | - list 109 | - watch 110 | - apiGroups: 111 | - networking.k8s.io 112 | resources: 113 | - ingresses/status 114 | verbs: 115 | - update 116 | - apiGroups: 117 | - rbac.authorization.k8s.io 118 | resources: 119 | - clusterrolebindings 120 | - clusterroles 121 | - rolebindings 122 | - roles 123 | verbs: 124 | - create 125 | - delete 126 | - get 127 | - list 128 | - patch 129 | - update 130 | - watch 131 | - apiGroups: 132 | - security.openshift.io 133 | resources: 134 | - securitycontextconstraints 135 | verbs: 136 | - create 137 | - get 138 | - list 139 | - update 140 | - watch 141 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/k8s_v1alpha1_nginxingresscontroller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k8s.nginx.org/v1alpha1 2 | kind: NginxIngressController 3 | metadata: 4 | name: my-nginx-ingress-controller 5 | spec: 6 | type: deployment 7 | nginxPlus: false 8 | image: 9 | repository: docker.io/nginx/nginx-ingress 10 | tag: 2.1.1-ubi 11 | pullPolicy: Always 12 | serviceType: NodePort 13 | ingressClass: nginx 14 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - k8s_v1alpha1_nginxingresscontroller.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.18.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.18.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.18.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.18.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.18.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.18.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/configmap.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | "k8s.io/apimachinery/pkg/runtime" 7 | ctrl "sigs.k8s.io/controller-runtime" 8 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 9 | 10 | v1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | func configMapForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*v1.ConfigMap, error) { 14 | cm := &v1.ConfigMap{ 15 | ObjectMeta: metav1.ObjectMeta{ 16 | Name: instance.Name, 17 | Namespace: instance.Namespace, 18 | }, 19 | Data: instance.Spec.ConfigMapData, 20 | } 21 | if err := ctrl.SetControllerReference(instance, cm, scheme); err != nil { 22 | return nil, err 23 | } 24 | return cm, nil 25 | } 26 | 27 | func configMapMutateFn(cm *v1.ConfigMap, configMapData map[string]string) controllerutil.MutateFn { 28 | return func() error { 29 | cm.Data = configMapData 30 | return nil 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /controllers/crds.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/go-logr/logr" 10 | v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 11 | apixv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" 12 | "k8s.io/apimachinery/pkg/api/errors" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/util/yaml" 15 | "sigs.k8s.io/controller-runtime/pkg/manager" 16 | ) 17 | 18 | const ( 19 | crdsPath = "./config/crd/kic" 20 | decoderBufferSize = 100 21 | ) 22 | 23 | func getCRDsManifests() ([]string, error) { 24 | files, err := ioutil.ReadDir(crdsPath) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | var manifests []string 30 | for _, f := range files { 31 | manifests = append(manifests, fmt.Sprintf("%v/%v", crdsPath, f.Name())) 32 | } 33 | 34 | return manifests, nil 35 | } 36 | 37 | func kicCRDs() ([]*v1.CustomResourceDefinition, error) { 38 | manifests, err := getCRDsManifests() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | var crds []*v1.CustomResourceDefinition 44 | for _, path := range manifests { 45 | f, err := os.Open(path) 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to open the CRD manifest %v: %w", path, err) 48 | } 49 | 50 | var crd v1.CustomResourceDefinition 51 | 52 | err = yaml.NewYAMLOrJSONDecoder(f, decoderBufferSize).Decode(&crd) 53 | 54 | if err != nil { 55 | return nil, fmt.Errorf("failed to parse the CRD manifest %v: %w", path, err) 56 | } 57 | 58 | err = f.Close() 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to close the CRD manifest %v: %w", path, err) 61 | } 62 | 63 | crds = append(crds, &crd) 64 | } 65 | 66 | return crds, nil 67 | } 68 | 69 | func createKICCustomResourceDefinitions(log logr.Logger, mgr manager.Manager) error { 70 | // Create CRDs with a different client (apiextensions) 71 | apixClient, err := apixv1client.NewForConfig(mgr.GetConfig()) 72 | if err != nil { 73 | log.Error(err, "unable to create client for CRD registration") 74 | return err 75 | } 76 | 77 | crds, err := kicCRDs() 78 | if err != nil { 79 | return err 80 | } 81 | 82 | crdsClient := apixClient.CustomResourceDefinitions() 83 | for _, crd := range crds { 84 | oldCRD, err := crdsClient.Get(context.TODO(), crd.Name, metav1.GetOptions{}) 85 | if err != nil { 86 | if errors.IsNotFound(err) { 87 | log.V(1).Info(fmt.Sprintf("no previous CRD %v found, creating a new one.", crd.Name)) 88 | _, err = crdsClient.Create(context.TODO(), crd, metav1.CreateOptions{}) 89 | if err != nil { 90 | return fmt.Errorf("error creating CRD %v: %w", crd.Name, err) 91 | } 92 | } else { 93 | return fmt.Errorf("error getting CRD %v: %w", crd.Name, err) 94 | } 95 | } else { 96 | // Update CRDs if they already exist 97 | log.V(1).Info(fmt.Sprintf("previous CRD %v found, updating.", crd.Name)) 98 | oldCRD.Spec = crd.Spec 99 | _, err = crdsClient.Update(context.TODO(), oldCRD, metav1.UpdateOptions{}) 100 | if err != nil { 101 | return fmt.Errorf("error updating CRD %v: %w", crd.Name, err) 102 | } 103 | } 104 | } 105 | 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /controllers/daemonset.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | appsv1 "k8s.io/api/apps/v1" 6 | corev1 "k8s.io/api/core/v1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | ) 11 | 12 | func daemonSetForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*appsv1.DaemonSet, error) { 13 | runAsUser := new(int64) 14 | allowPrivilegeEscalation := new(bool) 15 | *runAsUser = 101 16 | *allowPrivilegeEscalation = true 17 | 18 | dep := &appsv1.DaemonSet{ 19 | ObjectMeta: v1.ObjectMeta{ 20 | Name: instance.Name, 21 | Namespace: instance.Namespace, 22 | }, 23 | Spec: appsv1.DaemonSetSpec{ 24 | Selector: &v1.LabelSelector{ 25 | MatchLabels: map[string]string{"app": instance.Name}, 26 | }, 27 | Template: corev1.PodTemplateSpec{ 28 | ObjectMeta: v1.ObjectMeta{ 29 | Name: instance.Name, 30 | Namespace: instance.Namespace, 31 | Labels: map[string]string{"app": instance.Name}, 32 | }, 33 | Spec: corev1.PodSpec{ 34 | ServiceAccountName: instance.Name, 35 | Containers: []corev1.Container{ 36 | { 37 | Name: instance.Name, 38 | Image: generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag), 39 | ImagePullPolicy: corev1.PullPolicy(instance.Spec.Image.PullPolicy), 40 | Args: generatePodArgs(instance), 41 | Ports: []corev1.ContainerPort{ 42 | { 43 | Name: "http", 44 | ContainerPort: 80, 45 | }, 46 | { 47 | Name: "https", 48 | ContainerPort: 443, 49 | }, 50 | }, 51 | SecurityContext: &corev1.SecurityContext{ 52 | Capabilities: &corev1.Capabilities{ 53 | Drop: []corev1.Capability{"ALL"}, 54 | Add: []corev1.Capability{"NET_BIND_SERVICE"}, 55 | }, 56 | RunAsUser: runAsUser, 57 | AllowPrivilegeEscalation: allowPrivilegeEscalation, 58 | }, 59 | Env: []corev1.EnvVar{ 60 | { 61 | Name: "POD_NAMESPACE", 62 | ValueFrom: &corev1.EnvVarSource{ 63 | FieldRef: &corev1.ObjectFieldSelector{ 64 | FieldPath: "metadata.namespace", 65 | }, 66 | }, 67 | }, 68 | { 69 | Name: "POD_NAME", 70 | ValueFrom: &corev1.EnvVarSource{ 71 | FieldRef: &corev1.ObjectFieldSelector{ 72 | FieldPath: "metadata.name", 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | }, 81 | }, 82 | } 83 | if err := ctrl.SetControllerReference(instance, dep, scheme); err != nil { 84 | return nil, err 85 | } 86 | return dep, nil 87 | } 88 | 89 | func hasDaemonSetChanged(ds *appsv1.DaemonSet, instance *k8sv1alpha1.NginxIngressController) bool { 90 | // There is only 1 container in our template 91 | container := ds.Spec.Template.Spec.Containers[0] 92 | if container.Image != generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag) { 93 | return true 94 | } 95 | 96 | if container.ImagePullPolicy != corev1.PullPolicy(instance.Spec.Image.PullPolicy) { 97 | return true 98 | } 99 | 100 | return hasDifferentArguments(container, instance) 101 | } 102 | 103 | func updateDaemonSet(ds *appsv1.DaemonSet, instance *k8sv1alpha1.NginxIngressController) *appsv1.DaemonSet { 104 | ds.Spec.Template.Spec.Containers[0].Image = generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag) 105 | ds.Spec.Template.Spec.Containers[0].Args = generatePodArgs(instance) 106 | return ds 107 | } 108 | -------------------------------------------------------------------------------- /controllers/daemonset_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | appsv1 "k8s.io/api/apps/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/kubernetes/scheme" 13 | ) 14 | 15 | func TestDaemonSetForNginxIngressController(t *testing.T) { 16 | boolPointer := func(b bool) *bool { return &b } 17 | s := scheme.Scheme 18 | 19 | if err := k8sv1alpha1.AddToScheme(s); err != nil { 20 | t.Fatalf("Unable to add k8sv1alpha1 scheme: (%v)", err) 21 | } 22 | runAsUser := new(int64) 23 | allowPrivilegeEscalation := new(bool) 24 | *runAsUser = 101 25 | *allowPrivilegeEscalation = true 26 | 27 | instance := &k8sv1alpha1.NginxIngressController{ 28 | ObjectMeta: metav1.ObjectMeta{ 29 | Name: "my-nginx-ingress-controller", 30 | Namespace: "my-nginx-ingress-controller", 31 | }, 32 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 33 | Image: k8sv1alpha1.Image{ 34 | Repository: "nginx-ingress", 35 | Tag: "edge", 36 | }, 37 | }, 38 | } 39 | expected := &appsv1.DaemonSet{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Name: "my-nginx-ingress-controller", 42 | Namespace: "my-nginx-ingress-controller", 43 | OwnerReferences: []v1.OwnerReference{ 44 | { 45 | APIVersion: "k8s.nginx.org/v1alpha1", 46 | Name: instance.Name, 47 | Kind: "NginxIngressController", 48 | Controller: boolPointer(true), 49 | BlockOwnerDeletion: boolPointer(true), 50 | }, 51 | }, 52 | }, 53 | Spec: appsv1.DaemonSetSpec{ 54 | Selector: &v1.LabelSelector{ 55 | MatchLabels: map[string]string{"app": instance.Name}, 56 | }, 57 | Template: corev1.PodTemplateSpec{ 58 | ObjectMeta: v1.ObjectMeta{ 59 | Name: "my-nginx-ingress-controller", 60 | Namespace: "my-nginx-ingress-controller", 61 | Labels: map[string]string{"app": instance.Name}, 62 | }, 63 | Spec: corev1.PodSpec{ 64 | ServiceAccountName: "my-nginx-ingress-controller", 65 | Containers: []corev1.Container{ 66 | { 67 | Name: "my-nginx-ingress-controller", 68 | Image: "nginx-ingress:edge", 69 | Args: generatePodArgs(instance), 70 | Ports: []corev1.ContainerPort{ 71 | { 72 | Name: "http", 73 | ContainerPort: 80, 74 | }, 75 | { 76 | Name: "https", 77 | ContainerPort: 443, 78 | }, 79 | }, 80 | SecurityContext: &corev1.SecurityContext{ 81 | Capabilities: &corev1.Capabilities{ 82 | Drop: []corev1.Capability{"ALL"}, 83 | Add: []corev1.Capability{"NET_BIND_SERVICE"}, 84 | }, 85 | RunAsUser: runAsUser, 86 | AllowPrivilegeEscalation: allowPrivilegeEscalation, 87 | }, 88 | Env: []corev1.EnvVar{ 89 | { 90 | Name: "POD_NAMESPACE", 91 | ValueFrom: &corev1.EnvVarSource{ 92 | FieldRef: &corev1.ObjectFieldSelector{ 93 | FieldPath: "metadata.namespace", 94 | }, 95 | }, 96 | }, 97 | { 98 | Name: "POD_NAME", 99 | ValueFrom: &corev1.EnvVarSource{ 100 | FieldRef: &corev1.ObjectFieldSelector{ 101 | FieldPath: "metadata.name", 102 | }, 103 | }, 104 | }, 105 | }, 106 | }, 107 | }, 108 | }, 109 | }, 110 | }, 111 | } 112 | 113 | result, _ := daemonSetForNginxIngressController(instance, s) 114 | if diff := cmp.Diff(expected, result); diff != "" { 115 | t.Errorf("daemonSetForNginxIngressController() mismatch (-want +got):\n%s", diff) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /controllers/deployment.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | appsv1 "k8s.io/api/apps/v1" 6 | corev1 "k8s.io/api/core/v1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | ) 11 | 12 | func deploymentForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*appsv1.Deployment, error) { 13 | runAsUser := new(int64) 14 | allowPrivilegeEscalation := new(bool) 15 | *runAsUser = 101 16 | *allowPrivilegeEscalation = true 17 | 18 | dep := &appsv1.Deployment{ 19 | ObjectMeta: v1.ObjectMeta{ 20 | Name: instance.Name, 21 | Namespace: instance.Namespace, 22 | }, 23 | Spec: appsv1.DeploymentSpec{ 24 | Selector: &v1.LabelSelector{ 25 | MatchLabels: map[string]string{"app": instance.Name}, 26 | }, 27 | Replicas: instance.Spec.Replicas, 28 | Template: corev1.PodTemplateSpec{ 29 | ObjectMeta: v1.ObjectMeta{ 30 | Name: instance.Name, 31 | Namespace: instance.Namespace, 32 | Labels: map[string]string{"app": instance.Name}, 33 | }, 34 | Spec: corev1.PodSpec{ 35 | ServiceAccountName: instance.Name, 36 | Containers: []corev1.Container{ 37 | { 38 | Name: instance.Name, 39 | Image: generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag), 40 | ImagePullPolicy: corev1.PullPolicy(instance.Spec.Image.PullPolicy), 41 | Args: generatePodArgs(instance), 42 | Ports: []corev1.ContainerPort{ 43 | { 44 | Name: "http", 45 | ContainerPort: 80, 46 | }, 47 | { 48 | Name: "https", 49 | ContainerPort: 443, 50 | }, 51 | }, 52 | SecurityContext: &corev1.SecurityContext{ 53 | Capabilities: &corev1.Capabilities{ 54 | Drop: []corev1.Capability{"ALL"}, 55 | Add: []corev1.Capability{"NET_BIND_SERVICE"}, 56 | }, 57 | RunAsUser: runAsUser, 58 | AllowPrivilegeEscalation: allowPrivilegeEscalation, 59 | }, 60 | Env: []corev1.EnvVar{ 61 | { 62 | Name: "POD_NAMESPACE", 63 | ValueFrom: &corev1.EnvVarSource{ 64 | FieldRef: &corev1.ObjectFieldSelector{ 65 | FieldPath: "metadata.namespace", 66 | }, 67 | }, 68 | }, 69 | { 70 | Name: "POD_NAME", 71 | ValueFrom: &corev1.EnvVarSource{ 72 | FieldRef: &corev1.ObjectFieldSelector{ 73 | FieldPath: "metadata.name", 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | }, 81 | }, 82 | }, 83 | } 84 | if err := ctrl.SetControllerReference(instance, dep, scheme); err != nil { 85 | return nil, err 86 | } 87 | return dep, nil 88 | } 89 | 90 | func hasDeploymentChanged(dep *appsv1.Deployment, instance *k8sv1alpha1.NginxIngressController) bool { 91 | defaultReplicaCount := int32(1) 92 | if dep.Spec.Replicas != nil && instance.Spec.Replicas == nil && *dep.Spec.Replicas != defaultReplicaCount || 93 | dep.Spec.Replicas != nil && instance.Spec.Replicas != nil && *dep.Spec.Replicas != *instance.Spec.Replicas { 94 | return true 95 | } 96 | 97 | // There is only 1 container in our template 98 | container := dep.Spec.Template.Spec.Containers[0] 99 | if container.Image != generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag) { 100 | return true 101 | } 102 | 103 | if container.ImagePullPolicy != corev1.PullPolicy(instance.Spec.Image.PullPolicy) { 104 | return true 105 | } 106 | 107 | return hasDifferentArguments(container, instance) 108 | } 109 | 110 | func updateDeployment(dep *appsv1.Deployment, instance *k8sv1alpha1.NginxIngressController) *appsv1.Deployment { 111 | dep.Spec.Replicas = instance.Spec.Replicas 112 | if instance.Spec.Replicas == nil { 113 | defaultReplicaCount := new(int32) 114 | *defaultReplicaCount = 1 115 | dep.Spec.Replicas = defaultReplicaCount 116 | } 117 | dep.Spec.Template.Spec.Containers[0].Image = generateImage(instance.Spec.Image.Repository, instance.Spec.Image.Tag) 118 | dep.Spec.Template.Spec.Containers[0].Args = generatePodArgs(instance) 119 | return dep 120 | } 121 | -------------------------------------------------------------------------------- /controllers/deployment_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | appsv1 "k8s.io/api/apps/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/kubernetes/scheme" 13 | ) 14 | 15 | func TestDeploymentForNginxIngressController(t *testing.T) { 16 | boolPointer := func(b bool) *bool { return &b } 17 | s := scheme.Scheme 18 | 19 | if err := k8sv1alpha1.AddToScheme(s); err != nil { 20 | t.Fatalf("Unable to add k8sv1alpha1 scheme: (%v)", err) 21 | } 22 | runAsUser := new(int64) 23 | allowPrivilegeEscalation := new(bool) 24 | *runAsUser = 101 25 | *allowPrivilegeEscalation = true 26 | 27 | instance := &k8sv1alpha1.NginxIngressController{ 28 | ObjectMeta: metav1.ObjectMeta{ 29 | Name: "my-nginx-ingress-controller", 30 | Namespace: "my-nginx-ingress-controller", 31 | }, 32 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 33 | Image: k8sv1alpha1.Image{ 34 | Repository: "nginx-ingress", 35 | Tag: "edge", 36 | }, 37 | }, 38 | } 39 | expected := &appsv1.Deployment{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Name: "my-nginx-ingress-controller", 42 | Namespace: "my-nginx-ingress-controller", 43 | OwnerReferences: []v1.OwnerReference{ 44 | { 45 | APIVersion: "k8s.nginx.org/v1alpha1", 46 | Name: instance.Name, 47 | Kind: "NginxIngressController", 48 | Controller: boolPointer(true), 49 | BlockOwnerDeletion: boolPointer(true), 50 | }, 51 | }, 52 | }, 53 | Spec: appsv1.DeploymentSpec{ 54 | Selector: &v1.LabelSelector{ 55 | MatchLabels: map[string]string{"app": instance.Name}, 56 | }, 57 | Template: corev1.PodTemplateSpec{ 58 | ObjectMeta: v1.ObjectMeta{ 59 | Name: "my-nginx-ingress-controller", 60 | Namespace: "my-nginx-ingress-controller", 61 | Labels: map[string]string{"app": instance.Name}, 62 | }, 63 | Spec: corev1.PodSpec{ 64 | ServiceAccountName: "my-nginx-ingress-controller", 65 | Containers: []corev1.Container{ 66 | { 67 | Name: "my-nginx-ingress-controller", 68 | Image: "nginx-ingress:edge", 69 | Args: generatePodArgs(instance), 70 | Ports: []corev1.ContainerPort{ 71 | { 72 | Name: "http", 73 | ContainerPort: 80, 74 | }, 75 | { 76 | Name: "https", 77 | ContainerPort: 443, 78 | }, 79 | }, 80 | SecurityContext: &corev1.SecurityContext{ 81 | Capabilities: &corev1.Capabilities{ 82 | Drop: []corev1.Capability{"ALL"}, 83 | Add: []corev1.Capability{"NET_BIND_SERVICE"}, 84 | }, 85 | RunAsUser: runAsUser, 86 | AllowPrivilegeEscalation: allowPrivilegeEscalation, 87 | }, 88 | Env: []corev1.EnvVar{ 89 | { 90 | Name: "POD_NAMESPACE", 91 | ValueFrom: &corev1.EnvVarSource{ 92 | FieldRef: &corev1.ObjectFieldSelector{ 93 | FieldPath: "metadata.namespace", 94 | }, 95 | }, 96 | }, 97 | { 98 | Name: "POD_NAME", 99 | ValueFrom: &corev1.EnvVarSource{ 100 | FieldRef: &corev1.ObjectFieldSelector{ 101 | FieldPath: "metadata.name", 102 | }, 103 | }, 104 | }, 105 | }, 106 | }, 107 | }, 108 | }, 109 | }, 110 | }, 111 | } 112 | 113 | result, _ := deploymentForNginxIngressController(instance, s) 114 | if !reflect.DeepEqual(result, expected) { 115 | t.Errorf("deploymentForNginxIngressController(%+v) returned %+v but expected %+v", instance, result, expected) 116 | } 117 | } 118 | 119 | func TestHasDeploymentChanged(t *testing.T) { 120 | runAsUser := new(int64) 121 | allowPrivilegeEscalation := new(bool) 122 | *runAsUser = 101 123 | *allowPrivilegeEscalation = true 124 | replicas := new(int32) 125 | *replicas = 1 126 | 127 | instance := &k8sv1alpha1.NginxIngressController{ 128 | ObjectMeta: metav1.ObjectMeta{ 129 | Name: "my-nginx-ingress-controller", 130 | Namespace: "my-nginx-ingress-controller", 131 | }, 132 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 133 | Image: k8sv1alpha1.Image{ 134 | Repository: "nginx-ingress", 135 | Tag: "edge", 136 | }, 137 | Replicas: replicas, 138 | }, 139 | } 140 | 141 | defaultDeployment := &appsv1.Deployment{ 142 | ObjectMeta: metav1.ObjectMeta{ 143 | Name: "my-nginx-ingress-controller", 144 | Namespace: "my-nginx-ingress-controller", 145 | }, 146 | Spec: appsv1.DeploymentSpec{ 147 | Replicas: replicas, 148 | Template: corev1.PodTemplateSpec{ 149 | ObjectMeta: v1.ObjectMeta{ 150 | Name: "my-nginx-ingress-controller", 151 | Namespace: "my-nginx-ingress-controller", 152 | }, 153 | Spec: corev1.PodSpec{ 154 | Containers: []corev1.Container{ 155 | { 156 | Name: "my-nginx-ingress-controller", 157 | Image: "nginx-ingress:edge", 158 | Args: generatePodArgs(instance), 159 | }, 160 | }, 161 | }, 162 | }, 163 | }, 164 | } 165 | 166 | tenReplicas := int32(10) 167 | 168 | tests := []struct { 169 | deployment *appsv1.Deployment 170 | instance *k8sv1alpha1.NginxIngressController 171 | expected bool 172 | msg string 173 | }{ 174 | { 175 | deployment: defaultDeployment, 176 | instance: instance, 177 | expected: false, 178 | msg: "no changes", 179 | }, 180 | { 181 | deployment: defaultDeployment, 182 | instance: &k8sv1alpha1.NginxIngressController{ 183 | ObjectMeta: metav1.ObjectMeta{ 184 | Name: "my-nginx-ingress-controller", 185 | Namespace: "my-nginx-ingress-controller", 186 | }, 187 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 188 | Image: k8sv1alpha1.Image{ 189 | Repository: "nginx-ingress", 190 | Tag: "edge", 191 | }, 192 | Replicas: &tenReplicas, 193 | }, 194 | }, 195 | expected: true, 196 | msg: "replicas increased", 197 | }, 198 | { 199 | deployment: &appsv1.Deployment{ 200 | ObjectMeta: metav1.ObjectMeta{ 201 | Name: "my-nginx-ingress-controller", 202 | Namespace: "my-nginx-ingress-controller", 203 | }, 204 | Spec: appsv1.DeploymentSpec{ 205 | Replicas: &tenReplicas, // Deployment with 10 replicas 206 | Template: corev1.PodTemplateSpec{ 207 | ObjectMeta: v1.ObjectMeta{ 208 | Name: "my-nginx-ingress-controller", 209 | Namespace: "my-nginx-ingress-controller", 210 | }, 211 | Spec: corev1.PodSpec{ 212 | Containers: []corev1.Container{ 213 | { 214 | Name: "my-nginx-ingress-controller", 215 | Image: "nginx-ingress:edge", 216 | Args: generatePodArgs(instance), 217 | }, 218 | }, 219 | }, 220 | }, 221 | }, 222 | }, 223 | instance: &k8sv1alpha1.NginxIngressController{ 224 | ObjectMeta: metav1.ObjectMeta{ 225 | Name: "my-nginx-ingress-controller", 226 | Namespace: "my-nginx-ingress-controller", 227 | }, 228 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 229 | Image: k8sv1alpha1.Image{ 230 | Repository: "nginx-ingress", 231 | Tag: "edge", 232 | }, 233 | }, 234 | }, 235 | expected: true, 236 | msg: "replicas field removed", 237 | }, 238 | { 239 | deployment: defaultDeployment, 240 | instance: &k8sv1alpha1.NginxIngressController{ 241 | ObjectMeta: metav1.ObjectMeta{ 242 | Name: "my-nginx-ingress-controller", 243 | Namespace: "my-nginx-ingress-controller", 244 | }, 245 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 246 | Image: k8sv1alpha1.Image{ 247 | Repository: "nginx-plus-ingress", 248 | Tag: "edge", 249 | }, 250 | }, 251 | }, 252 | expected: true, 253 | msg: "image repository update", 254 | }, 255 | { 256 | deployment: defaultDeployment, 257 | instance: &k8sv1alpha1.NginxIngressController{ 258 | ObjectMeta: metav1.ObjectMeta{ 259 | Name: "my-nginx-ingress-controller", 260 | Namespace: "my-nginx-ingress-controller", 261 | }, 262 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 263 | Image: k8sv1alpha1.Image{ 264 | Repository: "nginx-ingress", 265 | Tag: "edge", 266 | PullPolicy: "Always", 267 | }, 268 | }, 269 | }, 270 | expected: true, 271 | msg: "pull policy update", 272 | }, 273 | } 274 | for _, test := range tests { 275 | result := hasDeploymentChanged(test.deployment, test.instance) 276 | if result != test.expected { 277 | t.Errorf("hasDeploymentChanged() returned %v but expected %v for the case of %v", result, test.expected, test.msg) 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /controllers/ingressclass.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | networking "k8s.io/api/networking/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func ingressClassForNginxIngressController(instance *k8sv1alpha1.NginxIngressController) *networking.IngressClass { 10 | ingressClassName := "nginx" 11 | if instance.Spec.IngressClass != "" { 12 | ingressClassName = instance.Spec.IngressClass 13 | } 14 | ic := &networking.IngressClass{ 15 | ObjectMeta: metav1.ObjectMeta{ 16 | Name: ingressClassName, 17 | }, 18 | Spec: networking.IngressClassSpec{ 19 | Controller: "nginx.org/ingress-controller", 20 | }, 21 | } 22 | return ic 23 | } 24 | -------------------------------------------------------------------------------- /controllers/ingressclass_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | networking "k8s.io/api/networking/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | func TestIngressClassForNginxIngressController(t *testing.T) { 13 | instance := &k8sv1alpha1.NginxIngressController{ 14 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 15 | IngressClass: "nginx", 16 | }, 17 | } 18 | expected := &networking.IngressClass{ 19 | ObjectMeta: metav1.ObjectMeta{ 20 | Name: "nginx", 21 | }, 22 | Spec: networking.IngressClassSpec{ 23 | Controller: "nginx.org/ingress-controller", 24 | }, 25 | } 26 | 27 | result := ingressClassForNginxIngressController(instance) 28 | if diff := cmp.Diff(expected, result); diff != "" { 29 | t.Errorf("ingressClassForNginxIngressController() mismatch (-want +got):\n%s", diff) 30 | } 31 | } 32 | 33 | func TestIngressClassForNginxIngressControllerDefault(t *testing.T) { 34 | instance := &k8sv1alpha1.NginxIngressController{ 35 | Spec: k8sv1alpha1.NginxIngressControllerSpec{}, 36 | } 37 | expected := &networking.IngressClass{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: "nginx", 40 | }, 41 | Spec: networking.IngressClassSpec{ 42 | Controller: "nginx.org/ingress-controller", 43 | }, 44 | } 45 | 46 | result := ingressClassForNginxIngressController(instance) 47 | if diff := cmp.Diff(expected, result); diff != "" { 48 | t.Errorf("ingressClassForNginxIngressController() mismatch (-want +got):\n%s", diff) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /controllers/prerequisites.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/nginxinc/nginx-ingress-operator/controllers/scc" 8 | 9 | "github.com/go-logr/logr" 10 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 11 | v1 "k8s.io/api/core/v1" 12 | "k8s.io/apimachinery/pkg/api/errors" 13 | "k8s.io/apimachinery/pkg/types" 14 | "k8s.io/apimachinery/pkg/util/version" 15 | ) 16 | 17 | // checkPrerequisites creates all necessary objects before the deployment of a new Ingress Controller. 18 | func (r *NginxIngressControllerReconciler) checkPrerequisites(log logr.Logger, instance *k8sv1alpha1.NginxIngressController) error { 19 | sa, err := serviceAccountForNginxIngressController(instance, r.Scheme) 20 | if err != nil { 21 | return err 22 | } 23 | existed, err := r.createIfNotExists(sa) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if !existed { 29 | log.Info("ServiceAccount created", "ServiceAccount.Namespace", sa.Namespace, "ServiceAccount.Name", sa.Name) 30 | } 31 | 32 | // Assign this new ServiceAccount to the ClusterRoleBinding (if is not present already) 33 | crb := clusterRoleBindingForNginxIngressController(clusterRoleName) 34 | 35 | err = r.Get(context.TODO(), types.NamespacedName{Name: clusterRoleName, Namespace: v1.NamespaceAll}, crb) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | subject := subjectForServiceAccount(sa.Namespace, sa.Name) 41 | found := false 42 | for _, s := range crb.Subjects { 43 | if s.Name == subject.Name && s.Namespace == subject.Namespace { 44 | found = true 45 | break 46 | } 47 | } 48 | 49 | if !found { 50 | crb.Subjects = append(crb.Subjects, subject) 51 | 52 | err = r.Update(context.TODO(), crb) 53 | if err != nil { 54 | return err 55 | } 56 | } 57 | 58 | // IngressClass is available from k8s 1.18+ 59 | minVersion, _ := version.ParseGeneric("v1.18.0") 60 | if RunningK8sVersion.AtLeast(minVersion) { 61 | ic := ingressClassForNginxIngressController(instance) 62 | existed, err = r.createIfNotExists(ic) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | if !existed { 68 | log.Info("IngressClass created", "IngressClass.Name", ic.Name) 69 | } 70 | } 71 | 72 | if instance.Spec.DefaultSecret == "" { 73 | err = r.Get(context.TODO(), types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, &v1.Secret{}) 74 | 75 | if err != nil && errors.IsNotFound(err) { 76 | secret, err := defaultSecretForNginxIngressController(instance, r.Scheme) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | err = r.Create(context.TODO(), secret) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | log.Info("Warning! A custom self-signed TLS Secret has been created for the default server. "+ 87 | "Update your 'DefaultSecret' with your own Secret in Production", 88 | "Secret.Namespace", secret.Namespace, "Secret.Name", secret.Name) 89 | 90 | } else if err != nil { 91 | return err 92 | } 93 | } 94 | 95 | if r.SccAPIExists { 96 | err := scc.AddServiceAccount(r.Client, sa.Namespace, sa.Name) 97 | if err != nil { 98 | return fmt.Errorf("failed to add service account user to scc: %w", err) 99 | } 100 | } 101 | 102 | return nil 103 | } 104 | 105 | // create common resources shared by all the Ingress Controllers 106 | func (r *NginxIngressControllerReconciler) createCommonResources(log logr.Logger) error { 107 | // Create ClusterRole and ClusterRoleBinding for all the NginxIngressController resources. 108 | var err error 109 | 110 | cr := clusterRoleForNginxIngressController(clusterRoleName) 111 | 112 | err = r.Get(context.TODO(), types.NamespacedName{Name: clusterRoleName, Namespace: v1.NamespaceAll}, cr) 113 | 114 | if err != nil { 115 | if errors.IsNotFound(err) { 116 | log.Info("no previous ClusterRole found, creating a new one.") 117 | err = r.Create(context.TODO(), cr) 118 | if err != nil { 119 | return fmt.Errorf("error creating ClusterRole: %w", err) 120 | } 121 | } else { 122 | return fmt.Errorf("error getting ClusterRole: %w", err) 123 | } 124 | } else { 125 | // For updates in the ClusterRole permissions (eg new CRDs of the Ingress Controller). 126 | log.Info("previous ClusterRole found, updating.") 127 | cr := clusterRoleForNginxIngressController(clusterRoleName) 128 | err = r.Update(context.TODO(), cr) 129 | if err != nil { 130 | return fmt.Errorf("error updating ClusterRole: %w", err) 131 | } 132 | } 133 | 134 | crb := clusterRoleBindingForNginxIngressController(clusterRoleName) 135 | 136 | err = r.Get(context.TODO(), types.NamespacedName{Name: clusterRoleName, Namespace: v1.NamespaceAll}, crb) 137 | if err != nil && errors.IsNotFound(err) { 138 | log.Info("no previous ClusterRoleBinding found, creating a new one.") 139 | err = r.Create(context.TODO(), crb) 140 | } 141 | 142 | if err != nil { 143 | return fmt.Errorf("error creating ClusterRoleBinding: %w", err) 144 | } 145 | 146 | err = createKICCustomResourceDefinitions(log, r.Mgr) 147 | if err != nil { 148 | return fmt.Errorf("error creating KIC CRDs: %w", err) 149 | } 150 | 151 | if r.SccAPIExists { 152 | log.Info("OpenShift detected as platform.") 153 | 154 | err := scc.Create(r.Client, log) 155 | if err != nil { 156 | return fmt.Errorf("failed to create SecurityContextConstraints: %w", err) 157 | } 158 | } 159 | 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /controllers/rbac.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | rbacv1 "k8s.io/api/rbac/v1" 5 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | func clusterRoleForNginxIngressController(name string) *rbacv1.ClusterRole { 9 | rules := []rbacv1.PolicyRule{ 10 | { 11 | Verbs: []string{"get", "list", "watch"}, 12 | APIGroups: []string{""}, 13 | Resources: []string{"services", "endpoints"}, 14 | }, 15 | { 16 | Verbs: []string{"get", "list", "watch"}, 17 | APIGroups: []string{""}, 18 | Resources: []string{"secrets"}, 19 | }, 20 | { 21 | Verbs: []string{"get", "list", "watch", "update", "create"}, 22 | APIGroups: []string{""}, 23 | Resources: []string{"configmaps"}, 24 | }, 25 | { 26 | Verbs: []string{"list", "watch"}, 27 | APIGroups: []string{""}, 28 | Resources: []string{"pods"}, 29 | }, 30 | { 31 | Verbs: []string{"create", "patch"}, 32 | APIGroups: []string{""}, 33 | Resources: []string{"events"}, 34 | }, 35 | { 36 | Verbs: []string{"get", "list", "watch"}, 37 | APIGroups: []string{"networking.k8s.io"}, 38 | Resources: []string{"ingresses"}, 39 | }, 40 | { 41 | Verbs: []string{"update"}, 42 | APIGroups: []string{"networking.k8s.io"}, 43 | Resources: []string{"ingresses/status"}, 44 | }, 45 | { 46 | Verbs: []string{"get", "create"}, 47 | APIGroups: []string{"networking.k8s.io"}, 48 | Resources: []string{"ingressclasses"}, 49 | }, 50 | { 51 | Verbs: []string{"get", "list", "watch"}, 52 | APIGroups: []string{"k8s.nginx.org"}, 53 | Resources: []string{"virtualservers", "virtualserverroutes", "globalconfigurations", "transportservers", "policies"}, 54 | }, 55 | { 56 | Verbs: []string{"update"}, 57 | APIGroups: []string{"k8s.nginx.org"}, 58 | Resources: []string{"virtualservers/status", "virtualserverroutes/status", "policies/status", "transportservers/status"}, 59 | }, 60 | { 61 | Verbs: []string{"get", "list", "watch"}, 62 | APIGroups: []string{"appprotect.f5.com"}, 63 | Resources: []string{"aplogconfs", "appolicies", "apusersigs"}, 64 | }, 65 | { 66 | Verbs: []string{"get", "list", "watch"}, 67 | APIGroups: []string{"appprotectdos.f5.com"}, 68 | Resources: []string{"apdoslogconfs", "apdospolicies", "dosprotectedresources"}, 69 | }, 70 | } 71 | rbac := &rbacv1.ClusterRole{ 72 | ObjectMeta: v1.ObjectMeta{ 73 | Name: name, 74 | }, 75 | Rules: rules, 76 | } 77 | return rbac 78 | } 79 | 80 | func subjectForServiceAccount(namespace string, name string) rbacv1.Subject { 81 | sa := rbacv1.Subject{ 82 | Kind: "ServiceAccount", 83 | Name: name, 84 | Namespace: namespace, 85 | } 86 | return sa 87 | } 88 | 89 | func clusterRoleBindingForNginxIngressController(name string) *rbacv1.ClusterRoleBinding { 90 | crb := &rbacv1.ClusterRoleBinding{ 91 | ObjectMeta: v1.ObjectMeta{ 92 | Name: name, 93 | }, 94 | RoleRef: rbacv1.RoleRef{ 95 | Kind: "ClusterRole", 96 | Name: name, 97 | APIGroup: "rbac.authorization.k8s.io", 98 | }, 99 | } 100 | return crb 101 | } 102 | -------------------------------------------------------------------------------- /controllers/rbac_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | rbacv1 "k8s.io/api/rbac/v1" 8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func TestClusterRoleForNginxIngressController(t *testing.T) { 12 | name := "my-rcluster-role" 13 | expected := &rbacv1.ClusterRole{ 14 | ObjectMeta: v1.ObjectMeta{ 15 | Name: name, 16 | }, 17 | Rules: []rbacv1.PolicyRule{ 18 | { 19 | Verbs: []string{"get", "list", "watch"}, 20 | APIGroups: []string{""}, 21 | Resources: []string{"services", "endpoints"}, 22 | }, 23 | { 24 | Verbs: []string{"get", "list", "watch"}, 25 | APIGroups: []string{""}, 26 | Resources: []string{"secrets"}, 27 | }, 28 | { 29 | Verbs: []string{"get", "list", "watch", "update", "create"}, 30 | APIGroups: []string{""}, 31 | Resources: []string{"configmaps"}, 32 | }, 33 | { 34 | Verbs: []string{"list", "watch"}, 35 | APIGroups: []string{""}, 36 | Resources: []string{"pods"}, 37 | }, 38 | { 39 | Verbs: []string{"create", "patch"}, 40 | APIGroups: []string{""}, 41 | Resources: []string{"events"}, 42 | }, 43 | { 44 | Verbs: []string{"get", "list", "watch"}, 45 | APIGroups: []string{"networking.k8s.io"}, 46 | Resources: []string{"ingresses"}, 47 | }, 48 | { 49 | Verbs: []string{"update"}, 50 | APIGroups: []string{"networking.k8s.io"}, 51 | Resources: []string{"ingresses/status"}, 52 | }, 53 | { 54 | Verbs: []string{"get", "create"}, 55 | APIGroups: []string{"networking.k8s.io"}, 56 | Resources: []string{"ingressclasses"}, 57 | }, 58 | { 59 | Verbs: []string{"get", "list", "watch"}, 60 | APIGroups: []string{"k8s.nginx.org"}, 61 | Resources: []string{"virtualservers", "virtualserverroutes", "globalconfigurations", "transportservers", "policies"}, 62 | }, 63 | { 64 | Verbs: []string{"update"}, 65 | APIGroups: []string{"k8s.nginx.org"}, 66 | Resources: []string{"virtualservers/status", "virtualserverroutes/status", "policies/status", "transportservers/status"}, 67 | }, 68 | { 69 | Verbs: []string{"get", "list", "watch"}, 70 | APIGroups: []string{"appprotect.f5.com"}, 71 | Resources: []string{"aplogconfs", "appolicies", "apusersigs"}, 72 | }, 73 | { 74 | Verbs: []string{"get", "list", "watch"}, 75 | APIGroups: []string{"appprotectdos.f5.com"}, 76 | Resources: []string{"apdoslogconfs", "apdospolicies", "dosprotectedresources"}, 77 | }, 78 | }, 79 | } 80 | 81 | result := clusterRoleForNginxIngressController(name) 82 | if diff := cmp.Diff(expected, result); diff != "" { 83 | t.Errorf("clusterRoleForNginxIngressController(%v) mismatch (-want +got):\n%s", name, diff) 84 | } 85 | } 86 | 87 | func TestSubjectForServiceAccount(t *testing.T) { 88 | name := "my-sa" 89 | namespace := "my-nginx-ingress" 90 | expected := rbacv1.Subject{ 91 | Kind: "ServiceAccount", 92 | Name: name, 93 | Namespace: namespace, 94 | } 95 | 96 | result := subjectForServiceAccount(namespace, name) 97 | if diff := cmp.Diff(expected, result); diff != "" { 98 | t.Errorf("subjectForServiceAccount(%v, %v) mismatch (-want +got):\n%s", namespace, name, diff) 99 | } 100 | } 101 | 102 | func TestClusterRoleBindingForNginxIngressController(t *testing.T) { 103 | name := "my-cluster-role-binding" 104 | expected := &rbacv1.ClusterRoleBinding{ 105 | ObjectMeta: v1.ObjectMeta{ 106 | Name: name, 107 | }, 108 | RoleRef: rbacv1.RoleRef{ 109 | Kind: "ClusterRole", 110 | Name: name, 111 | APIGroup: "rbac.authorization.k8s.io", 112 | }, 113 | } 114 | 115 | result := clusterRoleBindingForNginxIngressController(name) 116 | if diff := cmp.Diff(expected, result); diff != "" { 117 | t.Errorf("clusterRoleBindingForNginxIngressController(%v) mismatch (-want +got):\n%s", name, diff) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /controllers/scc/scc.go: -------------------------------------------------------------------------------- 1 | package scc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/go-logr/logr" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | "k8s.io/apimachinery/pkg/types" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | 12 | secv1 "github.com/openshift/api/security/v1" 13 | corev1 "k8s.io/api/core/v1" 14 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | ) 16 | 17 | const defaultName = "nginx-ingress-scc" 18 | 19 | func sccConfigTemplate() *secv1.SecurityContextConstraints { 20 | var uid int64 = 101 21 | allowPrivilegeEscalation := true 22 | 23 | return &secv1.SecurityContextConstraints{ 24 | ObjectMeta: v1.ObjectMeta{ 25 | Name: defaultName, 26 | }, 27 | AllowHostPorts: false, 28 | AllowPrivilegedContainer: false, 29 | RunAsUser: secv1.RunAsUserStrategyOptions{ 30 | Type: "MustRunAs", 31 | UID: &uid, 32 | }, 33 | Users: nil, 34 | AllowHostDirVolumePlugin: false, 35 | AllowHostIPC: false, 36 | SELinuxContext: secv1.SELinuxContextStrategyOptions{ 37 | Type: "MustRunAs", 38 | }, 39 | ReadOnlyRootFilesystem: false, 40 | FSGroup: secv1.FSGroupStrategyOptions{ 41 | Type: "MustRunAs", 42 | }, 43 | SupplementalGroups: secv1.SupplementalGroupsStrategyOptions{ 44 | Type: "MustRunAs", 45 | }, 46 | Volumes: []secv1.FSType{"secret"}, 47 | AllowHostPID: false, 48 | AllowHostNetwork: false, 49 | AllowPrivilegeEscalation: &allowPrivilegeEscalation, 50 | RequiredDropCapabilities: []corev1.Capability{"ALL"}, 51 | DefaultAddCapabilities: []corev1.Capability{"NET_BIND_SERVICE"}, 52 | AllowedCapabilities: nil, 53 | } 54 | } 55 | 56 | func serviceAccountName(namespace string, name string) string { 57 | return fmt.Sprintf("system:serviceaccount:%v:%v", namespace, name) 58 | } 59 | 60 | func Create(client client.Client, log logr.Logger) error { 61 | scc := sccConfigTemplate() 62 | err := client.Get(context.TODO(), types.NamespacedName{Name: defaultName, Namespace: v1.NamespaceAll}, scc) 63 | if err != nil { 64 | if errors.IsNotFound(err) { 65 | log.Info("no previous SecurityContextConstraints found, creating a new one.") 66 | err = client.Create(context.TODO(), scc) 67 | if err != nil { 68 | return fmt.Errorf("error creating SecurityContextConstraints: %w", err) 69 | } 70 | } 71 | return fmt.Errorf("error getting scc: %w", err) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func AddServiceAccount(client client.Client, namespace string, name string) error { 78 | scc := sccConfigTemplate() 79 | err := client.Get(context.TODO(), types.NamespacedName{Name: defaultName, Namespace: v1.NamespaceAll}, scc) 80 | if err != nil { 81 | return fmt.Errorf("failed to get scc: %w", err) 82 | } 83 | 84 | saName := serviceAccountName(namespace, name) 85 | for _, u := range scc.Users { 86 | if u == saName { 87 | // scc already has the service account name 88 | return nil 89 | } 90 | } 91 | 92 | scc.Users = append(scc.Users, saName) 93 | err = client.Update(context.TODO(), scc) 94 | if err != nil { 95 | return fmt.Errorf("failed to update scc: %w", err) 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func RemoveServiceAccount(client client.Client, namespace string, name string) error { 102 | scc := sccConfigTemplate() 103 | err := client.Get(context.TODO(), types.NamespacedName{Name: defaultName, Namespace: v1.NamespaceAll}, scc) 104 | if err != nil { 105 | return fmt.Errorf("failed to get scc: %w", err) 106 | } 107 | 108 | scc.Users = removeStringValue(scc.Users, serviceAccountName(namespace, name)) 109 | 110 | err = client.Update(context.TODO(), scc) 111 | if err != nil { 112 | return fmt.Errorf("failed to update scc: %w", err) 113 | } 114 | return nil 115 | } 116 | 117 | func removeStringValue(values []string, value string) []string { 118 | var filtered []string 119 | for _, v := range values { 120 | if v != value { 121 | filtered = append(filtered, v) 122 | } 123 | } 124 | return filtered 125 | } 126 | -------------------------------------------------------------------------------- /controllers/scc/scc_test.go: -------------------------------------------------------------------------------- 1 | package scc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | secv1 "github.com/openshift/api/security/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | func TestSccForNginxIngressController(t *testing.T) { 14 | var uid int64 = 101 15 | allowPrivilegeEscalation := true 16 | 17 | expected := &secv1.SecurityContextConstraints{ 18 | ObjectMeta: v1.ObjectMeta{ 19 | Name: "nginx-ingress-scc", 20 | }, 21 | AllowHostPorts: false, 22 | AllowPrivilegedContainer: false, 23 | RunAsUser: secv1.RunAsUserStrategyOptions{ 24 | Type: "MustRunAs", 25 | UID: &uid, 26 | }, 27 | Users: nil, 28 | AllowHostDirVolumePlugin: false, 29 | AllowHostIPC: false, 30 | SELinuxContext: secv1.SELinuxContextStrategyOptions{ 31 | Type: "MustRunAs", 32 | }, 33 | ReadOnlyRootFilesystem: false, 34 | FSGroup: secv1.FSGroupStrategyOptions{ 35 | Type: "MustRunAs", 36 | }, 37 | SupplementalGroups: secv1.SupplementalGroupsStrategyOptions{ 38 | Type: "MustRunAs", 39 | }, 40 | Volumes: []secv1.FSType{"secret"}, 41 | AllowHostPID: false, 42 | AllowHostNetwork: false, 43 | AllowPrivilegeEscalation: &allowPrivilegeEscalation, 44 | RequiredDropCapabilities: []corev1.Capability{"ALL"}, 45 | DefaultAddCapabilities: []corev1.Capability{"NET_BIND_SERVICE"}, 46 | AllowedCapabilities: nil, 47 | } 48 | 49 | result := sccConfigTemplate() 50 | if diff := cmp.Diff(expected, result); diff != "" { 51 | t.Errorf("sccConfigTemplate() mismatch (-want +got):\n%s", diff) 52 | } 53 | } 54 | 55 | func TestServiceAccountName(t *testing.T) { 56 | namespace := "my-nginx-ingress" 57 | name := "my-nginx-ingress-controller" 58 | expected := fmt.Sprintf("system:serviceaccount:%v:%v", namespace, name) 59 | 60 | result := serviceAccountName(namespace, name) 61 | if expected != result { 62 | t.Errorf("serviceAccountName(%v, %v) returned %v but expected %v", namespace, name, result, expected) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /controllers/secret.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "fmt" 10 | "math/big" 11 | "time" 12 | 13 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 14 | corev1 "k8s.io/api/core/v1" 15 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/runtime" 17 | ctrl "sigs.k8s.io/controller-runtime" 18 | ) 19 | 20 | const sslHost = "example.com" 21 | 22 | func defaultSecretForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*corev1.Secret, error) { 23 | crt, key, err := generateFakeCertAndKey() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | secret := &corev1.Secret{ 29 | ObjectMeta: v1.ObjectMeta{ 30 | Name: instance.Name, 31 | Namespace: instance.Namespace, 32 | }, 33 | Data: map[string][]byte{ 34 | corev1.TLSCertKey: crt, 35 | corev1.TLSPrivateKeyKey: key, 36 | }, 37 | Type: corev1.SecretTypeTLS, 38 | } 39 | err = ctrl.SetControllerReference(instance, secret, scheme) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return secret, nil 44 | } 45 | 46 | func generateFakeCertAndKey() ([]byte, []byte, error) { 47 | var priv interface{} 48 | 49 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 50 | if err != nil { 51 | return nil, nil, err 52 | } 53 | 54 | notBefore := time.Now() 55 | // This certificate is valid for 365 days 56 | notAfter := notBefore.Add(365 * 24 * time.Hour) 57 | 58 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 59 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | template := x509.Certificate{ 65 | SerialNumber: serialNumber, 66 | Subject: pkix.Name{ 67 | Organization: []string{"NGINX Inc"}, 68 | CommonName: "example.com", 69 | }, 70 | NotBefore: notBefore, 71 | NotAfter: notAfter, 72 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 73 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 74 | BasicConstraintsValid: true, 75 | DNSNames: []string{sslHost}, 76 | } 77 | 78 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.(*rsa.PrivateKey).PublicKey, priv) 79 | if err != nil { 80 | return nil, nil, err 81 | } 82 | 83 | cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 84 | key := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))}) 85 | 86 | if cert == nil || key == nil { 87 | return nil, nil, fmt.Errorf("error encoding ket/crt to PEM") 88 | } 89 | 90 | return cert, key, nil 91 | } 92 | -------------------------------------------------------------------------------- /controllers/secret_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes/scheme" 11 | ) 12 | 13 | func TestDefaultSecretForNginxIngressController(t *testing.T) { 14 | s := scheme.Scheme 15 | 16 | if err := k8sv1alpha1.AddToScheme(s); err != nil { 17 | t.Fatalf("Unable to add k8sv1alpha1 scheme: (%v)", err) 18 | } 19 | instance := &k8sv1alpha1.NginxIngressController{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "my-nginx-ingress-controller", 22 | Namespace: "my-nginx-ingress-controller-ns", 23 | }, 24 | } 25 | 26 | expectedObjectMeta := &metav1.ObjectMeta{ 27 | Name: "my-nginx-ingress-controller", 28 | Namespace: "my-nginx-ingress-controller-ns", 29 | } 30 | expectedType := corev1.SecretTypeTLS 31 | 32 | secret, err := defaultSecretForNginxIngressController(instance, s) 33 | if err != nil { 34 | t.Fatalf("defaultSecretForNginxIngressController() returned unexpected error %v", err) 35 | } 36 | 37 | if reflect.DeepEqual(expectedObjectMeta, secret.ObjectMeta) { 38 | t.Errorf("defaultSecretForNginxIngressController() returned %v but expected %v", secret.ObjectMeta, expectedObjectMeta) 39 | } 40 | if expectedType != secret.Type { 41 | t.Errorf("defaultSecretForNginxIngressController() returned %s but expected %s", secret.Type, expectedType) 42 | } 43 | if len(secret.Data[corev1.TLSCertKey]) == 0 { 44 | t.Errorf("defaultSecretForNginxIngressController() returned empty data key %s", corev1.TLSCertKey) 45 | } 46 | if len(secret.Data[corev1.TLSPrivateKeyKey]) == 0 { 47 | t.Errorf("defaultSecretForNginxIngressController() returned empty data key %s", corev1.TLSPrivateKeyKey) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /controllers/service.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | corev1 "k8s.io/api/core/v1" 6 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | "k8s.io/apimachinery/pkg/util/intstr" 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 11 | ) 12 | 13 | func serviceForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*corev1.Service, error) { 14 | extraLabels := map[string]string{} 15 | extraAnnotations := map[string]string{} 16 | if instance.Spec.Service != nil { 17 | extraLabels = instance.Spec.Service.ExtraLabels 18 | extraAnnotations = instance.Spec.Service.ExtraAnnotations 19 | } 20 | 21 | svc := &corev1.Service{ 22 | ObjectMeta: v1.ObjectMeta{ 23 | Name: instance.Name, 24 | Namespace: instance.Namespace, 25 | Labels: extraLabels, 26 | Annotations: extraAnnotations, 27 | }, 28 | Spec: corev1.ServiceSpec{ 29 | Ports: []corev1.ServicePort{ 30 | { 31 | Name: "http", 32 | Protocol: "TCP", 33 | Port: 80, 34 | TargetPort: intstr.IntOrString{ 35 | Type: 0, 36 | IntVal: 80, 37 | }, 38 | }, 39 | { 40 | Name: "https", 41 | Protocol: "TCP", 42 | Port: 443, 43 | TargetPort: intstr.IntOrString{ 44 | Type: 0, 45 | IntVal: 443, 46 | }, 47 | }, 48 | }, 49 | Selector: map[string]string{"app": instance.Name}, 50 | Type: corev1.ServiceType(instance.Spec.ServiceType), 51 | }, 52 | } 53 | 54 | if err := ctrl.SetControllerReference(instance, svc, scheme); err != nil { 55 | return nil, err 56 | } 57 | 58 | return svc, nil 59 | } 60 | 61 | func serviceMutateFn(svc *corev1.Service, serviceType string, labels map[string]string, annotations map[string]string) controllerutil.MutateFn { 62 | return func() error { 63 | svc.Spec.Type = corev1.ServiceType(serviceType) 64 | svc.Labels = labels 65 | svc.Annotations = annotations 66 | return nil 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /controllers/service_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | corev1 "k8s.io/api/core/v1" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/util/intstr" 11 | "k8s.io/client-go/kubernetes/scheme" 12 | ) 13 | 14 | func TestServiceForNginxIngressController(t *testing.T) { 15 | boolPointer := func(b bool) *bool { return &b } 16 | s := scheme.Scheme 17 | 18 | if err := k8sv1alpha1.AddToScheme(s); err != nil { 19 | t.Fatalf("Unable to add k8sv1alpha1 scheme: (%v)", err) 20 | } 21 | name := "my-service" 22 | namespace := "my-nginx-ingress" 23 | extraLabels := map[string]string{"app": "my-nginx-ingress"} 24 | extraAnnotations := map[string]string{"app": "my-nginx-ingress"} 25 | 26 | instance := &k8sv1alpha1.NginxIngressController{ 27 | ObjectMeta: v1.ObjectMeta{ 28 | Name: name, 29 | Namespace: namespace, 30 | Labels: extraLabels, 31 | Annotations: extraAnnotations, 32 | }, 33 | Spec: k8sv1alpha1.NginxIngressControllerSpec{ 34 | ServiceType: string(corev1.ServiceTypeLoadBalancer), 35 | Service: &k8sv1alpha1.Service{ 36 | ExtraLabels: extraLabels, 37 | ExtraAnnotations: extraAnnotations, 38 | }, 39 | }, 40 | } 41 | expected := &corev1.Service{ 42 | ObjectMeta: v1.ObjectMeta{ 43 | Name: name, 44 | Namespace: namespace, 45 | Labels: extraLabels, 46 | Annotations: extraAnnotations, 47 | OwnerReferences: []v1.OwnerReference{ 48 | { 49 | APIVersion: "k8s.nginx.org/v1alpha1", 50 | Name: instance.Name, 51 | Kind: "NginxIngressController", 52 | Controller: boolPointer(true), 53 | BlockOwnerDeletion: boolPointer(true), 54 | }, 55 | }, 56 | }, 57 | Spec: corev1.ServiceSpec{ 58 | Ports: []corev1.ServicePort{ 59 | { 60 | Name: "http", 61 | Protocol: "TCP", 62 | Port: 80, 63 | TargetPort: intstr.IntOrString{ 64 | Type: 0, 65 | IntVal: 80, 66 | }, 67 | }, 68 | { 69 | Name: "https", 70 | Protocol: "TCP", 71 | Port: 443, 72 | TargetPort: intstr.IntOrString{ 73 | Type: 0, 74 | IntVal: 443, 75 | }, 76 | }, 77 | }, 78 | Selector: map[string]string{"app": instance.Name}, 79 | Type: corev1.ServiceTypeLoadBalancer, 80 | }, 81 | } 82 | 83 | result, _ := serviceForNginxIngressController(instance, s) 84 | if diff := cmp.Diff(expected, result); diff != "" { 85 | t.Errorf("serviceForNginxIngressController() mismatch (-want +got):\n%s", diff) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /controllers/serviceaccount.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 5 | corev1 "k8s.io/api/core/v1" 6 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | ctrl "sigs.k8s.io/controller-runtime" 9 | ) 10 | 11 | func serviceAccountForNginxIngressController(instance *k8sv1alpha1.NginxIngressController, scheme *runtime.Scheme) (*corev1.ServiceAccount, error) { 12 | svca := &corev1.ServiceAccount{ 13 | ObjectMeta: v1.ObjectMeta{ 14 | Name: instance.Name, 15 | Namespace: instance.Namespace, 16 | }, 17 | } 18 | 19 | if err := ctrl.SetControllerReference(instance, svca, scheme); err != nil { 20 | return nil, err 21 | } 22 | 23 | return svca, nil 24 | } 25 | -------------------------------------------------------------------------------- /controllers/serviceaccount_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 8 | corev1 "k8s.io/api/core/v1" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes/scheme" 11 | ) 12 | 13 | func TestServiceAccountForNginxIngressController(t *testing.T) { 14 | boolPointer := func(b bool) *bool { return &b } 15 | s := scheme.Scheme 16 | 17 | if err := k8sv1alpha1.AddToScheme(s); err != nil { 18 | t.Fatalf("Unable to add k8sv1alpha1 scheme: (%v)", err) 19 | } 20 | namespace := "my-nginx-ingress" 21 | name := "my-sa" 22 | instance := &k8sv1alpha1.NginxIngressController{ 23 | ObjectMeta: v1.ObjectMeta{ 24 | Name: name, 25 | Namespace: namespace, 26 | }, 27 | } 28 | expected := &corev1.ServiceAccount{ 29 | ObjectMeta: v1.ObjectMeta{ 30 | Name: name, 31 | Namespace: namespace, 32 | OwnerReferences: []v1.OwnerReference{ 33 | { 34 | APIVersion: "k8s.nginx.org/v1alpha1", 35 | Name: instance.Name, 36 | Kind: "NginxIngressController", 37 | Controller: boolPointer(true), 38 | BlockOwnerDeletion: boolPointer(true), 39 | }, 40 | }, 41 | }, 42 | } 43 | 44 | result, _ := serviceAccountForNginxIngressController(instance, s) 45 | if diff := cmp.Diff(expected, result); diff != "" { 46 | t.Errorf("serviceAccountForNginxIngressController() mismatch (-want +got):\n%s", diff) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/envtest" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | logf "sigs.k8s.io/controller-runtime/pkg/log" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | 32 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 33 | //+kubebuilder:scaffold:imports 34 | ) 35 | 36 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 37 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 38 | 39 | var ( 40 | k8sClient client.Client 41 | testEnv *envtest.Environment 42 | ) 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func() { 53 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | ErrorIfCRDPathMissing: true, 59 | } 60 | 61 | cfg, err := testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = k8sv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | //+kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Expect(k8sClient).NotTo(BeNil()) 73 | }, 60) 74 | 75 | var _ = AfterSuite(func() { 76 | By("tearing down the test environment") 77 | err := testEnv.Stop() 78 | Expect(err).NotTo(HaveOccurred()) 79 | }) 80 | -------------------------------------------------------------------------------- /controllers/utils.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 9 | secv1 "github.com/openshift/api/security/v1" 10 | corev1 "k8s.io/api/core/v1" 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "k8s.io/apimachinery/pkg/util/version" 13 | "k8s.io/client-go/discovery" 14 | "k8s.io/client-go/kubernetes" 15 | "sigs.k8s.io/controller-runtime/pkg/client/config" 16 | ) 17 | 18 | const apiVersionUnsupportedError = "server does not support API version" 19 | 20 | // RunningK8sVersion contains the version of k8s 21 | var RunningK8sVersion *version.Version 22 | 23 | // generatePodArgs generate a list of arguments for the Ingress Controller pods based on the CRD. 24 | func generatePodArgs(instance *k8sv1alpha1.NginxIngressController) []string { 25 | var args []string 26 | 27 | args = append(args, fmt.Sprintf("-nginx-configmaps=%v/%v", instance.Namespace, instance.Name)) 28 | 29 | defaultSecretName := instance.Spec.DefaultSecret 30 | if instance.Spec.DefaultSecret == "" { 31 | defaultSecretName = fmt.Sprintf("%v/%v", instance.Namespace, instance.Name) 32 | } 33 | args = append(args, fmt.Sprintf("-default-server-tls-secret=%v", defaultSecretName)) 34 | 35 | if instance.Spec.NginxPlus { 36 | args = append(args, "-nginx-plus") 37 | 38 | if instance.Spec.AppProtect != nil && instance.Spec.AppProtect.Enable { 39 | args = append(args, "-enable-app-protect") 40 | } 41 | if instance.Spec.AppProtectDos != nil && instance.Spec.AppProtectDos.Enable { 42 | args = append(args, "-enable-app-protect-dos") 43 | if instance.Spec.AppProtectDos.Debug { 44 | args = append(args, "-app-protect-dos-debug") 45 | } 46 | if instance.Spec.AppProtectDos.MaxDaemons != 0 { 47 | args = append(args, fmt.Sprintf("-app-protect-dos-max-daemons=%v", instance.Spec.AppProtectDos.MaxDaemons)) 48 | } 49 | if instance.Spec.AppProtectDos.MaxWorkers != 0 { 50 | args = append(args, fmt.Sprintf("-app-protect-dos-max-workers=%v", instance.Spec.AppProtectDos.MaxWorkers)) 51 | } 52 | if instance.Spec.AppProtectDos.Memory != 0 { 53 | args = append(args, fmt.Sprintf("-app-protect-dos-memory=%v", instance.Spec.AppProtectDos.Memory)) 54 | } 55 | } 56 | } 57 | 58 | if instance.Spec.IngressClass != "" { 59 | args = append(args, fmt.Sprintf("-ingress-class=%v", instance.Spec.IngressClass)) 60 | } 61 | 62 | if instance.Spec.WatchNamespace != "" { 63 | args = append(args, fmt.Sprintf("-watch-namespace=%v", instance.Spec.WatchNamespace)) 64 | } 65 | 66 | if instance.Spec.HealthStatus != nil && instance.Spec.HealthStatus.Enable { 67 | args = append(args, "-health-status") 68 | if instance.Spec.HealthStatus.URI != "" { 69 | args = append(args, fmt.Sprintf("-health-status-uri=%v", instance.Spec.HealthStatus.URI)) 70 | } 71 | } 72 | 73 | if instance.Spec.NginxDebug { 74 | args = append(args, "-nginx-debug") 75 | } 76 | 77 | if instance.Spec.LogLevel != 0 { 78 | args = append(args, fmt.Sprintf("-v=%v", instance.Spec.LogLevel)) 79 | } 80 | 81 | if instance.Spec.NginxStatus != nil && instance.Spec.NginxStatus.Enable { 82 | args = append(args, "-nginx-status") 83 | 84 | if instance.Spec.NginxStatus.Port != nil { 85 | args = append(args, fmt.Sprintf("-nginx-status-port=%v", *instance.Spec.NginxStatus.Port)) 86 | } 87 | 88 | if instance.Spec.NginxStatus.AllowCidrs != "" { 89 | args = append(args, fmt.Sprintf("-nginx-status-allow-cidrs=%v", instance.Spec.NginxStatus.AllowCidrs)) 90 | } 91 | } 92 | 93 | if instance.Spec.ReportIngressStatus != nil && instance.Spec.ReportIngressStatus.Enable { 94 | args = append(args, "-report-ingress-status") 95 | 96 | if instance.Spec.ReportIngressStatus.ExternalService != "" { 97 | args = append(args, fmt.Sprintf("-external-service=%v", instance.Spec.ReportIngressStatus.ExternalService)) 98 | } else if instance.Spec.ServiceType == "LoadBalancer" { 99 | args = append(args, fmt.Sprintf("-external-service=%v", instance.Name)) 100 | } else if instance.Spec.ReportIngressStatus.IngressLink != "" { 101 | args = append(args, fmt.Sprintf("-ingresslink=%v", instance.Spec.ReportIngressStatus.IngressLink)) 102 | } 103 | } 104 | 105 | if instance.Spec.EnableLeaderElection == nil || *instance.Spec.EnableLeaderElection { 106 | args = append(args, fmt.Sprintf("-leader-election-lock-name=%v-lock", instance.Name)) 107 | } else { 108 | args = append(args, "-enable-leader-election=false") 109 | } 110 | 111 | if instance.Spec.WildcardTLS != "" { 112 | args = append(args, fmt.Sprintf("-wildcard-tls-secret=%v", instance.Spec.WildcardTLS)) 113 | } 114 | 115 | if instance.Spec.Prometheus != nil && instance.Spec.Prometheus.Enable { 116 | args = append(args, "-enable-prometheus-metrics") 117 | 118 | if instance.Spec.Prometheus.Port != nil { 119 | args = append(args, fmt.Sprintf("-prometheus-metrics-listen-port=%v", *instance.Spec.Prometheus.Port)) 120 | } 121 | 122 | if instance.Spec.EnableLatencyMetrics { 123 | args = append(args, "-enable-latency-metrics") 124 | } 125 | 126 | if instance.Spec.Prometheus.Secret != "" { 127 | args = append(args, fmt.Sprintf("-prometheus-tls-secret=%v", instance.Spec.Prometheus.Secret)) 128 | } 129 | } 130 | 131 | if instance.Spec.EnableCRDs != nil && !*instance.Spec.EnableCRDs { 132 | args = append(args, "-enable-custom-resources=false") 133 | } else { 134 | if instance.Spec.EnableTLSPassthrough { 135 | args = append(args, "-enable-tls-passthrough") 136 | } 137 | 138 | if instance.Spec.GlobalConfiguration != "" { 139 | args = append(args, fmt.Sprintf("-global-configuration=%v", instance.Spec.GlobalConfiguration)) 140 | } 141 | 142 | if instance.Spec.EnableSnippets { 143 | args = append(args, "-enable-snippets") 144 | } 145 | 146 | if instance.Spec.EnablePreviewPolicies { 147 | args = append(args, "-enable-preview-policies") 148 | } 149 | } 150 | 151 | if instance.Spec.NginxReloadTimeout != 0 { 152 | args = append(args, fmt.Sprintf("-nginx-reload-timeout=%v", instance.Spec.NginxReloadTimeout)) 153 | } 154 | 155 | return args 156 | } 157 | 158 | // hasDifferentArguments returns whether the arguments of a container are different than the NginxIngressController spec. 159 | func hasDifferentArguments(container corev1.Container, instance *k8sv1alpha1.NginxIngressController) bool { 160 | newArgs := generatePodArgs(instance) 161 | return !reflect.DeepEqual(newArgs, container.Args) 162 | } 163 | 164 | func VerifySCCAPIExists() (bool, error) { 165 | cfg, err := config.GetConfig() 166 | if err != nil { 167 | return false, err 168 | } 169 | 170 | clientSet, err := kubernetes.NewForConfig(cfg) 171 | if err != nil { 172 | return false, err 173 | } 174 | 175 | gv := schema.GroupVersion{ 176 | Group: secv1.GroupName, 177 | Version: secv1.GroupVersion.Version, 178 | } 179 | 180 | err = discovery.ServerSupportsVersion(clientSet, gv) 181 | if err != nil { 182 | // This error means the call could not find SCC in the API, but there was no API error. 183 | if strings.Contains(err.Error(), apiVersionUnsupportedError) { 184 | return false, nil 185 | } 186 | return false, err 187 | } 188 | 189 | return true, nil 190 | } 191 | 192 | func generateImage(repository string, tag string) string { 193 | return fmt.Sprintf("%v:%v", repository, tag) 194 | } 195 | 196 | // GetK8sVersion returns the running version of k8s 197 | func GetK8sVersion(client kubernetes.Interface) (v *version.Version, err error) { 198 | serverVersion, err := client.Discovery().ServerVersion() 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | runningVersion, err := version.ParseGeneric(serverVersion.String()) 204 | if err != nil { 205 | return nil, fmt.Errorf("unexpected error parsing running Kubernetes version: %w", err) 206 | } 207 | 208 | return runningVersion, nil 209 | } 210 | -------------------------------------------------------------------------------- /docs/images/openshift1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-ingress-operator/d99400f5cf7e86574c50a736835f5dda3064e623/docs/images/openshift1.png -------------------------------------------------------------------------------- /docs/images/openshift2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-ingress-operator/d99400f5cf7e86574c50a736835f5dda3064e623/docs/images/openshift2.png -------------------------------------------------------------------------------- /docs/images/openshift3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-ingress-operator/d99400f5cf7e86574c50a736835f5dda3064e623/docs/images/openshift3.png -------------------------------------------------------------------------------- /docs/images/openshift4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nginxinc/nginx-ingress-operator/d99400f5cf7e86574c50a736835f5dda3064e623/docs/images/openshift4.png -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | We currently support 2 methods of installation: 4 | 1. [Installation in an Openshift cluster](./openshift-installation.md) using the [OLM](https://github.com/operator-framework/operator-lifecycle-manager). 5 | 1. [Manual installation](./manual-installation.md) in a Kubernetes or Openshift cluster by manually deploying the operator manifests. 6 | 7 | After the installation, check the [examples](../examples) to deploy the NGINX Ingress Controller using the operator. 8 | -------------------------------------------------------------------------------- /docs/manual-installation.md: -------------------------------------------------------------------------------- 1 | # Manual installation 2 | 3 | This will deploy the operator in the `nginx-ingress-operator-system` namespace. 4 | 5 | 6 | 1. Deploy the Operator and associated resources: 7 | 1. Clone the `nginx-ingress-operator` repo and checkout the latest stable tag: 8 | ``` 9 | git clone https://github.com/nginxinc/nginx-ingress-operator/ 10 | cd nginx-ingress-operator/ 11 | git checkout v0.5.1 12 | ``` 13 | 14 | 2. `Openshift` To deploy the Operator and associated resources to an OpenShift environment, run: 15 | ``` 16 | make deploy IMG=registry.connect.redhat.com/nginx/nginx-ingress-operator:0.5.1 17 | ``` 18 | 19 | 3. Alternatively, to deploy the Operator and associated resources to all other environments: 20 | ``` 21 | make deploy IMG=nginx/nginx-ingress-operator:0.5.1 22 | ``` 23 | 24 | 2. Check that the Operator is running: 25 | ``` 26 | kubectl get deployments -n nginx-ingress-operator-system 27 | 28 | NAME READY UP-TO-DATE AVAILABLE AGE 29 | nginx-ingress-operator-controller-manager 1/1 1 1 15s 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/nginx-ingress-controller.md: -------------------------------------------------------------------------------- 1 | # NginxIngressController Custom Resource 2 | 3 | The `NginxIngressController` Custom Resource is the definition of a deployment of the Ingress Controller. 4 | With this Custom Resource, the NGINX Ingress Operator will be able to deploy and configure instances of the Ingress Controller in your cluster. 5 | 6 | ## Configuration 7 | 8 | There are several fields to configure the deployment of an Ingress Controller. 9 | 10 | The following example shows the minimum configuration using only required fields: 11 | 12 | ```yaml 13 | apiVersion: k8s.nginx.org/v1alpha1 14 | kind: NginxIngressController 15 | metadata: 16 | name: my-nginx-ingress-controller 17 | namespace: my-nginx-ingress 18 | spec: 19 | type: deployment 20 | image: 21 | repository: nginx/nginx-ingress 22 | tag: edge 23 | pullPolicy: Always 24 | serviceType: NodePort 25 | ``` 26 | 27 | The following example shows the usage of all fields (required and optional): 28 | 29 | ```yaml 30 | apiVersion: k8s.nginx.org/v1alpha1 31 | kind: NginxIngressController 32 | metadata: 33 | name: my-nginx-ingress-controller 34 | namespace: my-nginx-ingress 35 | spec: 36 | type: deployment 37 | nginxPlus: false 38 | image: 39 | repository: nginx/nginx-ingress 40 | tag: edge 41 | pullPolicy: Always 42 | replicas: 3 43 | serviceType: NodePort 44 | enableCRDs: true 45 | enableSnippets: false 46 | enablePreviewPolicies: false 47 | defaultSecret: my-nginx-ingress/default-secret 48 | ingressClass: my-nginx-ingress 49 | watchNamespace: default 50 | healthStatus: 51 | enable: true 52 | uri: "/my-health" 53 | nginxDebug: true 54 | logLevel: 3 55 | nginxStatus: 56 | enable: true 57 | port: 9090 58 | allowCidrs: "127.0.0.1" 59 | enableLeaderElection: true 60 | wildcardTLS: my-nginx-ingress/wildcard-secret 61 | reportIngressStatus: 62 | enable: true 63 | externalService: my-nginx-ingress 64 | prometheus: 65 | enable: true 66 | port: 9114 67 | secret: my-nginx-ingress/prometheus-secret 68 | enableLatencyMetrics: false 69 | configMapData: 70 | error-log-level: debug 71 | enableTLSPassthrough: true 72 | globalConfiguration: my-nginx-ingress/nginx-configuration 73 | nginxReloadTimeout: 5000 74 | appProtect: 75 | enable: false 76 | ``` 77 | 78 | | Field | Type | Description | Required | 79 | | --- | --- | --- | --- | 80 | | `type` | `string` | The type of the Ingress Controller installation - `deployment` or `daemonset`. | Yes | 81 | | `nginxPlus` | `boolean` | Deploys the Ingress Controller for NGINX Plus. The default is `false` meaning the Ingress Controller will be deployed for NGINX OSS. | No | 82 | | `image` | [image](#nginxingresscontrollerimage) | The image of the Ingress Controller. | Yes | 83 | | `replicas` | `int` | The number of replicas of the Ingress Controller pod. The default is 1. Only applies if the `type` is set to deployment. | No | 84 | | `defaultSecret` | `string` | The TLS Secret for TLS termination of the default server. The format is namespace/name. The secret must be of the type kubernetes.io/tls. If not specified, the operator will generate and deploy a TLS Secret with a self-signed certificate and key. | No | 85 | | `serviceType` | `string` | The type of the Service for the Ingress Controller. Valid Service types are `NodePort` or `LoadBalancer`. | Yes | 86 | | `enableCRDs` | `boolean` | Enables the use of NGINX Ingress Resource Definitions (VirtualServer and VirtualServerRoute). Default is `true`. | No | 87 | | `enableSnippets` | `boolean` | Enable custom NGINX configuration snippets in VirtualServer, VirtualServerRoute and TransportServer resources. Requires `enableCRDs` set to `true`. | No | 88 | | `enablePreviewPolicies` | `boolean` | Enables preview policies. Requires `enableCRDs` set to `true`. | No | 89 | | `ingressClass` | `string` | A class of the Ingress controller. The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. Additionally the Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field. Additionally, the Ingress Controller processes resources that do not have the class set. Default is `nginx`. | No | 90 | | `service` | [service](#nginxingresscontrollerservice) | The service of the Ingress Controller. | No | 91 | | `watchNamespace` | `boolean` | Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces. | No | 92 | | `healthStatus` | [healthStatus](#nginxingresscontrollerhealthstatus) | Adds a new location to the default server. The location responds with the 200 status code for any request. Useful for external health-checking of the Ingress Controller. | No | 93 | | `nginxDebug` | `boolean` | Enable debugging for NGINX. Uses the nginx-debug binary. Requires `error-log-level: debug` in the configMapData. | No | 94 | | `logLevel` | `int` | Log level for V logs. Format is `0 - 3` | No | 95 | | `nginxStatus` | [nginxStatus](#nginxingresscontrollernginxstatus) | Configures NGINX stub_status, or the NGINX Plus API. | No | 96 | | `reportIngressStatus` | [reportIngressStatus](#nginxingresscontrollerreportingressstatus) | Update the address field in the status of Ingresses resources. | No | 97 | | `enableLeaderElection` | `boolean` | Enables Leader election to avoid multiple replicas of the controller reporting the status of Ingress resources – only one replica will report status. Default is `true`. | No | 98 | | `wildcardTLS` | `string` | A Secret with a TLS certificate and key for TLS termination of every Ingress host for which TLS termination is enabled but the Secret is not specified. The secret must be of the type kubernetes.io/tls. If the argument is not set, for such Ingress hosts NGINX will break any attempt to establish a TLS connection. If the argument is set, but the Ingress controller is not able to fetch the Secret from Kubernetes API, the Ingress Controller will fail to start. Format is `namespace/name`. | No | 99 | | `prometheus` | [prometheus](#nginxingresscontrollerprometheus) | Configures NGINX or NGINX Plus metrics in the Prometheus format. | No | 100 | | `configMapData` | `map[string]string` | Initial values of the Ingress Controller ConfigMap. Check the [ConfigMap docs](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/configmap-resource/) for more information about possible values. | No | 101 | | `globalConfiguration` | `string` | The GlobalConfiguration resource for global configuration of the Ingress Controller. Format is namespace/name. Requires `enableCRDs` set to `true`. | No | 102 | | `enableTLSPassthrough` | `boolean` | Enable TLS Passthrough on port 443. Requires `enableCRDs` set to `true`. | No | 103 | | `appProtect` | [appProtect](#nginxingresscontrollerappprotect) | App Protect WAF support configuration. Requires `nginxPlus` set to `true`. | No | 104 | | `appProtectDos` | [appProtectDos](#nginxingresscontrollerappprotectdos) | App Protect DoS support configuration. Requires `nginxPlus` set to `true`. | No | 105 | | `nginxReloadTimeout` | `int`| Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. (default is 4000. Default is 20000 instead if `enable-app-protect` is true) | No | 106 | 107 | ## NginxIngressController.Image 108 | 109 | | Field | Type | Description | Required | 110 | | --- | --- | --- | --- | 111 | | `repository` | `string` | The repository of the image. | Yes | 112 | | `tag` | `string` | The version of the image. | Yes | 113 | | `pullPolicy` | `string` | The ImagePullPolicy of the image. Valid values are `Never`, `Always` or `IfNotPresent` | Yes | 114 | 115 | ## NginxIngressController.HealthStatus 116 | 117 | | Field | Type | Description | Required | 118 | | --- | --- | --- | --- | 119 | | `enable` | `boolean` | Enable the HealthStatus. | Yes | 120 | | `uri` | `string` | URI of the location. Default is `/nginx-health`. | No | 121 | 122 | ## NginxIngressController.NginxStatus 123 | 124 | | Field | Type | Description | Required | 125 | | --- | --- | --- | --- | 126 | | `enable` | `boolean` | Enable the NginxStatus. | Yes | 127 | | `port` | `int` | Set the port where the NGINX stub_status or the NGINX Plus API is exposed. Default is `8080`. Format is `1023 - 65535` | No | 128 | | `allowCidrs` | `string` | Whitelist IPv4 IP/CIDR blocks to allow access to NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas. (default `127.0.0.1`) | No | 129 | 130 | ## NginxIngressController.Service 131 | 132 | | Field | Type | Description | Required | 133 | | --- | --- | --- | --- | 134 | | `extraLabels` | `map[string]string` | Specifies extra labels of the service. | No | 135 | | `extraAnnotations` | `map[string]string` | Specifies extra annotations of the service. | No | 136 | 137 | ## NginxIngressController.ReportIngressStatus 138 | 139 | | Field | Type | Description | Required | 140 | | --- | --- | --- | --- | 141 | | `enable` | `boolean` | Enable reporting of the Ingress status. | Yes | 142 | | `externalService` | `string` | Specifies the name of the service with the type LoadBalancer through which the Ingress controller pods are exposed externally. The external address of the service is used when reporting the status of Ingress resources. Note: if `serviceType` is `LoadBalancer`, the value of this field will be ignored, and the operator will use the name of the created LoadBalancer service instead. | No | 143 | | `ingressLink` | `string` | Specifies the name of the IngressLink resource, which exposes the Ingress Controller pods via a BIG-IP system. The IP of the BIG-IP system is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. Requires `reportIngressStatus.enable` set to `true`. Note: If `serviceType` is `LoadBalancer` or `reportIngressStatus.externalService` is set, the value of this field will be ignored. | No | 144 | 145 | ## NginxIngressController.Prometheus 146 | 147 | | Field | Type | Description | Required | 148 | | --- | --- | --- | --- | 149 | | `enable` | `boolean` | Enable Prometheus metrics. | Yes | 150 | | `port` | `int` | Sets the port where the Prometheus metrics are exposed. Default is 9113. Format is `1023 - 65535`. | No | 151 | | `secret` | `string` | A Secret with a TLS certificate and key for TLS termination of the Prometheus endpoint. The secret must be of the type kubernetes.io/tls. If specified, but the Ingress controller is not able to fetch the Secret from Kubernetes API, the Ingress Controller will fail to start. Format is namespace/name. | No | 152 | | `enableLatencyMetrics` | `boolean` | Bucketed response times from when NGINX establishes a connection to an upstream server to when the last byte of the response body is received by NGINX. **Note** The metric for the upstream isn't available until traffic is sent to the upstream. Requires prometheus set to true | No | 153 | 154 | ## NginxIngressController.AppProtect 155 | 156 | | Field | Type | Description | Required | 157 | | --- | --- | --- | --- | 158 | | `enable` | `boolean` | Enable App Protect WAF. | Yes | 159 | 160 | ## NginxIngressController.AppProtectDos 161 | 162 | | Field | Type | Description | Required | 163 | | --- | --- | --- | --- | 164 | | `enable` | `boolean` | Enable App Protect DoS. | Yes | 165 | | `debug` | `boolean` | Enable debug mode. | No | 166 | | `maxDaemons` | `int` | Maximum number of ADMD instances. | No | 167 | | `maxWorkers` | `int` | Max number of nginx processes to support. | No | 168 | | `memory` | `int` | RAM memory size to consume in MB. | No | 169 | -------------------------------------------------------------------------------- /docs/openshift-installation.md: -------------------------------------------------------------------------------- 1 | # Installation in an Openshift cluster the OLM 2 | 3 | This installation method is the recommended way for Openshift users. **Note**: Openshift version must be 4.2 or higher. 4 | 5 | **Note: The `nginx-ingress-operator` supports `Basic Install` only - we do not support auto-updates. When you are installing the Operator using the OLM, the auto-update feature should be disabled to avoid breaking changes being auto-applied. In OpenShift, this can be done by setting the `Approval Strategy` to `Manual`. Please see the [Operator SDK docs](https://sdk.operatorframework.io/docs/advanced-topics/operator-capabilities/operator-capabilities/) for more details on the Operator Capability Levels.** 6 | 7 | The NGINX Ingress Operator is a [RedHat certified Operator](https://connect.redhat.com/en/partner-with-us/red-hat-openshift-operator-certification). 8 | 9 | 1. In the Openshift dashboard, click `Operators` > `Operator Hub` in the left menu and use the search box to type `nginx ingress`: 10 | ![alt text](./images/openshift1.png "Operators") 11 | 1. Click the `NGINX Ingress Operator` and click `Install`: 12 | ![alt text](./images/openshift2.png "NGINX Ingress Operator") 13 | 1. Click `Subscribe`: 14 | ![alt text](./images/openshift3.png "NGINX Ingress Operator Install") 15 | 16 | Openshift will install the NGINX Ingress Operator: 17 | 18 | ![alt text](./images/openshift4.png "NGINX Ingress Operator Subscribe") 19 | 20 | You can now deploy the NGINX Ingress Controller instances following the [examples](../examples). 21 | -------------------------------------------------------------------------------- /docs/upgrades.md: -------------------------------------------------------------------------------- 1 | # Upgrade - 0.2.0 to 0.3.0 2 | 3 | Release 0.3.0 includes a major upgrade of the Operator-SDK which has resulted in a number of changes in the layout of the project 4 | (see [the operator-sdk docs](https://sdk.operatorframework.io/docs/building-operators/golang/migration/) for more information). 5 | 6 | ## OLM upgrade - 0.2.0 to 0.3.0 7 | 8 | **Note: The `nginx-ingress-operator` supports `Basic Install` only - we do not support auto-updates. When you are installing the Operator using the OLM, the auto-update feature should be disabled to avoid breaking changes being auto-applied. In OpenShift, this can be done by setting the `Approval Strategy` to `Manual`. Please see the [Operator SDK docs](https://sdk.operatorframework.io/docs/advanced-topics/operator-capabilities/operator-capabilities/) for more details on the Operator Capability Levels.** 9 | 10 | ### 1. Uninstall the existing 0.2.0 operator 11 | 12 | Uninstall the operator using the web console - see [the OCP documentation for details](https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/pdf/operators/OpenShift_Container_Platform-4.6-Operators-en-US.pdf). 13 | 14 | ### 2. Reinstall the latest version of the operator 15 | 16 | Install the latest version of the Operator following the steps outlined in [OpenShift installation doc](./openshift-installation.md). 17 | 18 | ### 3. Upgrade the existing ingress controller deployments 19 | 20 | Upgrade to the latest 1.12.0 Ingress Controller image - see the release notes [here](https://docs.nginx.com/nginx-ingress-controller/releases/#nginx-ingress-controller-1-12-0) 21 | 22 | ## Manual upgrade - 0.2.0 to 0.3.0 23 | 24 | ### 1. Deploy the new operator 25 | 26 | Deploy the operator following the steps outlined in [manual installation doc](./manual-installation.md). 27 | 28 | ### 2. Cleanup the existing operator 29 | 30 | Uninstall the existing operator deployment: 31 | 32 | 1. Checkout the previous version of the nginx-ingress-operator [0.3.0](https://github.com/nginxinc/nginx-ingress-operator/releases/tag/v0.3.0). 33 | 1. Uninstall the resources by running the following commands (be sure to edit files to suit your environment, if required): 34 | ``` 35 | kubectl delete -f deploy/operator.yaml 36 | kubectl delete -f deploy/role_binding.yaml 37 | kubectl delete -f deploy/role.yaml 38 | kubectl delete -f deploy/service_account.yaml 39 | ``` 40 | 41 | ### 3. Upgrade the existing ingress controller deployments 42 | 43 | Upgrade to the latest 1.12.0 Ingress Controller image - see the release notes [here](https://docs.nginx.com/nginx-ingress-controller/releases/#nginx-ingress-controller-1-12-0) 44 | -------------------------------------------------------------------------------- /examples/deployment-oss-min/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we deploy the NGINX Ingress Controller (edge) as a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) using the NGINX Ingress Operator for NGINX Open Source. 4 | 5 | ## Prerequisites 6 | 7 | Have the NGINX Ingress Operator deployed in your cluster. Follow [installation](../../README.md#installation) steps. 8 | If you would like to use TransportServers, refer to [this section](README.md#TransportServers) for additional pre-requisites. 9 | 10 | ## Running the example 11 | 12 | 1. Create a new namespace for our Ingress Controller instance: 13 | ``` 14 | kubectl create -f ns.yaml 15 | ``` 16 | 17 | 2. Create a new NginxIngressController resource that defines our NGINX Ingress Controller instance (**Note**: If using Openshift, change the `image.tag` to `edge-ubi`): 18 | ``` 19 | kubectl create -f nginx-ingress-controller.yaml 20 | ``` 21 | 22 | 23 | 24 | This will deploy an NGINX Ingress Controller instance using a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) in the `my-nginx-controller` namespace. 25 | 26 | 3. Check if all resources were deployed: 27 | 28 | ``` 29 | kubectl -n my-nginx-ingress get all 30 | 31 | NAME READY STATUS RESTARTS AGE 32 | pod/my-nginx-ingress-controller-666854fb5f-f67fs 1/1 Running 0 3s 33 | 34 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 35 | service/my-nginx-ingress-controller NodePort 10.103.105.52 80:30298/TCP,443:32576/TCP 3s 36 | 37 | NAME READY UP-TO-DATE AVAILABLE AGE 38 | deployment.apps/my-nginx-ingress-controller 1/1 1 1 4s 39 | 40 | NAME DESIRED CURRENT READY AGE 41 | replicaset.apps/my-nginx-ingress-controller-666854fb5f 1 1 1 4s 42 | ``` 43 | 44 | For more information about how to configure the NGINX Ingress Controller, check the official [documentation](https://docs.nginx.com/nginx-ingress-controller/overview/). 45 | 46 | ## Remove 47 | 48 | 1. Delete the NginxIngressController: 49 | ``` 50 | kubectl delete -f nginx-ingress-controller.yaml 51 | ``` 52 | 53 | 1. Delete the namespace: 54 | ``` 55 | kubectl delete namespace my-nginx-ingress 56 | ``` 57 | 58 | ## TransportServers 59 | 60 | A GlobalConfiguration resource is used to specify the TCP/UDP listeners and is required by TransportServers. 61 | To use TransportServers, you must create a GlobalConfiguration resource *after* creating the namespace and *before* starting the Operator. 62 | 63 | 64 | ``` 65 | Step 1. namespace 66 | Step 2. global configuration <--- in this order 67 | Step 3. ingress controller 68 | ... 69 | ``` 70 | 71 | ``` 72 | kubectl apply -f global-configuration.yaml 73 | ``` 74 | 75 | Then update the NginxIngressController to use the GlobalConfiguration by adding the following config to `nginx-ingress-controller.yaml` 76 | ``` 77 | globalConfiguration: my-nginx-ingress/nginx-configuration 78 | ``` 79 | 80 | For more information, check the official [documentation](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/). 81 | -------------------------------------------------------------------------------- /examples/deployment-oss-min/global-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k8s.nginx.org/v1alpha1 2 | kind: GlobalConfiguration 3 | metadata: 4 | name: nginx-configuration 5 | namespace: my-nginx-ingress 6 | -------------------------------------------------------------------------------- /examples/deployment-oss-min/nginx-ingress-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k8s.nginx.org/v1alpha1 2 | kind: NginxIngressController 3 | metadata: 4 | name: my-nginx-ingress-controller 5 | namespace: my-nginx-ingress 6 | spec: 7 | type: deployment 8 | nginxPlus: false 9 | image: 10 | repository: nginx/nginx-ingress 11 | tag: 2.1.1-ubi 12 | pullPolicy: Always 13 | replicas: 1 14 | serviceType: NodePort 15 | -------------------------------------------------------------------------------- /examples/deployment-oss-min/ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: my-nginx-ingress -------------------------------------------------------------------------------- /examples/deployment-plus-min/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we deploy the NGINX Ingress Controller (edge) as a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) using the NGINX Ingress Operator for NGINX Plus. 4 | 5 | ## Prerequisites 6 | 7 | 1. Have the NGINX Ingress Operator deployed in your cluster. Follow [installation](../../README.md#installation) steps. 8 | 2. Build the NGINX Ingress Controller for Plus image and push it to a private repository following 9 | [these instructions](https://docs.nginx.com/nginx-ingress-controller/installation/building-ingress-controller-image/#building-the-image-and-pushing-it-to-the-private-registry) 10 | (**Note**: For the build process, if using Openshift, use the `openshift-image-plus` or `openshift-image-nap-plus` targets). 11 | 12 | If you would like to use TransportServers, refer to [this section](README.md#TransportServers) for additional pre-requisites. 13 | 14 | ## Running the example 15 | 16 | 1. Create a new namespace for our Ingress Controller instance: 17 | ``` 18 | kubectl create -f ns.yaml 19 | ``` 20 | 21 | 2. Create a new NginxIngressController resource that defines our NGINX Ingress Controller instance (**Note:** Update the `image.repository` field in the `nginx-ingress-controller.yaml` with your previously built image for NGINX Plus): 22 | ``` 23 | kubectl create -f nginx-ingress-controller.yaml 24 | ``` 25 | 26 | This will deploy an NGINX Ingress Controller instance using a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) in the `my-nginx-controller` namespace. 27 | 28 | 3. Check if all resources were deployed: 29 | 30 | ``` 31 | kubectl -n my-nginx-ingress get all 32 | 33 | NAME READY STATUS RESTARTS AGE 34 | pod/my-nginx-ingress-controller-666854fb5f-f67fs 1/1 Running 0 3s 35 | 36 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 37 | service/my-nginx-ingress-controller NodePort 10.103.105.52 80:30298/TCP,443:32576/TCP 3s 38 | 39 | NAME READY UP-TO-DATE AVAILABLE AGE 40 | deployment.apps/my-nginx-ingress-controller 1/1 1 1 4s 41 | 42 | NAME DESIRED CURRENT READY AGE 43 | replicaset.apps/my-nginx-ingress-controller-666854fb5f 1 1 1 4s 44 | ``` 45 | 46 | For more information about how to configure the NGINX Ingress Controller, check the official [documentation](https://docs.nginx.com/nginx-ingress-controller/overview/). 47 | 48 | ## Remove 49 | 50 | 1. Delete the NginxIngressController: 51 | ``` 52 | kubectl delete -f nginx-ingress-controller.yaml 53 | ``` 54 | 55 | 1. Delete the namespace: 56 | ``` 57 | kubectl delete namespace my-nginx-ingress 58 | ``` 59 | 60 | ## TransportServers 61 | 62 | A GlobalConfiguration resource is used to specify the TCP/UDP listeners and is required by TransportServers. 63 | To use TransportServers, you must create a GlobalConfiguration resource *after* creating the namespace and *before* starting the Operator. 64 | 65 | 66 | ``` 67 | Step 1. namespace 68 | Step 2. global configuration <--- in this order 69 | Step 3. ingress controller 70 | ... 71 | ``` 72 | 73 | 74 | ``` 75 | kubectl apply -f global-configuration.yaml 76 | ``` 77 | 78 | Then update the NginxIngressController to use the GlobalConfiguration by adding the following config to `nginx-ingress-controller.yaml` 79 | ``` 80 | globalConfiguration: my-nginx-ingress/nginx-configuration 81 | ``` 82 | 83 | For more information, check the official [documentation](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/). 84 | -------------------------------------------------------------------------------- /examples/deployment-plus-min/global-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k8s.nginx.org/v1alpha1 2 | kind: GlobalConfiguration 3 | metadata: 4 | name: nginx-configuration 5 | namespace: my-nginx-ingress 6 | -------------------------------------------------------------------------------- /examples/deployment-plus-min/nginx-ingress-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k8s.nginx.org/v1alpha1 2 | kind: NginxIngressController 3 | metadata: 4 | name: my-nginx-ingress-controller 5 | namespace: my-nginx-ingress 6 | spec: 7 | type: deployment 8 | nginxPlus: true 9 | image: 10 | repository: nginx-plus-ingress 11 | tag: 2.1.1-ubi 12 | pullPolicy: IfNotPresent 13 | replicas: 1 14 | serviceType: NodePort 15 | ingressClass: nginx 16 | -------------------------------------------------------------------------------- /examples/deployment-plus-min/ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: my-nginx-ingress 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nginxinc/nginx-ingress-operator 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/go-logr/logr v1.2.3 7 | github.com/google/go-cmp v0.5.7 8 | github.com/onsi/ginkgo v1.16.5 9 | github.com/onsi/gomega v1.18.1 10 | github.com/openshift/api v0.0.0-20201013121701-9d5ee23b507d 11 | k8s.io/api v0.23.5 12 | k8s.io/apiextensions-apiserver v0.23.1 13 | k8s.io/apimachinery v0.23.5 14 | k8s.io/client-go v0.23.5 15 | sigs.k8s.io/controller-runtime v0.11.0 16 | ) 17 | 18 | require ( 19 | cloud.google.com/go v0.81.0 // indirect 20 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 21 | github.com/Azure/go-autorest/autorest v0.11.18 // indirect 22 | github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect 23 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 24 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 25 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 26 | github.com/beorn7/perks v1.0.1 // indirect 27 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 30 | github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect 31 | github.com/fsnotify/fsnotify v1.5.1 // indirect 32 | github.com/go-logr/zapr v1.2.0 // indirect 33 | github.com/gogo/protobuf v1.3.2 // indirect 34 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 35 | github.com/golang/protobuf v1.5.2 // indirect 36 | github.com/google/gofuzz v1.1.0 // indirect 37 | github.com/google/uuid v1.1.2 // indirect 38 | github.com/googleapis/gnostic v0.5.5 // indirect 39 | github.com/imdario/mergo v0.3.12 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 43 | github.com/modern-go/reflect2 v1.0.2 // indirect 44 | github.com/nxadm/tail v1.4.8 // indirect 45 | github.com/pkg/errors v0.9.1 // indirect 46 | github.com/prometheus/client_golang v1.11.0 // indirect 47 | github.com/prometheus/client_model v0.2.0 // indirect 48 | github.com/prometheus/common v0.28.0 // indirect 49 | github.com/prometheus/procfs v0.6.0 // indirect 50 | github.com/spf13/pflag v1.0.5 // indirect 51 | go.uber.org/atomic v1.7.0 // indirect 52 | go.uber.org/multierr v1.6.0 // indirect 53 | go.uber.org/zap v1.19.1 // indirect 54 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 55 | golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect 56 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect 57 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 58 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect 59 | golang.org/x/text v0.3.7 // indirect 60 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 61 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 62 | google.golang.org/appengine v1.6.7 // indirect 63 | google.golang.org/protobuf v1.27.1 // indirect 64 | gopkg.in/inf.v0 v0.9.1 // indirect 65 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 68 | k8s.io/component-base v0.23.1 // indirect 69 | k8s.io/klog/v2 v2.30.0 // indirect 70 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect 71 | k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect 72 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect 73 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 74 | sigs.k8s.io/yaml v1.3.0 // indirect 75 | ) 76 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/get_image_info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | image=$1 4 | version=$2 5 | 6 | kube_image=kubebuilder/kube-rbac-proxy 7 | kube_image_version=v0.8.0 8 | 9 | token="$(curl 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:'${image}':pull' 2>/dev/null | jq -r '.token')" 10 | 11 | image_digest=$(curl -sSfL -I -H "Authorization: Bearer ${token}" -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" "https://index.docker.io/v2/${image}/manifests/${version}" | awk 'BEGIN {FS=": "}/^docker-content-digest/{gsub(/"/, "", $2); print $2}') 12 | 13 | digest="$(curl -sSfL -H "Authorization: Bearer ${token}" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "https://index.docker.io/v2/${image}/manifests/${version}" | jq -r '.config.digest')" 14 | 15 | created=$(curl -sSfL -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer ${token}" "https://index.docker.io/v2/${image}/blobs/${digest}" | jq -r '.config.Labels."org.opencontainers.image.created"') 16 | 17 | proxy="./config/default/manager_auth_proxy_patch.yaml" 18 | kube_proxy=$(yq e '.spec.template.spec.containers.[0].image' $proxy) 19 | full_image=${kube_proxy%:*} 20 | kube_image=${full_image#*/} 21 | kube_version=${kube_proxy#*:} 22 | 23 | kube_digest=$(curl -sSfL -I -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" "https://gcr.io/v2/${kube_image}/manifests/${kube_version}" | awk 'BEGIN {FS=": "}/^docker-content-digest/{gsub(/"/, "", $2); print $2}') 24 | 25 | printf "%s\n\n" "Manually replace the following values in bundle/manifests/nginx-ingress-operator.clusterserviceversion.yaml" 26 | printf "%s\n" "metadata.annotations.createdAt: ${created}" 27 | printf "%s\n" "metadata.annotations.containerImage: docker.io/${image}@${image_digest}" 28 | printf "%s\n" "spec.install.spec.deployments[0].spec.template.spec.containers[1].image (nginx-ingress-operator): docker.io/${image}@${image_digest}" 29 | printf "%s\n" "spec.install.spec.deployments[0].spec.template.spec.containers[0].image (kube-rbac-proxy): ${full_image}@${kube_digest}" 30 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "os" 23 | runt "runtime" 24 | "strings" 25 | 26 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 27 | // to ensure that exec-entrypoint and run can make use of them. 28 | "k8s.io/client-go/kubernetes" 29 | _ "k8s.io/client-go/plugin/pkg/client/auth" 30 | 31 | "k8s.io/apimachinery/pkg/runtime" 32 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 33 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | "sigs.k8s.io/controller-runtime/pkg/cache" 36 | "sigs.k8s.io/controller-runtime/pkg/healthz" 37 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 38 | 39 | secv1 "github.com/openshift/api/security/v1" 40 | 41 | k8sv1alpha1 "github.com/nginxinc/nginx-ingress-operator/api/v1alpha1" 42 | "github.com/nginxinc/nginx-ingress-operator/controllers" 43 | "sigs.k8s.io/controller-runtime/pkg/client/config" 44 | //+kubebuilder:scaffold:imports 45 | ) 46 | 47 | var ( 48 | scheme = runtime.NewScheme() 49 | setupLog = ctrl.Log.WithName("setup") 50 | version string 51 | ) 52 | 53 | func printVersion() { 54 | setupLog.Info(fmt.Sprintf("Operator Version: %s", version)) 55 | setupLog.Info(fmt.Sprintf("Go Version: %s", runt.Version())) 56 | setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runt.GOOS, runt.GOARCH)) 57 | setupLog.Info(fmt.Sprintf("Version of kubernetes: %v", controllers.RunningK8sVersion)) 58 | } 59 | 60 | func init() { 61 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 62 | utilruntime.Must(k8sv1alpha1.AddToScheme(scheme)) 63 | 64 | //+kubebuilder:scaffold:scheme 65 | } 66 | 67 | func main() { 68 | var metricsAddr string 69 | var enableLeaderElection bool 70 | var probeAddr string 71 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 72 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 73 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 74 | "Enable leader election for controller manager. "+ 75 | "Enabling this will ensure there is only one active controller manager.") 76 | opts := zap.Options{ 77 | Development: false, 78 | } 79 | opts.BindFlags(flag.CommandLine) 80 | flag.Parse() 81 | 82 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 83 | // Get a config to talk to the apiserver 84 | cfg, err := config.GetConfig() 85 | if err != nil { 86 | setupLog.Error(err, "problem getting k8s config") 87 | os.Exit(1) 88 | } 89 | clientset, err := kubernetes.NewForConfig(cfg) 90 | if err != nil { 91 | setupLog.Error(err, "problem creating clientset") 92 | os.Exit(1) 93 | } 94 | controllers.RunningK8sVersion, err = controllers.GetK8sVersion(clientset) 95 | if err != nil { 96 | setupLog.Error(err, "problem getting k8s version") 97 | os.Exit(1) 98 | } 99 | 100 | printVersion() 101 | 102 | watchNamespace, err := getWatchNamespace() 103 | if err != nil { 104 | setupLog.Error(err, "unable to get WatchNamespace, "+ 105 | "the manager will watch and manage resources in all Namespaces") 106 | } 107 | 108 | options := ctrl.Options{ 109 | Scheme: scheme, 110 | MetricsBindAddress: metricsAddr, 111 | Port: 9443, 112 | HealthProbeBindAddress: probeAddr, 113 | LeaderElection: enableLeaderElection, 114 | LeaderElectionID: "ca5c10a7.nginx.org", 115 | Namespace: watchNamespace, 116 | } 117 | 118 | // Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2) 119 | if strings.Contains(watchNamespace, ",") { 120 | setupLog.Info("manager set up with multiple namespaces", "namespaces", watchNamespace) 121 | // configure cluster-scoped with MultiNamespacedCacheBuilder 122 | options.Namespace = "" 123 | options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(watchNamespace, ",")) 124 | } 125 | 126 | if options.Namespace != "" { 127 | setupLog.Info("manager set up with namespace", "namespace", options.Namespace) 128 | } 129 | 130 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) 131 | if err != nil { 132 | setupLog.Error(err, "unable to start manager") 133 | os.Exit(1) 134 | } 135 | 136 | // Setup Scheme for SCC if deployed in OpenShift 137 | sccAPIExists, err := controllers.VerifySCCAPIExists() 138 | if err != nil { 139 | setupLog.Error(err, "could not check if SCC API exists") 140 | os.Exit(1) 141 | } 142 | 143 | if sccAPIExists { 144 | utilruntime.Must(secv1.AddToScheme(scheme)) 145 | } 146 | 147 | if err = (&controllers.NginxIngressControllerReconciler{ 148 | Client: mgr.GetClient(), 149 | Scheme: mgr.GetScheme(), 150 | SccAPIExists: sccAPIExists, 151 | Mgr: mgr, 152 | }).SetupWithManager(mgr); err != nil { 153 | setupLog.Error(err, "unable to create controller", "controller", "NginxIngressController") 154 | os.Exit(1) 155 | } 156 | //+kubebuilder:scaffold:builder 157 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 158 | setupLog.Error(err, "unable to set up health check") 159 | os.Exit(1) 160 | } 161 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 162 | setupLog.Error(err, "unable to set up ready check") 163 | os.Exit(1) 164 | } 165 | 166 | setupLog.Info("starting manager") 167 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 168 | setupLog.Error(err, "problem running manager") 169 | os.Exit(1) 170 | } 171 | } 172 | 173 | // getWatchNamespace returns the Namespace the operator should be watching for changes 174 | func getWatchNamespace() (string, error) { 175 | // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE 176 | // which specifies the Namespace to watch. 177 | // An empty value means the operator is running with cluster scope. 178 | watchNamespaceEnvVar := "WATCH_NAMESPACE" 179 | 180 | ns, found := os.LookupEnv(watchNamespaceEnvVar) 181 | if !found { 182 | return "", fmt.Errorf("%s must be set", watchNamespaceEnvVar) 183 | } 184 | return ns, nil 185 | } 186 | --------------------------------------------------------------------------------