├── .github ├── dependabot.yml └── workflows │ ├── codeql.yaml │ ├── pr.yaml │ ├── tags.yaml │ └── trivy.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .mockery.yaml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── api ├── config │ └── v2alpha2 │ │ ├── groupversion_info.go │ │ ├── projectconfig_types.go │ │ └── zz_generated.deepcopy.go ├── styra │ ├── v1alpha1 │ │ ├── doc.go │ │ ├── groupversion_info.go │ │ ├── library_types.go │ │ └── zz_generated.deepcopy.go │ └── v1beta1 │ │ ├── doc.go │ │ ├── groupversion_info.go │ │ ├── system_types.go │ │ ├── system_types_test.go │ │ ├── v1beta1_suite_test.go │ │ └── zz_generated.deepcopy.go └── test │ └── v1 │ ├── groupversion_info.go │ ├── object_types.go │ ├── v1.go │ └── zz_generated.deepcopy.go ├── build └── package │ └── Dockerfile ├── cmd ├── main.go ├── main_test.go └── suite_test.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── styra.bankdata.dk_libraries.yaml │ │ └── styra.bankdata.dk_systems.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_config_projectconfigs.yaml │ │ ├── cainjection_in_styra_libraries.yaml │ │ ├── cainjection_in_styra_systems.yaml │ │ ├── webhook_in_config_projectconfigs.yaml │ │ ├── webhook_in_styra_libraries.yaml │ │ └── webhook_in_styra_systems.yaml ├── default │ ├── config.yaml │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_webhook_patch.yaml │ ├── webhookcainjection_mutating_patch.yaml │ ├── webhookcainjection_patch.yaml │ └── webhookcainjection_validating_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── config_projectconfig_editor_role.yaml │ ├── config_projectconfig_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── service_account.yaml │ ├── styra_library_editor_role.yaml │ ├── styra_library_viewer_role.yaml │ ├── styra_system_editor_role.yaml │ ├── styra_system_viewer_role.yaml │ ├── test_object_editor_role.yaml │ └── test_object_viewer_role.yaml ├── samples │ ├── config_v2alpha2_projectconfig.yaml │ ├── kustomization.yaml │ ├── styra_v1alpha1_library.yaml │ ├── styra_v1beta1_system.yaml │ └── test_v1_object.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── docs ├── apis │ └── styra │ │ ├── v1alpha1.md │ │ └── v1beta1.md ├── configuration.md ├── design.md ├── images │ ├── Styra │ │ └── system.png │ ├── controller-arch.dark.excalidraw.png │ └── controller-arch.light.excalidraw.png ├── installation.md └── releasing.md ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal ├── config │ ├── config.go │ ├── config_suite_test.go │ └── config_test.go ├── controller │ └── styra │ │ ├── library_controller.go │ │ ├── styra.go │ │ ├── styra_suite_test.go │ │ ├── system_controller.go │ │ └── system_controller_test.go ├── errors │ └── errors.go ├── fields │ └── fields.go ├── finalizer │ └── finalizer.go ├── k8sconv │ ├── k8sconv.go │ ├── k8sconv_test.go │ └── suite_test.go ├── labels │ ├── labels.go │ ├── labels_suite_test.go │ └── labels_test.go ├── predicate │ ├── predicate.go │ ├── predicate_test.go │ └── suite_test.go ├── sentry │ └── sentry.go ├── template │ ├── members.tpl │ ├── pkg.tpl │ ├── placeholder.go │ └── type.tpl └── webhook │ ├── mocks │ └── client.go │ ├── styra │ ├── v1alpha1 │ │ ├── library_webhook.go │ │ ├── library_webhook_test.go │ │ └── webhook_suite_test.go │ └── v1beta1 │ │ ├── system_webhook.go │ │ ├── system_webhook_test.go │ │ └── webhook_suite_test.go │ ├── suite_test.go │ ├── webhook.go │ └── webhook_test.go ├── pkg ├── ptr │ ├── ptr.go │ ├── ptr_test.go │ └── suite_test.go └── styra │ ├── authz.go │ ├── authz_test.go │ ├── client.go │ ├── client_test.go │ ├── datasources.go │ ├── datasources_test.go │ ├── http_error.go │ ├── invitations.go │ ├── invitations_test.go │ ├── library.go │ ├── library_test.go │ ├── mocks │ └── client_interface.go │ ├── opaconfig.go │ ├── opaconfig_test.go │ ├── policies.go │ ├── policies_test.go │ ├── secrets.go │ ├── secrets_test.go │ ├── styra.go │ ├── suite_test.go │ ├── systems.go │ ├── systems_test.go │ ├── users.go │ ├── users_test.go │ ├── workspace.go │ └── workspace_test.go ├── scripts └── gen-api-docs │ ├── README.md │ ├── config.json │ └── gen-api-docs.sh ├── test └── integration │ └── controller │ ├── controller_suite_test.go │ ├── library_controller_test.go │ └── system_controller_test.go └── tools.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # To get started with Dependabot version updates, you'll need to specify which 16 | # package ecosystems to update and where the package manifests are located. 17 | # Please see the documentation for all configuration options: 18 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 19 | 20 | version: 2 21 | updates: 22 | - package-ecosystem: "gomod" # See documentation for possible values 23 | directory: "/" # Location of package manifests 24 | schedule: 25 | interval: "daily" 26 | commit-message: 27 | prefix: ":arrow_up: " 28 | groups: 29 | k8s-controller-dependencies: 30 | patterns: 31 | - "k8s.io/*" 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: "Code Scanning - Action" 16 | 17 | on: 18 | push: 19 | branches: master 20 | schedule: 21 | - cron: '0 4 * * *' 22 | 23 | jobs: 24 | CodeQL-Build: 25 | runs-on: ubuntu-latest 26 | 27 | permissions: 28 | # required for all workflows 29 | security-events: write 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | 35 | - uses: actions/setup-go@v4 36 | with: 37 | go-version: '>=1.21.3' 38 | 39 | - name: Initialize CodeQL 40 | uses: github/codeql-action/init@v2 41 | 42 | - run: | 43 | make build 44 | 45 | - name: Perform CodeQL Analysis 46 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Pull Request 16 | 17 | on: 18 | pull_request: 19 | branches: 20 | - 'master' 21 | 22 | jobs: 23 | build: 24 | name: Build 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-go@v4 29 | with: 30 | go-version: '>=1.21.3' 31 | - name: build 32 | run: make build 33 | testing: 34 | name: Run tests 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.cache/go-build 42 | ~/go/pkg/mod 43 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 44 | restore-keys: | 45 | ${{ runner.os }}-go- 46 | - uses: actions/cache@v3 47 | with: 48 | path: ./bin/ 49 | key: ${{ runner.os }}-binaries-${{ hashFiles('**/go.sum') }} 50 | - uses: actions/setup-go@v4 51 | with: 52 | go-version: '>=1.21.3' 53 | - name: run tests 54 | run: make test 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/tags.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Tags 16 | 17 | on: 18 | push: 19 | tags: 20 | - 'v[0-9]+.[0-9]+.[0-9]+' 21 | - 'v[0-9]+.[0-9]+.[0-9]+-rc.*' 22 | 23 | jobs: 24 | release: 25 | name: Release 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: write 29 | packages: write 30 | steps: 31 | - uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | - run: git fetch --force --tags 35 | - uses: docker/setup-qemu-action@v2 36 | - uses: docker/setup-buildx-action@v2 37 | - uses: docker/login-action@v2 38 | with: 39 | registry: ghcr.io 40 | username: ${{ github.actor }} 41 | password: ${{ secrets.GITHUB_TOKEN }} 42 | - uses: actions/setup-go@v4 43 | with: 44 | go-version: '>=1.21.3' 45 | - name: release 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | run: make release 49 | -------------------------------------------------------------------------------- /.github/workflows/trivy.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Trivy scan master 16 | 17 | on: 18 | schedule: 19 | - cron: '0 5 * * *' 20 | 21 | env: 22 | REGISTRY: ghcr.io 23 | 24 | jobs: 25 | docker: 26 | name: Trivy scan 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: actions/cache@v3 31 | with: 32 | path: | 33 | ~/.cache/go-build 34 | ~/go/pkg/mod 35 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 36 | restore-keys: | 37 | ${{ runner.os }}-go- 38 | - uses: actions/cache@v3 39 | with: 40 | path: ./bin/ 41 | key: ${{ runner.os }}-binaries-${{ hashFiles('**/go.sum') }} 42 | - uses: actions/setup-go@v4 43 | with: 44 | go-version: '>=1.21.3' 45 | - name: build 46 | run: make docker-build 47 | - uses: aquasecurity/trivy-action@master 48 | with: 49 | scan-type: 'image' 50 | image-ref: 'controller:latest' 51 | ignore-unfixed: true 52 | severity: 'CRITICAL,HIGH,MEDIUM,LOW' 53 | exit-code: '1' 54 | # format: 'table' 55 | format: 'sarif' 56 | output: 'trivy-results-image.sarif' 57 | - name: Upload Trivy scan image results to GitHub Security tab 58 | uses: github/codeql-action/upload-sarif@v2 59 | with: 60 | sarif_file: 'trivy-results-image.sarif' 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | Dockerfile.cross 11 | 12 | # Test binary, build with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Kubernetes Generated files - skip generated files, except for vendored files 19 | 20 | !vendor/**/zz_generated.* 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | *.swp 25 | *.swo 26 | *~ 27 | 28 | /dist 29 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | run: 16 | timeout: 10m 17 | 18 | linters: 19 | enable: 20 | - misspell 21 | - revive 22 | - goimports 23 | - stylecheck 24 | - lll 25 | 26 | issues: 27 | exclude-use-default: false 28 | exclude: 29 | # The list of ids of default excludes to include or disable. 30 | # https://golangci-lint.run/usage/false-positives/#default-exclusions 31 | - EXC0001 32 | - EXC0011 33 | exclude-rules: 34 | - linters: 35 | - lll 36 | source: "^//\\+kubebuilder" 37 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | builds: 16 | - main: cmd/main.go 17 | env: 18 | - CGO_ENABLED=0 19 | goos: 20 | - linux 21 | goarch: 22 | - amd64 23 | - arm64 24 | 25 | changelog: 26 | sort: asc 27 | filters: 28 | exclude: [] 29 | 30 | archives: 31 | - format: tar.gz 32 | # this name template makes the OS and Arch compatible with the results of uname. 33 | name_template: >- 34 | {{ .ProjectName }}_ 35 | {{- .Os }}_ 36 | {{- if eq .Arch "amd64" }}x86_64 37 | {{- else }}{{ .Arch }}{{ end }} 38 | {{- if .Arm }}v{{ .Arm }}{{ end }} 39 | format_overrides: 40 | - goos: windows 41 | format: zip 42 | 43 | checksum: 44 | name_template: 'checksums.txt' 45 | 46 | dockers: 47 | - image_templates: 48 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-amd64" 49 | use: buildx 50 | goarch: amd64 51 | dockerfile: build/package/Dockerfile 52 | build_flag_templates: 53 | - "--platform=linux/amd64" 54 | - image_templates: 55 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-arm64" 56 | use: buildx 57 | goarch: arm64 58 | dockerfile: build/package/Dockerfile 59 | build_flag_templates: 60 | - "--platform=linux/arm64" 61 | 62 | docker_manifests: 63 | - name_template: "ghcr.io/bankdata/styra-controller:latest" 64 | image_templates: 65 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-amd64" 66 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-arm64" 67 | - name_template: "ghcr.io/bankdata/styra-controller:{{ .Major }}" 68 | image_templates: 69 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-amd64" 70 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-arm64" 71 | - name_template: "ghcr.io/bankdata/styra-controller:{{ .Major }}.{{ .Minor }}" 72 | image_templates: 73 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-amd64" 74 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-arm64" 75 | - name_template: "ghcr.io/bankdata/styra-controller:{{ .Version }}" 76 | image_templates: 77 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-amd64" 78 | - "ghcr.io/bankdata/styra-controller:{{ .Version }}-arm64" 79 | 80 | release: 81 | github: 82 | owner: Bankdata 83 | name: styra-controller 84 | footer: | 85 | ## Docker Image 86 | - `ghcr.io/bankdata/styra-controller:{{ .Version }}` 87 | -------------------------------------------------------------------------------- /.mockery.yaml: -------------------------------------------------------------------------------- 1 | dir: "{{ .InterfaceDir }}/mocks" 2 | filename: "{{ .InterfaceNameSnake }}.go" 3 | outpkg: "mocks" 4 | mockname: "{{ .InterfaceName }}" 5 | with-expecter: false 6 | disable-version-string: true 7 | packages: 8 | github.com/bankdata/styra-controller/pkg/styra: 9 | config: 10 | all: true 11 | github.com/bankdata/styra-controller/internal/webhook: 12 | config: 13 | all: true 14 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Bankdata/team-styra 2 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: bankdata.dk 6 | layout: 7 | - go.kubebuilder.io/v4 8 | multigroup: true 9 | projectName: styra-controller 10 | repo: github.com/bankdata/styra-controller 11 | resources: 12 | - api: 13 | crdVersion: v1 14 | namespaced: true 15 | controller: true 16 | domain: bankdata.dk 17 | group: styra 18 | kind: System 19 | path: github.com/bankdata/styra-controller/api/styra/v1beta1 20 | version: v1beta1 21 | webhooks: 22 | defaulting: true 23 | validation: true 24 | webhookVersion: v1 25 | - api: 26 | crdVersion: v1 27 | namespaced: true 28 | domain: bankdata.dk 29 | group: test 30 | kind: Object 31 | path: github.com/bankdata/styra-controller/api/test/v1 32 | version: v1 33 | - api: 34 | crdVersion: v1 35 | domain: bankdata.dk 36 | group: config 37 | kind: ProjectConfig 38 | path: github.com/bankdata/styra-controller/api/config/v2alpha2 39 | version: v2alpha2 40 | - api: 41 | crdVersion: v1 42 | namespaced: true 43 | controller: true 44 | domain: bankdata.dk 45 | group: styra 46 | kind: Library 47 | path: github.com/bankdata/styra-controller/api/styra/v1alpha1 48 | version: v1alpha1 49 | webhooks: 50 | defaulting: true 51 | validation: true 52 | webhookVersion: v1 53 | version: "3" 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/bankdata/styra-controller)](https://goreportcard.com/report/github.com/bankdata/styra-controller) 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/bankdata/styra-controller.svg)](https://pkg.go.dev/github.com/bankdata/styra-controller) 3 | [![Release](https://img.shields.io/github/release/bankdata/styra-controller.svg?style=flat-square)](https://github.com/bankdata/styra-controller/releases/latest) 4 | [![Gitmoji](https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square)](https://gitmoji.dev) 5 | 6 | # styra-controller 7 | 8 | styra-controller is a Kubernetes controller designed to automate configuration 9 | of [Styra DAS](https://www.styra.com/styra-das/). With the use of 10 | [CustomResourceDefinitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), 11 | styra-controller enables systems and datasources to be configured, without 12 | having to do it through the GUI. By doing this we can gurantee that no changes 13 | are done to Styra DAS manually, which makes change management and compliance 14 | easier. 15 | 16 | In order to ease configuration of OPA and 17 | [Styra Local Plane (SLP)](https://docs.styra.com/das/policies/policy-organization/systems/use-styra-local-plane), 18 | the controller automatically creates ConfigMaps and Secrets which contain the 19 | configuration and connection details for these components. 20 | 21 | ## Arcitectural overview 22 | 23 | styra-controller sits in a Kubernetes cluster and ensures that systems and 24 | datasources are created in Styra DAS. It then creates ConfigMaps and Secrets 25 | where relevant configuration and connection details can be read. 26 | 27 | 28 | 29 | diagram over the controller architecture 30 | 31 | 32 | ## CustomResourceDefinitions 33 | 34 | A core feature of the styra-controller is to monitor the Kubernetes API 35 | server for changes to specific objects and ensure that the current Styra DAS 36 | resources match these objects. The controller acts on the following custom 37 | resource definitions (CRDs). 38 | 39 | - `System`, which defines a Styra DAS system configuration, its datasources and 40 | users with access. 41 | - `Library`, which defines a Library resource in Styra DAS. 42 | 43 | For more information about these resources see the 44 | [design document](docs/design.md) 45 | or the full 46 | [api reference](docs/apis). 47 | 48 | ## Installation 49 | 50 | For a guide on how to install styra-controller see 51 | [the installation instructions](docs/installation.md). 52 | 53 | ## Limitations 54 | 55 | The styra-controller is a rather new project made to accomodate the needs we 56 | have in Bankdata. This means that the feature set currently has some 57 | limitations. The following is a few of the most important ones. 58 | 59 | - Only supported datasource category for datasources added to systems is `JSON` 60 | - Git ssh auth is not supported 61 | - Only supported system type is `custom` 62 | - Stacks are currently unsupported 63 | 64 | These limitations merely reflect the current state, and we might change them 65 | and add new features when the need for them arises. If you want to help 66 | removing any of these limitations feel free to open an issue or submit a pull 67 | request. 68 | 69 | ## Contributing 70 | 71 | For a guide on how to contribute to the styra-controller project as well as how 72 | to deploy the styra-controller for testing purposes see 73 | [CONTRIBUTING.md](CONTRIBUTING.md). 74 | 75 | ## Security 76 | 77 | For more information about the security policy of the project see [SECURITY.md](SECURITY.md) 78 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following versions will be supported with security fixes. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.3.0 < | :white_check_mark: | 10 | 11 | ## Reporting a vulnerability 12 | 13 | Please don't open an issue if you find a vulnerability in the project, instead 14 | use the github 15 | [security vulnerability report form](https://github.com/Bankdata/styra-controller/security/advisories/new). 16 | -------------------------------------------------------------------------------- /api/config/v2alpha2/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 v2alpha2 contains API Schema definitions for the config v2alpha2 API group 18 | // +kubebuilder:object:generate=true 19 | // +kubebuilder:skip 20 | // +groupName=config.bankdata.dk 21 | package v2alpha2 22 | 23 | import ( 24 | "k8s.io/apimachinery/pkg/runtime/schema" 25 | "sigs.k8s.io/controller-runtime/pkg/scheme" 26 | ) 27 | 28 | var ( 29 | // GroupVersion is group version used to register these objects 30 | GroupVersion = schema.GroupVersion{Group: "config.bankdata.dk", Version: "v2alpha2"} 31 | 32 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 33 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 34 | 35 | // AddToScheme adds the types in this group-version to the given scheme. 36 | AddToScheme = SchemeBuilder.AddToScheme 37 | ) 38 | -------------------------------------------------------------------------------- /api/styra/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Bankdata. 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 | // +groupName=styra.bankdata.dk 18 | 19 | // Package v1alpha1 contains API Schema definitions for the styra v1alpha1 API 20 | // group. 21 | package v1alpha1 22 | -------------------------------------------------------------------------------- /api/styra/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 | // +kubebuilder:object:generate=true 18 | // +groupName=styra.bankdata.dk 19 | 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: "styra.bankdata.dk", 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/styra/v1alpha1/library_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // LibrarySpec defines the desired state of Library 24 | type LibrarySpec struct { 25 | 26 | // Name is the name the Library will have in Styra DAS 27 | Name string `json:"name"` 28 | 29 | // Description is the description of the Library 30 | Description string `json:"description"` 31 | 32 | // Subjects is the list of subjects which should have access to the system. 33 | Subjects []LibrarySubject `json:"subjects,omitempty"` 34 | 35 | // SourceControl is the sourcecontrol configuration for the Library 36 | SourceControl *SourceControl `json:"sourceControl,omitempty"` 37 | 38 | // Datasources is the list of datasources in the Library 39 | Datasources []LibraryDatasource `json:"datasources,omitempty"` 40 | } 41 | 42 | // LibraryDatasource contains metadata of a datasource, stored in a library 43 | type LibraryDatasource struct { 44 | // Path is the path within the system where the datasource should reside. 45 | Path string `json:"path"` 46 | 47 | // Description is a description of the datasource 48 | Description string `json:"description,omitempty"` 49 | } 50 | 51 | // SourceControl is a struct from styra where we only use a single field 52 | // but kept for clarity when comparing to the API 53 | type SourceControl struct { 54 | LibraryOrigin *GitRepo `json:"libraryOrigin"` 55 | } 56 | 57 | // LibrarySubjectKind represents a kind of a subject. 58 | type LibrarySubjectKind string 59 | 60 | const ( 61 | // LibrarySubjectKindUser is the subject kind user. 62 | LibrarySubjectKindUser LibrarySubjectKind = "user" 63 | 64 | // LibrarySubjectKindGroup is the subject kind group. 65 | LibrarySubjectKindGroup LibrarySubjectKind = "group" 66 | ) 67 | 68 | // LibrarySubject represents a subject which has been granted access to the Library. 69 | // The subject is assigned to the LibraryViewer role. 70 | type LibrarySubject struct { 71 | // Kind is the LibrarySubjectKind of the subject. 72 | //+kubebuilder:validation:Enum=user;group 73 | Kind LibrarySubjectKind `json:"kind,omitempty"` 74 | 75 | // Name is the name of the subject. The meaning of this field depends on the 76 | // SubjectKind. 77 | Name string `json:"name"` 78 | } 79 | 80 | // IsUser returns whether or not the kind of the subject is a user. 81 | func (subject LibrarySubject) IsUser() bool { 82 | return subject.Kind == LibrarySubjectKindUser || subject.Kind == "" 83 | } 84 | 85 | // GitRepo defines the Git configurations a library can be defined by 86 | type GitRepo struct { 87 | // Path is the path in the git repo where the policies are located. 88 | Path string `json:"path,omitempty"` 89 | 90 | // Reference is used to point to a tag or branch. This will be ignored if 91 | // `Commit` is specified. 92 | Reference string `json:"reference,omitempty"` 93 | 94 | // Commit is used to point to a specific commit SHA. This takes precedence 95 | // over `Reference` if both are specified. 96 | Commit string `json:"commit,omitempty"` 97 | 98 | // URL is the URL of the git repo. 99 | URL string `json:"url"` 100 | } 101 | 102 | // LibrarySecretRef defines how to access a k8s secret for the library. 103 | type LibrarySecretRef struct { 104 | // Namespace is the namespace where the secret resides. 105 | Namespace string `json:"namespace"` 106 | // Name is the name of the secret. 107 | Name string `json:"name"` 108 | } 109 | 110 | // LibraryStatus defines the observed state of Library 111 | type LibraryStatus struct { 112 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 113 | // Important: Run "make" to regenerate code after modifying this file 114 | } 115 | 116 | //+kubebuilder:object:root=true 117 | //+kubebuilder:subresource:status 118 | //+kubebuilder:resource:scope=Cluster 119 | 120 | // Library is the Schema for the libraries API 121 | type Library struct { 122 | metav1.TypeMeta `json:",inline"` 123 | metav1.ObjectMeta `json:"metadata,omitempty"` 124 | 125 | Spec LibrarySpec `json:"spec,omitempty"` 126 | Status LibraryStatus `json:"status,omitempty"` 127 | } 128 | 129 | //+kubebuilder:object:root=true 130 | 131 | // LibraryList contains a list of Library 132 | type LibraryList struct { 133 | metav1.TypeMeta `json:",inline"` 134 | metav1.ListMeta `json:"metadata,omitempty"` 135 | Items []Library `json:"items"` 136 | } 137 | 138 | func init() { 139 | SchemeBuilder.Register(&Library{}, &LibraryList{}) 140 | } 141 | -------------------------------------------------------------------------------- /api/styra/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Bankdata. 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 | // +groupName=styra.bankdata.dk 18 | 19 | // Package v1beta1 contains API Schema definitions for the styra v1beta1 API 20 | // group. 21 | package v1beta1 22 | -------------------------------------------------------------------------------- /api/styra/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 | // +kubebuilder:object:generate=true 18 | // +groupName=styra.bankdata.dk 19 | 20 | package v1beta1 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: "styra.bankdata.dk", Version: "v1beta1"} 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/styra/v1beta1/system_types_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 v1beta1 18 | 19 | import ( 20 | "time" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | 27 | "github.com/bankdata/styra-controller/pkg/ptr" 28 | ) 29 | 30 | var _ = ginkgo.Describe("Expected", func() { 31 | ginkgo.Describe("Value", func() { 32 | ginkgo.It("returns true by default", func() { 33 | gomega.Expect(Expected{}.Value()).To(gomega.BeTrue()) 34 | }) 35 | 36 | ginkgo.It("returns true in invalid configurations", func() { 37 | gomega.Expect(Expected{ 38 | Boolean: ptr.Bool(false), 39 | String: ptr.String("test"), 40 | Integer: ptr.Int(42), 41 | }.Value()).To(gomega.BeTrue()) 42 | }) 43 | 44 | ginkgo.It("returns the value set", func() { 45 | gomega.Expect(Expected{Boolean: ptr.Bool(true)}.Value()).To(gomega.BeTrue()) 46 | gomega.Expect(Expected{Boolean: ptr.Bool(false)}.Value()).To(gomega.BeFalse()) 47 | gomega.Expect(Expected{String: ptr.String("")}.Value()).To(gomega.Equal("")) 48 | gomega.Expect(Expected{String: ptr.String("test")}.Value()).To(gomega.Equal("test")) 49 | gomega.Expect(Expected{Integer: ptr.Int(0)}.Value()).To(gomega.Equal(0)) 50 | gomega.Expect(Expected{Integer: ptr.Int(42)}.Value()).To(gomega.Equal(42)) 51 | }) 52 | }) 53 | }) 54 | 55 | var _ = ginkgo.Describe("System", func() { 56 | 57 | ginkgo.DescribeTable("SetCondition", 58 | func( 59 | conditions []Condition, 60 | conditionType ConditionType, 61 | status metav1.ConditionStatus, 62 | expectedConditions []Condition, 63 | ) { 64 | ss := System{ 65 | Status: SystemStatus{ 66 | Conditions: conditions, 67 | }, 68 | } 69 | ss.setCondition(func() time.Time { 70 | return time.Time{} 71 | }, conditionType, status) 72 | 73 | gomega.Ω(ss.Status.Conditions).To(gomega.Equal(expectedConditions)) 74 | }, 75 | 76 | ginkgo.Entry("Add first condition", nil, 77 | ConditionTypeCreatedInStyra, metav1.ConditionTrue, 78 | []Condition{ 79 | { 80 | Type: ConditionTypeCreatedInStyra, 81 | Status: metav1.ConditionTrue, 82 | }, 83 | }, 84 | ), 85 | 86 | ginkgo.Entry("Add new condition", 87 | []Condition{ 88 | { 89 | Type: ConditionTypeCreatedInStyra, 90 | Status: metav1.ConditionTrue, 91 | }, 92 | }, 93 | ConditionTypeGitCredentialsUpdated, metav1.ConditionFalse, 94 | []Condition{ 95 | { 96 | Type: ConditionTypeCreatedInStyra, 97 | Status: metav1.ConditionTrue, 98 | }, 99 | { 100 | Type: ConditionTypeGitCredentialsUpdated, 101 | Status: metav1.ConditionFalse, 102 | }, 103 | }, 104 | ), 105 | 106 | ginkgo.Entry("Update status on existing condition", 107 | []Condition{ 108 | { 109 | Type: ConditionTypeCreatedInStyra, 110 | Status: metav1.ConditionTrue, 111 | }, 112 | { 113 | Type: ConditionTypeGitCredentialsUpdated, 114 | Status: metav1.ConditionFalse, 115 | }, 116 | }, 117 | ConditionTypeGitCredentialsUpdated, metav1.ConditionTrue, 118 | []Condition{ 119 | { 120 | Type: ConditionTypeCreatedInStyra, 121 | Status: metav1.ConditionTrue, 122 | }, 123 | { 124 | Type: ConditionTypeGitCredentialsUpdated, 125 | Status: metav1.ConditionTrue, 126 | }, 127 | }, 128 | ), 129 | ) 130 | 131 | ginkgo.DescribeTable("DisplayName", 132 | func(system *System, prefix, suffix, expected string) { 133 | gomega.Expect(system.DisplayName(prefix, suffix)).To(gomega.Equal(expected)) 134 | }, 135 | 136 | ginkgo.Entry("only namespace and name", &System{ObjectMeta: metav1.ObjectMeta{ 137 | Namespace: "namespace", 138 | Name: "name", 139 | }}, "", "", "namespace/name"), 140 | 141 | ginkgo.Entry("with prefix", &System{ObjectMeta: metav1.ObjectMeta{ 142 | Namespace: "namespace", 143 | Name: "name", 144 | }}, "test", "", "test/namespace/name"), 145 | 146 | ginkgo.Entry("also with suffix", &System{ObjectMeta: metav1.ObjectMeta{ 147 | Namespace: "namespace", 148 | Name: "name", 149 | }}, "test", "cluster1", "test/namespace/name/cluster1"), 150 | ) 151 | 152 | ginkgo.Describe("GitSecretID", func() { 153 | ginkgo.It("creates the git secret ID", func() { 154 | s := &System{Status: SystemStatus{ID: "testid"}} 155 | gomega.Expect(s.GitSecretID()).To(gomega.Equal("systems/testid/git")) 156 | }) 157 | }) 158 | }) 159 | -------------------------------------------------------------------------------- /api/styra/v1beta1/v1beta1_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 v1beta1 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestAPIs(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "api/styra/v1beta1") 29 | } 30 | -------------------------------------------------------------------------------- /api/test/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 | // +kubebuilder:object:generate=true 18 | // +groupName=test.bankdata.dk 19 | 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "test.bankdata.dk", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/test/v1/object_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | // +kubebuilder:skip 20 | 21 | import ( 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | // Object is a very simple kubernetes object which doesn't have a spec or 26 | // status. It only has type and object metadata. This type is useful for 27 | // testing reconciliation predicates. 28 | // +kubebuilder:object:root=true 29 | type Object struct { 30 | metav1.TypeMeta `json:",inline"` 31 | metav1.ObjectMeta `json:"metadata,omitempty"` 32 | } 33 | 34 | func init() { 35 | SchemeBuilder.Register(&Object{}) 36 | } 37 | -------------------------------------------------------------------------------- /api/test/v1/v1.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Bankdata. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the test v1 API group. The 18 | // types in this package are only used for tests. 19 | package v1 20 | -------------------------------------------------------------------------------- /api/test/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *Object) DeepCopyInto(out *Object) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Object. 35 | func (in *Object) DeepCopy() *Object { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(Object) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 45 | func (in *Object) DeepCopyObject() runtime.Object { 46 | if c := in.DeepCopy(); c != nil { 47 | return c 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /build/package/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM alpine:3.17.2 AS certs 16 | 17 | RUN apk add --no-cache ca-certificates 18 | 19 | FROM scratch 20 | 21 | ARG BINARY=styra-controller 22 | 23 | USER 65532:65532 24 | 25 | COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 26 | COPY $BINARY /styra-controller 27 | 28 | ENTRYPOINT ["/styra-controller"] 29 | -------------------------------------------------------------------------------- /cmd/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestMain(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "cmd") 29 | } 30 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: certificate 9 | app.kubernetes.io/instance: serving-cert 10 | app.kubernetes.io/component: certificate 11 | app.kubernetes.io/created-by: styra-controller 12 | app.kubernetes.io/part-of: styra-controller 13 | app.kubernetes.io/managed-by: kustomize 14 | name: selfsigned-issuer 15 | namespace: system 16 | spec: 17 | selfSigned: {} 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | labels: 23 | app.kubernetes.io/name: certificate 24 | app.kubernetes.io/instance: serving-cert 25 | app.kubernetes.io/component: certificate 26 | app.kubernetes.io/created-by: styra-controller 27 | app.kubernetes.io/part-of: styra-controller 28 | app.kubernetes.io/managed-by: kustomize 29 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 30 | namespace: system 31 | spec: 32 | # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize 33 | dnsNames: 34 | - SERVICE_NAME.SERVICE_NAMESPACE.svc 35 | - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local 36 | issuerRef: 37 | kind: Issuer 38 | name: selfsigned-issuer 39 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 40 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | # This kustomization.yaml is not intended to be run by itself, 5 | # since it depends on service name and namespace that are out of this kustomize package. 6 | # It should be run by config/default 7 | resources: 8 | - bases/styra.bankdata.dk_systems.yaml 9 | - bases/styra.bankdata.dk_libraries.yaml 10 | #+kubebuilder:scaffold:crdkustomizeresource 11 | 12 | patches: 13 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 14 | # patches here are for enabling the conversion webhook for each CRD 15 | - path: patches/webhook_in_styra_systems.yaml 16 | - path: patches/webhook_in_styra_libraries.yaml 17 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 18 | 19 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 20 | # patches here are for enabling the CA injection for each CRD 21 | - path: patches/cainjection_in_styra_systems.yaml 22 | - path: patches/cainjection_in_styra_libraries.yaml 23 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 24 | 25 | # the following config is for teaching kustomize how to do kustomization for CRDs. 26 | configurations: 27 | - kustomizeconfig.yaml 28 | -------------------------------------------------------------------------------- /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_config_projectconfigs.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: projectconfigs.config.bankdata.dk 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_styra_libraries.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: libraries.styra.bankdata.dk 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_styra_systems.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: systems.styra.bankdata.dk 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_config_projectconfigs.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: projectconfigs.config.bankdata.dk 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_styra_libraries.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: libraries.styra.bankdata.dk 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_styra_systems.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: systems.styra.bankdata.dk 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: config.bankdata.dk/v2alpha2 2 | kind: ProjectConfig 3 | #controllerClass: 4 | deletionProtectionDefault: false 5 | #disableCRDWebhooks: 6 | readOnly: true 7 | enableMigrations: false 8 | enableDeltaBundlesDefault: false # This does affect the thingy 9 | #gitCredentials: 10 | logLevel: 0 11 | # leaderElection: 12 | # leaseDuration: "60s" 13 | # renewDeadline: "30s" 14 | # retryPeriod: "5s" 15 | notificationWebhooks: {} 16 | # systemDatasourceChanged: google.com 17 | # libraryDatasourceChanged: test.dk 18 | #sentry: 19 | #sso: 20 | styra: 21 | address: https://styra-url.example.com 22 | token: styra-token 23 | # tokenSecretPath: /etc/styra-controller-token/styra_token 24 | #systemPrefix: 25 | #systemSuffix: 26 | systemUserRoles: 27 | - SystemOwner 28 | - SystemMetadataManager 29 | 30 | #datasourceIgnorePatterns: 31 | # - "^systems/[a-z0-9]+/dontdeleteme/.*" 32 | # - "^libraries/[a-z0-9_]+/deletemenot/.*" 33 | 34 | #decisionsExporter: 35 | 36 | #activityExporter: 37 | 38 | podRestart: 39 | slpRestart: 40 | enabled: true 41 | deploymentType: StatefulSet 42 | 43 | 44 | #opa: 45 | # decision_logs: 46 | # request_context: 47 | # http: 48 | # headers: 49 | # - "Accept" -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: styra-controller-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: styra-controller- 10 | 11 | # Labels to add to all resources and selectors. 12 | #labels: 13 | #- includeSelectors: true 14 | # pairs: 15 | # someName: someValue 16 | 17 | resources: 18 | - ../crd 19 | - ../rbac 20 | - ../manager 21 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 22 | # crd/kustomization.yaml 23 | - ../webhook 24 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 25 | - ../certmanager 26 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 27 | #- ../prometheus 28 | 29 | patches: 30 | # Protect the /metrics endpoint by putting it behind auth. 31 | # If you want your controller-manager to expose the /metrics 32 | # endpoint w/o any authn/z, please comment the following line. 33 | - path: manager_auth_proxy_patch.yaml 34 | - path: manager_config_patch.yaml 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 37 | # crd/kustomization.yaml 38 | - path: manager_webhook_patch.yaml 39 | 40 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 41 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 42 | # 'CERTMANAGER' needs to be enabled to use ca injection 43 | - path: webhookcainjection_validating_patch.yaml 44 | - path: webhookcainjection_mutating_patch.yaml 45 | 46 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 47 | # Uncomment the following replacements to add the cert-manager CA injection annotations 48 | replacements: 49 | - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs 50 | kind: Certificate 51 | group: cert-manager.io 52 | version: v1 53 | name: serving-cert # this name should match the one in certificate.yaml 54 | fieldPath: .metadata.namespace # namespace of the certificate CR 55 | targets: 56 | - select: 57 | kind: ValidatingWebhookConfiguration 58 | fieldPaths: 59 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 60 | options: 61 | delimiter: '/' 62 | index: 0 63 | create: true 64 | - select: 65 | kind: MutatingWebhookConfiguration 66 | fieldPaths: 67 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 68 | options: 69 | delimiter: '/' 70 | index: 0 71 | create: true 72 | - select: 73 | kind: CustomResourceDefinition 74 | fieldPaths: 75 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 76 | options: 77 | delimiter: '/' 78 | index: 0 79 | create: true 80 | - source: 81 | kind: Certificate 82 | group: cert-manager.io 83 | version: v1 84 | name: serving-cert # this name should match the one in certificate.yaml 85 | fieldPath: .metadata.name 86 | targets: 87 | - select: 88 | kind: ValidatingWebhookConfiguration 89 | fieldPaths: 90 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 91 | options: 92 | delimiter: '/' 93 | index: 1 94 | create: true 95 | - select: 96 | kind: MutatingWebhookConfiguration 97 | fieldPaths: 98 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 99 | options: 100 | delimiter: '/' 101 | index: 1 102 | create: true 103 | - select: 104 | kind: CustomResourceDefinition 105 | fieldPaths: 106 | - .metadata.annotations.[cert-manager.io/inject-ca-from] 107 | options: 108 | delimiter: '/' 109 | index: 1 110 | create: true 111 | - source: # Add cert-manager annotation to the webhook Service 112 | kind: Service 113 | version: v1 114 | name: webhook-service 115 | fieldPath: .metadata.name # namespace of the service 116 | targets: 117 | - select: 118 | kind: Certificate 119 | group: cert-manager.io 120 | version: v1 121 | fieldPaths: 122 | - .spec.dnsNames.0 123 | - .spec.dnsNames.1 124 | options: 125 | delimiter: '.' 126 | index: 0 127 | create: true 128 | - source: 129 | kind: Service 130 | version: v1 131 | name: webhook-service 132 | fieldPath: .metadata.namespace # namespace of the service 133 | targets: 134 | - select: 135 | kind: Certificate 136 | group: cert-manager.io 137 | version: v1 138 | fieldPaths: 139 | - .spec.dnsNames.0 140 | - .spec.dnsNames.1 141 | options: 142 | delimiter: '.' 143 | index: 1 144 | create: true 145 | 146 | secretGenerator: 147 | - name: config 148 | files: 149 | - config.yaml 150 | - name: token 151 | literals: 152 | - styra_token=token -------------------------------------------------------------------------------- /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 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | -------------------------------------------------------------------------------- /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=/etc/styra-controller/config.yaml 13 | volumeMounts: 14 | - name: config 15 | mountPath: /etc/styra-controller 16 | - name: token 17 | mountPath: /etc/styra-controller-token 18 | volumes: 19 | - name: config 20 | secret: 21 | secretName: config 22 | - name: token 23 | secret: 24 | secretName: token -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_mutating_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: mutatingwebhookconfiguration 8 | app.kubernetes.io/instance: mutating-webhook-configuration 9 | app.kubernetes.io/component: webhook 10 | app.kubernetes.io/created-by: styra-controller 11 | app.kubernetes.io/part-of: styra-controller 12 | app.kubernetes.io/managed-by: kustomize 13 | name: mutating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 16 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: mutatingwebhookconfiguration 8 | app.kubernetes.io/instance: mutating-webhook-configuration 9 | app.kubernetes.io/component: webhook 10 | app.kubernetes.io/created-by: styra-controller 11 | app.kubernetes.io/part-of: styra-controller 12 | app.kubernetes.io/managed-by: kustomize 13 | name: mutating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 16 | --- 17 | apiVersion: admissionregistration.k8s.io/v1 18 | kind: ValidatingWebhookConfiguration 19 | metadata: 20 | labels: 21 | app.kubernetes.io/name: validatingwebhookconfiguration 22 | app.kubernetes.io/instance: validating-webhook-configuration 23 | app.kubernetes.io/component: webhook 24 | app.kubernetes.io/created-by: styra-controller 25 | app.kubernetes.io/part-of: styra-controller 26 | app.kubernetes.io/managed-by: kustomize 27 | name: validating-webhook-configuration 28 | annotations: 29 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 30 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_validating_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: validatingwebhookconfiguration 8 | app.kubernetes.io/instance: validating-webhook-configuration 9 | app.kubernetes.io/component: webhook 10 | app.kubernetes.io/created-by: styra-controller 11 | app.kubernetes.io/part-of: styra-controller 12 | app.kubernetes.io/managed-by: kustomize 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: controller 8 | newTag: latest 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: manager 24 | app.kubernetes.io/created-by: styra-controller 25 | app.kubernetes.io/part-of: styra-controller 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 40 | # according to the platforms which are supported by your solution. 41 | # It is considered best practice to support multiple architectures. You can 42 | # build your manager image using the makefile target docker-buildx. 43 | # affinity: 44 | # nodeAffinity: 45 | # requiredDuringSchedulingIgnoredDuringExecution: 46 | # nodeSelectorTerms: 47 | # - matchExpressions: 48 | # - key: kubernetes.io/arch 49 | # operator: In 50 | # values: 51 | # - amd64 52 | # - arm64 53 | # - ppc64le 54 | # - s390x 55 | # - key: kubernetes.io/os 56 | # operator: In 57 | # values: 58 | # - linux 59 | securityContext: 60 | runAsNonRoot: true 61 | # TODO(user): For common cases that do not require escalating privileges 62 | # it is recommended to ensure that all your Pods/Containers are restrictive. 63 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 64 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 65 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 66 | # seccompProfile: 67 | # type: RuntimeDefault 68 | containers: 69 | - command: 70 | - /styra-controller 71 | image: controller:latest 72 | imagePullPolicy: IfNotPresent 73 | name: manager 74 | securityContext: 75 | allowPrivilegeEscalation: false 76 | capabilities: 77 | drop: 78 | - "ALL" 79 | livenessProbe: 80 | httpGet: 81 | path: /healthz 82 | port: 8081 83 | initialDelaySeconds: 15 84 | periodSeconds: 20 85 | readinessProbe: 86 | httpGet: 87 | path: /readyz 88 | port: 8081 89 | initialDelaySeconds: 5 90 | periodSeconds: 10 91 | # TODO(user): Configure the resources accordingly based on the project requirements. 92 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 93 | resources: 94 | limits: 95 | cpu: 500m 96 | memory: 128Mi 97 | requests: 98 | cpu: 10m 99 | memory: 64Mi 100 | serviceAccountName: controller-manager 101 | terminationGracePeriodSeconds: 10 102 | -------------------------------------------------------------------------------- /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 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: styra-controller 12 | app.kubernetes.io/part-of: styra-controller 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /config/rbac/config_projectconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit projectconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: projectconfig-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: projectconfig-editor-role 13 | rules: 14 | - apiGroups: 15 | - config.bankdata.dk 16 | resources: 17 | - projectconfigs 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - config.bankdata.dk 28 | resources: 29 | - projectconfigs/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/config_projectconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view projectconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: projectconfig-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: projectconfig-viewer-role 13 | rules: 14 | - apiGroups: 15 | - config.bankdata.dk 16 | resources: 17 | - projectconfigs 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - config.bankdata.dk 24 | resources: 25 | - projectconfigs/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - secrets 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - events 24 | verbs: 25 | - create 26 | - patch 27 | - apiGroups: 28 | - apps 29 | resources: 30 | - statefulsets 31 | verbs: 32 | - get 33 | - list 34 | - patch 35 | - watch 36 | - apiGroups: 37 | - styra.bankdata.dk 38 | resources: 39 | - libraries 40 | - systems 41 | verbs: 42 | - create 43 | - delete 44 | - get 45 | - list 46 | - patch 47 | - update 48 | - watch 49 | - apiGroups: 50 | - styra.bankdata.dk 51 | resources: 52 | - libraries/finalizers 53 | - systems/finalizers 54 | verbs: 55 | - update 56 | - apiGroups: 57 | - styra.bankdata.dk 58 | resources: 59 | - libraries/status 60 | - systems/status 61 | verbs: 62 | - get 63 | - patch 64 | - update 65 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: styra-controller 9 | app.kubernetes.io/part-of: styra-controller 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/styra_library_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit libraries. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: library-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: library-editor-role 13 | rules: 14 | - apiGroups: 15 | - styra.bankdata.dk 16 | resources: 17 | - libraries 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - styra.bankdata.dk 28 | resources: 29 | - libraries/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/styra_library_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view libraries. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: library-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: library-viewer-role 13 | rules: 14 | - apiGroups: 15 | - styra.bankdata.dk 16 | resources: 17 | - libraries 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - styra.bankdata.dk 24 | resources: 25 | - libraries/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/styra_system_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit systems. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: system-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system-editor-role 13 | rules: 14 | - apiGroups: 15 | - styra.bankdata.dk 16 | resources: 17 | - systems 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - styra.bankdata.dk 28 | resources: 29 | - systems/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/styra_system_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view systems. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: system-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system-viewer-role 13 | rules: 14 | - apiGroups: 15 | - styra.bankdata.dk 16 | resources: 17 | - systems 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - styra.bankdata.dk 24 | resources: 25 | - systems/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/test_object_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit objects. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: object-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: object-editor-role 13 | rules: 14 | - apiGroups: 15 | - test.bankdata.dk 16 | resources: 17 | - objects 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - test.bankdata.dk 28 | resources: 29 | - objects/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/test_object_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view objects. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: object-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: object-viewer-role 13 | rules: 14 | - apiGroups: 15 | - test.bankdata.dk 16 | resources: 17 | - objects 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - test.bankdata.dk 24 | resources: 25 | - objects/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/samples/config_v2alpha2_projectconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: config.bankdata.dk/v2alpha2 2 | kind: ProjectConfig 3 | 4 | # controllerClass sets a controller class for this controller. This allows the 5 | # provided CRDs to target a specific controller. This is useful when running 6 | # multiple controllers in the same cluster. 7 | controllerClass: "" 8 | 9 | # deletionProtectionDefault sets the default to use with regards to deletion 10 | # protection if it is not set on the resource. 11 | deletionProtectionDefault: false 12 | 13 | # disableCRDWebhooks disables the CRD webhooks on the controller. If running 14 | # multiple controllers in the same cluster, only one will need to have it's 15 | # webhooks enabled. 16 | disableCRDWebhooks: false 17 | 18 | # enableMigrations enables the system migration annotation. This should be kept 19 | # disabled unless migrations need to be done. 20 | enableMigrations: false 21 | 22 | # enableDeltaBundlesDefault sets the default to use with regards to delta 23 | enableDeltaBundlesDefault: false 24 | 25 | # gitCredentials holds a list of git credential configurations. The repoPrefix 26 | # of the git credential will be matched angainst repository URL in order to 27 | # determine which credential to use. The git credential with the longest 28 | # matching repoPrefix will be selected. 29 | gitCredentials: [] 30 | # - user: my-git-user 31 | # password: my-git-password 32 | # repoPrefix: https://github.com/my-org 33 | 34 | # leaderElection contains configuration for the controller-runtime leader 35 | # election. 36 | #leaderElection: 37 | # leaseDuration: 15s 38 | # renewDeadLine: 10s 39 | # retryPeriod: 2s 40 | 41 | # logLevel sets the logging level of the controller. A higher number gives more 42 | # verbosity. A number higher than 0 should only be used for debugging purposes. 43 | logLevel: 0 44 | 45 | # notificationWebhook contains configuration for how to call the notification 46 | # webhook. 47 | #notificationWebhook: 48 | # address: "" 49 | 50 | # sentry contains configuration for how errors should be reported to sentry. 51 | #sentry: 52 | # debug: false 53 | # dsn: "" 54 | # environment: "" 55 | # httpsProxy: "" 56 | 57 | # sso contains configuration for how to use SSO tokens for determining what 58 | # groups a user belongs to. This can be used to grant members of a certain 59 | # group access to systems. 60 | #sso: 61 | # identityProvider: "" 62 | # jwtGroupsClaim: "" 63 | 64 | # styra contains configuration for connecting to the Styra DAS apis 65 | styra: 66 | address: "" 67 | token: "" 68 | 69 | # systemPrefix is a prefix for all the systems that the controller creates 70 | # in Styra DAS. This is useful in order to be able to identify what 71 | # controller created a system in a shared Styra DAS instance. 72 | systemPrefix: "" 73 | 74 | # systemSuffix is a suffix for all the systems that the controller creates 75 | # in Styra DAS. This is useful in order to be able to identify what 76 | # controller created a system in a shared Styra DAS instance. 77 | systemSuffix: "" 78 | 79 | # systemUserRoles is a list of Styra DAS system level roles which the subjects of 80 | # a system will be granted. 81 | systemUserRoles: [] 82 | # - SystemViewer 83 | # - SystemInstall 84 | 85 | readOnly: true 86 | 87 | # opa contains default configuration for the opa configmap holding the opa config generated by the styra-controller 88 | # decision_logs: https://www.openpolicyagent.org/docs/latest/configuration/#decision-logs 89 | # request_context.http.headers: list of strings that will be added to the decision_logs 90 | #opa: 91 | # decision_logs: 92 | # request_context: 93 | # http: 94 | # headers: 95 | # - "Accept" -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - styra_v1beta1_system.yaml 4 | - test_v1_object.yaml 5 | - config_v2alpha2_projectconfig.yaml 6 | - styra_v1alpha1_library.yaml 7 | #+kubebuilder:scaffold:manifestskustomizesamples 8 | -------------------------------------------------------------------------------- /config/samples/styra_v1alpha1_library.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: styra.bankdata.dk/v1alpha1 2 | kind: Library 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: library 6 | app.kubernetes.io/instance: library-sample 7 | app.kubernetes.io/part-of: styra-controller 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: styra-controller 10 | name: my-library 11 | spec: 12 | name: mylibrary 13 | description: my library 14 | sourceControl: 15 | libraryOrigin: 16 | url: https://github.com/Bankdata/styra-controller.git 17 | reference: refs/heads/master 18 | commit: f37cc9d87251921cbe49349235d9b5305c833769 19 | path: path 20 | datasources: 21 | - path: seconds/datasource 22 | description: this is the second datasource 23 | subjects: 24 | - kind: user 25 | name: user1@mail.dk 26 | - kind: group 27 | name: mygroup 28 | -------------------------------------------------------------------------------- /config/samples/styra_v1beta1_system.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: styra.bankdata.dk/v1beta1 2 | kind: System 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: system 6 | app.kubernetes.io/instance: system-sample 7 | app.kubernetes.io/part-of: styra-controller 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: styra-controller 10 | name: system-sample 11 | spec: 12 | decisionMappings: 13 | - allowed: 14 | expected: 15 | boolean: true 16 | path: result.allowed 17 | name: api/authz/decision 18 | reason: 19 | path: result.reasons 20 | datasources: 21 | - path: "test" 22 | # TODO(user): Add fields here 23 | -------------------------------------------------------------------------------- /config/samples/test_v1_object.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: test.bankdata.dk/v1 2 | kind: Object 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: object 6 | app.kubernetes.io/instance: object-sample 7 | app.kubernetes.io/part-of: styra-controller 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: styra-controller 10 | name: object-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting nameReference. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-styra-bankdata-dk-v1alpha1-library 14 | failurePolicy: Fail 15 | name: mlibrary-v1alpha1.kb.io 16 | rules: 17 | - apiGroups: 18 | - styra.bankdata.dk 19 | apiVersions: 20 | - v1alpha1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - libraries 26 | sideEffects: None 27 | - admissionReviewVersions: 28 | - v1 29 | clientConfig: 30 | service: 31 | name: webhook-service 32 | namespace: system 33 | path: /mutate-styra-bankdata-dk-v1beta1-system 34 | failurePolicy: Fail 35 | name: msystem-v1beta1.kb.io 36 | rules: 37 | - apiGroups: 38 | - styra.bankdata.dk 39 | apiVersions: 40 | - v1beta1 41 | operations: 42 | - CREATE 43 | - UPDATE 44 | resources: 45 | - systems 46 | sideEffects: None 47 | --- 48 | apiVersion: admissionregistration.k8s.io/v1 49 | kind: ValidatingWebhookConfiguration 50 | metadata: 51 | name: validating-webhook-configuration 52 | webhooks: 53 | - admissionReviewVersions: 54 | - v1 55 | clientConfig: 56 | service: 57 | name: webhook-service 58 | namespace: system 59 | path: /validate-styra-bankdata-dk-v1alpha1-library 60 | failurePolicy: Fail 61 | name: vlibrary-v1alpha1.kb.io 62 | rules: 63 | - apiGroups: 64 | - styra.bankdata.dk 65 | apiVersions: 66 | - v1alpha1 67 | operations: 68 | - CREATE 69 | - UPDATE 70 | resources: 71 | - libraries 72 | sideEffects: None 73 | - admissionReviewVersions: 74 | - v1 75 | clientConfig: 76 | service: 77 | name: webhook-service 78 | namespace: system 79 | path: /validate-styra-bankdata-dk-v1beta1-system 80 | failurePolicy: Fail 81 | name: vsystem-v1beta1.kb.io 82 | rules: 83 | - apiGroups: 84 | - styra.bankdata.dk 85 | apiVersions: 86 | - v1beta1 87 | operations: 88 | - CREATE 89 | - UPDATE 90 | resources: 91 | - systems 92 | sideEffects: None 93 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: webhook-service 8 | app.kubernetes.io/component: webhook 9 | app.kubernetes.io/created-by: styra-controller 10 | app.kubernetes.io/part-of: styra-controller 11 | app.kubernetes.io/managed-by: kustomize 12 | name: webhook-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - port: 443 17 | protocol: TCP 18 | targetPort: 9443 19 | selector: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # CustomResourceDefinition Design 2 | 3 | This document describes the design of the custom resource definitions that the 4 | styra-controller manages. 5 | 6 | The custom resources managed by the styra-controller are: 7 | 8 | * `System` 9 | * `Library` 10 | 11 | ## System 12 | 13 | The `System` custom resource definition (CRD) declaratively defines a desired 14 | system in Styra DAS. It provides options for configuring the name of the 15 | system, datasources, decision mappings, git settings, and access control as a 16 | list of users and/or SSO claims. It also supports the use of 17 | [Styra Local Plane](https://docs.styra.com/das/policies/policy-organization/systems/use-styra-local-plane). 18 | 19 | ```yaml 20 | apiVersion: styra.bankdata.dk/v1beta1 21 | kind: System 22 | metadata: 23 | name: example-system 24 | labels: 25 | app: example-system 26 | spec: 27 | datasources: 28 | - path: datasources/example 29 | decisionMappings: 30 | - allowed: 31 | expected: 32 | boolean: true 33 | path: result.allowed 34 | columns: 35 | - key: extra 36 | path: input.extra 37 | name: path/to/example/rule 38 | reason: 39 | path: result.reasons 40 | deletionProtection: true 41 | enableDeltaBundles: true 42 | localPlane: 43 | name: styra-local-plane-example 44 | sourceControl: 45 | origin: 46 | commit: commitSHA 47 | path: path/to/policy/in/git/repo 48 | url: 'git-repo-url' 49 | subjects: 50 | - name: user@user.com 51 | - kind: group 52 | name: my-group 53 | ``` 54 | 55 | The git credentials which Styra DAS will need for fetching policy are specified 56 | by referencing a secret by setting 57 | `.spec.sourceControl.origin.credentialsSecretName`. For instance, if you set 58 | `.spec.sourceControler.origin.credentialsSecretName: my-git-credentials` the 59 | styra-controller will look for a secret called `my-git-credentials` in the same 60 | namespace as the `System` resource. The secret is expected to contain a `name` 61 | and a `secret` key. The `name` key should contain the basic auth username and 62 | `secret` should contain the basic auth password. 63 | 64 | If you would rather not have to set this for every system, the controller also 65 | supports default credentials which will be used if the `credentialsSecretName` 66 | field is left empty. Read more about this in the 67 | [controller configuration documentation](configuration.md#default-git-credentials). 68 | 69 | ## Library 70 | 71 | The `Library` custom resource definition (CRD) declaratively defines a desired library 72 | in Styra DAS. It provides options for configuring the name of the library, a 73 | description of it, permissions, git settings, and datasources. 74 | 75 | ```yaml 76 | apiVersion: styra.bankdata.dk/v1alpha1 77 | kind: Library 78 | metadata: 79 | name: my-library 80 | spec: 81 | name: mylibrary 82 | description: my library 83 | sourceControl: 84 | libraryOrigin: 85 | url: https://github.com/Bankdata/styra-controller.git 86 | reference: refs/heads/master 87 | commit: f37cc9d87251921cbe49349235d9b5305c833769 88 | path: rego/path 89 | datasources: 90 | - path: seconds/datasource 91 | description: this is the second datasource 92 | subjects: 93 | - kind: user 94 | name: user1@mail.dk 95 | - kind: group 96 | name: mygroup 97 | ``` 98 | 99 | The content of the library is what is found in the folder `/libraries/`. 100 | There is therefore a high coupling between the library name and the path to the library in 101 | the git repository. The library name is also used as the name of the library in Styra DAS. 102 | With the above example, the content of the library would be the files found at 103 | `https://github.com/Bankdata/styra-controller/tree/master/rego/path/libraries/mylibrary` 104 | together with the datasource. -------------------------------------------------------------------------------- /docs/images/Styra/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/Styra/system.png -------------------------------------------------------------------------------- /docs/images/controller-arch.dark.excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/controller-arch.dark.excalidraw.png -------------------------------------------------------------------------------- /docs/images/controller-arch.light.excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/controller-arch.light.excalidraw.png -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Compatibility Table 2 | 3 | | Styra Controller | Styra DAS SaaS | Styra DAS Self-Hosted | 4 | |------------------|----------------|-----------------------| 5 | | v0.1.0 | 20230125 | 0.10.2 | 6 | 7 | 8 | # Configuration of the Styra Controller 9 | The [configuration](https://github.com/Bankdata/styra-controller/blob/master/docs/configuration.md) document contains information about the configuration options for the Styra 10 | Controller. 11 | -------------------------------------------------------------------------------- /docs/releasing.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | Only maintainers with access to pushing tags are able to perform releases. If 4 | changes have been merged into the master branch but a release has not yet been 5 | scheduled, you can contact one of the maintainers to request and plan the 6 | release. 7 | 8 | ## Binaries and docker images 9 | 10 | In order to make a new release push a semver tag eg. `v0.1.0`. If you want to 11 | publish a prerelase, simply do a prerelease tag eg. `v0.2.0-rc.1`. 12 | 13 | This will run [goreleaser](https://goreleaser.com/) according to the 14 | configuration in `.goreleaser.yaml`. 15 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 config provides utilities for reading configfiles 18 | package config 19 | 20 | import ( 21 | "os" 22 | "regexp" 23 | 24 | "github.com/bankdata/styra-controller/api/config/v2alpha2" 25 | "github.com/pkg/errors" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "k8s.io/apimachinery/pkg/runtime/serializer" 28 | "sigs.k8s.io/controller-runtime/pkg/manager" 29 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 30 | "sigs.k8s.io/controller-runtime/pkg/webhook" 31 | ) 32 | 33 | const ( 34 | healthProbeBindAddress = ":8081" 35 | metricsBindAddress = ":8080" 36 | leaderElectionID = "5d272013.bankdata.dk" 37 | webhookPort = 9443 38 | ) 39 | 40 | // Load loads controller configuration from the given file using the types 41 | // registered in the scheme. 42 | func Load(file string, scheme *runtime.Scheme) (*v2alpha2.ProjectConfig, error) { 43 | bs, err := os.ReadFile(file) 44 | if err != nil { 45 | return nil, errors.Wrap(err, "could not read config file") 46 | } 47 | return deserialize(bs, scheme) 48 | } 49 | 50 | // OptionsFromConfig creates a manager.Options based on a configuration file 51 | func OptionsFromConfig(cfg *v2alpha2.ProjectConfig, scheme *runtime.Scheme) manager.Options { 52 | o := manager.Options{ 53 | Scheme: scheme, 54 | HealthProbeBindAddress: healthProbeBindAddress, 55 | WebhookServer: webhook.NewServer(webhook.Options{Port: webhookPort}), 56 | Metrics: metricsserver.Options{ 57 | BindAddress: metricsBindAddress, 58 | }, 59 | } 60 | 61 | if cfg.LeaderElection != nil { 62 | o.LeaderElection = true 63 | o.LeaseDuration = &cfg.LeaderElection.LeaseDuration.Duration 64 | o.RenewDeadline = &cfg.LeaderElection.RenewDeadline.Duration 65 | o.RetryPeriod = &cfg.LeaderElection.RetryPeriod.Duration 66 | o.LeaderElectionID = leaderElectionID 67 | } 68 | 69 | return o 70 | } 71 | 72 | // TokenFromConfig returns the Styra DAS api token directly from "styra.token" 73 | // in the config or using the "styra.tokenSecretPath" to retrieve it fra a secret 74 | func TokenFromConfig(cfg *v2alpha2.ProjectConfig) (string, error) { 75 | if cfg.Styra.Token != "" { 76 | return cfg.Styra.Token, nil 77 | } 78 | 79 | if cfg.Styra.TokenSecretPath != "" { 80 | styraURLBytes, err := os.ReadFile(cfg.Styra.TokenSecretPath) 81 | if err != nil { 82 | return "", errors.Wrapf(err, "Could not ready Styra token from TokenSecretPath: %s", cfg.Styra.TokenSecretPath) 83 | } 84 | return string(styraURLBytes), nil 85 | } 86 | 87 | return "", errors.New("No token or tokenSecretPath defined in the config") 88 | } 89 | 90 | func deserialize(data []byte, scheme *runtime.Scheme) (*v2alpha2.ProjectConfig, error) { 91 | decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer() 92 | _, gvk, err := decoder.Decode(data, nil, nil) 93 | if err != nil { 94 | return nil, errors.Wrap(err, "could not decode config") 95 | } 96 | 97 | if gvk.Group != v2alpha2.GroupVersion.Group { 98 | return nil, errors.New("unsupported api group") 99 | } 100 | 101 | if gvk.Kind != "ProjectConfig" { 102 | return nil, errors.New("unsupported api kind") 103 | } 104 | 105 | cfg := &v2alpha2.ProjectConfig{} 106 | 107 | switch gvk.Version { 108 | case v2alpha2.GroupVersion.Version: 109 | if _, _, err := decoder.Decode(data, nil, cfg); err != nil { 110 | return nil, errors.Wrap(err, "could not decode into kind") 111 | } 112 | default: 113 | return nil, errors.New("unsupported api version") 114 | } 115 | 116 | return cfg, nil 117 | } 118 | 119 | // MatchesIgnorePattern matches a specified ignore pattern, and excludes matches from being deleted 120 | func MatchesIgnorePattern(ignorePatterns []string, id string) (bool, error) { 121 | for _, patternString := range ignorePatterns { 122 | matches, err := regexp.MatchString(patternString, id) 123 | if err != nil { 124 | return false, errors.Wrapf(err, "could not compile regex pattern: %s", patternString) 125 | } 126 | if matches { 127 | return true, nil 128 | } 129 | } 130 | return false, nil 131 | } 132 | -------------------------------------------------------------------------------- /internal/config/config_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 config 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestConfig(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "internal/config") 29 | } 30 | -------------------------------------------------------------------------------- /internal/config/config_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 config 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | 25 | "github.com/bankdata/styra-controller/api/config/v2alpha2" 26 | ) 27 | 28 | var _ = ginkgo.DescribeTable("deserialize", 29 | func(data []byte, expected *v2alpha2.ProjectConfig, shouldErr bool) { 30 | scheme := runtime.NewScheme() 31 | err := v2alpha2.AddToScheme(scheme) 32 | gomega.Ω(err).ShouldNot(gomega.HaveOccurred()) 33 | actual, err := deserialize(data, scheme) 34 | if shouldErr { 35 | gomega.Ω(err).Should(gomega.HaveOccurred()) 36 | } else { 37 | gomega.Ω(err).ShouldNot(gomega.HaveOccurred()) 38 | } 39 | gomega.Ω(actual).Should(gomega.Equal(expected)) 40 | }, 41 | 42 | ginkgo.Entry("errors on unexpected api group", 43 | []byte(` 44 | apiVersion: myconfig.bankdata.dk/v1 45 | kind: ProjectConfig 46 | styra: 47 | token: my-token 48 | `), 49 | nil, 50 | true, 51 | ), 52 | 53 | ginkgo.Entry("can deserialize v2alpha2", 54 | []byte(` 55 | apiVersion: config.bankdata.dk/v2alpha2 56 | kind: ProjectConfig 57 | styra: 58 | token: my-token 59 | `), 60 | &v2alpha2.ProjectConfig{ 61 | TypeMeta: metav1.TypeMeta{ 62 | Kind: "ProjectConfig", 63 | APIVersion: v2alpha2.GroupVersion.Identifier(), 64 | }, 65 | Styra: v2alpha2.StyraConfig{ 66 | Token: "my-token", 67 | }, 68 | }, 69 | false, 70 | ), 71 | ) 72 | -------------------------------------------------------------------------------- /internal/controller/styra/styra.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Bankdata. 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 styra holds controllers for the styra API group. 18 | package styra 19 | -------------------------------------------------------------------------------- /internal/controller/styra/styra_suite_test.go: -------------------------------------------------------------------------------- 1 | package styra_test 2 | 3 | import ( 4 | "testing" 5 | 6 | ginkgo "github.com/onsi/ginkgo/v2" 7 | gomega "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestStyra(t *testing.T) { 11 | gomega.RegisterFailHandler(ginkgo.Fail) 12 | ginkgo.RunSpecs(t, "internal/controller/styra") 13 | } 14 | -------------------------------------------------------------------------------- /internal/controller/styra/system_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | 23 | configv2alpha2 "github.com/bankdata/styra-controller/api/config/v2alpha2" 24 | styrav1beta1 "github.com/bankdata/styra-controller/api/styra/v1beta1" 25 | "github.com/bankdata/styra-controller/pkg/styra" 26 | ) 27 | 28 | var _ = ginkgo.DescribeTable("createRolebindingSubjects", 29 | func(subjects []styrav1beta1.Subject, expectedSubject []*styra.Subject) { 30 | gomega.Ω(createRolebindingSubjects(subjects, &configv2alpha2.SSOConfig{ 31 | IdentityProvider: "BDAD", 32 | JWTGroupsClaim: "groups", 33 | })).To(gomega.Equal(expectedSubject)) 34 | }, 35 | 36 | ginkgo.Entry("returns same adgroup", 37 | []styrav1beta1.Subject{{Kind: "group", Name: "ADTEST"}}, 38 | []*styra.Subject{{ 39 | Kind: "claim", 40 | ClaimConfig: &styra.ClaimConfig{ 41 | IdentityProvider: "BDAD", 42 | Key: "groups", 43 | Value: "ADTEST", 44 | }, 45 | }}, 46 | ), 47 | 48 | ginkgo.Entry("defaults empty kind value to user", 49 | []styrav1beta1.Subject{ 50 | {Kind: "user", Name: "test1@test.dk"}, 51 | {Name: "test2@test.dk"}, 52 | {Kind: "group", Name: "ADTEST"}, 53 | }, 54 | []*styra.Subject{ 55 | { 56 | Kind: "user", 57 | ID: "test1@test.dk", 58 | }, { 59 | Kind: "user", 60 | ID: "test2@test.dk", 61 | }, { 62 | Kind: "claim", 63 | ClaimConfig: &styra.ClaimConfig{ 64 | IdentityProvider: "BDAD", 65 | Key: "groups", 66 | Value: "ADTEST", 67 | }, 68 | }, 69 | }, 70 | ), 71 | 72 | ginkgo.Entry("does not return duplicates adgroups", 73 | []styrav1beta1.Subject{ 74 | {Kind: "group", Name: "ADTEST"}, 75 | {Kind: "group", Name: "ADTEST1"}, 76 | {Kind: "group", Name: "ADTEST1"}, 77 | }, 78 | []*styra.Subject{ 79 | { 80 | Kind: "claim", 81 | ClaimConfig: &styra.ClaimConfig{ 82 | IdentityProvider: "BDAD", 83 | Key: "groups", 84 | Value: "ADTEST", 85 | }, 86 | }, { 87 | Kind: "claim", 88 | ClaimConfig: &styra.ClaimConfig{ 89 | IdentityProvider: "BDAD", 90 | Key: "groups", 91 | Value: "ADTEST1", 92 | }, 93 | }, 94 | }, 95 | ), 96 | 97 | ginkgo.Entry("does not return duplicates users and groups", 98 | []styrav1beta1.Subject{ 99 | {Kind: "user", Name: "test@test.dk"}, 100 | {Kind: "user", Name: "test@test.dk"}, 101 | {Kind: "group", Name: "ADTEST"}, 102 | {Kind: "group", Name: "ADTEST"}, 103 | }, 104 | []*styra.Subject{ 105 | { 106 | Kind: "user", 107 | ID: "test@test.dk", 108 | }, { 109 | Kind: "claim", 110 | ClaimConfig: &styra.ClaimConfig{ 111 | IdentityProvider: "BDAD", 112 | Key: "groups", 113 | Value: "ADTEST", 114 | }, 115 | }, 116 | }, 117 | ), 118 | ) 119 | 120 | // test the isURLValid method 121 | var _ = ginkgo.DescribeTable("isURLValid", 122 | func(url string, expected bool) { 123 | gomega.Ω(isURLValid(url)).To(gomega.Equal(expected)) 124 | }, 125 | ginkgo.Entry("valid url", "", true), 126 | ginkgo.Entry("valid url", "https://www.github.com/test/repo.git", true), 127 | ginkgo.Entry("valid url", "https://www.github.com/test/repo", true), 128 | ginkgo.Entry("invalid url", "https://www.github.com/[test]/repo", false), 129 | ginkgo.Entry("invalid url", "https://www.github.com/[test]/repo.git", false), 130 | ginkgo.Entry("invalid url", "www.google.com", false), 131 | ginkgo.Entry("invalid url", "google.com", false), 132 | ginkgo.Entry("invalid url", "google", false), 133 | ) 134 | -------------------------------------------------------------------------------- /internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 errors contains errors. 18 | package errors 19 | 20 | import ( 21 | "github.com/pkg/errors" 22 | 23 | "github.com/bankdata/styra-controller/api/styra/v1beta1" 24 | ) 25 | 26 | type stackTracer interface { 27 | StackTrace() errors.StackTrace 28 | } 29 | 30 | // ReconcilerErr defines an error that occurs during reconciliation. 31 | type ReconcilerErr struct { 32 | err error 33 | Event string 34 | ConditionType string 35 | } 36 | 37 | // New returns a new RenconcilerErr. 38 | func New(msg string) *ReconcilerErr { 39 | return Wrap(errors.New(msg), "") 40 | } 41 | 42 | // Wrap wraps an error as a RenconcilerErr. 43 | func Wrap(err error, msg string) *ReconcilerErr { 44 | return &ReconcilerErr{ 45 | err: errors.Wrap(err, msg), 46 | } 47 | } 48 | 49 | // WithEvent adds event metadata to the ReconcilerErr. 50 | func (err *ReconcilerErr) WithEvent(event v1beta1.EventType) *ReconcilerErr { 51 | err.Event = string(event) 52 | return err 53 | } 54 | 55 | // WithSystemCondition adds condition metadata to the ReconcilerErr. 56 | func (err *ReconcilerErr) WithSystemCondition(contype v1beta1.ConditionType) *ReconcilerErr { 57 | err.ConditionType = string(contype) 58 | return err 59 | } 60 | 61 | // Error implements the error interface. 62 | func (err *ReconcilerErr) Error() string { 63 | return err.err.Error() 64 | } 65 | 66 | // Cause returns the cause of the error. 67 | func (err *ReconcilerErr) Cause() error { 68 | return err.err 69 | } 70 | 71 | // Unwrap is the same as `Cause()` 72 | func (err *ReconcilerErr) Unwrap() error { 73 | return err.Cause() 74 | } 75 | 76 | // StackTrace implements the stackTracer interface. 77 | func (err *ReconcilerErr) StackTrace() errors.StackTrace { 78 | var st stackTracer 79 | if errors.As(err.err, &st) { 80 | return st.StackTrace() 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /internal/fields/fields.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 fields contains helpers for working with fields in the CRDs. These 18 | // are mostly used when setting up field indexers. 19 | package fields 20 | 21 | import "k8s.io/apimachinery/pkg/fields" 22 | 23 | const ( 24 | // SystemCredentialsSecretName is the path to the credential secret name. 25 | SystemCredentialsSecretName = ".spec.sourceControl.origin.credentialsSecretName" 26 | ) 27 | 28 | // SystemCredentialsSecretNameSelector returns a field selector for finding the 29 | // Systems referencing a secret. 30 | func SystemCredentialsSecretNameSelector(name string) fields.Selector { 31 | return fields.OneTermEqualSelector(SystemCredentialsSecretName, name) 32 | } 33 | -------------------------------------------------------------------------------- /internal/finalizer/finalizer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 finalizer contains helpers for working with the controller finalizer. 18 | package finalizer 19 | 20 | import ( 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 23 | ) 24 | 25 | const name = "styra.bankdata.dk/finalizer" 26 | 27 | // IsSet returns true if an object has the "styra.bankdata.dk/finalizer" finalizer. 28 | func IsSet(o client.Object) bool { 29 | return controllerutil.ContainsFinalizer(o, name) 30 | } 31 | 32 | // Add adds the "styra.bankdata.dk/finalizer" finalizer to an object. 33 | func Add(o client.Object) { 34 | controllerutil.AddFinalizer(o, name) 35 | } 36 | 37 | // Remove removes the "styra.bankdata.dk/finalizer" finalizer from an object. 38 | func Remove(o client.Object) { 39 | controllerutil.RemoveFinalizer(o, name) 40 | } 41 | -------------------------------------------------------------------------------- /internal/k8sconv/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 k8sconv_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestStyraClient(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "internal/k8sconv") 29 | } 30 | -------------------------------------------------------------------------------- /internal/labels/labels.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 labels contains helpers for working with labels. 18 | package labels 19 | 20 | import ( 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | ) 25 | 26 | const ( 27 | labelControllerClass = "styra-controller/class" 28 | labelManagedBy = "app.kubernetes.io/managed-by" 29 | labelValueManagedBy = "styra-controller" 30 | ) 31 | 32 | // ControllerClassLabelSelector creates a metav1.LabelSelector which selects 33 | // objects that has the "styra-controller/class" label with the value `class`. 34 | func ControllerClassLabelSelector(class string) metav1.LabelSelector { 35 | var selector metav1.LabelSelector 36 | if class != "" { 37 | selector = metav1.LabelSelector{ 38 | MatchLabels: map[string]string{ 39 | labelControllerClass: class, 40 | }, 41 | } 42 | } else { 43 | selector = metav1.LabelSelector{ 44 | MatchExpressions: []metav1.LabelSelectorRequirement{{ 45 | Key: labelControllerClass, 46 | Operator: metav1.LabelSelectorOpDoesNotExist, 47 | }}, 48 | } 49 | } 50 | return selector 51 | } 52 | 53 | // ControllerClassLabelSelectorAsSelector creates a labels.Selecter which 54 | // selects objects that has the "styra-controller/class" label with the value `class`. 55 | func ControllerClassLabelSelectorAsSelector(class string) (labels.Selector, error) { 56 | ls := ControllerClassLabelSelector(class) 57 | return metav1.LabelSelectorAsSelector(&ls) 58 | } 59 | 60 | // SetManagedBy sets the `app.kubernetes.io/managed-by` label to 61 | // styra-controller. 62 | func SetManagedBy(o client.Object) { 63 | labels := o.GetLabels() 64 | if labels == nil { 65 | labels = map[string]string{} 66 | } 67 | labels[labelManagedBy] = labelValueManagedBy 68 | o.SetLabels(labels) 69 | } 70 | 71 | // HasManagedBy checks if the object has the label `app.kubernetes.io/managed-by` 72 | // set to styra-controller 73 | func HasManagedBy(o client.Object) bool { 74 | managedBy, ok := o.GetLabels()[labelManagedBy] 75 | return ok && managedBy == labelValueManagedBy 76 | } 77 | 78 | // ControllerClassMatches checks if the object has the `styra-controller/class` label 79 | // with the value `class`. 80 | func ControllerClassMatches(o client.Object, class string) bool { 81 | labels := o.GetLabels() 82 | if labels == nil { 83 | return class == "" 84 | } 85 | return labels[labelControllerClass] == class 86 | } 87 | -------------------------------------------------------------------------------- /internal/labels/labels_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 labels_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestLabels(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "internal/labels") 29 | } 30 | -------------------------------------------------------------------------------- /internal/labels/labels_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 labels_test 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | testv1 "github.com/bankdata/styra-controller/api/test/v1" 26 | "github.com/bankdata/styra-controller/internal/labels" 27 | ) 28 | 29 | var _ = ginkgo.Describe("SetManagedBy", func() { 30 | ginkgo.It("should set the managed-by label", func() { 31 | o := testv1.Object{} 32 | labels.SetManagedBy(&o) 33 | gomega.Ω(o.Labels["app.kubernetes.io/managed-by"]).To(gomega.Equal("styra-controller")) 34 | }) 35 | }) 36 | 37 | var _ = ginkgo.DescribeTable("HasManagedBy", 38 | func(o client.Object, expected bool) { 39 | gomega.Ω(labels.HasManagedBy(o)).To(gomega.Equal(expected)) 40 | }, 41 | ginkgo.Entry( 42 | "should return false if labels is nil", 43 | &testv1.Object{}, 44 | false, 45 | ), 46 | ginkgo.Entry( 47 | "should return false if label is set to something unexpected", 48 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{ 49 | "app.kubernetes.io/managed-by": "something-unexpected", 50 | }}}, 51 | false, 52 | ), 53 | ginkgo.Entry( 54 | "should return true if label is set as expected", 55 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{ 56 | "app.kubernetes.io/managed-by": "styra-controller", 57 | }}}, 58 | true, 59 | ), 60 | ) 61 | 62 | var _ = ginkgo.DescribeTable("ControllerClassMatches", 63 | func(o client.Object, class string, expected bool) { 64 | gomega.Ω(labels.ControllerClassMatches(o, class)).To(gomega.Equal(expected)) 65 | }, 66 | ginkgo.Entry( 67 | "should return false if labels is nil and class is non-empty", 68 | &testv1.Object{}, 69 | "test", 70 | false, 71 | ), 72 | ginkgo.Entry( 73 | "should return true if labels is nil and class empty", 74 | &testv1.Object{}, 75 | "", 76 | true, 77 | ), 78 | ginkgo.Entry( 79 | "should return true if label is missing but class is empty", 80 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}}, 81 | "", 82 | true, 83 | ), 84 | ginkgo.Entry( 85 | "should return true if label value matches", 86 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{ 87 | "styra-controller/class": "test", 88 | }}}, 89 | "test", 90 | true, 91 | ), 92 | ) 93 | -------------------------------------------------------------------------------- /internal/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 predicate contains predicates used by the controllers. 18 | package predicate 19 | 20 | import ( 21 | "github.com/pkg/errors" 22 | "sigs.k8s.io/controller-runtime/pkg/predicate" 23 | 24 | "github.com/bankdata/styra-controller/internal/labels" 25 | ) 26 | 27 | // ControllerClass creates a predicate which ensures that we only reconcile 28 | // resources that match the controller class label selector 29 | // labels.ControllerClassLabelSelector. 30 | func ControllerClass(class string) (predicate.Predicate, error) { 31 | labelSelector := labels.ControllerClassLabelSelector(class) 32 | predicate, err := predicate.LabelSelectorPredicate(labelSelector) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "could not create LabelSelectorPredicate") 35 | } 36 | return predicate, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/predicate/predicate_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 predicate_test 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | 26 | testv1 "github.com/bankdata/styra-controller/api/test/v1" 27 | "github.com/bankdata/styra-controller/internal/predicate" 28 | ) 29 | 30 | var _ = ginkgo.DescribeTable("ControllerClass", 31 | func(class string, obj client.Object, expected bool) { 32 | p, err := predicate.ControllerClass(class) 33 | gomega.Ω(err).NotTo(gomega.HaveOccurred()) 34 | gomega.Ω(p.Create(event.CreateEvent{Object: obj})).To(gomega.Equal(expected)) 35 | }, 36 | 37 | ginkgo.Entry("empty class. no label.", "", &testv1.Object{}, true), 38 | 39 | ginkgo.Entry("empty class. label is set.", "", &testv1.Object{ 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Labels: map[string]string{"styra-controller/class": "test"}, 42 | }, 43 | }, false), 44 | 45 | ginkgo.Entry("empty class. label is empty.", "", &testv1.Object{ 46 | ObjectMeta: metav1.ObjectMeta{ 47 | Labels: map[string]string{"styra-controller/class": ""}, 48 | }, 49 | }, false), 50 | 51 | ginkgo.Entry("class set. no label.", "test", &testv1.Object{}, false), 52 | 53 | ginkgo.Entry("class set. label mismatch", "test", &testv1.Object{ 54 | ObjectMeta: metav1.ObjectMeta{ 55 | Labels: map[string]string{"styra-controller/class": "tset"}, 56 | }, 57 | }, false), 58 | 59 | ginkgo.Entry("class set. label match.", "test", &testv1.Object{ 60 | ObjectMeta: metav1.ObjectMeta{ 61 | Labels: map[string]string{"styra-controller/class": "test"}, 62 | }, 63 | }, true), 64 | ) 65 | -------------------------------------------------------------------------------- /internal/predicate/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 predicate_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestPtr(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "internal/predicate") 29 | } 30 | -------------------------------------------------------------------------------- /internal/sentry/sentry.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 sentry contains a reconciler middleware which sends errors to 18 | // Sentry. 19 | package sentry 20 | 21 | import ( 22 | "context" 23 | "errors" 24 | "strings" 25 | 26 | "github.com/getsentry/sentry-go" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 29 | 30 | "github.com/bankdata/styra-controller/pkg/styra" 31 | ) 32 | 33 | type sentryReconciler struct { 34 | next reconcile.Reconciler 35 | } 36 | 37 | // Reconcile implements reconciler.Reconcile for the sentry middleware. 38 | func (r *sentryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 39 | result, err := r.next.Reconcile(ctx, req) 40 | 41 | if sentry.CurrentHub().Client() != nil { 42 | if err != nil { 43 | hub := sentry.CurrentHub().Clone() 44 | var styraerror *styra.HTTPError 45 | if errors.As(err, &styraerror) { 46 | hub.ConfigureScope(func(scope *sentry.Scope) { 47 | scope.SetContext("Styra Client", map[string]interface{}{ 48 | "body": styraerror.Body, 49 | "statuscode": styraerror.StatusCode, 50 | }) 51 | }) 52 | } 53 | 54 | hub.ConfigureScope(func(scope *sentry.Scope) { 55 | scope.SetTags(map[string]string{ 56 | "namespace": req.Namespace, 57 | "name": req.Name, 58 | }) 59 | }) 60 | if !isUserError(err.Error()) { 61 | hub.CaptureException(err) 62 | } 63 | } 64 | } 65 | return result, err 66 | } 67 | 68 | // Decorate applies the sentry middleware to the given reconcile.Reconciler. 69 | func Decorate(r reconcile.Reconciler) reconcile.Reconciler { 70 | return &sentryReconciler{next: r} 71 | } 72 | 73 | func isUserError(msg string) bool { 74 | uniqueGitConfig := "the combination of url, branch, commit-sha and path must be unique across all git repos" 75 | couldNotFindCredentialsSecret := "Could not find credentials Secret" 76 | 77 | return strings.Contains(msg, uniqueGitConfig) || 78 | strings.Contains(msg, couldNotFindCredentialsSecret) 79 | } 80 | -------------------------------------------------------------------------------- /internal/template/members.tpl: -------------------------------------------------------------------------------- 1 | {{ define "members" }} 2 | 3 | {{ range .Members }} 4 | {{ if not (hiddenMember .)}} 5 | 6 | 7 | {{ fieldName . }}
8 | 9 | {{ if linkForType .Type }} 10 | 11 | {{ typeDisplayName .Type }} 12 | 13 | {{ else }} 14 | {{ typeDisplayName .Type }} 15 | {{ end }} 16 | 17 | 18 | 19 | {{ if fieldEmbedded . }} 20 |

21 | (Members of {{ fieldName . }} are embedded into this type.) 22 |

23 | {{ end}} 24 | 25 | {{ if isOptionalMember .}} 26 | (Optional) 27 | {{ end }} 28 | 29 | {{ safe (renderComments .CommentLines) }} 30 | 31 | {{ if and (eq (.Type.Name.Name) "ObjectMeta") }} 32 | Refer to the Kubernetes API documentation for the fields of the 33 | metadata field. 34 | {{ end }} 35 | 36 | {{ if or (eq (fieldName .) "spec") }} 37 |
38 |
39 | 40 | {{ template "members" .Type }} 41 |
42 | {{ end }} 43 | 44 | 45 | {{ end }} 46 | {{ end }} 47 | 48 | {{ end }} 49 | -------------------------------------------------------------------------------- /internal/template/pkg.tpl: -------------------------------------------------------------------------------- 1 | {{ define "packages" }} 2 | 3 | {{ with .packages}} 4 |

Packages:

5 | 12 | {{ end}} 13 | 14 | {{ range .packages }} 15 |

16 | {{- packageDisplayName . -}} 17 |

18 | 19 | {{ with (index .GoPackages 0 )}} 20 | {{ with .DocComments }} 21 |
22 | {{ safe (renderComments .) }} 23 |
24 | {{ end }} 25 | {{ end }} 26 | 27 | Resource Types: 28 | 37 | 38 | {{ range (visibleTypes (sortedTypes .Types))}} 39 | {{ template "type" . }} 40 | {{ end }} 41 |
42 | {{ end }} 43 | 44 |

45 | Generated with gen-crd-api-reference-docs 46 | {{ with .gitCommit }} on git commit {{ . }}{{end}}. 47 |

48 | 49 | {{ end }} 50 | -------------------------------------------------------------------------------- /internal/template/placeholder.go: -------------------------------------------------------------------------------- 1 | // Package template is a placeholder file to make Go vendor this directory properly. 2 | package template 3 | -------------------------------------------------------------------------------- /internal/template/type.tpl: -------------------------------------------------------------------------------- 1 | {{ define "type" }} 2 | 3 |

4 | {{- .Name.Name }} 5 | {{ if eq .Kind "Alias" }}({{.Underlying}} alias){{ end -}} 6 |

7 | {{ with (typeReferences .) }} 8 |

9 | (Appears on: 10 | {{- $prev := "" -}} 11 | {{- range . -}} 12 | {{- if $prev -}}, {{ end -}} 13 | {{- $prev = . -}} 14 | {{ typeDisplayName . }} 15 | {{- end -}} 16 | ) 17 |

18 | {{ end }} 19 | 20 |
21 | {{ safe (renderComments .CommentLines) }} 22 |
23 | 24 | {{ with (constantsOfType .) }} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{- range . -}} 34 | 35 | {{- /* 36 | renderComments implicitly creates a

element, so we 37 | add one to the display name as well to make the contents 38 | of the two cells align evenly. 39 | */ -}} 40 |

41 | 42 | 43 | {{- end -}} 44 | 45 |
ValueDescription

{{ typeDisplayName . }}

{{ safe (renderComments .CommentLines) }}
46 | {{ end }} 47 | 48 | {{ if .Members }} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {{ if isExportedType . }} 58 | 59 | 62 | 67 | 68 | 69 | 73 | 74 | 75 | {{ end }} 76 | {{ template "members" .}} 77 | 78 |
FieldDescription
60 | apiVersion
61 | string
63 | 64 | {{apiGroup .}} 65 | 66 |
70 | kind
71 | string 72 |
{{.Name.Name}}
79 | {{ end }} 80 | 81 | {{ end }} 82 | -------------------------------------------------------------------------------- /internal/webhook/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | logr "github.com/go-logr/logr" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // Client is an autogenerated mock type for the Client type 13 | type Client struct { 14 | mock.Mock 15 | } 16 | 17 | // LibraryDatasourceChanged provides a mock function with given fields: _a0, _a1, _a2 18 | func (_m *Client) LibraryDatasourceChanged(_a0 context.Context, _a1 logr.Logger, _a2 string) error { 19 | ret := _m.Called(_a0, _a1, _a2) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for LibraryDatasourceChanged") 23 | } 24 | 25 | var r0 error 26 | if rf, ok := ret.Get(0).(func(context.Context, logr.Logger, string) error); ok { 27 | r0 = rf(_a0, _a1, _a2) 28 | } else { 29 | r0 = ret.Error(0) 30 | } 31 | 32 | return r0 33 | } 34 | 35 | // SystemDatasourceChanged provides a mock function with given fields: _a0, _a1, _a2, _a3 36 | func (_m *Client) SystemDatasourceChanged(_a0 context.Context, _a1 logr.Logger, _a2 string, _a3 string) error { 37 | ret := _m.Called(_a0, _a1, _a2, _a3) 38 | 39 | if len(ret) == 0 { 40 | panic("no return value specified for SystemDatasourceChanged") 41 | } 42 | 43 | var r0 error 44 | if rf, ok := ret.Get(0).(func(context.Context, logr.Logger, string, string) error); ok { 45 | r0 = rf(_a0, _a1, _a2, _a3) 46 | } else { 47 | r0 = ret.Error(0) 48 | } 49 | 50 | return r0 51 | } 52 | 53 | // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 54 | // The first argument is typically a *testing.T value. 55 | func NewClient(t interface { 56 | mock.TestingT 57 | Cleanup(func()) 58 | }) *Client { 59 | mock := &Client{} 60 | mock.Mock.Test(t) 61 | 62 | t.Cleanup(func() { mock.AssertExpectations(t) }) 63 | 64 | return mock 65 | } 66 | -------------------------------------------------------------------------------- /internal/webhook/styra/v1alpha1/library_webhook_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | 23 | styrav1alpha1 "github.com/bankdata/styra-controller/api/styra/v1alpha1" 24 | // TODO (user): Add any additional imports if needed 25 | ) 26 | 27 | var _ = ginkgo.Describe("Library Webhook", func() { 28 | var ( 29 | obj *styrav1alpha1.Library 30 | oldObj *styrav1alpha1.Library 31 | validator LibraryCustomValidator 32 | defaulter LibraryCustomDefaulter 33 | ) 34 | 35 | ginkgo.BeforeEach(func() { 36 | obj = &styrav1alpha1.Library{} 37 | oldObj = &styrav1alpha1.Library{} 38 | validator = LibraryCustomValidator{} 39 | gomega.Expect(validator).NotTo(gomega.BeNil(), "Expected validator to be initialized") 40 | defaulter = LibraryCustomDefaulter{} 41 | gomega.Expect(defaulter).NotTo(gomega.BeNil(), "Expected defaulter to be initialized") 42 | gomega.Expect(oldObj).NotTo(gomega.BeNil(), "Expected oldObj to be initialized") 43 | gomega.Expect(obj).NotTo(gomega.BeNil(), "Expected obj to be initialized") 44 | // TODO (user): Add any setup logic common to all tests 45 | }) 46 | 47 | ginkgo.AfterEach(func() { 48 | // TODO (user): Add any teardown logic common to all tests 49 | }) 50 | 51 | ginkgo.Context("When creating Library under Defaulting Webhook", func() { 52 | // TODO (user): Add logic for defaulting webhooks 53 | // Example: 54 | // It("Should apply defaults when a required field is empty", func() { 55 | // By("simulating a scenario where defaults should be applied") 56 | // obj.SomeFieldWithDefault = "" 57 | // By("calling the Default method to apply defaults") 58 | // defaulter.Default(ctx, obj) 59 | // By("checking that the default values are set") 60 | // Expect(obj.SomeFieldWithDefault).To(Equal("default_value")) 61 | // }) 62 | }) 63 | 64 | ginkgo.Context("When creating or updating Library under Validating Webhook", func() { 65 | // TODO (user): Add logic for validating webhooks 66 | // Example: 67 | // It("Should deny creation if a required field is missing", func() { 68 | // By("simulating an invalid creation scenario") 69 | // obj.SomeRequiredField = "" 70 | // Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred()) 71 | // }) 72 | // 73 | // It("Should admit creation if all required fields are present", func() { 74 | // By("simulating an invalid creation scenario") 75 | // obj.SomeRequiredField = "valid_value" 76 | // Expect(validator.ValidateCreate(ctx, obj)).To(BeNil()) 77 | // }) 78 | // 79 | // It("Should validate updates correctly", func() { 80 | // By("simulating a valid update scenario") 81 | // oldObj.SomeRequiredField = "updated_value" 82 | // obj.SomeRequiredField = "updated_value" 83 | // Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil()) 84 | // }) 85 | }) 86 | 87 | }) 88 | -------------------------------------------------------------------------------- /internal/webhook/styra/v1beta1/webhook_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 v1beta1 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "fmt" 23 | "net" 24 | "path/filepath" 25 | "testing" 26 | "time" 27 | 28 | ginkgo "github.com/onsi/ginkgo/v2" 29 | gomega "github.com/onsi/gomega" 30 | 31 | "k8s.io/apimachinery/pkg/runtime" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/envtest" 35 | logf "sigs.k8s.io/controller-runtime/pkg/log" 36 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 37 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 38 | "sigs.k8s.io/controller-runtime/pkg/webhook" 39 | 40 | //+kubebuilder:scaffold:imports 41 | "github.com/bankdata/styra-controller/api/styra/v1alpha1" 42 | "github.com/bankdata/styra-controller/api/styra/v1beta1" 43 | ) 44 | 45 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 46 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 47 | 48 | var ( 49 | k8sClient client.Client 50 | testEnv *envtest.Environment 51 | ctx context.Context 52 | cancel context.CancelFunc 53 | ) 54 | 55 | func TestAPIs(t *testing.T) { 56 | gomega.RegisterFailHandler(ginkgo.Fail) 57 | 58 | ginkgo.RunSpecs(t, "test/integration/webhook") 59 | } 60 | 61 | var _ = ginkgo.BeforeSuite(func() { 62 | logf.SetLogger(zap.New(zap.WriteTo(ginkgo.GinkgoWriter), zap.UseDevMode(true))) 63 | 64 | if !ginkgo.Label("integration").MatchesLabelFilter(ginkgo.GinkgoLabelFilter()) { 65 | return 66 | } 67 | 68 | scheme := runtime.NewScheme() 69 | err := v1alpha1.AddToScheme(scheme) 70 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 71 | err = v1beta1.AddToScheme(scheme) 72 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 73 | 74 | //+kubebuilder:scaffold:scheme 75 | 76 | ginkgo.By("bootstrapping test environment") 77 | testEnv = &envtest.Environment{ 78 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")}, 79 | ErrorIfCRDPathMissing: false, 80 | WebhookInstallOptions: envtest.WebhookInstallOptions{ 81 | Paths: []string{filepath.Join("..", "..", "..", "..", "config", "webhook")}, 82 | }, 83 | CRDInstallOptions: envtest.CRDInstallOptions{ 84 | Scheme: scheme, 85 | }, 86 | } 87 | 88 | cfg, err := testEnv.Start() 89 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 90 | gomega.Expect(cfg).NotTo(gomega.BeNil()) 91 | 92 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 93 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 94 | gomega.Expect(k8sClient).NotTo(gomega.BeNil()) 95 | 96 | // start webhook server using Manager 97 | webhookInstallOptions := &testEnv.WebhookInstallOptions 98 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 99 | Scheme: scheme, 100 | WebhookServer: webhook.NewServer(webhook.Options{ 101 | Host: webhookInstallOptions.LocalServingHost, 102 | Port: webhookInstallOptions.LocalServingPort, 103 | CertDir: webhookInstallOptions.LocalServingCertDir, 104 | }), 105 | LeaderElection: false, 106 | Metrics: metricsserver.Options{ 107 | BindAddress: "0", 108 | }, 109 | }) 110 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 111 | 112 | err = SetupSystemWebhookWithManager(mgr) 113 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 114 | 115 | //+kubebuilder:scaffold:webhook 116 | 117 | ctx, cancel = context.WithCancel(context.Background()) 118 | 119 | go func() { 120 | defer ginkgo.GinkgoRecover() 121 | err = mgr.Start(ctx) 122 | if err != nil { 123 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 124 | } 125 | }() 126 | 127 | // wait for the webhook server to get ready 128 | dialer := &net.Dialer{Timeout: time.Second} 129 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) 130 | gomega.Eventually(func() error { 131 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) 132 | if err != nil { 133 | return err 134 | } 135 | err = conn.Close() 136 | if err != nil { 137 | return err 138 | } 139 | return nil 140 | }).Should(gomega.Succeed()) 141 | 142 | }) 143 | 144 | var _ = ginkgo.AfterSuite(func() { 145 | if testing.Short() { 146 | return 147 | } 148 | 149 | if cancel != nil { 150 | cancel() 151 | } 152 | 153 | ginkgo.By("tearing down the test environment") 154 | if testEnv != nil { 155 | err := testEnv.Stop() 156 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 157 | } 158 | }) 159 | -------------------------------------------------------------------------------- /internal/webhook/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 webhook 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestWebhookClient(t *testing.T) { 27 | 28 | gomega.RegisterFailHandler(ginkgo.Fail) 29 | ginkgo.RunSpecs(t, "internal/webhook") 30 | } 31 | -------------------------------------------------------------------------------- /internal/webhook/webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 webhook contains helpers for the notifaction webhooks of the 18 | // controller. These webhooks can be used to notify other systems when 19 | // something happens in the controller. 20 | package webhook 21 | 22 | import ( 23 | "bytes" 24 | "context" 25 | "encoding/json" 26 | "io" 27 | "net/http" 28 | 29 | "github.com/go-logr/logr" 30 | "github.com/pkg/errors" 31 | ) 32 | 33 | // Client defines the interface for the notification webhook client. 34 | type Client interface { 35 | SystemDatasourceChanged(context.Context, logr.Logger, string, string) error 36 | LibraryDatasourceChanged(context.Context, logr.Logger, string) error 37 | } 38 | 39 | type client struct { 40 | hc http.Client 41 | libraryDatasourceChanged string 42 | systemDatasourceChanged string 43 | } 44 | 45 | // New creates a new webhook notification Client. 46 | func New(systemDatasourceChanged string, libraryDatasourceChanged string) Client { 47 | return &client{ 48 | hc: http.Client{}, 49 | systemDatasourceChanged: systemDatasourceChanged, 50 | libraryDatasourceChanged: libraryDatasourceChanged, 51 | } 52 | } 53 | 54 | func (client *client) LibraryDatasourceChanged(ctx context.Context, log logr.Logger, datasourceID string) error { 55 | if client.libraryDatasourceChanged == "" { 56 | return errors.New("libraryDatasourceChanged webhook not configured") 57 | } 58 | 59 | body := map[string]string{"datasourceID": datasourceID} 60 | jsonData, err := json.Marshal(body) 61 | 62 | if err != nil { 63 | log.Error(err, "Failed to marshal request body") 64 | return errors.Wrap(err, "Failed to marshal request body") 65 | } 66 | 67 | r, err := http.NewRequestWithContext(ctx, http.MethodPost, client.libraryDatasourceChanged, bytes.NewBuffer(jsonData)) 68 | if err != nil { 69 | log.Error(err, "Failed to create request to webhook") 70 | return errors.Wrap(err, "Failed to create request to webhook") 71 | } 72 | r.Header.Set("Content-Type", "application/json") 73 | 74 | resp, err := client.hc.Do(r) 75 | if err != nil { 76 | log.Error(err, "Failed in call to webhook") 77 | return errors.Wrap(err, "Failed in call to webhook") 78 | } 79 | 80 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 81 | log.Info("Response status code is not 2XX") 82 | bodyBytes, err := io.ReadAll(resp.Body) 83 | if err != nil { 84 | log.Error(err, "Could not read response body") 85 | return errors.Errorf("Could not read response body") 86 | } 87 | bodyString := string(bodyBytes) 88 | return errors.Errorf("response status code is %d, response body is %s", resp.StatusCode, bodyString) 89 | } 90 | 91 | log.Info("Called library webhook successfully") 92 | return nil 93 | } 94 | 95 | // DatasourceChanged notifies the webhook that a datasource has changed. 96 | func (client *client) SystemDatasourceChanged( 97 | ctx context.Context, 98 | log logr.Logger, 99 | systemID string, 100 | dsID string) error { 101 | if client.systemDatasourceChanged == "" { 102 | return errors.New("systemDatasourceChanged webhook not configured") 103 | } 104 | 105 | body := map[string]string{"systemId": systemID, "datasourceId": dsID} 106 | jsonData, err := json.Marshal(body) 107 | 108 | if err != nil { 109 | log.Error(err, "Failed to marshal request body") 110 | return errors.Wrap(err, "Failed to marshal request body") 111 | } 112 | 113 | r, err := http.NewRequestWithContext(ctx, http.MethodPost, client.systemDatasourceChanged, bytes.NewBuffer(jsonData)) 114 | if err != nil { 115 | log.Error(err, "Failed to create request to webhook") 116 | return errors.Wrap(err, "Failed to create request to webhook") 117 | } 118 | 119 | r.Header.Set("Content-Type", "application/json") 120 | 121 | resp, err := client.hc.Do(r) 122 | 123 | if err != nil { 124 | log.Error(err, "Failed in call to webhook") 125 | return errors.Wrap(err, "Failed in call to webhook") 126 | } 127 | 128 | if resp.StatusCode < 200 || resp.StatusCode > 299 { 129 | log.Info("Response status code is not 2XX") 130 | bodyBytes, err := io.ReadAll(resp.Body) 131 | if err != nil { 132 | log.Error(err, "Could not read response body") 133 | return errors.Errorf("Could not read response body") 134 | } 135 | bodyString := string(bodyBytes) 136 | return errors.Errorf("response status code is %d, response body is %s", resp.StatusCode, bodyString) 137 | } 138 | 139 | log.Info("Called system webhook successfully") 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /pkg/ptr/ptr.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 ptr contains helpers for creating pointers to built-in types. 18 | package ptr 19 | 20 | // Bool creates a pointer to a bool. 21 | func Bool(b bool) *bool { 22 | return &b 23 | } 24 | 25 | // String creates a pointer to a string. 26 | func String(s string) *string { 27 | return &s 28 | } 29 | 30 | // Int creates a pointer to an int. 31 | func Int(i int) *int { 32 | return &i 33 | } 34 | -------------------------------------------------------------------------------- /pkg/ptr/ptr_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 ptr_test 18 | 19 | import ( 20 | ginkgo "github.com/onsi/ginkgo/v2" 21 | gomega "github.com/onsi/gomega" 22 | 23 | "github.com/bankdata/styra-controller/pkg/ptr" 24 | ) 25 | 26 | var _ = ginkgo.Describe("Bool", func() { 27 | ginkgo.It("should return a pointer to the boolean", func() { 28 | gomega.Expect(*ptr.Bool(true)).To(gomega.BeTrue()) 29 | gomega.Expect(*ptr.Bool(false)).To(gomega.BeFalse()) 30 | }) 31 | }) 32 | 33 | var _ = ginkgo.Describe("String", func() { 34 | ginkgo.It("should return a pointer to the string", func() { 35 | gomega.Expect(*ptr.String("")).To(gomega.Equal("")) 36 | gomega.Expect(*ptr.String("test")).To(gomega.Equal("test")) 37 | }) 38 | }) 39 | 40 | var _ = ginkgo.Describe("Int", func() { 41 | ginkgo.It("should return a pointer to the int", func() { 42 | gomega.Expect(*ptr.Int(0)).To(gomega.Equal(0)) 43 | gomega.Expect(*ptr.Int(42)).To(gomega.Equal(42)) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /pkg/ptr/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 ptr_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestPtr(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "pkg/ptr") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/styra/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "encoding/json" 23 | "fmt" 24 | "net/http" 25 | "time" 26 | 27 | "github.com/patrickmn/go-cache" 28 | "github.com/pkg/errors" 29 | ) 30 | 31 | // ClientInterface defines the interface for the Styra client. 32 | type ClientInterface interface { 33 | GetSystem(ctx context.Context, id string) (*GetSystemResponse, error) 34 | GetSystemByName(ctx context.Context, name string) (*GetSystemResponse, error) 35 | 36 | CreateUpdateSecret( 37 | ctx context.Context, 38 | secretID string, 39 | request *CreateUpdateSecretsRequest, 40 | ) (*CreateUpdateSecretResponse, error) 41 | DeleteSecret( 42 | ctx context.Context, 43 | secretID string, 44 | ) (*DeleteSecretResponse, error) 45 | 46 | GetUser(ctx context.Context, name string) (*GetUserResponse, error) 47 | GetUsers(ctx context.Context) (*GetUsersResponse, bool, error) 48 | InvalidateCache() 49 | 50 | CreateInvitation(ctx context.Context, email bool, name string) (*CreateInvitationResponse, error) 51 | 52 | ListRoleBindingsV2(ctx context.Context, params *ListRoleBindingsV2Params) (*ListRoleBindingsV2Response, error) 53 | 54 | CreateRoleBinding(ctx context.Context, request *CreateRoleBindingRequest) (*CreateRoleBindingResponse, error) 55 | 56 | UpdateRoleBindingSubjects( 57 | ctx context.Context, 58 | id string, 59 | request *UpdateRoleBindingSubjectsRequest, 60 | ) (*UpdateRoleBindingSubjectsResponse, error) 61 | 62 | DeleteRoleBindingV2(ctx context.Context, id string) (*DeleteRoleBindingV2Response, error) 63 | 64 | GetDatasource(ctx context.Context, id string) (*GetDatasourceResponse, error) 65 | 66 | UpsertDatasource( 67 | ctx context.Context, 68 | id string, 69 | request *UpsertDatasourceRequest, 70 | ) (*UpsertDatasourceResponse, error) 71 | 72 | DeleteDatasource(ctx context.Context, id string) (*DeleteDatasourceResponse, error) 73 | 74 | GetLibrary(ctx context.Context, id string) (*GetLibraryResponse, error) 75 | UpsertLibrary(ctx context.Context, id string, request *UpsertLibraryRequest) (*UpsertLibraryResponse, error) 76 | 77 | UpdateSystem(ctx context.Context, id string, request *UpdateSystemRequest) (*UpdateSystemResponse, error) 78 | 79 | DeleteSystem(ctx context.Context, id string) (*DeleteSystemResponse, error) 80 | 81 | CreateSystem(ctx context.Context, request *CreateSystemRequest) (*CreateSystemResponse, error) 82 | 83 | PutSystem(context.Context, *PutSystemRequest, string, map[string]string) (*PutSystemResponse, error) 84 | 85 | GetOPAConfig(ctx context.Context, systemID string) (OPAConfig, error) 86 | 87 | VerifyGitConfiguration(ctx context.Context, request *VerfiyGitConfigRequest) (*VerfiyGitConfigResponse, error) 88 | 89 | DeletePolicy(ctx context.Context, policyName string) (*DeletePolicyResponse, error) 90 | 91 | UpdateWorkspace(ctx context.Context, request *UpdateWorkspaceRequest) (*UpdateWorkspaceResponse, error) 92 | UpdateWorkspaceRaw(ctx context.Context, request interface{}) (*UpdateWorkspaceResponse, error) 93 | } 94 | 95 | // Client is a client for the Styra APIs. 96 | type Client struct { 97 | HTTPClient http.Client 98 | URL string 99 | token string 100 | Cache *cache.Cache 101 | } 102 | 103 | // New creates a new Styra ClientInterface. 104 | func New(url string, token string) ClientInterface { 105 | c := cache.New(1*time.Hour, 10*time.Minute) 106 | 107 | return &Client{ 108 | URL: url, 109 | HTTPClient: http.Client{}, 110 | token: token, 111 | Cache: c, 112 | } 113 | } 114 | 115 | // InvalidateCache invalidates the entire cache 116 | func (c *Client) InvalidateCache() { 117 | c.Cache.Flush() 118 | } 119 | 120 | func (c *Client) newRequest( 121 | ctx context.Context, 122 | method string, 123 | endpoint string, 124 | body interface{}, 125 | headers map[string]string, 126 | ) (*http.Request, error) { 127 | u := fmt.Sprintf("%s%s", c.URL, endpoint) 128 | 129 | var b bytes.Buffer 130 | if body != nil { 131 | if err := json.NewEncoder(&b).Encode(body); err != nil { 132 | return nil, errors.Wrap(err, "could not encode body") 133 | } 134 | } 135 | 136 | r, err := http.NewRequestWithContext(ctx, method, u, &b) 137 | if err != nil { 138 | return nil, errors.Wrap(err, "could not create request") 139 | } 140 | 141 | r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) 142 | r.Header.Set("Content-Type", "application/json") 143 | 144 | for k, v := range headers { 145 | r.Header.Set(k, v) 146 | } 147 | 148 | return r, nil 149 | } 150 | 151 | func (c *Client) request( 152 | ctx context.Context, 153 | method string, 154 | endpoint string, 155 | body interface{}, 156 | headers map[string]string, 157 | ) (*http.Response, error) { 158 | req, err := c.newRequest(ctx, method, endpoint, body, headers) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | res, err := c.HTTPClient.Do(req) 164 | if err != nil { 165 | return nil, errors.Wrap(err, "could not send request") 166 | } 167 | 168 | return res, nil 169 | } 170 | -------------------------------------------------------------------------------- /pkg/styra/client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "net/http" 21 | 22 | "github.com/bankdata/styra-controller/pkg/styra" 23 | "github.com/patrickmn/go-cache" 24 | ) 25 | 26 | type roundTripFunc func(req *http.Request) *http.Response 27 | 28 | func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 29 | return f(req), nil 30 | } 31 | 32 | func newTestClient(f roundTripFunc) styra.ClientInterface { 33 | return &styra.Client{ 34 | URL: "http://test.com", 35 | HTTPClient: http.Client{ 36 | Transport: roundTripFunc(f), 37 | }, 38 | } 39 | } 40 | 41 | func newTestClientWithCache(f roundTripFunc, cache *cache.Cache) styra.ClientInterface { 42 | return &styra.Client{ 43 | URL: "http://test.com", 44 | HTTPClient: http.Client{ 45 | Transport: roundTripFunc(f), 46 | }, 47 | Cache: cache, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/styra/http_error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | // HTTPError represents an error that occurred when interacting with the Styra 27 | // API. 28 | type HTTPError struct { 29 | StatusCode int 30 | Body string 31 | } 32 | 33 | // Error implements the error interface. 34 | func (styraerror *HTTPError) Error() string { 35 | return fmt.Sprintf("styra: unexpected statuscode: %d, body: %s", styraerror.StatusCode, styraerror.Body) 36 | } 37 | 38 | // NewHTTPError creates a new HTTPError based on the statuscode and body from a 39 | // failed call to the Styra API. 40 | func NewHTTPError(statuscode int, body string) error { 41 | styraerror := &HTTPError{ 42 | StatusCode: statuscode, 43 | } 44 | 45 | if isValidJSON(body) { 46 | styraerror.Body = body 47 | } else { 48 | styraerror.Body = "invalid JSON response" 49 | } 50 | 51 | return errors.WithStack(styraerror) 52 | } 53 | 54 | func isValidJSON(data string) bool { 55 | var out interface{} 56 | return json.Unmarshal([]byte(data), &out) == nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/styra/invitations.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "strconv" 25 | 26 | "github.com/pkg/errors" 27 | ) 28 | 29 | const ( 30 | endpointV1Invitations = "/v1/invitations" 31 | ) 32 | 33 | // CreateInvitationResponse is the response type for calls to the 34 | // POST /v1/invitations endpoint in the Styra API. 35 | type CreateInvitationResponse struct { 36 | StatusCode int 37 | Body []byte 38 | } 39 | 40 | // CreateInvitationRequest is the request body for the 41 | // POST /v1/invitations endpoint in the Styra API. 42 | type CreateInvitationRequest struct { 43 | UserID string `json:"user_id"` 44 | } 45 | 46 | // CreateInvitation calls the POST /v1/invitations endpoint in the Styra API. 47 | func (c *Client) CreateInvitation(ctx context.Context, email bool, name string) (*CreateInvitationResponse, error) { 48 | createInvitationData := CreateInvitationRequest{ 49 | UserID: name, 50 | } 51 | 52 | res, err := c.request( 53 | ctx, 54 | http.MethodPost, 55 | fmt.Sprintf("%s?email=%s", endpointV1Invitations, strconv.FormatBool(email)), 56 | createInvitationData, 57 | nil, 58 | ) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | body, err := io.ReadAll(res.Body) 64 | if err != nil { 65 | return nil, errors.Wrap(err, "could not read body") 66 | } 67 | 68 | if res.StatusCode != http.StatusOK { 69 | err := NewHTTPError(res.StatusCode, string(body)) 70 | return nil, err 71 | } 72 | 73 | r := CreateInvitationResponse{ 74 | StatusCode: res.StatusCode, 75 | Body: body, 76 | } 77 | 78 | return &r, nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/styra/invitations_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "encoding/json" 23 | "errors" 24 | "io" 25 | "net/http" 26 | "strconv" 27 | 28 | ginkgo "github.com/onsi/ginkgo/v2" 29 | gomega "github.com/onsi/gomega" 30 | 31 | "github.com/bankdata/styra-controller/pkg/styra" 32 | ) 33 | 34 | var _ = ginkgo.Describe("CreateInvitation", func() { 35 | 36 | type test struct { 37 | email bool 38 | name string 39 | responseCode int 40 | responseBody string 41 | createInvitationRequest *styra.CreateInvitationRequest 42 | expectStyraErr bool 43 | } 44 | 45 | ginkgo.DescribeTable("CreateInvitation", func(test test) { 46 | c := newTestClient(func(r *http.Request) *http.Response { 47 | bs, err := io.ReadAll(r.Body) 48 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 49 | var b bytes.Buffer 50 | gomega.Expect(json.NewEncoder(&b).Encode(test.createInvitationRequest)).To(gomega.Succeed()) 51 | gomega.Expect(bs).To(gomega.Equal(b.Bytes())) 52 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPost)) 53 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/invitations?email=" + 54 | strconv.FormatBool(test.email))) 55 | 56 | return &http.Response{ 57 | Header: make(http.Header), 58 | StatusCode: test.responseCode, 59 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 60 | } 61 | }) 62 | 63 | res, err := c.CreateInvitation(context.Background(), test.email, test.name) 64 | if test.expectStyraErr { 65 | gomega.Expect(res).To(gomega.BeNil()) 66 | target := &styra.HTTPError{} 67 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 68 | } else { 69 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 70 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode)) 71 | } 72 | }, 73 | 74 | ginkgo.Entry("something", test{ 75 | name: "name", 76 | responseCode: http.StatusOK, 77 | responseBody: `{ 78 | "request_id": "id", 79 | "result": { 80 | "url": "url" 81 | } 82 | }`, 83 | createInvitationRequest: &styra.CreateInvitationRequest{ 84 | UserID: "name", 85 | }, 86 | }), 87 | 88 | ginkgo.Entry("styra http error", test{ 89 | name: "name", 90 | createInvitationRequest: &styra.CreateInvitationRequest{ 91 | UserID: "name", 92 | }, 93 | responseCode: http.StatusInternalServerError, 94 | expectStyraErr: true, 95 | }), 96 | ) 97 | }) 98 | -------------------------------------------------------------------------------- /pkg/styra/library.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | 26 | "github.com/pkg/errors" 27 | ) 28 | 29 | const ( 30 | endpointV1Libraries = "/v1/libraries" 31 | ) 32 | 33 | type getLibraryJSONResponse struct { 34 | Result *LibraryEntityExpanded `json:"result"` 35 | } 36 | 37 | // GetLibraryResponse is the response type for calls to the 38 | // GET /v1/libraries/{id} endpoint in the Styra API. 39 | type GetLibraryResponse struct { 40 | Statuscode int 41 | Body []byte 42 | LibraryEntityExpanded *LibraryEntityExpanded 43 | } 44 | 45 | // LibraryEntityExpanded is the type that defines of a Library 46 | type LibraryEntityExpanded struct { 47 | DataSources []LibraryDatasourceConfig `json:"datasources"` 48 | Description string `json:"description"` 49 | ID string `json:"id"` 50 | ReadOnly bool `json:"read_only"` 51 | SourceControl *LibrarySourceControlConfig `json:"source_control"` 52 | } 53 | 54 | // LibraryDatasourceConfig defines metadata of a datasource 55 | type LibraryDatasourceConfig struct { 56 | Category string `json:"category"` 57 | ID string `json:"id"` 58 | } 59 | 60 | // LibrarySourceControlConfig is a struct from styra where we only use a single field 61 | // but kept for clarity when comparing to the API 62 | type LibrarySourceControlConfig struct { 63 | LibraryOrigin *LibraryGitRepoConfig `json:"library_origin"` 64 | } 65 | 66 | // LibraryGitRepoConfig defines the Git configurations a library can be defined by 67 | type LibraryGitRepoConfig struct { 68 | Commit string `json:"commit"` 69 | Credentials string `json:"credentials"` 70 | Path string `json:"path"` 71 | Reference string `json:"reference"` 72 | URL string `json:"url"` 73 | } 74 | 75 | // UpsertLibraryRequest is the request body for the 76 | // PUT /v1/libraries/{id} endpoint in the Styra API. 77 | type UpsertLibraryRequest struct { 78 | Description string `json:"description"` 79 | ReadOnly bool `json:"read_only"` 80 | SourceControl *LibrarySourceControlConfig `json:"source_control"` 81 | } 82 | 83 | // UpsertLibraryResponse is the response body for the 84 | // PUT /v1/libraries/{id} endpoint in the Styra API. 85 | type UpsertLibraryResponse struct { 86 | StatusCode int 87 | Body []byte 88 | } 89 | 90 | // GetLibrary calls the GET /v1/libraries/{id} endpoint in the 91 | // Styra API. 92 | func (c *Client) GetLibrary(ctx context.Context, id string) (*GetLibraryResponse, error) { 93 | res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("%s/%s", endpointV1Libraries, id), nil, nil) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | body, err := io.ReadAll(res.Body) 99 | if err != nil { 100 | return nil, errors.Wrap(err, "could not read body") 101 | } 102 | 103 | if res.StatusCode != http.StatusOK { 104 | err := NewHTTPError(res.StatusCode, string(body)) 105 | return nil, err 106 | } 107 | 108 | var jsonRes getLibraryJSONResponse 109 | if err := json.Unmarshal(body, &jsonRes); err != nil { 110 | return nil, errors.Wrap(err, "could not unmarshal body") 111 | } 112 | 113 | return &GetLibraryResponse{ 114 | Statuscode: res.StatusCode, 115 | Body: body, 116 | LibraryEntityExpanded: jsonRes.Result, 117 | }, nil 118 | } 119 | 120 | // UpsertLibrary calls the PUT /v1/libraries/{id} endpoint in the 121 | // Styra API. 122 | func (c *Client) UpsertLibrary(ctx context.Context, id string, request *UpsertLibraryRequest, 123 | ) (*UpsertLibraryResponse, error) { 124 | res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("%s/%s", endpointV1Libraries, id), request, nil) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | body, err := io.ReadAll(res.Body) 130 | if err != nil { 131 | return nil, errors.Wrap(err, "could not read body") 132 | } 133 | 134 | if res.StatusCode != http.StatusOK { 135 | err := NewHTTPError(res.StatusCode, string(body)) 136 | return nil, err 137 | } 138 | 139 | resp := UpsertLibraryResponse{ 140 | StatusCode: res.StatusCode, 141 | Body: body, 142 | } 143 | 144 | return &resp, nil 145 | } 146 | -------------------------------------------------------------------------------- /pkg/styra/opaconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | 25 | "github.com/pkg/errors" 26 | "gopkg.in/yaml.v2" 27 | ) 28 | 29 | // OPAConfig stores the information retrieved from calling the GET 30 | // /v1/systems/{systemId}/assets/opa-config endpoint in the Styra API. 31 | type OPAConfig struct { 32 | HostURL string 33 | Token string 34 | SystemID string 35 | SystemType string 36 | } 37 | 38 | type getOPAConfigResponse struct { 39 | Discovery getOPAConfigDiscovery `yaml:"discovery"` 40 | Labels getOPAConfigLabels `yaml:"labels"` 41 | Services []getOPAConfigService `yaml:"services"` 42 | } 43 | 44 | type getOPAConfigDiscovery struct { 45 | Name string `yaml:"name"` 46 | Prefix string `yaml:"prefix"` 47 | Service string `yaml:"service"` 48 | } 49 | 50 | type getOPAConfigLabels struct { 51 | SystemID string `yaml:"system-id"` 52 | SystemType string `yaml:"system-type"` 53 | } 54 | 55 | type getOPAConfigService struct { 56 | Credentials getOPAConfigServiceCredentials `yaml:"credentials"` 57 | URL string `yaml:"url"` 58 | } 59 | 60 | type getOPAConfigServiceCredentials struct { 61 | Bearer getOPAConfigServiceBearerCredentials `yaml:"bearer"` 62 | } 63 | 64 | type getOPAConfigServiceBearerCredentials struct { 65 | Token string `yaml:"token"` 66 | } 67 | 68 | // GetOPAConfig calls the GET /v1/systems/{systemId}/assets/opa-config endpoint 69 | // in the Styra API. 70 | func (c *Client) GetOPAConfig(ctx context.Context, systemID string) (OPAConfig, error) { 71 | res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/v1/systems/%s/assets/opa-config", systemID), nil, nil) 72 | if err != nil { 73 | return OPAConfig{}, errors.Wrap(err, "could not get opaconf file") 74 | } 75 | 76 | if res.StatusCode != http.StatusOK { 77 | body, err := io.ReadAll(res.Body) 78 | if err != nil { 79 | return OPAConfig{}, errors.Wrap(err, "could not read body") 80 | } 81 | 82 | err = NewHTTPError(res.StatusCode, string(body)) 83 | return OPAConfig{}, err 84 | } 85 | 86 | var getOPAConfigResponse getOPAConfigResponse 87 | if err := yaml.NewDecoder(res.Body).Decode(&getOPAConfigResponse); err != nil { 88 | return OPAConfig{}, errors.Wrap(err, "could not decode opa-config asset response") 89 | } 90 | 91 | if getOPAConfigResponse.Services == nil { 92 | return OPAConfig{}, errors.Errorf("No services in opa config") 93 | } 94 | 95 | opaConfig := OPAConfig{ 96 | HostURL: getOPAConfigResponse.Services[0].URL, 97 | Token: getOPAConfigResponse.Services[0].Credentials.Bearer.Token, 98 | SystemID: getOPAConfigResponse.Labels.SystemID, 99 | SystemType: getOPAConfigResponse.Labels.SystemType, 100 | } 101 | 102 | return opaConfig, nil 103 | } 104 | -------------------------------------------------------------------------------- /pkg/styra/opaconfig_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "errors" 23 | "io" 24 | "net/http" 25 | 26 | ginkgo "github.com/onsi/ginkgo/v2" 27 | gomega "github.com/onsi/gomega" 28 | 29 | "github.com/bankdata/styra-controller/pkg/styra" 30 | ) 31 | 32 | var _ = ginkgo.Describe("GetOPAConfig", func() { 33 | 34 | type test struct { 35 | responseBody string 36 | responseCode int 37 | expectedOPAConf styra.OPAConfig 38 | expectStyraErr bool 39 | } 40 | 41 | ginkgo.DescribeTable("GetOPAConfig", func(test test) { 42 | c := newTestClient(func(r *http.Request) *http.Response { 43 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/systems/test_id/assets/opa-config")) 44 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet)) 45 | return &http.Response{ 46 | Header: make(http.Header), 47 | StatusCode: test.responseCode, 48 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 49 | } 50 | }) 51 | 52 | opaconf, err := c.GetOPAConfig(context.Background(), "test_id") 53 | if test.expectStyraErr { 54 | gomega.Expect(opaconf).To(gomega.Equal(styra.OPAConfig{})) 55 | target := &styra.HTTPError{} 56 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 57 | } else { 58 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 59 | gomega.Expect(opaconf).To(gomega.Equal(test.expectedOPAConf)) 60 | } 61 | }, 62 | 63 | ginkgo.Entry("success", test{ 64 | responseBody: ` 65 | discovery: 66 | name: discovery-123 67 | prefix: prefix-123 68 | service: service-123 69 | labels: 70 | system-id: system-123 71 | system-type: custom-123 72 | services: 73 | - credentials: 74 | bearer: 75 | token: opa-token-123 76 | url: styra-url-123 77 | - credentials: 78 | bearer: 79 | token: opa-token-1234 80 | url: styra-url-1234`, 81 | expectedOPAConf: styra.OPAConfig{ 82 | HostURL: "styra-url-123", 83 | Token: "opa-token-123", 84 | SystemID: "system-123", 85 | SystemType: "custom-123", 86 | }, 87 | responseCode: http.StatusOK, 88 | }), 89 | ginkgo.Entry("styra http error", test{ 90 | responseCode: http.StatusInternalServerError, 91 | expectStyraErr: true, 92 | }), 93 | ) 94 | }) 95 | -------------------------------------------------------------------------------- /pkg/styra/policies.go: -------------------------------------------------------------------------------- 1 | package styra 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // DeletePolicyResponse is the response type for calls to 13 | // the DELETE /v1/policies/{policy} endpoint in the Styra API. 14 | type DeletePolicyResponse struct { 15 | StatusCode int 16 | Body []byte 17 | } 18 | 19 | // DeletePolicy calls the DELETE /v1/policies/{policy} endpoint in the Styra API. 20 | func (c *Client) DeletePolicy(ctx context.Context, policyName string) (*DeletePolicyResponse, error) { 21 | res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/v1/policies/%s", policyName), nil, nil) 22 | if err != nil { 23 | return nil, errors.Wrap(err, fmt.Sprintf("could not delete policy: %s", policyName)) 24 | } 25 | 26 | body, err := io.ReadAll(res.Body) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "failed to read response body") 29 | } 30 | 31 | if res.StatusCode != http.StatusNotFound && res.StatusCode != http.StatusOK { 32 | err := NewHTTPError(res.StatusCode, string(body)) 33 | return nil, err 34 | } 35 | 36 | r := DeletePolicyResponse{ 37 | StatusCode: res.StatusCode, 38 | Body: body, 39 | } 40 | 41 | return &r, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/styra/policies_test.go: -------------------------------------------------------------------------------- 1 | package styra_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net/http" 9 | 10 | ginkgo "github.com/onsi/ginkgo/v2" 11 | gomega "github.com/onsi/gomega" 12 | 13 | "github.com/bankdata/styra-controller/pkg/styra" 14 | ) 15 | 16 | var _ = ginkgo.Describe("DeletePolicy", func() { 17 | 18 | type test struct { 19 | policyName string 20 | responseCode int 21 | responseBody string 22 | expectedBody []byte 23 | expectStyraErr bool 24 | } 25 | 26 | ginkgo.DescribeTable("DeletePolicy", func(test test) { 27 | c := newTestClient(func(r *http.Request) *http.Response { 28 | bs, err := io.ReadAll(r.Body) 29 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 30 | gomega.Expect(bs).To(gomega.Equal([]byte(""))) 31 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodDelete)) 32 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/policies/" + test.policyName)) 33 | 34 | return &http.Response{ 35 | Header: make(http.Header), 36 | StatusCode: test.responseCode, 37 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 38 | } 39 | }) 40 | 41 | res, err := c.DeletePolicy(context.Background(), test.policyName) 42 | if test.expectStyraErr { 43 | gomega.Expect(res).To(gomega.BeNil()) 44 | target := &styra.HTTPError{} 45 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 46 | } else { 47 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 48 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode)) 49 | gomega.Expect(res.Body).To(gomega.Equal(test.expectedBody)) 50 | } 51 | }, 52 | 53 | ginkgo.Entry("something", test{ 54 | policyName: "policyname", 55 | responseCode: http.StatusOK, 56 | responseBody: `expected response from styra api`, 57 | expectedBody: []byte(`expected response from styra api`)}, 58 | ), 59 | 60 | ginkgo.Entry("styra http error", test{ 61 | policyName: "policyname", 62 | responseCode: http.StatusInternalServerError, 63 | expectStyraErr: true, 64 | }), 65 | ) 66 | }) 67 | -------------------------------------------------------------------------------- /pkg/styra/secrets.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | 25 | "github.com/pkg/errors" 26 | ) 27 | 28 | const ( 29 | endpointV1Secrets = "/v1/secrets" 30 | ) 31 | 32 | // DeleteSecretResponse is the response type for calls to the 33 | // DELETE /v1/secrets/{secretId} endpoint in the Styra API. 34 | type DeleteSecretResponse struct { 35 | StatusCode int 36 | Body []byte 37 | } 38 | 39 | // CreateUpdateSecretResponse is the response type for calls to the 40 | // PUT /v1/secrets/{secretId} endpoint in the Styra API. 41 | type CreateUpdateSecretResponse struct { 42 | StatusCode int 43 | Body []byte 44 | } 45 | 46 | // CreateUpdateSecretsRequest is the response body for the 47 | // PUT /v1/secrets/{secretId} endpoint in the Styra API. 48 | type CreateUpdateSecretsRequest struct { 49 | Description string `json:"description"` 50 | Name string `json:"name"` 51 | Secret string `json:"secret"` 52 | } 53 | 54 | // CreateUpdateSecret calls the PUT /v1/secrets/{secretId} endpoint in the 55 | // Styra API. 56 | func (c *Client) CreateUpdateSecret( 57 | ctx context.Context, 58 | secretID string, 59 | createUpdateSecretsRequest *CreateUpdateSecretsRequest, 60 | ) (*CreateUpdateSecretResponse, error) { 61 | res, err := c.request( 62 | ctx, 63 | http.MethodPut, 64 | fmt.Sprintf("%s/%s", endpointV1Secrets, secretID), 65 | createUpdateSecretsRequest, 66 | nil, 67 | ) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | body, err := io.ReadAll(res.Body) 73 | if err != nil { 74 | return nil, errors.Wrap(err, "could not read body") 75 | } 76 | 77 | if res.StatusCode != http.StatusOK { 78 | err := NewHTTPError(res.StatusCode, string(body)) 79 | return nil, err 80 | } 81 | 82 | r := CreateUpdateSecretResponse{ 83 | StatusCode: res.StatusCode, 84 | Body: body, 85 | } 86 | 87 | return &r, nil 88 | } 89 | 90 | // DeleteSecret calls the DELETE /v1/secrets/{secretId} endpoint in the 91 | // Styra API. 92 | func (c *Client) DeleteSecret( 93 | ctx context.Context, 94 | secretID string, 95 | ) (*DeleteSecretResponse, error) { 96 | res, err := c.request( 97 | ctx, 98 | http.MethodDelete, 99 | fmt.Sprintf("%s/%s", endpointV1Secrets, secretID), 100 | nil, 101 | nil, 102 | ) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | body, err := io.ReadAll(res.Body) 108 | if err != nil { 109 | return nil, errors.Wrap(err, "could not read body") 110 | } 111 | 112 | if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound { 113 | err := NewHTTPError(res.StatusCode, string(body)) 114 | return nil, err 115 | } 116 | 117 | r := DeleteSecretResponse{ 118 | StatusCode: res.StatusCode, 119 | Body: body, 120 | } 121 | 122 | return &r, nil 123 | } 124 | -------------------------------------------------------------------------------- /pkg/styra/secrets_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "encoding/json" 23 | "errors" 24 | "io" 25 | "net/http" 26 | 27 | ginkgo "github.com/onsi/ginkgo/v2" 28 | gomega "github.com/onsi/gomega" 29 | 30 | "github.com/bankdata/styra-controller/pkg/styra" 31 | ) 32 | 33 | var _ = ginkgo.Describe("CreateUpdateSecret", func() { 34 | type test struct { 35 | secretID string 36 | responseCode int 37 | responseBody string 38 | createUpdateSecretsRequest *styra.CreateUpdateSecretsRequest 39 | expectStyraErr bool 40 | } 41 | 42 | ginkgo.DescribeTable("CreateUpdateSecret", func(test test) { 43 | c := newTestClient(func(r *http.Request) *http.Response { 44 | bs, err := io.ReadAll(r.Body) 45 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 46 | var b bytes.Buffer 47 | gomega.Expect(json.NewEncoder(&b).Encode(test.createUpdateSecretsRequest)).To(gomega.Succeed()) 48 | gomega.Expect(bs).To(gomega.Equal((b.Bytes()))) 49 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPut)) 50 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/secrets/" + test.secretID)) 51 | 52 | return &http.Response{ 53 | Header: make(http.Header), 54 | StatusCode: test.responseCode, 55 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 56 | } 57 | }) 58 | 59 | res, err := c.CreateUpdateSecret(context.Background(), test.secretID, test.createUpdateSecretsRequest) 60 | if test.expectStyraErr { 61 | gomega.Expect(res).To(gomega.BeNil()) 62 | target := &styra.HTTPError{} 63 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 64 | } else { 65 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 66 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode)) 67 | } 68 | 69 | }, 70 | ginkgo.Entry("something", test{ 71 | secretID: "name", 72 | responseCode: http.StatusOK, 73 | responseBody: `{"test"}`, 74 | createUpdateSecretsRequest: &styra.CreateUpdateSecretsRequest{ 75 | Description: "description", 76 | Name: "name", 77 | Secret: "secret", 78 | }, 79 | }), 80 | 81 | ginkgo.Entry("styra http error", test{ 82 | responseCode: http.StatusInternalServerError, 83 | expectStyraErr: true, 84 | }), 85 | ) 86 | }) 87 | -------------------------------------------------------------------------------- /pkg/styra/styra.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Bankdata. 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 styra holds a client and helpers for interacting with the Styra 18 | // APIs. 19 | package styra 20 | -------------------------------------------------------------------------------- /pkg/styra/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "testing" 21 | 22 | ginkgo "github.com/onsi/ginkgo/v2" 23 | gomega "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestStyraClient(t *testing.T) { 27 | gomega.RegisterFailHandler(ginkgo.Fail) 28 | ginkgo.RunSpecs(t, "pkg/styra") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/styra/users.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | 26 | "github.com/pkg/errors" 27 | ) 28 | 29 | const ( 30 | endpointV1Users = "/v1/users" 31 | ) 32 | 33 | // GetUserResponse is the response type for calls to the GET /v1/users/{userId} endpoint 34 | // in the Styra API. 35 | type GetUserResponse struct { 36 | StatusCode int 37 | Body []byte 38 | } 39 | 40 | // GetUsersResponse is the response type for calls to the GET /v1/users endpoint 41 | // in the Styra API. 42 | type GetUsersResponse struct { 43 | Users []User 44 | } 45 | 46 | // Struct to unmarshal the JSON response from the GET /v1/users endpoint 47 | type getUsersJSONResponse struct { 48 | Result []User `json:"result"` 49 | } 50 | 51 | // User is the struct for a user in the Styra API. 52 | type User struct { 53 | Enabled bool `json:"enabled"` 54 | ID string `json:"id"` 55 | } 56 | 57 | // GetUsers calls the GET /v1/users endpoint in the Styra API. 58 | func (c *Client) GetUsers(ctx context.Context) (*GetUsersResponse, bool, error) { 59 | const cacheKey = "allUsersResponse" 60 | 61 | // Check if the response is in the cache 62 | if cachedResponse, found := c.Cache.Get(cacheKey); found { 63 | return cachedResponse.(*GetUsersResponse), true, nil 64 | } 65 | 66 | res, err := c.GetUserEndpoint(ctx, endpointV1Users) 67 | if err != nil { 68 | return nil, false, err 69 | } 70 | 71 | var js getUsersJSONResponse 72 | if err := json.Unmarshal(res.Body, &js); err != nil { 73 | return nil, false, errors.Wrap(err, "could not unmarshal body: ") 74 | } 75 | 76 | r := GetUsersResponse{ 77 | Users: js.Result, 78 | } 79 | 80 | // Cache the response 81 | c.Cache.Set(cacheKey, &r, 0) 82 | 83 | return &r, false, nil 84 | } 85 | 86 | // GetUser calls the GET /v1/users/{userId} endpoint in the Styra API. 87 | func (c *Client) GetUser(ctx context.Context, name string) (*GetUserResponse, error) { 88 | return c.GetUserEndpoint(ctx, fmt.Sprintf("%s/%s", endpointV1Users, name)) 89 | } 90 | 91 | // GetUserEndpoint is a helper function to call the Styra API. 92 | func (c *Client) GetUserEndpoint(ctx context.Context, endpoint string) (*GetUserResponse, error) { 93 | res, err := c.request(ctx, http.MethodGet, endpoint, nil, nil) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | body, err := io.ReadAll(res.Body) 99 | if err != nil { 100 | return nil, errors.Wrap(err, "could not read body") 101 | } 102 | 103 | if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound { 104 | err := NewHTTPError(res.StatusCode, string(body)) 105 | return nil, err 106 | } 107 | 108 | r := GetUserResponse{ 109 | StatusCode: res.StatusCode, 110 | Body: body, 111 | } 112 | 113 | return &r, nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/styra/users_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "errors" 23 | "io" 24 | "net/http" 25 | "time" 26 | 27 | ginkgo "github.com/onsi/ginkgo/v2" 28 | gomega "github.com/onsi/gomega" 29 | "github.com/patrickmn/go-cache" 30 | 31 | "github.com/bankdata/styra-controller/pkg/styra" 32 | ) 33 | 34 | var _ = ginkgo.Describe("GetUser", func() { 35 | 36 | type test struct { 37 | name string 38 | responseCode int 39 | responseBody string 40 | expectStyraErr bool 41 | } 42 | 43 | ginkgo.DescribeTable("GetUser", func(test test) { 44 | c := newTestClient(func(r *http.Request) *http.Response { 45 | bs, err := io.ReadAll(r.Body) 46 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 47 | gomega.Expect(bs).To(gomega.Equal([]byte(""))) 48 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet)) 49 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/users/" + test.name)) 50 | 51 | return &http.Response{ 52 | Header: make(http.Header), 53 | StatusCode: test.responseCode, 54 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 55 | } 56 | }) 57 | 58 | res, err := c.GetUser(context.Background(), test.name) 59 | if test.expectStyraErr { 60 | gomega.Expect(res).To(gomega.BeNil()) 61 | target := &styra.HTTPError{} 62 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 63 | } else { 64 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 65 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode)) 66 | } 67 | }, 68 | 69 | ginkgo.Entry("something", test{ 70 | name: "name", 71 | responseCode: http.StatusOK, 72 | responseBody: `{ 73 | "request_id": "id", 74 | "result": { 75 | "enabled": false, 76 | "id": "name" 77 | } 78 | }`, 79 | }), 80 | 81 | ginkgo.Entry("styra http error", test{ 82 | responseCode: http.StatusInternalServerError, 83 | expectStyraErr: true, 84 | }), 85 | ) 86 | }) 87 | 88 | var _ = ginkgo.Describe("GetUsers", func() { 89 | type test struct { 90 | responseCode int 91 | responseBody string 92 | expectStyraErr bool 93 | } 94 | 95 | ginkgo.DescribeTable("GetUsers", 96 | func(test test) { 97 | c := newTestClientWithCache(func(r *http.Request) *http.Response { 98 | bs, err := io.ReadAll(r.Body) 99 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 100 | gomega.Expect(bs).To(gomega.Equal([]byte(""))) 101 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet)) 102 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/users")) 103 | 104 | return &http.Response{ 105 | Header: make(http.Header), 106 | StatusCode: test.responseCode, 107 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 108 | } 109 | }, cache.New(1*time.Hour, 10*time.Minute)) 110 | 111 | // Call GetUsers 112 | res, _, err := c.GetUsers(context.Background()) 113 | if test.expectStyraErr { 114 | gomega.Expect(res).To(gomega.BeNil()) 115 | target := &styra.HTTPError{} 116 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 117 | } else { 118 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 119 | gomega.Expect(res.Users).ToNot(gomega.BeNil()) 120 | gomega.Expect(res.Users[0].ID).To(gomega.Equal("user1")) 121 | gomega.Expect(res.Users[0].Enabled).To(gomega.BeTrue()) 122 | gomega.Expect(res.Users[1].ID).To(gomega.Equal("user2")) 123 | gomega.Expect(res.Users[1].Enabled).To(gomega.BeFalse()) 124 | } 125 | }, 126 | 127 | ginkgo.Entry("successful response", test{ 128 | responseCode: http.StatusOK, 129 | responseBody: `{ 130 | "result": [ 131 | {"enabled": true, "id": "user1"}, 132 | {"enabled": false, "id": "user2"} 133 | ] 134 | }`, 135 | }), 136 | 137 | ginkgo.Entry("styra http error", test{ 138 | responseCode: http.StatusInternalServerError, 139 | expectStyraErr: true, 140 | }), 141 | ) 142 | }) 143 | -------------------------------------------------------------------------------- /pkg/styra/workspace.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra 18 | 19 | import ( 20 | "context" 21 | "io" 22 | "net/http" 23 | 24 | "github.com/pkg/errors" 25 | ) 26 | 27 | const ( 28 | endpointV1Workspace = "/v1/workspace" 29 | ) 30 | 31 | // UpdateWorkspaceRequest is the request type for calls to the PUT /v1/workspace endpoint 32 | // in the Styra API. 33 | type UpdateWorkspaceRequest struct { 34 | DecisionsExporter *ExporterConfig `json:"decisions_exporter,omitempty"` 35 | ActivityExporter *ExporterConfig `json:"activity_exporter,omitempty"` 36 | } 37 | 38 | // UpdateWorkspaceResponse is the response type for calls to the PUT /v1/workspace endpoint 39 | // in the Styra API. 40 | type UpdateWorkspaceResponse struct { 41 | StatusCode int 42 | Body []byte 43 | } 44 | 45 | // ExporterConfig is the configuration for the decision and activity exporter in the Styra API. 46 | type ExporterConfig struct { 47 | Interval string `json:"interval,omitempty"` 48 | Kafka *KafkaConfig `json:"kafka,omitempty"` 49 | } 50 | 51 | // KafkaConfig is the configuration for the Kafka exporter in the Styra API. 52 | type KafkaConfig struct { 53 | Authentication string `json:"authentication"` 54 | Brokers []string `json:"brokers"` 55 | RequredAcks string `json:"required_acks"` 56 | Topic string `json:"topic"` 57 | TLS *KafkaTLS `json:"tls"` 58 | } 59 | 60 | // KafkaTLS is the TLS configuration for the Kafka exporter in the Styra API. 61 | type KafkaTLS struct { 62 | ClientCert string `json:"client_cert"` 63 | RootCA string `json:"rootca"` 64 | InsecureSkipVerify bool `json:"insecure_skip_verify"` 65 | } 66 | 67 | // UpdateWorkspace calls the PATCH /v1/workspace endpoint in the Styra API. 68 | func (c *Client) UpdateWorkspace( 69 | ctx context.Context, 70 | request *UpdateWorkspaceRequest, 71 | ) (*UpdateWorkspaceResponse, error) { 72 | return c.UpdateWorkspaceRaw(ctx, request) 73 | } 74 | 75 | // UpdateWorkspaceRaw calls the PATCH /v1/workspace endpoint in the Styra API. 76 | func (c *Client) UpdateWorkspaceRaw( 77 | ctx context.Context, 78 | request interface{}, 79 | ) (*UpdateWorkspaceResponse, error) { 80 | res, err := c.request(ctx, http.MethodPatch, endpointV1Workspace, request, nil) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | body, err := io.ReadAll(res.Body) 86 | if err != nil { 87 | return nil, errors.Wrap(err, "could not read body") 88 | } 89 | 90 | if res.StatusCode != http.StatusOK { 91 | err := NewHTTPError(res.StatusCode, string(body)) 92 | return nil, err 93 | } 94 | 95 | r := UpdateWorkspaceResponse{ 96 | StatusCode: res.StatusCode, 97 | Body: body, 98 | } 99 | 100 | return &r, nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/styra/workspace_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 styra_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "encoding/json" 23 | "errors" 24 | "io" 25 | "net/http" 26 | 27 | ginkgo "github.com/onsi/ginkgo/v2" 28 | gomega "github.com/onsi/gomega" 29 | 30 | "github.com/bankdata/styra-controller/pkg/styra" 31 | ) 32 | 33 | var _ = ginkgo.Describe("UpdateWorkspace", func() { 34 | 35 | type test struct { 36 | request *styra.UpdateWorkspaceRequest 37 | responseCode int 38 | responseBody string 39 | expectStyraErr bool 40 | } 41 | 42 | ginkgo.DescribeTable("UpdateWorkspace", func(test test) { 43 | c := newTestClient(func(r *http.Request) *http.Response { 44 | bs, err := io.ReadAll(r.Body) 45 | gomega.Expect(err).NotTo(gomega.HaveOccurred()) 46 | 47 | var b bytes.Buffer 48 | gomega.Expect(json.NewEncoder(&b).Encode(test.request)).To(gomega.Succeed()) 49 | gomega.Expect(bs).To(gomega.Equal(b.Bytes())) 50 | 51 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPatch)) 52 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/workspace")) 53 | 54 | return &http.Response{ 55 | Header: make(http.Header), 56 | StatusCode: test.responseCode, 57 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)), 58 | } 59 | }) 60 | 61 | res, err := c.UpdateWorkspace(context.Background(), test.request) 62 | if test.expectStyraErr { 63 | gomega.Expect(res).To(gomega.BeNil()) 64 | target := &styra.HTTPError{} 65 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue()) 66 | } else { 67 | gomega.Expect(err).ToNot(gomega.HaveOccurred()) 68 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode)) 69 | } 70 | }, 71 | 72 | ginkgo.Entry("update workspace DecisionsExporter", test{ 73 | request: &styra.UpdateWorkspaceRequest{ 74 | DecisionsExporter: &styra.ExporterConfig{ 75 | Interval: "1m", 76 | Kafka: &styra.KafkaConfig{ 77 | Authentication: "auth", 78 | Brokers: []string{"broker"}, 79 | RequredAcks: "acks", 80 | Topic: "topic", 81 | TLS: &styra.KafkaTLS{ 82 | ClientCert: "clientcert", 83 | RootCA: "rootca", 84 | }, 85 | }, 86 | }, 87 | }, 88 | responseCode: http.StatusOK, 89 | responseBody: `{ 90 | "request_id": "id" 91 | }`, 92 | }), 93 | 94 | ginkgo.Entry("update workspace ActivityExporter", test{ 95 | request: &styra.UpdateWorkspaceRequest{ 96 | ActivityExporter: &styra.ExporterConfig{ 97 | Interval: "1m", 98 | Kafka: &styra.KafkaConfig{ 99 | Authentication: "auth", 100 | Brokers: []string{"broker"}, 101 | RequredAcks: "acks", 102 | Topic: "topic", 103 | TLS: &styra.KafkaTLS{ 104 | ClientCert: "clientcert", 105 | RootCA: "rootca", 106 | InsecureSkipVerify: true, 107 | }, 108 | }, 109 | }, 110 | }, 111 | responseCode: http.StatusOK, 112 | responseBody: `{ 113 | "request_id": "id" 114 | }`, 115 | }), 116 | ginkgo.Entry("styra http error", test{ 117 | responseCode: http.StatusInternalServerError, 118 | expectStyraErr: true, 119 | }), 120 | ) 121 | }) 122 | -------------------------------------------------------------------------------- /scripts/gen-api-docs/README.md: -------------------------------------------------------------------------------- 1 | `gen-crd-api-reference-docs` is a binary that can be used to generate documentation for CRDs. Repository is https://github.com/ahmetb/gen-crd-api-reference-docs. 2 | 3 | Download the binary to `./bin` by running `make gen-crd-api-reference-docs`. 4 | 5 | There exists `make` targets for generating all documentation and a `make` target for each API. 6 | -------------------------------------------------------------------------------- /scripts/gen-api-docs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hideMemberFields": [ 3 | "TypeMeta" 4 | ], 5 | "hideTypePatterns": [ 6 | "ParseError$", 7 | "List$" 8 | ], 9 | "externalPackages": [ 10 | { 11 | "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", 12 | "docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" 13 | }, 14 | { 15 | "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", 16 | "docsURLTemplate": "https://v1-20.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" 17 | } 18 | ], 19 | "markdownDisabled": false 20 | } 21 | -------------------------------------------------------------------------------- /scripts/gen-api-docs/gen-api-docs.sh: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #!/usr/bin/env bash 16 | 17 | gen-styra-v1alpha1 () { 18 | echo "generating docs for api/styra/v1alpha1" 19 | ./bin/gen-crd-api-reference-docs \ 20 | -config "scripts/gen-api-docs/config.json" \ 21 | -api-dir "github.com/bankdata/styra-controller/api/styra/v1alpha1" \ 22 | -template-dir "internal/template" \ 23 | -out-file ./docs/apis/styra/v1alpha1.md 24 | } 25 | 26 | 27 | gen-styra-v1beta1 () { 28 | echo "generating docs for api/styra/v1beta1" 29 | ./bin/gen-crd-api-reference-docs \ 30 | -config "scripts/gen-api-docs/config.json" \ 31 | -api-dir "github.com/bankdata/styra-controller/api/styra/v1beta1" \ 32 | -template-dir "internal/template" \ 33 | -out-file ./docs/apis/styra/v1beta1.md 34 | } 35 | 36 | case $1 in 37 | styra-v1alpha1) 38 | gen-styra-v1alpha1 39 | ;; 40 | styra-v1beta1) 41 | gen-styra-v1beta1 42 | ;; 43 | all) 44 | gen-styra-v1alpha1 45 | gen-styra-v1beta1 46 | ;; 47 | *) 48 | echo "Usage: gen-api-docs.sh styra-v1alpha1|styra-v1beta1|all" 49 | ;; 50 | esac 51 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | /* 5 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk) 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 | package main 21 | 22 | import ( 23 | _ "github.com/ahmetb/gen-crd-api-reference-docs" 24 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 25 | _ "github.com/goreleaser/goreleaser" 26 | _ "github.com/onsi/ginkgo/v2/ginkgo" 27 | _ "github.com/vektra/mockery/v2" 28 | _ "sigs.k8s.io/controller-runtime/tools/setup-envtest" 29 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 30 | _ "sigs.k8s.io/kind" 31 | _ "sigs.k8s.io/kustomize/kustomize/v5" 32 | ) 33 | --------------------------------------------------------------------------------