├── .dockerignore ├── .github ├── dependabot.yaml └── workflows │ ├── pr.yaml │ └── push.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── Tiltfile ├── api └── v1alpha1 │ ├── groupconfig_types.go │ ├── groupversion_info.go │ ├── namespaceconfig_types.go │ ├── userconfig_types.go │ └── zz_generated.deepcopy.go ├── ci.Dockerfile ├── ci.Dockerfile.dockerignore ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── community-operators │ └── ci.yaml ├── crd │ ├── bases │ │ ├── redhatcop.redhat.io_groupconfigs.yaml │ │ ├── redhatcop.redhat.io_namespaceconfigs.yaml │ │ └── redhatcop.redhat.io_userconfigs.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_groupconfigs.yaml │ │ ├── cainjection_in_namespaceconfigs.yaml │ │ ├── cainjection_in_userconfigs.yaml │ │ ├── webhook_in_groupconfigs.yaml │ │ ├── webhook_in_namespaceconfigs.yaml │ │ └── webhook_in_userconfigs.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── helmchart │ ├── .helmignore │ ├── Chart.yaml.tpl │ ├── cert-manager-ca-injection.yaml │ ├── kustomization.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── certificate.yaml │ │ └── manager.yaml │ └── values.yaml.tpl ├── local-development │ ├── kustomization.yaml │ └── tilt │ │ ├── ca-injection.yaml │ │ ├── env-replace-image.yaml │ │ ├── kustomization.yaml │ │ ├── readme.md │ │ ├── replace-image.yaml │ │ ├── secret-injection.yaml │ │ ├── service-account.yaml │ │ └── set-image.sh ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── namespace-configuration-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── operatorhub │ └── operator.yaml ├── prometheus │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── monitor.yaml │ ├── role.yaml │ └── rolebinding.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── groupconfig_editor_role.yaml │ ├── groupconfig_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── namespaceconfig_editor_role.yaml │ ├── namespaceconfig_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── userconfig_editor_role.yaml │ └── userconfig_viewer_role.yaml ├── samples │ ├── kustomization.yaml │ ├── redhatcop_v1alpha1_groupconfig.yaml │ ├── redhatcop_v1alpha1_namespaceconfig.yaml │ └── redhatcop_v1alpha1_userconfig.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── common │ └── common.go ├── groupconfig_controller.go ├── namespaceconfig_controller.go ├── suite_test.go └── userconfig_controller.go ├── examples ├── namespace-config │ ├── multitenant-networkpolicy.yaml │ ├── overcommit-limitrange.yaml │ ├── readme.md │ ├── serviceaccount-permissions.yaml │ ├── special-pod.yaml │ └── tshirt-quotas.yaml ├── team-onboarding │ ├── admin-no-build.yaml │ ├── group-config.yaml │ ├── groups.yaml │ ├── namespace-config.yaml │ └── readme.md └── user-sandbox │ ├── identities.yaml │ ├── readme.md │ ├── user-config.yaml │ └── users.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── integration ├── cluster-kind.yaml ├── helm │ ├── ingress-nginx │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── templates │ │ │ ├── _helpers.tpl │ │ │ └── deploy.yaml │ │ └── values.yaml │ └── prometheus-rbac │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── templates │ │ ├── _helpers.tpl │ │ ├── clusterrole-prometheus.yaml │ │ └── clusterrolebinding-prometheus.yaml │ │ └── values.yaml └── kube-prometheus-stack-values.yaml ├── main.go ├── media └── namespace-configuration-operator.png ├── renovate.json └── test ├── advanced-group-config-test.yaml ├── group-config-test.yaml ├── groups.yaml ├── identities.yaml ├── namespace-config-test.yaml ├── namespaces.yaml ├── user-config-test.yaml └── users.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: docker 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: gomod 12 | directory: / 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: pull request 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | shared-operator-workflow: 10 | name: shared-operator-workflow 11 | uses: redhat-cop/github-workflows-operators/.github/workflows/pr-operator.yml@6005dbbf6e4349cd92304439eb34518080086f62 # v1.1.0 12 | with: 13 | RUN_UNIT_TESTS: true 14 | RUN_INTEGRATION_TESTS: true 15 | RUN_HELMCHART_TEST: true 16 | GO_VERSION: ~1.21 17 | OPERATOR_SDK_VERSION: v1.31.0 18 | -------------------------------------------------------------------------------- /.github/workflows/push.yaml: -------------------------------------------------------------------------------- 1 | name: push 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | tags: 8 | - v* 9 | 10 | jobs: 11 | shared-operator-workflow: 12 | name: shared-operator-workflow 13 | uses: redhat-cop/github-workflows-operators/.github/workflows/release-operator.yml@6005dbbf6e4349cd92304439eb34518080086f62 # v1.1.0 14 | secrets: 15 | COMMUNITY_OPERATOR_PAT: ${{ secrets.COMMUNITY_OPERATOR_PAT }} 16 | REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} 17 | REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} 18 | with: 19 | PR_ACTOR: "raffaele.spazzoli@gmail.com" 20 | RUN_UNIT_TESTS: true 21 | RUN_INTEGRATION_TESTS: true 22 | RUN_HELMCHART_TEST: true 23 | GO_VERSION: ~1.21 24 | OPERATOR_SDK_VERSION: v1.31.0 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | bundle/ 28 | bundle.Dockerfile 29 | charts/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.21 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o manager main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM registry.access.redhat.com/ubi9/ubi-minimal 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | USER 65532:65532 26 | 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: redhat.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | projectName: namespace-configuration-operator 5 | repo: github.com/redhat-cop/namespace-configuration-operator 6 | resources: 7 | - api: 8 | crdVersion: v1 9 | namespaced: false 10 | controller: true 11 | domain: redhat.io 12 | group: redhatcop 13 | kind: NamespaceConfig 14 | path: github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1 15 | version: v1alpha1 16 | - api: 17 | crdVersion: v1 18 | namespaced: false 19 | controller: true 20 | domain: redhat.io 21 | group: redhatcop 22 | kind: UserConfig 23 | path: github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1 24 | version: v1alpha1 25 | - api: 26 | crdVersion: v1 27 | namespaced: false 28 | controller: true 29 | domain: redhat.io 30 | group: redhatcop 31 | kind: GroupConfig 32 | path: github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1 33 | version: v1alpha1 34 | version: "3" 35 | plugins: 36 | manifests.sdk.operatorframework.io/v2: {} 37 | scorecard.sdk.operatorframework.io/v2: {} 38 | -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | 3 | compile_cmd = 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/manager main.go' 4 | image = 'quay.io/' + os.environ['repo'] + '/namespace-configuration-operator' 5 | 6 | local_resource( 7 | 'namespace-configuration-operator-compile', 8 | compile_cmd, 9 | deps=['./main.go','./api','./controllers']) 10 | 11 | 12 | custom_build( 13 | image, 14 | 'podman build -t $EXPECTED_REF --ignorefile ci.Dockerfile.dockerignore -f ./ci.Dockerfile . && podman push $EXPECTED_REF $EXPECTED_REF', 15 | entrypoint=['/manager'], 16 | deps=['./bin'], 17 | live_update=[ 18 | sync('./bin/manager',"/manager"), 19 | ], 20 | skips_local_docker=True, 21 | ) 22 | 23 | allow_k8s_contexts(k8s_context()) 24 | k8s_yaml(kustomize('./config/local-development/tilt')) 25 | k8s_resource('namespace-configuration-operator-controller-manager',resource_deps=['namespace-configuration-operator-compile']) -------------------------------------------------------------------------------- /api/v1alpha1/groupconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 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 | apis "github.com/redhat-cop/operator-utils/api/v1alpha1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // GroupConfigSpec defines the desired state of GroupConfig 28 | // There are two selectors: "labelSelector", "annotationSelector". 29 | // Selectors are considered in AND, so if multiple are defined they must all be true for a Group to be selected. 30 | type GroupConfigSpec struct { 31 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 32 | // Important: Run "make" to regenerate code after modifying this file 33 | 34 | // LabelSelector selects Groups by label. 35 | // +kubebuilder:validation:Optional 36 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 37 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 38 | LabelSelector metav1.LabelSelector `json:"labelSelector,omitempty"` 39 | 40 | // AnnotationSelector selects Groups by annotation. 41 | // +kubebuilder:validation:Optional 42 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 43 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 44 | AnnotationSelector metav1.LabelSelector `json:"annotationSelector,omitempty"` 45 | 46 | // Templates these are the templates of the resources to be created when a selected groups is created/updated 47 | // +kubebuilder:validation:Optional 48 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 49 | Templates []apis.LockedResourceTemplate `json:"templates,omitempty"` 50 | } 51 | 52 | // GroupConfigStatus defines the observed state of GroupConfig 53 | type GroupConfigStatus struct { 54 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 55 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 56 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 57 | 58 | // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors=true 59 | apis.EnforcingReconcileStatus `json:",inline"` 60 | } 61 | 62 | func (m *GroupConfig) GetEnforcingReconcileStatus() apis.EnforcingReconcileStatus { 63 | return m.Status.EnforcingReconcileStatus 64 | } 65 | 66 | func (m *GroupConfig) SetEnforcingReconcileStatus(reconcileStatus apis.EnforcingReconcileStatus) { 67 | m.Status.EnforcingReconcileStatus = reconcileStatus 68 | } 69 | 70 | // +kubebuilder:object:root=true 71 | // +kubebuilder:subresource:status 72 | 73 | // GroupConfig is the Schema for the groupconfigs API 74 | // +kubebuilder:resource:path=groupconfigs,scope=Cluster 75 | type GroupConfig struct { 76 | metav1.TypeMeta `json:",inline"` 77 | metav1.ObjectMeta `json:"metadata,omitempty"` 78 | 79 | Spec GroupConfigSpec `json:"spec,omitempty"` 80 | Status GroupConfigStatus `json:"status,omitempty"` 81 | } 82 | 83 | // +kubebuilder:object:root=true 84 | 85 | // GroupConfigList contains a list of GroupConfig 86 | type GroupConfigList struct { 87 | metav1.TypeMeta `json:",inline"` 88 | metav1.ListMeta `json:"metadata,omitempty"` 89 | Items []GroupConfig `json:"items"` 90 | } 91 | 92 | func init() { 93 | SchemeBuilder.Register(&GroupConfig{}, &GroupConfigList{}) 94 | } 95 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the redhatcop v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=redhatcop.redhat.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "redhatcop.redhat.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/namespaceconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 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 | apis "github.com/redhat-cop/operator-utils/api/v1alpha1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // NamespaceConfigSpec defines the desired state of NamespaceConfig 28 | // There are two selectors: "labelSelector", "annotationSelector". 29 | // Selectors are considered in AND, so if multiple are defined they must all be true for a Namespace to be selected. 30 | type NamespaceConfigSpec struct { 31 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 32 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 33 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 34 | 35 | // LabelSelector selects Namespaces by label. 36 | // +kubebuilder:validation:Optional 37 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 38 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 39 | LabelSelector metav1.LabelSelector `json:"labelSelector,omitempty"` 40 | 41 | // AnnotationSelector selects Namespaces by annotation. 42 | // +kubebuilder:validation:Optional 43 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 44 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 45 | AnnotationSelector metav1.LabelSelector `json:"annotationSelector,omitempty"` 46 | 47 | // Templates these are the templates of the resources to be created when a selected namespace is created/updated 48 | // +kubebuilder:validation:Optional 49 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 50 | Templates []apis.LockedResourceTemplate `json:"templates,omitempty"` 51 | } 52 | 53 | // NamespaceConfigStatus defines the observed state of NamespaceSConfig 54 | type NamespaceConfigStatus struct { 55 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 56 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 57 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 58 | 59 | // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors=true 60 | apis.EnforcingReconcileStatus `json:",inline"` 61 | } 62 | 63 | func (m *NamespaceConfig) GetEnforcingReconcileStatus() apis.EnforcingReconcileStatus { 64 | return m.Status.EnforcingReconcileStatus 65 | } 66 | 67 | func (m *NamespaceConfig) SetEnforcingReconcileStatus(reconcileStatus apis.EnforcingReconcileStatus) { 68 | m.Status.EnforcingReconcileStatus = reconcileStatus 69 | } 70 | 71 | // +kubebuilder:object:root=true 72 | // +kubebuilder:subresource:status 73 | 74 | // NamespaceConfig is the Schema for the namespaceconfigs API 75 | // +kubebuilder:resource:path=namespaceconfigs,scope=Cluster 76 | type NamespaceConfig struct { 77 | metav1.TypeMeta `json:",inline"` 78 | metav1.ObjectMeta `json:"metadata,omitempty"` 79 | 80 | Spec NamespaceConfigSpec `json:"spec,omitempty"` 81 | Status NamespaceConfigStatus `json:"status,omitempty"` 82 | } 83 | 84 | // +kubebuilder:object:root=true 85 | 86 | // NamespaceConfigList contains a list of NamespaceConfig 87 | type NamespaceConfigList struct { 88 | metav1.TypeMeta `json:",inline"` 89 | metav1.ListMeta `json:"metadata,omitempty"` 90 | Items []NamespaceConfig `json:"items"` 91 | } 92 | 93 | func init() { 94 | SchemeBuilder.Register(&NamespaceConfig{}, &NamespaceConfigList{}) 95 | } 96 | -------------------------------------------------------------------------------- /api/v1alpha1/userconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 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 | apis "github.com/redhat-cop/operator-utils/api/v1alpha1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // UserConfigSpec defines the desired state of UserConfig 28 | // There are four selectors: "labelSelector", "annotationSelector", "identityExtraFieldSelector" and "providerName". 29 | // labelSelector and annoationSelector are matches against the User object 30 | // identityExtraFieldSelector and providerName are matched against any of the Identities associated with User 31 | // Selectors are considered in AND, so if multiple are defined tthey must all be true for a User to be selected. 32 | type UserConfigSpec struct { 33 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 34 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 35 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 36 | 37 | // LabelSelector selects Users by label. 38 | // +kubebuilder:validation:Optional 39 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 40 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 41 | LabelSelector metav1.LabelSelector `json:"labelSelector,omitempty"` 42 | 43 | // AnnotationSelector selects Users by annotation. 44 | // +kubebuilder:validation:Optional 45 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 46 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 47 | AnnotationSelector metav1.LabelSelector `json:"annotationSelector,omitempty"` 48 | 49 | //IdentityExtraSelector allows you to specify a selector for the extra fields of the User's identities. 50 | //If one of the user identities matches the selector the User is selected 51 | //This condition is in OR with ProviderName 52 | // +kubebuilder:validation:Optional 53 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 54 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:selector:" 55 | IdentityExtraFieldSelector metav1.LabelSelector `json:"identityExtraFieldSelector,omitempty"` 56 | 57 | //ProviderName allows you to specify an identity provider. If a user logged in with that provider it is selected. 58 | //This condition is in OR with IdentityExtraSelector 59 | // +kubebuilder:validation:Optional 60 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 61 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:text" 62 | ProviderName string `json:"providerName,omitempty"` 63 | 64 | // Templates these are the templates of the resources to be created when a selected user is created/updated 65 | // +kubebuilder:validation:Optional 66 | // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true 67 | Templates []apis.LockedResourceTemplate `json:"templates,omitempty"` 68 | } 69 | 70 | // UserConfigStatus defines the observed state of UserConfig 71 | type UserConfigStatus struct { 72 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 73 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 74 | // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html 75 | 76 | // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors=true 77 | apis.EnforcingReconcileStatus `json:",inline"` 78 | } 79 | 80 | func (m *UserConfig) GetEnforcingReconcileStatus() apis.EnforcingReconcileStatus { 81 | return m.Status.EnforcingReconcileStatus 82 | } 83 | 84 | func (m *UserConfig) SetEnforcingReconcileStatus(reconcileStatus apis.EnforcingReconcileStatus) { 85 | m.Status.EnforcingReconcileStatus = reconcileStatus 86 | } 87 | 88 | // +kubebuilder:object:root=true 89 | // +kubebuilder:subresource:status 90 | 91 | // UserConfig is the Schema for the userconfigs API 92 | // +kubebuilder:resource:path=userconfigs,scope=Cluster 93 | type UserConfig struct { 94 | metav1.TypeMeta `json:",inline"` 95 | metav1.ObjectMeta `json:"metadata,omitempty"` 96 | 97 | Spec UserConfigSpec `json:"spec,omitempty"` 98 | Status UserConfigStatus `json:"status,omitempty"` 99 | } 100 | 101 | // +kubebuilder:object:root=true 102 | 103 | // UserConfigList contains a list of UserConfig 104 | type UserConfigList struct { 105 | metav1.TypeMeta `json:",inline"` 106 | metav1.ListMeta `json:"metadata,omitempty"` 107 | Items []UserConfig `json:"items"` 108 | } 109 | 110 | func init() { 111 | SchemeBuilder.Register(&UserConfig{}, &UserConfigList{}) 112 | } 113 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2020 Red Hat Community of Practice. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | apiv1alpha1 "github.com/redhat-cop/operator-utils/api/v1alpha1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *GroupConfig) DeepCopyInto(out *GroupConfig) { 31 | *out = *in 32 | out.TypeMeta = in.TypeMeta 33 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 34 | in.Spec.DeepCopyInto(&out.Spec) 35 | in.Status.DeepCopyInto(&out.Status) 36 | } 37 | 38 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupConfig. 39 | func (in *GroupConfig) DeepCopy() *GroupConfig { 40 | if in == nil { 41 | return nil 42 | } 43 | out := new(GroupConfig) 44 | in.DeepCopyInto(out) 45 | return out 46 | } 47 | 48 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 49 | func (in *GroupConfig) DeepCopyObject() runtime.Object { 50 | if c := in.DeepCopy(); c != nil { 51 | return c 52 | } 53 | return nil 54 | } 55 | 56 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 57 | func (in *GroupConfigList) DeepCopyInto(out *GroupConfigList) { 58 | *out = *in 59 | out.TypeMeta = in.TypeMeta 60 | in.ListMeta.DeepCopyInto(&out.ListMeta) 61 | if in.Items != nil { 62 | in, out := &in.Items, &out.Items 63 | *out = make([]GroupConfig, len(*in)) 64 | for i := range *in { 65 | (*in)[i].DeepCopyInto(&(*out)[i]) 66 | } 67 | } 68 | } 69 | 70 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupConfigList. 71 | func (in *GroupConfigList) DeepCopy() *GroupConfigList { 72 | if in == nil { 73 | return nil 74 | } 75 | out := new(GroupConfigList) 76 | in.DeepCopyInto(out) 77 | return out 78 | } 79 | 80 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 81 | func (in *GroupConfigList) DeepCopyObject() runtime.Object { 82 | if c := in.DeepCopy(); c != nil { 83 | return c 84 | } 85 | return nil 86 | } 87 | 88 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 89 | func (in *GroupConfigSpec) DeepCopyInto(out *GroupConfigSpec) { 90 | *out = *in 91 | in.LabelSelector.DeepCopyInto(&out.LabelSelector) 92 | in.AnnotationSelector.DeepCopyInto(&out.AnnotationSelector) 93 | if in.Templates != nil { 94 | in, out := &in.Templates, &out.Templates 95 | *out = make([]apiv1alpha1.LockedResourceTemplate, len(*in)) 96 | for i := range *in { 97 | (*in)[i].DeepCopyInto(&(*out)[i]) 98 | } 99 | } 100 | } 101 | 102 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupConfigSpec. 103 | func (in *GroupConfigSpec) DeepCopy() *GroupConfigSpec { 104 | if in == nil { 105 | return nil 106 | } 107 | out := new(GroupConfigSpec) 108 | in.DeepCopyInto(out) 109 | return out 110 | } 111 | 112 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 113 | func (in *GroupConfigStatus) DeepCopyInto(out *GroupConfigStatus) { 114 | *out = *in 115 | in.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus) 116 | } 117 | 118 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupConfigStatus. 119 | func (in *GroupConfigStatus) DeepCopy() *GroupConfigStatus { 120 | if in == nil { 121 | return nil 122 | } 123 | out := new(GroupConfigStatus) 124 | in.DeepCopyInto(out) 125 | return out 126 | } 127 | 128 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 129 | func (in *NamespaceConfig) DeepCopyInto(out *NamespaceConfig) { 130 | *out = *in 131 | out.TypeMeta = in.TypeMeta 132 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 133 | in.Spec.DeepCopyInto(&out.Spec) 134 | in.Status.DeepCopyInto(&out.Status) 135 | } 136 | 137 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceConfig. 138 | func (in *NamespaceConfig) DeepCopy() *NamespaceConfig { 139 | if in == nil { 140 | return nil 141 | } 142 | out := new(NamespaceConfig) 143 | in.DeepCopyInto(out) 144 | return out 145 | } 146 | 147 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 148 | func (in *NamespaceConfig) DeepCopyObject() runtime.Object { 149 | if c := in.DeepCopy(); c != nil { 150 | return c 151 | } 152 | return nil 153 | } 154 | 155 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 156 | func (in *NamespaceConfigList) DeepCopyInto(out *NamespaceConfigList) { 157 | *out = *in 158 | out.TypeMeta = in.TypeMeta 159 | in.ListMeta.DeepCopyInto(&out.ListMeta) 160 | if in.Items != nil { 161 | in, out := &in.Items, &out.Items 162 | *out = make([]NamespaceConfig, len(*in)) 163 | for i := range *in { 164 | (*in)[i].DeepCopyInto(&(*out)[i]) 165 | } 166 | } 167 | } 168 | 169 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceConfigList. 170 | func (in *NamespaceConfigList) DeepCopy() *NamespaceConfigList { 171 | if in == nil { 172 | return nil 173 | } 174 | out := new(NamespaceConfigList) 175 | in.DeepCopyInto(out) 176 | return out 177 | } 178 | 179 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 180 | func (in *NamespaceConfigList) DeepCopyObject() runtime.Object { 181 | if c := in.DeepCopy(); c != nil { 182 | return c 183 | } 184 | return nil 185 | } 186 | 187 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 188 | func (in *NamespaceConfigSpec) DeepCopyInto(out *NamespaceConfigSpec) { 189 | *out = *in 190 | in.LabelSelector.DeepCopyInto(&out.LabelSelector) 191 | in.AnnotationSelector.DeepCopyInto(&out.AnnotationSelector) 192 | if in.Templates != nil { 193 | in, out := &in.Templates, &out.Templates 194 | *out = make([]apiv1alpha1.LockedResourceTemplate, len(*in)) 195 | for i := range *in { 196 | (*in)[i].DeepCopyInto(&(*out)[i]) 197 | } 198 | } 199 | } 200 | 201 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceConfigSpec. 202 | func (in *NamespaceConfigSpec) DeepCopy() *NamespaceConfigSpec { 203 | if in == nil { 204 | return nil 205 | } 206 | out := new(NamespaceConfigSpec) 207 | in.DeepCopyInto(out) 208 | return out 209 | } 210 | 211 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 212 | func (in *NamespaceConfigStatus) DeepCopyInto(out *NamespaceConfigStatus) { 213 | *out = *in 214 | in.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus) 215 | } 216 | 217 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceConfigStatus. 218 | func (in *NamespaceConfigStatus) DeepCopy() *NamespaceConfigStatus { 219 | if in == nil { 220 | return nil 221 | } 222 | out := new(NamespaceConfigStatus) 223 | in.DeepCopyInto(out) 224 | return out 225 | } 226 | 227 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 228 | func (in *UserConfig) DeepCopyInto(out *UserConfig) { 229 | *out = *in 230 | out.TypeMeta = in.TypeMeta 231 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 232 | in.Spec.DeepCopyInto(&out.Spec) 233 | in.Status.DeepCopyInto(&out.Status) 234 | } 235 | 236 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConfig. 237 | func (in *UserConfig) DeepCopy() *UserConfig { 238 | if in == nil { 239 | return nil 240 | } 241 | out := new(UserConfig) 242 | in.DeepCopyInto(out) 243 | return out 244 | } 245 | 246 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 247 | func (in *UserConfig) DeepCopyObject() runtime.Object { 248 | if c := in.DeepCopy(); c != nil { 249 | return c 250 | } 251 | return nil 252 | } 253 | 254 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 255 | func (in *UserConfigList) DeepCopyInto(out *UserConfigList) { 256 | *out = *in 257 | out.TypeMeta = in.TypeMeta 258 | in.ListMeta.DeepCopyInto(&out.ListMeta) 259 | if in.Items != nil { 260 | in, out := &in.Items, &out.Items 261 | *out = make([]UserConfig, len(*in)) 262 | for i := range *in { 263 | (*in)[i].DeepCopyInto(&(*out)[i]) 264 | } 265 | } 266 | } 267 | 268 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConfigList. 269 | func (in *UserConfigList) DeepCopy() *UserConfigList { 270 | if in == nil { 271 | return nil 272 | } 273 | out := new(UserConfigList) 274 | in.DeepCopyInto(out) 275 | return out 276 | } 277 | 278 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 279 | func (in *UserConfigList) DeepCopyObject() runtime.Object { 280 | if c := in.DeepCopy(); c != nil { 281 | return c 282 | } 283 | return nil 284 | } 285 | 286 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 287 | func (in *UserConfigSpec) DeepCopyInto(out *UserConfigSpec) { 288 | *out = *in 289 | in.LabelSelector.DeepCopyInto(&out.LabelSelector) 290 | in.AnnotationSelector.DeepCopyInto(&out.AnnotationSelector) 291 | in.IdentityExtraFieldSelector.DeepCopyInto(&out.IdentityExtraFieldSelector) 292 | if in.Templates != nil { 293 | in, out := &in.Templates, &out.Templates 294 | *out = make([]apiv1alpha1.LockedResourceTemplate, len(*in)) 295 | for i := range *in { 296 | (*in)[i].DeepCopyInto(&(*out)[i]) 297 | } 298 | } 299 | } 300 | 301 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConfigSpec. 302 | func (in *UserConfigSpec) DeepCopy() *UserConfigSpec { 303 | if in == nil { 304 | return nil 305 | } 306 | out := new(UserConfigSpec) 307 | in.DeepCopyInto(out) 308 | return out 309 | } 310 | 311 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 312 | func (in *UserConfigStatus) DeepCopyInto(out *UserConfigStatus) { 313 | *out = *in 314 | in.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus) 315 | } 316 | 317 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConfigStatus. 318 | func (in *UserConfigStatus) DeepCopy() *UserConfigStatus { 319 | if in == nil { 320 | return nil 321 | } 322 | out := new(UserConfigStatus) 323 | in.DeepCopyInto(out) 324 | return out 325 | } 326 | -------------------------------------------------------------------------------- /ci.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi9/ubi-minimal 2 | WORKDIR / 3 | COPY bin/manager . 4 | USER 65532:65532 5 | 6 | ENTRYPOINT ["/manager"] 7 | -------------------------------------------------------------------------------- /ci.Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | api/ 2 | bundle/ 3 | config/ 4 | controllers/ 5 | examples/ 6 | hack/ 7 | test/ 8 | -------------------------------------------------------------------------------- /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 | name: selfsigned-issuer 8 | namespace: system 9 | spec: 10 | selfSigned: {} 11 | --- 12 | apiVersion: cert-manager.io/v1 13 | kind: Certificate 14 | metadata: 15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 16 | namespace: system 17 | spec: 18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 22 | issuerRef: 23 | kind: Issuer 24 | name: selfsigned-issuer 25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 26 | -------------------------------------------------------------------------------- /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 and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/community-operators/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use `replaces-mode` or `semver-mode`. Once you switch to `semver-mode`, there is no easy way back. 3 | updateGraph: semver-mode 4 | addReviewers: true 5 | reviewers: 6 | - raffaelespazzoli -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/redhatcop.redhat.io_namespaceconfigs.yaml 6 | - bases/redhatcop.redhat.io_userconfigs.yaml 7 | - bases/redhatcop.redhat.io_groupconfigs.yaml 8 | # +kubebuilder:scaffold:crdkustomizeresource 9 | 10 | patchesStrategicMerge: 11 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 12 | # patches here are for enabling the conversion webhook for each CRD 13 | #- patches/webhook_in_namespaceconfigs.yaml 14 | #- patches/webhook_in_userconfigs.yaml 15 | #- patches/webhook_in_groupconfigs.yaml 16 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 17 | 18 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 19 | # patches here are for enabling the CA injection for each CRD 20 | #- patches/cainjection_in_namespaceconfigs.yaml 21 | #- patches/cainjection_in_userconfigs.yaml 22 | #- patches/cainjection_in_groupconfigs.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_groupconfigs.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: groupconfigs.redhatcop.redhat.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_namespaceconfigs.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: namespaceconfigs.redhatcop.redhat.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_userconfigs.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: userconfigs.redhatcop.redhat.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_groupconfigs.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: groupconfigs.redhatcop.redhat.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_namespaceconfigs.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: namespaceconfigs.redhatcop.redhat.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_userconfigs.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: userconfigs.redhatcop.redhat.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: namespace-configuration-operator 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: namespace-configuration-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | - ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | - name: METRICS_SERVICE_NAME 49 | objref: 50 | kind: Service 51 | version: v1 52 | name: metrics-service 53 | - name: METRICS_SERVICE_NAMESPACE 54 | objref: 55 | kind: Service 56 | version: v1 57 | name: metrics-service 58 | fieldref: 59 | fieldpath: metadata.namespace 60 | - name: ROLE_NAME 61 | objref: 62 | kind: Role 63 | apiVersion: rbac.authorization.k8s.io/v1 64 | name: prometheus-k8s 65 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 66 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 67 | # objref: 68 | # kind: Certificate 69 | # group: cert-manager.io 70 | # version: v1 71 | # name: serving-cert # this name should match the one in certificate.yaml 72 | # fieldref: 73 | # fieldpath: metadata.namespace 74 | #- name: CERTIFICATE_NAME 75 | # objref: 76 | # kind: Certificate 77 | # group: cert-manager.io 78 | # version: v1 79 | # name: serving-cert # this name should match the one in certificate.yaml 80 | #- name: SERVICE_NAMESPACE # namespace of the service 81 | # objref: 82 | # kind: Service 83 | # version: v1 84 | # name: webhook-service 85 | # fieldref: 86 | # fieldpath: metadata.namespace 87 | #- name: SERVICE_NAME 88 | # objref: 89 | # kind: Service 90 | # version: v1 91 | # name: webhook-service 92 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: quay.io/redhat-cop/kube-rbac-proxy:v0.11.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--tls-cert-file=/etc/certs/tls/tls.crt" 19 | - "--tls-private-key-file=/etc/certs/tls/tls.key" 20 | - "--v=10" 21 | volumeMounts: 22 | - mountPath: /etc/certs/tls 23 | name: tls-cert 24 | ports: 25 | - containerPort: 8443 26 | name: https 27 | - name: manager 28 | args: 29 | - "--health-probe-bind-address=:8081" 30 | - "--metrics-bind-address=127.0.0.1:8080" 31 | - "--leader-elect" 32 | volumes: 33 | - name: tls-cert 34 | secret: 35 | defaultMode: 420 36 | secretName: namespace-configuration-operator-certs 37 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/helmchart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /config/helmchart/Chart.yaml.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: namespace-configuration-operator 3 | version: ${version} 4 | appVersion: ${version} 5 | description: Helm chart that deploys namespace-configuration-operator 6 | keywords: 7 | - volume 8 | - storage 9 | - csi 10 | - expansion 11 | - monitoring 12 | sources: 13 | - https://github.com/redhat-cop/namespace-configuration-operator 14 | engine: gotpl -------------------------------------------------------------------------------- /config/helmchart/cert-manager-ca-injection.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /metadata/annotations 3 | value: 4 | cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" -------------------------------------------------------------------------------- /config/helmchart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: release-namespace 3 | 4 | bases: 5 | - ../local-development/tilt 6 | 7 | patchesJson6902: 8 | - target: 9 | group: admissionregistration.k8s.io 10 | version: v1 11 | kind: MutatingWebhookConfiguration 12 | name: namespace-configuration-operator-mutating-webhook-configuration 13 | path: ./cert-manager-ca-injection.yaml 14 | - target: 15 | group: admissionregistration.k8s.io 16 | version: v1 17 | kind: ValidatingWebhookConfiguration 18 | name: namespace-configuration-validating-webhook-configuration 19 | path: ./cert-manager-ca-injection.yaml -------------------------------------------------------------------------------- /config/helmchart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "namespace-configuration-operator.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 7 | {{- end }} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "namespace-configuration-operator.fullname" -}} 15 | {{- if .Values.fullnameOverride }} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 17 | {{- else }} 18 | {{- $name := default .Chart.Name .Values.nameOverride }} 19 | {{- if contains $name .Release.Name }} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 21 | {{- else }} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 23 | {{- end }} 24 | {{- end }} 25 | {{- end }} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "namespace-configuration-operator.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 32 | {{- end }} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "namespace-configuration-operator.labels" -}} 38 | helm.sh/chart: {{ include "namespace-configuration-operator.chart" . }} 39 | {{ include "namespace-configuration-operator.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end }} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "namespace-configuration-operator.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "namespace-configuration-operator.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end }} 53 | -------------------------------------------------------------------------------- /config/helmchart/templates/certificate.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.enableCertManager }} 2 | apiVersion: cert-manager.io/v1 3 | kind: Issuer 4 | metadata: 5 | name: selfsigned-issuer 6 | spec: 7 | selfSigned: {} 8 | --- 9 | apiVersion: cert-manager.io/v1 10 | kind: Certificate 11 | metadata: 12 | name: serving-cert 13 | spec: 14 | dnsNames: 15 | - namespace-configuration-operator-webhook-service.{{ .Release.Namespace }}.svc 16 | - namespace-configuration-operator-webhook-service.{{ .Release.Namespace }}.svc.cluster.local 17 | issuerRef: 18 | kind: Issuer 19 | name: selfsigned-issuer 20 | secretName: webhook-server-cert 21 | --- 22 | apiVersion: cert-manager.io/v1 23 | kind: Certificate 24 | metadata: 25 | name: metrics-serving-cert 26 | spec: 27 | dnsNames: 28 | - namespace-configuration-operator-metrics-service.{{ .Release.Namespace }}.svc 29 | - namespace-configuration-operator-metrics-service.{{ .Release.Namespace }}.svc.cluster.local 30 | issuerRef: 31 | kind: Issuer 32 | name: selfsigned-issuer 33 | secretName: namespace-configuration-operator-certs 34 | {{ end }} 35 | -------------------------------------------------------------------------------- /config/helmchart/templates/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "namespace-configuration-operator.fullname" . }} 5 | labels: 6 | {{- include "namespace-configuration-operator.labels" . | nindent 4 }} 7 | spec: 8 | selector: 9 | matchLabels: 10 | {{- include "namespace-configuration-operator.selectorLabels" . | nindent 6 }} 11 | control-plane: namespace-configuration-operator 12 | replicas: {{ .Values.replicaCount }} 13 | template: 14 | metadata: 15 | {{- with .Values.podAnnotations }} 16 | annotations: 17 | {{- toYaml . | nindent 8 }} 18 | {{- end }} 19 | labels: 20 | {{- include "namespace-configuration-operator.selectorLabels" . | nindent 8 }} 21 | control-plane: namespace-configuration-operator 22 | spec: 23 | serviceAccountName: controller-manager 24 | {{- with .Values.imagePullSecrets }} 25 | imagePullSecrets: 26 | {{- toYaml . | nindent 8 }} 27 | {{- end }} 28 | containers: 29 | - args: 30 | - --secure-listen-address=0.0.0.0:8443 31 | - --upstream=http://127.0.0.1:8080/ 32 | - --logtostderr=true 33 | - --tls-cert-file=/etc/certs/tls/tls.crt 34 | - --tls-private-key-file=/etc/certs/tls/tls.key 35 | - --v=10 36 | image: "{{ .Values.kube_rbac_proxy.image.repository }}:{{ .Values.kube_rbac_proxy.image.tag }}" 37 | name: kube-rbac-proxy 38 | ports: 39 | - containerPort: 8443 40 | name: https 41 | volumeMounts: 42 | - mountPath: /etc/certs/tls 43 | name: namespace-configuration-operator-certs 44 | imagePullPolicy: {{ .Values.kube_rbac_proxy.image.pullPolicy }} 45 | resources: 46 | {{- toYaml .Values.kube_rbac_proxy.resources | nindent 10 }} 47 | - command: 48 | - /manager 49 | args: 50 | - --leader-elect 51 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 52 | imagePullPolicy: {{ .Values.image.pullPolicy }} 53 | volumeMounts: 54 | - name: webhook-server-cert 55 | readOnly: true 56 | mountPath: /tmp/k8s-webhook-server/serving-certs 57 | {{- with .Values.env }} 58 | env: 59 | {{- toYaml . | nindent 8 }} 60 | {{- end }} 61 | name: {{ .Chart.Name }} 62 | resources: 63 | {{- toYaml .Values.resources | nindent 10 }} 64 | livenessProbe: 65 | httpGet: 66 | path: /healthz 67 | port: 8081 68 | initialDelaySeconds: 15 69 | periodSeconds: 20 70 | readinessProbe: 71 | httpGet: 72 | path: /readyz 73 | port: 8081 74 | initialDelaySeconds: 5 75 | periodSeconds: 10 76 | {{- with .Values.nodeSelector }} 77 | nodeSelector: 78 | {{- toYaml . | nindent 8 }} 79 | {{- end }} 80 | {{- with .Values.affinity }} 81 | affinity: 82 | {{- toYaml . | nindent 8 }} 83 | {{- end }} 84 | {{- with .Values.tolerations }} 85 | tolerations: 86 | {{- toYaml . | nindent 8 }} 87 | {{- end }} 88 | volumes: 89 | - name: namespace-configuration-operator-certs 90 | secret: 91 | defaultMode: 420 92 | secretName: namespace-configuration-operator-certs 93 | - name: webhook-server-cert 94 | secret: 95 | secretName: webhook-server-cert 96 | defaultMode: 420 97 | -------------------------------------------------------------------------------- /config/helmchart/values.yaml.tpl: -------------------------------------------------------------------------------- 1 | # Default values for helm-try. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: ${image_repo} 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: ${version} 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | env: [] 17 | podAnnotations: {} 18 | 19 | resources: 20 | requests: 21 | cpu: 100m 22 | memory: 20Mi 23 | 24 | nodeSelector: {} 25 | 26 | tolerations: [] 27 | 28 | affinity: {} 29 | 30 | kube_rbac_proxy: 31 | image: 32 | repository: quay.io/redhat-cop/kube-rbac-proxy 33 | pullPolicy: IfNotPresent 34 | tag: v0.11.0 35 | resources: 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | 40 | enableMonitoring: true 41 | -------------------------------------------------------------------------------- /config/local-development/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: namespace-configuration-operator-local 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: namespace-configuration- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../rbac -------------------------------------------------------------------------------- /config/local-development/tilt/ca-injection.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /metadata/annotations 3 | value: 4 | service.beta.openshift.io/inject-cabundle: "true" -------------------------------------------------------------------------------- /config/local-development/tilt/env-replace-image.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | image: quay.io/$repo/namespace-configuration-operator:latest 12 | -------------------------------------------------------------------------------- /config/local-development/tilt/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: namespace-configuration-operator 3 | 4 | # Labels to add to all resources and selectors. 5 | #commonLabels: 6 | # someName: someValue 7 | 8 | bases: 9 | - ../../default 10 | - ./service-account.yaml 11 | 12 | 13 | patchesJson6902: 14 | - target: 15 | group: admissionregistration.k8s.io 16 | version: v1 17 | kind: MutatingWebhookConfiguration 18 | name: namespace-configuration-operator-mutating-webhook-configuration 19 | path: ./ca-injection.yaml 20 | - target: 21 | group: admissionregistration.k8s.io 22 | version: v1 23 | kind: ValidatingWebhookConfiguration 24 | name: namespace-configuration-operator-validating-webhook-configuration 25 | path: ./ca-injection.yaml 26 | - target: 27 | group: "" 28 | version: v1 29 | kind: Service 30 | name: namespace-configuration-operator-webhook-service 31 | path: ./secret-injection.yaml 32 | 33 | patchesStrategicMerge: 34 | - replace-image.yaml -------------------------------------------------------------------------------- /config/local-development/tilt/readme.md: -------------------------------------------------------------------------------- 1 | remove namespace 2 | 3 | annotation in webhook service -> webhook-server-cert 4 | annotation in mutating and validating webhooks -------------------------------------------------------------------------------- /config/local-development/tilt/replace-image.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | image: quay.io/namespace-configuration-operator/namespace-configuration-operator:latest 12 | -------------------------------------------------------------------------------- /config/local-development/tilt/secret-injection.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /metadata/annotations 3 | value: 4 | service.alpha.openshift.io/serving-cert-secret-name: webhook-server-cert -------------------------------------------------------------------------------- /config/local-development/tilt/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/local-development/tilt/set-image.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | # 3 | # Used by Tiltfile and make helmchart to dinamically set the controller image. 4 | # 5 | 6 | image="quay.io/$repo/namespace-configuration-operator" 7 | cd ./config/manager && kustomize edit set image controller=$image:latest 8 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: b0b2f089.redhat.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: quay.io/redhat-cop/namespace-configuration-operator 16 | newTag: latest 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: namespace-configuration-operator 6 | openshift.io/cluster-monitoring: "true" 7 | name: system 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: controller-manager 13 | namespace: system 14 | labels: 15 | control-plane: namespace-configuration-operator 16 | spec: 17 | selector: 18 | matchLabels: 19 | control-plane: namespace-configuration-operator 20 | replicas: 1 21 | template: 22 | metadata: 23 | labels: 24 | control-plane: namespace-configuration-operator 25 | spec: 26 | containers: 27 | - command: 28 | - /manager 29 | args: 30 | - --leader-elect 31 | image: controller:latest 32 | name: manager 33 | securityContext: 34 | allowPrivilegeEscalation: false 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8081 39 | scheme: HTTP 40 | initialDelaySeconds: 15 41 | periodSeconds: 20 42 | readinessProbe: 43 | httpGet: 44 | path: /readyz 45 | port: 8081 46 | scheme: HTTP 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | resources: 50 | requests: 51 | cpu: 100m 52 | memory: 20Mi 53 | serviceAccountName: controller-manager 54 | terminationGracePeriodSeconds: 10 55 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../default 3 | - ../samples 4 | - ../scorecard 5 | -------------------------------------------------------------------------------- /config/operatorhub/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: Subscription 3 | metadata: 4 | name: namespace-configuration-operator 5 | spec: 6 | channel: alpha 7 | installPlanApproval: Automatic 8 | name: namespace-configuration-operator 9 | source: community-operators 10 | sourceNamespace: openshift-marketplace 11 | --- 12 | apiVersion: operators.coreos.com/v1 13 | kind: OperatorGroup 14 | metadata: 15 | name: namespace-configuration-operator 16 | spec: 17 | targetNamespaces: [] -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | - role.yaml 4 | - rolebinding.yaml 5 | 6 | configurations: 7 | - kustomizeconfig.yaml 8 | -------------------------------------------------------------------------------- /config/prometheus/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | varReference: 3 | - path: spec/endpoints/tlsConfig/serverName 4 | kind: ServiceMonitor 5 | - path: roleRef/name 6 | kind: RoleBinding 7 | -------------------------------------------------------------------------------- /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: namespace-configuration-operator 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 13 | interval: 30s 14 | port: https 15 | scheme: https 16 | tlsConfig: 17 | ca: 18 | secret: 19 | key: 'tls.crt' 20 | name: namespace-configuration-operator-certs 21 | optional: false 22 | serverName: $(METRICS_SERVICE_NAME).$(METRICS_SERVICE_NAMESPACE).svc 23 | selector: 24 | matchLabels: 25 | control-plane: namespace-configuration-operator 26 | -------------------------------------------------------------------------------- /config/prometheus/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: prometheus-k8s 5 | namespace: system 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - endpoints 11 | - pods 12 | - services 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | -------------------------------------------------------------------------------- /config/prometheus/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: prometheus-k8s 5 | namespace: system 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: $(ROLE_NAME) 10 | subjects: 11 | - kind: ServiceAccount 12 | name: prometheus-k8s 13 | namespace: openshift-monitoring 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: namespace-configuration-operator 6 | annotations: 7 | service.alpha.openshift.io/serving-cert-secret-name: namespace-configuration-operator-certs 8 | name: metrics-service 9 | namespace: system 10 | spec: 11 | ports: 12 | - name: https 13 | port: 8443 14 | protocol: TCP 15 | targetPort: https 16 | selector: 17 | control-plane: namespace-configuration-operator 18 | -------------------------------------------------------------------------------- /config/rbac/groupconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit groupconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: groupconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - groupconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - redhatcop.redhat.io 21 | resources: 22 | - groupconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/groupconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view groupconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: groupconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - groupconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - redhatcop.redhat.io 17 | resources: 18 | - groupconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/namespaceconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit namespaceconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: namespaceconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - namespaceconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - redhatcop.redhat.io 21 | resources: 22 | - namespaceconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/namespaceconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view namespaceconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: namespaceconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - namespaceconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - redhatcop.redhat.io 17 | resources: 18 | - namespaceconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - '*' 10 | resources: 11 | - '*' 12 | verbs: 13 | - '*' 14 | - apiGroups: 15 | - redhatcop.redhat.io 16 | resources: 17 | - groupconfigs 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - redhatcop.redhat.io 28 | resources: 29 | - groupconfigs/finalizers 30 | verbs: 31 | - update 32 | - apiGroups: 33 | - redhatcop.redhat.io 34 | resources: 35 | - groupconfigs/status 36 | verbs: 37 | - get 38 | - patch 39 | - update 40 | - apiGroups: 41 | - redhatcop.redhat.io 42 | resources: 43 | - namespaceconfigs 44 | verbs: 45 | - create 46 | - delete 47 | - get 48 | - list 49 | - patch 50 | - update 51 | - watch 52 | - apiGroups: 53 | - redhatcop.redhat.io 54 | resources: 55 | - namespaceconfigs/finalizers 56 | verbs: 57 | - update 58 | - apiGroups: 59 | - redhatcop.redhat.io 60 | resources: 61 | - namespaceconfigs/status 62 | verbs: 63 | - get 64 | - patch 65 | - update 66 | - apiGroups: 67 | - redhatcop.redhat.io 68 | resources: 69 | - userconfigs 70 | verbs: 71 | - create 72 | - delete 73 | - get 74 | - list 75 | - patch 76 | - update 77 | - watch 78 | - apiGroups: 79 | - redhatcop.redhat.io 80 | resources: 81 | - userconfigs/finalizers 82 | verbs: 83 | - update 84 | - apiGroups: 85 | - redhatcop.redhat.io 86 | resources: 87 | - userconfigs/status 88 | verbs: 89 | - get 90 | - patch 91 | - update 92 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/userconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit userconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: userconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - userconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - redhatcop.redhat.io 21 | resources: 22 | - userconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/userconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view userconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: userconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - redhatcop.redhat.io 9 | resources: 10 | - userconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - redhatcop.redhat.io 17 | resources: 18 | - userconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - redhatcop_v1alpha1_namespaceconfig.yaml 4 | - redhatcop_v1alpha1_userconfig.yaml 5 | - redhatcop_v1alpha1_groupconfig.yaml 6 | # +kubebuilder:scaffold:manifestskustomizesamples 7 | -------------------------------------------------------------------------------- /config/samples/redhatcop_v1alpha1_groupconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: GroupConfig 3 | metadata: 4 | name: test-groupconfig 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: team 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: Namespace 13 | metadata: 14 | name: {{ .Name }}-dev 15 | labels: 16 | group: {{ .Name }} 17 | - objectTemplate: | 18 | apiVersion: v1 19 | kind: Namespace 20 | metadata: 21 | name: {{ .Name }}-qa 22 | labels: 23 | group: {{ .Name }} 24 | - objectTemplate: | 25 | apiVersion: v1 26 | kind: Namespace 27 | metadata: 28 | name: {{ .Name }}-prod 29 | labels: 30 | group: {{ .Name }} 31 | - objectTemplate: | 32 | apiVersion: quota.openshift.io/v1 33 | kind: ClusterResourceQuota 34 | metadata: 35 | name: {{ .Name }}-quota 36 | spec: 37 | quota: 38 | hard: 39 | pods: "4" 40 | requests.cpu: "1" 41 | requests.memory: 1Gi 42 | requests.ephemeral-storage: 2Gi 43 | limits.cpu: "2" 44 | limits.memory: 2Gi 45 | limits.ephemeral-storage: 4Gi 46 | selector: 47 | labels: 48 | matchLabels: 49 | group: {{ .Name }} -------------------------------------------------------------------------------- /config/samples/redhatcop_v1alpha1_namespaceconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: test-namespaceconfig 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: secure 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: ResourceQuota 13 | metadata: 14 | name: standard-sandbox 15 | namespace: {{ .Name }} 16 | spec: 17 | hard: 18 | pods: "4" 19 | requests.cpu: "1" 20 | requests.memory: 1Gi 21 | requests.ephemeral-storage: 2Gi 22 | limits.cpu: "2" 23 | limits.memory: 2Gi 24 | limits.ephemeral-storage: 4Gi 25 | - objectTemplate: | 26 | kind: EgressNetworkPolicy 27 | apiVersion: network.openshift.io/v1 28 | metadata: 29 | name: air-gapped 30 | namespace: {{ .Name }} 31 | spec: 32 | egress: 33 | - type: Deny 34 | to: 35 | cidrSelector: 0.0.0.0/0 -------------------------------------------------------------------------------- /config/samples/redhatcop_v1alpha1_userconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: UserConfig 3 | metadata: 4 | name: test-user-config 5 | spec: 6 | providerName: my-provider 7 | templates: 8 | - objectTemplate: | 9 | apiVersion: v1 10 | kind: Namespace 11 | metadata: 12 | name: {{ .Name }}-sandbox 13 | - objectTemplate: | 14 | apiVersion: v1 15 | kind: ResourceQuota 16 | metadata: 17 | name: standard-sandbox 18 | namespace: {{ .Name }}-sandbox 19 | spec: 20 | hard: 21 | pods: "4" 22 | requests.cpu: "1" 23 | requests.memory: 1Gi 24 | requests.ephemeral-storage: 2Gi 25 | limits.cpu: "2" 26 | limits.memory: 2Gi 27 | limits.ephemeral-storage: 4Gi -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | # +kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.5.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.5.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.5.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.5.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.5.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.5.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource" 5 | "github.com/scylladb/go-set/strset" 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | ) 8 | 9 | // DefaultExcludedPaths represents paths that are exlcuded by default in all resources 10 | var DefaultExcludedPaths = []string{".metadata", ".status", ".spec.replicas"} 11 | 12 | // DefaultExcludedPathsSet represents paths that are exlcuded by default in all resources 13 | var DefaultExcludedPathsSet = strset.New(DefaultExcludedPaths...) 14 | 15 | func GetResources(lockedResources []lockedresource.LockedResource) []client.Object { 16 | resources := []client.Object{} 17 | for _, lockedResource := range lockedResources { 18 | resources = append(resources, &lockedResource.Unstructured) 19 | } 20 | return resources 21 | } 22 | -------------------------------------------------------------------------------- /controllers/groupconfig_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/go-logr/logr" 23 | userv1 "github.com/openshift/api/user/v1" 24 | redhatcopv1alpha1 "github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1" 25 | "github.com/redhat-cop/namespace-configuration-operator/controllers/common" 26 | "github.com/redhat-cop/operator-utils/pkg/util" 27 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller" 28 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch" 29 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource" 30 | "github.com/scylladb/go-set/strset" 31 | "k8s.io/apimachinery/pkg/api/errors" 32 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 | "k8s.io/apimachinery/pkg/labels" 34 | "k8s.io/apimachinery/pkg/types" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/builder" 37 | "sigs.k8s.io/controller-runtime/pkg/client" 38 | "sigs.k8s.io/controller-runtime/pkg/handler" 39 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 | "sigs.k8s.io/controller-runtime/pkg/source" 41 | ) 42 | 43 | // GroupConfigReconciler reconciles a GroupConfig object 44 | type GroupConfigReconciler struct { 45 | lockedresourcecontroller.EnforcingReconciler 46 | Log logr.Logger 47 | controllerName string 48 | } 49 | 50 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=groupconfigs,verbs=get;list;watch;create;update;patch;delete 51 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=groupconfigs/status,verbs=get;update;patch 52 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=groupconfigs/finalizers,verbs=update 53 | // +kubebuilder:rbac:groups=*,resources=*,verbs=* 54 | 55 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 56 | // move the current state of the cluster closer to the desired state. 57 | // TODO(user): Modify the Reconcile function to compare the state specified by 58 | // the GroupConfig object against the actual cluster state, and then 59 | // perform operations to make the cluster state reflect the state specified by 60 | // the user. 61 | // 62 | // For more details, check Reconcile and its Result here: 63 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile 64 | func (r *GroupConfigReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) { 65 | log := r.Log.WithValues("groupconfig", req.NamespacedName) 66 | 67 | // Fetch the GroupConfig instance 68 | instance := &redhatcopv1alpha1.GroupConfig{} 69 | err := r.GetClient().Get(context, req.NamespacedName, instance) 70 | if err != nil { 71 | if errors.IsNotFound(err) { 72 | // Request object not found, could have been deleted after reconcile request. 73 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 74 | // Return and don't requeue 75 | return reconcile.Result{}, nil 76 | } 77 | // Error reading the object - requeue the request. 78 | return reconcile.Result{}, err 79 | } 80 | 81 | if !r.IsInitialized(instance) { 82 | err := r.GetClient().Update(context, instance) 83 | if err != nil { 84 | log.Error(err, "unable to update instance", "instance", instance) 85 | return r.ManageError(context, instance, err) 86 | } 87 | return reconcile.Result{}, nil 88 | } 89 | 90 | if util.IsBeingDeleted(instance) { 91 | if !util.HasFinalizer(instance, r.controllerName) { 92 | return reconcile.Result{}, nil 93 | } 94 | err := r.manageCleanUpLogic(instance) 95 | if err != nil { 96 | log.Error(err, "unable to delete instance", "instance", instance) 97 | return r.ManageError(context, instance, err) 98 | } 99 | util.RemoveFinalizer(instance, r.controllerName) 100 | err = r.GetClient().Update(context, instance) 101 | if err != nil { 102 | log.Error(err, "unable to update instance", "instance", instance) 103 | return r.ManageError(context, instance, err) 104 | } 105 | return reconcile.Result{}, nil 106 | } 107 | 108 | //get selected users 109 | selectedGroups, err := r.getSelectedGroups(context, instance) 110 | if err != nil { 111 | log.Error(err, "unable to get groups selected by", "GroupConfig", instance) 112 | return r.ManageError(context, instance, err) 113 | } 114 | 115 | lockedResources, err := r.getResourceList(instance, selectedGroups) 116 | if err != nil { 117 | log.Error(err, "unable to process resources", "GroupConfig", instance, "groups", selectedGroups) 118 | return r.ManageError(context, instance, err) 119 | } 120 | 121 | err = r.UpdateLockedResources(context, instance, lockedResources, []lockedpatch.LockedPatch{}) 122 | if err != nil { 123 | log.Error(err, "unable to update locked resources") 124 | return r.ManageError(context, instance, err) 125 | } 126 | 127 | return r.ManageSuccess(context, instance) 128 | } 129 | 130 | func (r *GroupConfigReconciler) getResourceList(instance *redhatcopv1alpha1.GroupConfig, groups []userv1.Group) ([]lockedresource.LockedResource, error) { 131 | lockedresources := []lockedresource.LockedResource{} 132 | for _, group := range groups { 133 | lrs, err := lockedresource.GetLockedResourcesFromTemplatesWithRestConfig(instance.Spec.Templates, r.GetRestConfig(), group) 134 | if err != nil { 135 | r.Log.Error(err, "unable to process", "templates", instance.Spec.Templates, "with param", group) 136 | return []lockedresource.LockedResource{}, err 137 | } 138 | lockedresources = append(lockedresources, lrs...) 139 | } 140 | return lockedresources, nil 141 | } 142 | 143 | func (r *GroupConfigReconciler) getSelectedGroups(context context.Context, instance *redhatcopv1alpha1.GroupConfig) ([]userv1.Group, error) { 144 | groupList := &userv1.GroupList{} 145 | 146 | labelSelector, err := metav1.LabelSelectorAsSelector(&instance.Spec.LabelSelector) 147 | if err != nil { 148 | r.Log.Error(err, "unable to create ", "selector from", instance.Spec.LabelSelector) 149 | return []userv1.Group{}, err 150 | } 151 | 152 | annotationSelector, err := metav1.LabelSelectorAsSelector(&instance.Spec.AnnotationSelector) 153 | if err != nil { 154 | r.Log.Error(err, "unable to create ", "selector from", instance.Spec.AnnotationSelector) 155 | return []userv1.Group{}, err 156 | } 157 | 158 | err = r.GetClient().List(context, groupList, &client.ListOptions{ 159 | LabelSelector: labelSelector, 160 | }) 161 | if err != nil { 162 | r.Log.Error(err, "unable to get groups with", "selector", labelSelector) 163 | return []userv1.Group{}, err 164 | } 165 | 166 | selectedGroups := []userv1.Group{} 167 | for _, group := range groupList.Items { 168 | annotationsAsLabels := labels.Set(group.Annotations) 169 | if annotationSelector.Matches(annotationsAsLabels) { 170 | selectedGroups = append(selectedGroups, group) 171 | } 172 | } 173 | 174 | return selectedGroups, nil 175 | } 176 | 177 | func (r *GroupConfigReconciler) findApplicableGroupConfigsFromGroup(ctx context.Context, group userv1.Group) ([]redhatcopv1alpha1.GroupConfig, error) { 178 | groupConfigList := &redhatcopv1alpha1.GroupConfigList{} 179 | err := r.GetClient().List(ctx, groupConfigList, &client.ListOptions{}) 180 | if err != nil { 181 | r.Log.Error(err, "unable to get all userconfigs") 182 | return []redhatcopv1alpha1.GroupConfig{}, err 183 | } 184 | applicableGroupConfigs := []redhatcopv1alpha1.GroupConfig{} 185 | 186 | for _, groupConfig := range groupConfigList.Items { 187 | labelSelector, err := metav1.LabelSelectorAsSelector(&groupConfig.Spec.LabelSelector) 188 | if err != nil { 189 | r.Log.Error(err, "unable to create ", "selector from", groupConfig.Spec.LabelSelector) 190 | return []redhatcopv1alpha1.GroupConfig{}, err 191 | } 192 | 193 | annotationSelector, err := metav1.LabelSelectorAsSelector(&groupConfig.Spec.AnnotationSelector) 194 | if err != nil { 195 | r.Log.Error(err, "unable to create ", "selector from", groupConfig.Spec.AnnotationSelector) 196 | return []redhatcopv1alpha1.GroupConfig{}, err 197 | } 198 | 199 | labelsAslabels := labels.Set(group.GetLabels()) 200 | annotationsAsLabels := labels.Set(group.GetAnnotations()) 201 | if labelSelector.Matches(labelsAslabels) && annotationSelector.Matches(annotationsAsLabels) { 202 | applicableGroupConfigs = append(applicableGroupConfigs, groupConfig) 203 | } 204 | } 205 | 206 | return applicableGroupConfigs, nil 207 | } 208 | 209 | // IsInitialized none 210 | func (r *GroupConfigReconciler) IsInitialized(instance *redhatcopv1alpha1.GroupConfig) bool { 211 | needsUpdate := true 212 | for i := range instance.Spec.Templates { 213 | currentSet := strset.New(instance.Spec.Templates[i].ExcludedPaths...) 214 | if !currentSet.IsEqual(strset.Union(common.DefaultExcludedPathsSet, currentSet)) { 215 | instance.Spec.Templates[i].ExcludedPaths = strset.Union(common.DefaultExcludedPathsSet, currentSet).List() 216 | needsUpdate = false 217 | } 218 | } 219 | if len(instance.Spec.Templates) > 0 && !util.HasFinalizer(instance, r.controllerName) { 220 | util.AddFinalizer(instance, r.controllerName) 221 | needsUpdate = false 222 | } 223 | if len(instance.Spec.Templates) == 0 && util.HasFinalizer(instance, r.controllerName) { 224 | util.RemoveFinalizer(instance, r.controllerName) 225 | needsUpdate = false 226 | } 227 | 228 | return needsUpdate 229 | } 230 | 231 | func (r *GroupConfigReconciler) manageCleanUpLogic(instance *redhatcopv1alpha1.GroupConfig) error { 232 | err := r.Terminate(instance, true) 233 | if err != nil { 234 | r.Log.Error(err, "unable to terminate enforcing reconciler for", "instance", instance) 235 | return err 236 | } 237 | return nil 238 | } 239 | 240 | // SetupWithManager sets up the controller with the Manager. 241 | func (r *GroupConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { 242 | r.controllerName = "groupconfig-controller" 243 | 244 | return ctrl.NewControllerManagedBy(mgr). 245 | For(&redhatcopv1alpha1.GroupConfig{}, builder.WithPredicates(util.ResourceGenerationOrFinalizerChangedPredicate{})). 246 | Watches(&userv1.Group{ 247 | TypeMeta: metav1.TypeMeta{ 248 | Kind: "Group", 249 | }, 250 | }, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 251 | reconcileRequests := []reconcile.Request{} 252 | group := a.(*userv1.Group) 253 | groupConfigs, err := r.findApplicableGroupConfigsFromGroup(ctx, *group) 254 | if err != nil { 255 | r.Log.Error(err, "unable to find applicable GroupConfigs for", "group", group) 256 | return []reconcile.Request{} 257 | } 258 | for _, userconfig := range groupConfigs { 259 | reconcileRequests = append(reconcileRequests, reconcile.Request{ 260 | NamespacedName: types.NamespacedName{ 261 | Name: userconfig.GetName(), 262 | Namespace: userconfig.GetNamespace(), 263 | }, 264 | }) 265 | } 266 | return reconcileRequests 267 | })). 268 | WatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}). 269 | Complete(r) 270 | } 271 | -------------------------------------------------------------------------------- /controllers/namespaceconfig_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "strings" 22 | 23 | "github.com/go-logr/logr" 24 | redhatcopv1alpha1 "github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1" 25 | "github.com/redhat-cop/namespace-configuration-operator/controllers/common" 26 | "github.com/redhat-cop/operator-utils/pkg/util" 27 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller" 28 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch" 29 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource" 30 | "github.com/scylladb/go-set/strset" 31 | corev1 "k8s.io/api/core/v1" 32 | apierrors "k8s.io/apimachinery/pkg/api/errors" 33 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 | "k8s.io/apimachinery/pkg/labels" 35 | "k8s.io/apimachinery/pkg/types" 36 | ctrl "sigs.k8s.io/controller-runtime" 37 | "sigs.k8s.io/controller-runtime/pkg/builder" 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | "sigs.k8s.io/controller-runtime/pkg/handler" 40 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 41 | "sigs.k8s.io/controller-runtime/pkg/source" 42 | ) 43 | 44 | // NamespaceConfigReconciler reconciles a NamespaceConfig object 45 | type NamespaceConfigReconciler struct { 46 | lockedresourcecontroller.EnforcingReconciler 47 | Log logr.Logger 48 | controllerName string 49 | AllowSystemNamespaces bool 50 | } 51 | 52 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=namespaceconfigs,verbs=get;list;watch;create;update;patch;delete 53 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=namespaceconfigs/status,verbs=get;update;patch 54 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=namespaceconfigs/finalizers,verbs=update 55 | // +kubebuilder:rbac:groups=*,resources=*,verbs=* 56 | 57 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 58 | // move the current state of the cluster closer to the desired state. 59 | // TODO(user): Modify the Reconcile function to compare the state specified by 60 | // the NamespaceConfig object against the actual cluster state, and then 61 | // perform operations to make the cluster state reflect the state specified by 62 | // the user. 63 | // 64 | // For more details, check Reconcile and its Result here: 65 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile 66 | func (r *NamespaceConfigReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) { 67 | log := r.Log.WithValues("namespaceconfig", req.NamespacedName) 68 | log.Info("reconciling started") 69 | // Fetch the NamespaceConfig instance 70 | instance := &redhatcopv1alpha1.NamespaceConfig{} 71 | err := r.GetClient().Get(context, req.NamespacedName, instance) 72 | if err != nil { 73 | if apierrors.IsNotFound(err) { 74 | // Request object not found, could have been deleted after reconcile request. 75 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 76 | // Return and don't requeue 77 | return reconcile.Result{}, nil 78 | } 79 | // Error reading the object - requeue the request. 80 | return reconcile.Result{}, err 81 | } 82 | if !r.IsInitialized(instance) { 83 | err := r.GetClient().Update(context, instance) 84 | if err != nil { 85 | log.Error(err, "unable to update instance", "instance", instance) 86 | return r.ManageError(context, instance, err) 87 | } 88 | return reconcile.Result{}, nil 89 | } 90 | 91 | if util.IsBeingDeleted(instance) { 92 | if !util.HasFinalizer(instance, r.controllerName) { 93 | return reconcile.Result{}, nil 94 | } 95 | err := r.manageCleanUpLogic(instance) 96 | if err != nil { 97 | log.Error(err, "unable to delete instance", "instance", instance) 98 | return r.ManageError(context, instance, err) 99 | } 100 | util.RemoveFinalizer(instance, r.controllerName) 101 | err = r.GetClient().Update(context, instance) 102 | if err != nil { 103 | log.Error(err, "unable to update instance", "instance", instance) 104 | return r.ManageError(context, instance, err) 105 | } 106 | return reconcile.Result{}, nil 107 | } 108 | //get selected namespaces 109 | selectedNamespaces, err := r.getSelectedNamespaces(context, instance) 110 | if err != nil { 111 | log.Error(err, "unable to get namespaces selected by", "NamespaceConfig", instance) 112 | return r.ManageError(context, instance, err) 113 | } 114 | 115 | lockedResources, err := r.getResourceList(instance, selectedNamespaces) 116 | if err != nil { 117 | log.Error(err, "unable to process resources", "NamespaceConfig", instance, "namespaces", selectedNamespaces) 118 | return r.ManageError(context, instance, err) 119 | } 120 | 121 | err = r.UpdateLockedResources(context, instance, lockedResources, []lockedpatch.LockedPatch{}) 122 | if err != nil { 123 | log.Error(err, "unable to update locked resources") 124 | return r.ManageError(context, instance, err) 125 | } 126 | 127 | return r.ManageSuccess(context, instance) 128 | } 129 | 130 | func (r *NamespaceConfigReconciler) manageCleanUpLogic(instance *redhatcopv1alpha1.NamespaceConfig) error { 131 | err := r.Terminate(instance, true) 132 | if err != nil { 133 | r.Log.Error(err, "unable to terminate enforcing reconciler for", "instance", instance) 134 | return err 135 | } 136 | return nil 137 | } 138 | 139 | // IsInitialized none 140 | func (r *NamespaceConfigReconciler) IsInitialized(instance *redhatcopv1alpha1.NamespaceConfig) bool { 141 | needsUpdate := true 142 | for i := range instance.Spec.Templates { 143 | currentSet := strset.New(instance.Spec.Templates[i].ExcludedPaths...) 144 | if !currentSet.IsEqual(strset.Union(common.DefaultExcludedPathsSet, currentSet)) { 145 | instance.Spec.Templates[i].ExcludedPaths = strset.Union(common.DefaultExcludedPathsSet, currentSet).List() 146 | needsUpdate = false 147 | } 148 | } 149 | if len(instance.Spec.Templates) > 0 && !util.HasFinalizer(instance, r.controllerName) { 150 | util.AddFinalizer(instance, r.controllerName) 151 | needsUpdate = false 152 | } 153 | if len(instance.Spec.Templates) == 0 && util.HasFinalizer(instance, r.controllerName) { 154 | util.RemoveFinalizer(instance, r.controllerName) 155 | needsUpdate = false 156 | } 157 | 158 | return needsUpdate 159 | } 160 | 161 | func (r *NamespaceConfigReconciler) getResourceList(instance *redhatcopv1alpha1.NamespaceConfig, groups []corev1.Namespace) ([]lockedresource.LockedResource, error) { 162 | lockedresources := []lockedresource.LockedResource{} 163 | for _, group := range groups { 164 | lrs, err := lockedresource.GetLockedResourcesFromTemplatesWithRestConfig(instance.Spec.Templates, r.GetRestConfig(), group) 165 | if err != nil { 166 | r.Log.Error(err, "unable to process", "templates", instance.Spec.Templates, "with param", group) 167 | return []lockedresource.LockedResource{}, err 168 | } 169 | lockedresources = append(lockedresources, lrs...) 170 | } 171 | return lockedresources, nil 172 | } 173 | 174 | func (r *NamespaceConfigReconciler) getSelectedNamespaces(context context.Context, namespaceconfig *redhatcopv1alpha1.NamespaceConfig) ([]corev1.Namespace, error) { 175 | nl := corev1.NamespaceList{} 176 | selector, err := metav1.LabelSelectorAsSelector(&namespaceconfig.Spec.LabelSelector) 177 | if err != nil { 178 | r.Log.Error(err, "unable to create selector from label selector", "selector", &namespaceconfig.Spec.LabelSelector) 179 | return []corev1.Namespace{}, err 180 | } 181 | 182 | annotationSelector, err := metav1.LabelSelectorAsSelector(&namespaceconfig.Spec.AnnotationSelector) 183 | if err != nil { 184 | r.Log.Error(err, "unable to create ", "selector from", namespaceconfig.Spec.AnnotationSelector) 185 | return []corev1.Namespace{}, err 186 | } 187 | 188 | err = r.GetClient().List(context, &nl, &client.ListOptions{LabelSelector: selector}) 189 | if err != nil { 190 | r.Log.Error(err, "unable to list namespaces with selector", "selector", selector) 191 | return []corev1.Namespace{}, err 192 | } 193 | 194 | selectedNamespaces := []corev1.Namespace{} 195 | 196 | for _, namespace := range nl.Items { 197 | annotationsAsLabels := labels.Set(namespace.Annotations) 198 | if annotationSelector.Matches(annotationsAsLabels) && (r.AllowSystemNamespaces || !isProhibitedNamespaceName(namespace.GetName())) { 199 | selectedNamespaces = append(selectedNamespaces, namespace) 200 | } 201 | } 202 | 203 | return selectedNamespaces, nil 204 | } 205 | 206 | func (r *NamespaceConfigReconciler) findApplicableNameSpaceConfigs(ctx context.Context, namespace corev1.Namespace) ([]redhatcopv1alpha1.NamespaceConfig, error) { 207 | if !r.AllowSystemNamespaces && isProhibitedNamespaceName(namespace.GetName()) { 208 | return []redhatcopv1alpha1.NamespaceConfig{}, nil 209 | } 210 | //find all the namespaceconfig 211 | result := []redhatcopv1alpha1.NamespaceConfig{} 212 | ncl := redhatcopv1alpha1.NamespaceConfigList{} 213 | err := r.GetClient().List(ctx, &ncl, &client.ListOptions{}) 214 | if err != nil { 215 | r.Log.Error(err, "unable to retrieve the list of namespace configs") 216 | return []redhatcopv1alpha1.NamespaceConfig{}, err 217 | } 218 | //for each namespaceconfig see if it selects the namespace 219 | for _, nc := range ncl.Items { 220 | labelSelector, err := metav1.LabelSelectorAsSelector(&nc.Spec.LabelSelector) 221 | if err != nil { 222 | r.Log.Error(err, "unable to create selector from label selector", "selector", &nc.Spec.LabelSelector) 223 | return []redhatcopv1alpha1.NamespaceConfig{}, err 224 | } 225 | annotationSelector, err := metav1.LabelSelectorAsSelector(&nc.Spec.AnnotationSelector) 226 | if err != nil { 227 | r.Log.Error(err, "unable to create ", "selector from", nc.Spec.AnnotationSelector) 228 | return []redhatcopv1alpha1.NamespaceConfig{}, err 229 | } 230 | 231 | labelsAslabels := labels.Set(namespace.GetLabels()) 232 | annotationsAsLabels := labels.Set(namespace.GetAnnotations()) 233 | if labelSelector.Matches(labelsAslabels) && annotationSelector.Matches(annotationsAsLabels) { 234 | result = append(result, nc) 235 | } 236 | } 237 | return result, nil 238 | } 239 | 240 | func isProhibitedNamespaceName(name string) bool { 241 | return name == "default" || strings.HasPrefix(name, "openshift-") || strings.HasPrefix(name, "kube-") 242 | } 243 | 244 | // SetupWithManager sets up the controller with the Manager. 245 | func (r *NamespaceConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { 246 | r.controllerName = "namespaceconfig-controller" 247 | return ctrl.NewControllerManagedBy(mgr). 248 | For(&redhatcopv1alpha1.NamespaceConfig{}, builder.WithPredicates(util.ResourceGenerationOrFinalizerChangedPredicate{})). 249 | Watches(&corev1.Namespace{ 250 | TypeMeta: metav1.TypeMeta{ 251 | Kind: "Namespace", 252 | }, 253 | }, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 254 | res := []reconcile.Request{} 255 | ns := a.(*corev1.Namespace) 256 | ncl, err := r.findApplicableNameSpaceConfigs(ctx, *ns) 257 | if err != nil { 258 | r.Log.Error(err, "unable to find applicable NamespaceConfig for namespace", "namespace", ns.Name) 259 | return []reconcile.Request{} 260 | } 261 | for _, namespaceconfig := range ncl { 262 | res = append(res, reconcile.Request{ 263 | NamespacedName: types.NamespacedName{ 264 | Name: namespaceconfig.GetName(), 265 | Namespace: namespaceconfig.GetNamespace(), 266 | }, 267 | }) 268 | } 269 | return res 270 | })). 271 | WatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}). 272 | Complete(r) 273 | } 274 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | //go:build !integration 2 | // +build !integration 3 | 4 | /* 5 | Copyright 2021. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package controllers 21 | 22 | import ( 23 | "path/filepath" 24 | "testing" 25 | 26 | . "github.com/onsi/ginkgo/v2" 27 | . "github.com/onsi/gomega" 28 | "k8s.io/client-go/kubernetes/scheme" 29 | "k8s.io/client-go/rest" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | "sigs.k8s.io/controller-runtime/pkg/envtest" 32 | logf "sigs.k8s.io/controller-runtime/pkg/log" 33 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 34 | 35 | redhatcopv1alpha1 "github.com/redhat-cop/vault-config-operator/api/v1alpha1" 36 | //+kubebuilder:scaffold:imports 37 | ) 38 | 39 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 40 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 41 | 42 | var _ *rest.Config 43 | var k8sClient client.Client 44 | var testEnv *envtest.Environment 45 | 46 | func TestAPIs(t *testing.T) { 47 | RegisterFailHandler(Fail) 48 | 49 | RunSpecs(t, "Controller Suite") 50 | } 51 | 52 | var _ = BeforeSuite(func() { 53 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | ErrorIfCRDPathMissing: true, 59 | } 60 | 61 | cfg, err := testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = redhatcopv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | err = redhatcopv1alpha1.AddToScheme(scheme.Scheme) 69 | Expect(err).NotTo(HaveOccurred()) 70 | 71 | err = redhatcopv1alpha1.AddToScheme(scheme.Scheme) 72 | Expect(err).NotTo(HaveOccurred()) 73 | 74 | //+kubebuilder:scaffold:scheme 75 | 76 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 77 | Expect(err).NotTo(HaveOccurred()) 78 | Expect(k8sClient).NotTo(BeNil()) 79 | 80 | }) 81 | 82 | var _ = AfterSuite(func() { 83 | By("tearing down the test environment") 84 | err := testEnv.Stop() 85 | Expect(err).NotTo(HaveOccurred()) 86 | }) 87 | -------------------------------------------------------------------------------- /controllers/userconfig_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | errs "errors" 22 | 23 | "github.com/go-logr/logr" 24 | userv1 "github.com/openshift/api/user/v1" 25 | redhatcopv1alpha1 "github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1" 26 | "github.com/redhat-cop/namespace-configuration-operator/controllers/common" 27 | "github.com/redhat-cop/operator-utils/pkg/util" 28 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller" 29 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch" 30 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource" 31 | "github.com/scylladb/go-set/strset" 32 | "k8s.io/apimachinery/pkg/api/errors" 33 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 | "k8s.io/apimachinery/pkg/labels" 35 | "k8s.io/apimachinery/pkg/types" 36 | ctrl "sigs.k8s.io/controller-runtime" 37 | "sigs.k8s.io/controller-runtime/pkg/builder" 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | "sigs.k8s.io/controller-runtime/pkg/handler" 40 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 41 | "sigs.k8s.io/controller-runtime/pkg/source" 42 | ) 43 | 44 | // UserConfigReconciler reconciles a UserConfig object 45 | type UserConfigReconciler struct { 46 | lockedresourcecontroller.EnforcingReconciler 47 | Log logr.Logger 48 | controllerName string 49 | } 50 | 51 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=userconfigs,verbs=get;list;watch;create;update;patch;delete 52 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=userconfigs/status,verbs=get;update;patch 53 | // +kubebuilder:rbac:groups=redhatcop.redhat.io,resources=userconfigs/finalizers,verbs=update 54 | // +kubebuilder:rbac:groups=*,resources=*,verbs=* 55 | 56 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 57 | // move the current state of the cluster closer to the desired state. 58 | // TODO(user): Modify the Reconcile function to compare the state specified by 59 | // the UserConfig object against the actual cluster state, and then 60 | // perform operations to make the cluster state reflect the state specified by 61 | // the user. 62 | // 63 | // For more details, check Reconcile and its Result here: 64 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile 65 | func (r *UserConfigReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) { 66 | log := r.Log.WithValues("userconfig", req.NamespacedName) 67 | 68 | // Fetch the UserConfig instance 69 | instance := &redhatcopv1alpha1.UserConfig{} 70 | err := r.GetClient().Get(context, req.NamespacedName, instance) 71 | if err != nil { 72 | if errors.IsNotFound(err) { 73 | // Request object not found, could have been deleted after reconcile request. 74 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 75 | // Return and don't requeue 76 | return reconcile.Result{}, nil 77 | } 78 | // Error reading the object - requeue the request. 79 | return reconcile.Result{}, err 80 | } 81 | 82 | if !r.IsInitialized(instance) { 83 | err := r.GetClient().Update(context, instance) 84 | if err != nil { 85 | log.Error(err, "unable to update instance", "instance", instance) 86 | return r.ManageError(context, instance, err) 87 | } 88 | return reconcile.Result{}, nil 89 | } 90 | 91 | if util.IsBeingDeleted(instance) { 92 | if !util.HasFinalizer(instance, r.controllerName) { 93 | return reconcile.Result{}, nil 94 | } 95 | err := r.manageCleanUpLogic(instance) 96 | if err != nil { 97 | log.Error(err, "unable to delete instance", "instance", instance) 98 | return r.ManageError(context, instance, err) 99 | } 100 | util.RemoveFinalizer(instance, r.controllerName) 101 | err = r.GetClient().Update(context, instance) 102 | if err != nil { 103 | log.Error(err, "unable to update instance", "instance", instance) 104 | return r.ManageError(context, instance, err) 105 | } 106 | return reconcile.Result{}, nil 107 | } 108 | 109 | //get selected users 110 | selectedUsers, err := r.getSelectedUsers(context, instance) 111 | if err != nil { 112 | log.Error(err, "unable to get users selected by", "UserConfig", instance) 113 | return r.ManageError(context, instance, err) 114 | } 115 | 116 | lockedResources, err := r.getResourceList(instance, selectedUsers) 117 | if err != nil { 118 | log.Error(err, "unable to process resources", "UserConfig", instance, "users", selectedUsers) 119 | return r.ManageError(context, instance, err) 120 | } 121 | 122 | err = r.UpdateLockedResources(context, instance, lockedResources, []lockedpatch.LockedPatch{}) 123 | if err != nil { 124 | log.Error(err, "unable to update locked resources") 125 | return r.ManageError(context, instance, err) 126 | } 127 | 128 | return r.ManageSuccess(context, instance) 129 | } 130 | 131 | func (r *UserConfigReconciler) getResourceList(instance *redhatcopv1alpha1.UserConfig, users []userv1.User) ([]lockedresource.LockedResource, error) { 132 | lockedresources := []lockedresource.LockedResource{} 133 | for _, user := range users { 134 | lrs, err := lockedresource.GetLockedResourcesFromTemplatesWithRestConfig(instance.Spec.Templates, r.GetRestConfig(), user) 135 | if err != nil { 136 | r.Log.Error(err, "unable to process", "templates", instance.Spec.Templates, "with param", user) 137 | return []lockedresource.LockedResource{}, err 138 | } 139 | lockedresources = append(lockedresources, lrs...) 140 | } 141 | return lockedresources, nil 142 | } 143 | 144 | func (r *UserConfigReconciler) getSelectedUsers(context context.Context, instance *redhatcopv1alpha1.UserConfig) ([]userv1.User, error) { 145 | userList := &userv1.UserList{} 146 | identitiesList := &userv1.IdentityList{} 147 | 148 | err := r.GetClient().List(context, userList, &client.ListOptions{}) 149 | if err != nil { 150 | r.Log.Error(err, "unable to get all users") 151 | return []userv1.User{}, err 152 | } 153 | 154 | err = r.GetClient().List(context, identitiesList, &client.ListOptions{}) 155 | if err != nil { 156 | r.Log.Error(err, "unable to get all identities") 157 | return []userv1.User{}, err 158 | } 159 | 160 | selectedUsers := []userv1.User{} 161 | 162 | for _, user := range userList.Items { 163 | for _, identity := range identitiesList.Items { 164 | if user.GetUID() == identity.User.UID { 165 | if r.matches(instance, &user, &identity) { 166 | selectedUsers = append(selectedUsers, user) 167 | } 168 | } 169 | } 170 | } 171 | return selectedUsers, nil 172 | } 173 | 174 | func (r *UserConfigReconciler) matches(instance *redhatcopv1alpha1.UserConfig, user *userv1.User, indentity *userv1.Identity) bool { 175 | extraFieldSelector, err := metav1.LabelSelectorAsSelector(&instance.Spec.IdentityExtraFieldSelector) 176 | if err != nil { 177 | r.Log.Error(err, "unable to create ", "selector from", instance.Spec.IdentityExtraFieldSelector) 178 | return false 179 | } 180 | labelSelector, err := metav1.LabelSelectorAsSelector(&instance.Spec.LabelSelector) 181 | if err != nil { 182 | r.Log.Error(err, "unable to create ", "selector from", instance.Spec.LabelSelector) 183 | return false 184 | } 185 | annotationSelector, err := metav1.LabelSelectorAsSelector(&instance.Spec.AnnotationSelector) 186 | if err != nil { 187 | r.Log.Error(err, "unable to create ", "selector from", instance.Spec.AnnotationSelector) 188 | return false 189 | } 190 | 191 | extraFieldAsLabels := labels.Set(indentity.Extra) 192 | labelsAsLabels := labels.Set(user.Labels) 193 | annotationsAsLabels := labels.Set(user.Annotations) 194 | if instance.Spec.ProviderName != "" { 195 | return extraFieldSelector.Matches(extraFieldAsLabels) && labelSelector.Matches(labelsAsLabels) && annotationSelector.Matches(annotationsAsLabels) && indentity.ProviderName == instance.Spec.ProviderName 196 | } 197 | return extraFieldSelector.Matches(extraFieldAsLabels) && labelSelector.Matches(labelsAsLabels) && annotationSelector.Matches(annotationsAsLabels) 198 | } 199 | 200 | func (r *UserConfigReconciler) findApplicableUserConfigsFromIdentities(user *userv1.User, identities []userv1.Identity) ([]redhatcopv1alpha1.UserConfig, error) { 201 | userConfigList := &redhatcopv1alpha1.UserConfigList{} 202 | err := r.GetClient().List(context.TODO(), userConfigList, &client.ListOptions{}) 203 | if err != nil { 204 | r.Log.Error(err, "unable to get all userconfigs") 205 | return []redhatcopv1alpha1.UserConfig{}, err 206 | } 207 | applicableUserConfigs := []redhatcopv1alpha1.UserConfig{} 208 | for _, userConfig := range userConfigList.Items { 209 | for _, identity := range identities { 210 | if r.matches(&userConfig, user, &identity) { 211 | applicableUserConfigs = append(applicableUserConfigs, userConfig) 212 | } 213 | } 214 | } 215 | return applicableUserConfigs, nil 216 | } 217 | 218 | func (r *UserConfigReconciler) findApplicableUserConfigsFromUser(ctx context.Context, user *userv1.User) ([]redhatcopv1alpha1.UserConfig, error) { 219 | identitiesList := &userv1.IdentityList{} 220 | err := r.GetClient().List(ctx, identitiesList, &client.ListOptions{}) 221 | if err != nil { 222 | r.Log.Error(err, "unable to get all identities") 223 | return []redhatcopv1alpha1.UserConfig{}, err 224 | } 225 | matchingIdentities := []userv1.Identity{} 226 | for _, identity := range identitiesList.Items { 227 | cidentity := identity.DeepCopy() 228 | matchingIdentities = append(matchingIdentities, *cidentity) 229 | } 230 | return r.findApplicableUserConfigsFromIdentities(user, matchingIdentities) 231 | } 232 | 233 | // IsInitialized none 234 | func (r *UserConfigReconciler) IsInitialized(instance *redhatcopv1alpha1.UserConfig) bool { 235 | needsUpdate := true 236 | for i := range instance.Spec.Templates { 237 | currentSet := strset.New(instance.Spec.Templates[i].ExcludedPaths...) 238 | if !currentSet.IsEqual(strset.Union(common.DefaultExcludedPathsSet, currentSet)) { 239 | instance.Spec.Templates[i].ExcludedPaths = strset.Union(common.DefaultExcludedPathsSet, currentSet).List() 240 | needsUpdate = false 241 | } 242 | } 243 | if len(instance.Spec.Templates) > 0 && !util.HasFinalizer(instance, r.controllerName) { 244 | util.AddFinalizer(instance, r.controllerName) 245 | needsUpdate = false 246 | } 247 | if len(instance.Spec.Templates) == 0 && util.HasFinalizer(instance, r.controllerName) { 248 | util.RemoveFinalizer(instance, r.controllerName) 249 | needsUpdate = false 250 | } 251 | 252 | return needsUpdate 253 | } 254 | 255 | func (r *UserConfigReconciler) manageCleanUpLogic(instance *redhatcopv1alpha1.UserConfig) error { 256 | err := r.Terminate(instance, true) 257 | if err != nil { 258 | r.Log.Error(err, "unable to terminate enforcing reconciler for", "instance", instance) 259 | return err 260 | } 261 | return nil 262 | } 263 | 264 | func (r *UserConfigReconciler) findUserFromIdentity(ctx context.Context, identity *userv1.Identity) (*userv1.User, error) { 265 | userList := &userv1.UserList{} 266 | err := r.GetClient().List(ctx, userList, &client.ListOptions{}) 267 | if err != nil { 268 | r.Log.Error(err, "unable to get all users") 269 | return &userv1.User{}, err 270 | } 271 | 272 | for _, user := range userList.Items { 273 | r.Log.V(1).Info("comparing", "user uid", user.GetUID(), " and identity uid", identity.User.UID) 274 | if user.GetUID() == identity.User.UID { 275 | return &user, nil 276 | } 277 | } 278 | return &userv1.User{}, errs.New("user not found") 279 | } 280 | 281 | // SetupWithManager sets up the controller with the Manager. 282 | func (r *UserConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { 283 | r.controllerName = "userconfig-controller" 284 | return ctrl.NewControllerManagedBy(mgr). 285 | For(&redhatcopv1alpha1.UserConfig{}, builder.WithPredicates(util.ResourceGenerationOrFinalizerChangedPredicate{})). 286 | Watches(&userv1.User{ 287 | TypeMeta: metav1.TypeMeta{ 288 | Kind: "User", 289 | }, 290 | }, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 291 | reconcileRequests := []reconcile.Request{} 292 | user := a.(*userv1.User) 293 | userConfigs, err := r.findApplicableUserConfigsFromUser(ctx, user) 294 | if err != nil { 295 | r.Log.Error(err, "unable to find applicable UserConfigs for", "user", user) 296 | return []reconcile.Request{} 297 | } 298 | for _, userconfig := range userConfigs { 299 | reconcileRequests = append(reconcileRequests, reconcile.Request{ 300 | NamespacedName: types.NamespacedName{ 301 | Name: userconfig.GetName(), 302 | Namespace: userconfig.GetNamespace(), 303 | }, 304 | }) 305 | } 306 | return reconcileRequests 307 | })). 308 | Watches(&userv1.Identity{ 309 | TypeMeta: metav1.TypeMeta{ 310 | Kind: "Identity", 311 | }, 312 | }, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request { 313 | reconcileRequests := []reconcile.Request{} 314 | identity := a.(*userv1.Identity) 315 | user, err := r.findUserFromIdentity(ctx, identity) 316 | if err != nil { 317 | r.Log.Error(err, "unable to find applicable User for", "identity", identity) 318 | return []reconcile.Request{} 319 | } 320 | userConfigs, err := r.findApplicableUserConfigsFromIdentities(user, []userv1.Identity{*identity}) 321 | if err != nil { 322 | r.Log.Error(err, "unable to find applicable UserConfigs for", "identity", identity) 323 | return []reconcile.Request{} 324 | } 325 | for _, userconfig := range userConfigs { 326 | reconcileRequests = append(reconcileRequests, reconcile.Request{ 327 | NamespacedName: types.NamespacedName{ 328 | Name: userconfig.GetName(), 329 | Namespace: userconfig.GetNamespace(), 330 | }, 331 | }) 332 | } 333 | return reconcileRequests 334 | })). 335 | WatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}). 336 | Complete(r) 337 | } 338 | -------------------------------------------------------------------------------- /examples/namespace-config/multitenant-networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: multitenant 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | multitenant: "true" 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: networking.k8s.io/v1 12 | kind: NetworkPolicy 13 | metadata: 14 | name: allow-from-same-namespace 15 | namespace: {{ .Name }} 16 | spec: 17 | podSelector: 18 | ingress: 19 | - from: 20 | - podSelector: {} 21 | - objectTemplate: | 22 | apiVersion: networking.k8s.io/v1 23 | kind: NetworkPolicy 24 | metadata: 25 | name: allow-from-default-namespace 26 | namespace: {{ .Name }} 27 | spec: 28 | podSelector: 29 | ingress: 30 | - from: 31 | - namespaceSelector: 32 | matchLabels: 33 | name: default -------------------------------------------------------------------------------- /examples/namespace-config/overcommit-limitrange.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: overcommit-limitrange 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | overcommit: "limited" 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: "v1" 12 | kind: "LimitRange" 13 | metadata: 14 | name: "overcommit-limits" 15 | namespace: {{ .Name }} 16 | spec: 17 | limits: 18 | - type: "Container" 19 | maxLimitRequestRatio: 20 | cpu: "100" 21 | memory: 1 22 | -------------------------------------------------------------------------------- /examples/namespace-config/readme.md: -------------------------------------------------------------------------------- 1 | # NamespaceConfig Configuration Examples 2 | 3 | Here is a list of use cases in which the Namespace Configuration Controller can be useful: 4 | 5 | 1. [T-Shirt Sized Quotas](#T-Shirt-Sized-Quotas) 6 | 2. [Default Network Policy](#Default-Network-Policy) 7 | 3. [Defining the Overcommitment Ratio](#Defining-the-Overcommitment-Ratio) 8 | 4. [ServiceAccount with Special Permission](#ServiceAccount-with-Special-Permission) 9 | 5. [Pod with Special Permissions](#Pod-with-Special-Permissions) 10 | 11 | This examples are ported from the previous version of the namespace-configuration-operator and can be used also as guidelines on how to perform the CR conversion. 12 | 13 | 14 | 15 | Examples will be deployed in the `test-namespace-config` (you can pick any other name): 16 | 17 | ```shell 18 | oc new-project test-namespace-config 19 | ``` 20 | 21 | ## T-Shirt Sized Quotas 22 | 23 | During the provisioning of the projects to dev teams some organizations start with T-shirt sized quotas. Here is an example of how this can be done with the Namespace Configuration Controller 24 | 25 | ```yaml 26 | apiVersion: redhatcop.redhat.io/v1alpha1 27 | kind: NamespaceConfig 28 | metadata: 29 | name: small-size 30 | spec: 31 | labelSelector: 32 | matchLabels: 33 | size: small 34 | templates: 35 | - objectTemplate: | 36 | apiVersion: v1 37 | kind: ResourceQuota 38 | metadata: 39 | name: small-size 40 | namespace: {{ .Name }} 41 | spec: 42 | hard: 43 | requests.cpu: "4" 44 | requests.memory: "2Gi" 45 | --- 46 | apiVersion: redhatcop.redhat.io/v1alpha1 47 | kind: NamespaceConfig 48 | metadata: 49 | name: large-size 50 | spec: 51 | labelSelector: 52 | matchLabels: 53 | size: large 54 | templates: 55 | - objectTemplate: | 56 | apiVersion: v1 57 | kind: ResourceQuota 58 | metadata: 59 | name: large-size 60 | namespace: {{ .Name }} 61 | spec: 62 | hard: 63 | requests.cpu: "8" 64 | requests.memory: "4Gi" 65 | ``` 66 | 67 | We can test the above configuration as follows: 68 | 69 | ```shell 70 | oc apply -f examples/namespace-config/tshirt-quotas.yaml 71 | oc new-project large-project 72 | oc label namespace large-project size=large 73 | oc new-project small-project 74 | oc label namespace small-project size=small 75 | ``` 76 | 77 | ## Default Network Policy 78 | 79 | Network policy are like firewall rules. There can be some reasonable defaults. 80 | In most cases isolating one project from other projects is a good way to start. In OpenShift this is the default behavior of the multitenant SDN plugin. 81 | The configuration would be as follows: 82 | 83 | ```yaml 84 | apiVersion: redhatcop.redhat.io/v1alpha1 85 | kind: NamespaceConfig 86 | metadata: 87 | name: multitenant 88 | spec: 89 | labelSelector: 90 | matchLabels: 91 | multitenant: "true" 92 | templates: 93 | - objectTemplate: | 94 | apiVersion: networking.k8s.io/v1 95 | kind: NetworkPolicy 96 | metadata: 97 | name: allow-from-same-namespace 98 | namespace: {{ .Name }} 99 | spec: 100 | podSelector: 101 | ingress: 102 | - from: 103 | - podSelector: {} 104 | - objectTemplate: | 105 | apiVersion: networking.k8s.io/v1 106 | kind: NetworkPolicy 107 | metadata: 108 | name: allow-from-default-namespace 109 | namespace: {{ .Name }} 110 | spec: 111 | podSelector: 112 | ingress: 113 | - from: 114 | - namespaceSelector: 115 | matchLabels: 116 | name: default 117 | ``` 118 | 119 | We can deploy it with the following commands: 120 | 121 | ```shell 122 | oc apply -f examples/namespace-config/multitenant-networkpolicy.yaml 123 | oc new-project multitenant-project 124 | oc label namespace multitenant-project multitenant=true 125 | ``` 126 | 127 | ## Defining the Overcommitment Ratio 128 | 129 | I don't personally use limit range much. I prefer to define quotas and let the developers decide if they need a few large pods or many small pods. 130 | That said limit range can still be useful to define the ratio between request and limit, which at the node level will determine the node overcommitment ratio. 131 | Here is how it can be done: 132 | 133 | ```yaml 134 | apiVersion: redhatcop.redhat.io/v1alpha1 135 | kind: NamespaceConfig 136 | metadata: 137 | name: overcommit-limitrange 138 | spec: 139 | labelSelector: 140 | matchLabels: 141 | overcommit: "limited" 142 | templates: 143 | - objectTemplate: | 144 | apiVersion: "v1" 145 | kind: "LimitRange" 146 | metadata: 147 | name: "overcommit-limits" 148 | namespace: {{ .Name }} 149 | spec: 150 | limits: 151 | - type: "Container" 152 | maxLimitRequestRatio: 153 | cpu: 100 154 | memory: 1 155 | ``` 156 | 157 | We can deploy it with the following commands: 158 | 159 | ```shell 160 | oc apply -f examples/namespace-config/overcommit-limitrange.yaml 161 | oc new-project overcommit-project 162 | oc label namespace overcommit-project overcommit=limited 163 | ``` 164 | 165 | ## ServiceAccount with Special Permission 166 | 167 | Another scenario is an application that needs to talk to the master API and needs specific permissions to do that. As an example, we are creating a service account with the `registry-viewer` and `registry-editor` accounts. Here is what we can do: 168 | 169 | ```yaml 170 | apiVersion: redhatcop.redhat.io/v1alpha1 171 | kind: NamespaceConfig 172 | metadata: 173 | name: special-sa 174 | spec: 175 | labelSelector: 176 | matchLabels: 177 | special-sa: "true" 178 | templates: 179 | - objectTemplate: | 180 | apiVersion: v1 181 | kind: ServiceAccount 182 | metadata: 183 | name: special-sa 184 | namespace: {{ .Name }} 185 | - objectTemplate: | 186 | apiVersion: rbac.authorization.k8s.io/v1 187 | kind: RoleBinding 188 | metadata: 189 | name: special-sa-registry-editor 190 | namespace: {{ .Name }} 191 | roleRef: 192 | apiGroup: rbac.authorization.k8s.io 193 | kind: ClusterRole 194 | name: registry-editor 195 | subjects: 196 | - kind: ServiceAccount 197 | name: special-sa 198 | - objectTemplate: | 199 | apiVersion: rbac.authorization.k8s.io/v1 200 | kind: RoleBinding 201 | metadata: 202 | name: special-sa-registry-viewer 203 | namespace: {{ .Name }} 204 | roleRef: 205 | apiGroup: rbac.authorization.k8s.io 206 | kind: ClusterRole 207 | name: registry-viewer 208 | subjects: 209 | - kind: ServiceAccount 210 | name: special-sa 211 | ``` 212 | 213 | Here is how it can be deployed: 214 | 215 | ```shell 216 | oc apply -f examples/namespace-config/serviceaccount-permissions.yaml 217 | oc new-project special-sa 218 | oc label namespace special-sa special-sa=true 219 | ``` 220 | 221 | ## Pod with Special Permissions 222 | 223 | Another scenario is a pod that needs to run with special permissions, i.e. a custom PodSecurityPolicy, and we don't want to give permission to the dev team to grant PodSecurityPolicy permissions. 224 | In OpenShift SCCs have represented the PodSecurityPolicy since the beginning of the product. 225 | SCCs are not compatible with `namespace-configuration-operator` because of the way SCC profiles are granted to serviceaccounts. 226 | With PodSecurityPolicy, this grant is done simply with a RoleBinding object. 227 | Here is how this might work: 228 | 229 | ```yaml 230 | apiVersion: policy/v1beta1 231 | kind: PodSecurityPolicy 232 | metadata: 233 | name: forbid-privileged-pods 234 | spec: 235 | privileged: false 236 | seLinux: 237 | rule: RunAsAny 238 | supplementalGroups: 239 | rule: RunAsAny 240 | runAsUser: 241 | rule: RunAsAny 242 | fsGroup: 243 | rule: RunAsAny 244 | volumes: 245 | - '*' 246 | --- 247 | kind: ClusterRole 248 | apiVersion: rbac.authorization.k8s.io/v1 249 | metadata: 250 | name: forbid-privileged-pods 251 | rules: 252 | - apiGroups: ['policy'] 253 | resources: ['podsecuritypolicies'] 254 | verbs: ['use'] 255 | resourceNames: 256 | - forbid-privileged-pods 257 | --- 258 | apiVersion: redhatcop.redhat.io/v1alpha1 259 | kind: NamespaceConfig 260 | metadata: 261 | name: unprivileged-pods 262 | spec: 263 | labelSelector: 264 | matchLabels: 265 | unprivileged-pods: "true" 266 | templates: 267 | - objectTemplate: | 268 | apiVersion: v1 269 | kind: ServiceAccount 270 | metadata: 271 | name: unprivileged-pods 272 | namespace: {{ .Name }} 273 | - objectTemplate: | 274 | apiVersion: rbac.authorization.k8s.io/v1 275 | kind: RoleBinding 276 | metadata: 277 | name: unprivileged-pods-rb 278 | namespace: {{ .Name }} 279 | roleRef: 280 | apiGroup: rbac.authorization.k8s.io 281 | kind: ClusterRole 282 | name: forbid-privileged-pods 283 | subjects: 284 | - kind: ServiceAccount 285 | name: unprivileged-pods 286 | ``` 287 | 288 | Here is how this example can be run: 289 | 290 | ```shell 291 | oc apply -f examples/namespace-config/special-pod.yaml 292 | oc new-project special-pod 293 | oc label namespace special-pod unprivileged-pods=true 294 | ``` 295 | 296 | ## Cleaning up 297 | 298 | To clean up the previous example you can run the following: 299 | 300 | ```shell 301 | oc delete -f examples/namespace-config/special-pod.yaml 302 | oc delete -f examples/namespace-config/serviceaccount-permissions.yaml 303 | oc delete -f examples/namespace-config/overcommit-limitrange.yaml 304 | oc delete -f examples/namespace-config/multitenant-networkpolicy.yaml 305 | oc delete -f examples/namespace-config/tshirt-quotas.yaml 306 | oc delete project special-pod special-sa overcommit-project multitenant-project small-project large-project test-namespace-config 307 | ``` 308 | -------------------------------------------------------------------------------- /examples/namespace-config/serviceaccount-permissions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: special-sa 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | special-sa: "true" 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: ServiceAccount 13 | metadata: 14 | name: special-sa 15 | namespace: {{ .Name }} 16 | - objectTemplate: | 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | kind: RoleBinding 19 | metadata: 20 | name: special-sa-registry-editor 21 | namespace: {{ .Name }} 22 | roleRef: 23 | apiGroup: rbac.authorization.k8s.io 24 | kind: ClusterRole 25 | name: registry-editor 26 | subjects: 27 | - kind: ServiceAccount 28 | name: special-sa 29 | - objectTemplate: | 30 | apiVersion: rbac.authorization.k8s.io/v1 31 | kind: RoleBinding 32 | metadata: 33 | name: special-sa-registry-viewer 34 | namespace: {{ .Name }} 35 | roleRef: 36 | apiGroup: rbac.authorization.k8s.io 37 | kind: ClusterRole 38 | name: registry-viewer 39 | subjects: 40 | - kind: ServiceAccount 41 | name: special-sa 42 | -------------------------------------------------------------------------------- /examples/namespace-config/special-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policy/v1beta1 2 | kind: PodSecurityPolicy 3 | metadata: 4 | name: forbid-privileged-pods 5 | spec: 6 | privileged: false 7 | seLinux: 8 | rule: RunAsAny 9 | supplementalGroups: 10 | rule: RunAsAny 11 | runAsUser: 12 | rule: RunAsAny 13 | fsGroup: 14 | rule: RunAsAny 15 | volumes: 16 | - '*' 17 | --- 18 | kind: ClusterRole 19 | apiVersion: rbac.authorization.k8s.io/v1 20 | metadata: 21 | name: forbid-privileged-pods 22 | rules: 23 | - apiGroups: ['policy'] 24 | resources: ['podsecuritypolicies'] 25 | verbs: ['use'] 26 | resourceNames: 27 | - forbid-privileged-pods 28 | --- 29 | apiVersion: redhatcop.redhat.io/v1alpha1 30 | kind: NamespaceConfig 31 | metadata: 32 | name: unprivileged-pods 33 | spec: 34 | labelSelector: 35 | matchLabels: 36 | unprivileged-pods: "true" 37 | templates: 38 | - objectTemplate: | 39 | apiVersion: v1 40 | kind: ServiceAccount 41 | metadata: 42 | name: unprivileged-pods 43 | namespace: {{ .Name }} 44 | - objectTemplate: | 45 | apiVersion: rbac.authorization.k8s.io/v1 46 | kind: RoleBinding 47 | metadata: 48 | name: unprivileged-pods-rb 49 | namespace: {{ .Name }} 50 | roleRef: 51 | apiGroup: rbac.authorization.k8s.io 52 | kind: ClusterRole 53 | name: forbid-privileged-pods 54 | subjects: 55 | - kind: ServiceAccount 56 | name: unprivileged-pods -------------------------------------------------------------------------------- /examples/namespace-config/tshirt-quotas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: small-size 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | size: small 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: ResourceQuota 13 | metadata: 14 | name: small-size 15 | namespace: {{ .Name }} 16 | spec: 17 | hard: 18 | requests.cpu: "4" 19 | requests.memory: "2Gi" 20 | --- 21 | apiVersion: redhatcop.redhat.io/v1alpha1 22 | kind: NamespaceConfig 23 | metadata: 24 | name: large-size 25 | spec: 26 | labelSelector: 27 | matchLabels: 28 | size: large 29 | templates: 30 | - objectTemplate: | 31 | apiVersion: v1 32 | kind: ResourceQuota 33 | metadata: 34 | name: large-size 35 | namespace: {{ .Name }} 36 | spec: 37 | hard: 38 | requests.cpu: "8" 39 | requests.memory: "4Gi" -------------------------------------------------------------------------------- /examples/team-onboarding/admin-no-build.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: admin-no-build 6 | aggregationRule: 7 | clusterRoleSelectors: 8 | - matchLabels: 9 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 10 | rules: 11 | - apiGroups: 12 | - operators.coreos.com 13 | resources: 14 | - subscriptions 15 | verbs: 16 | - create 17 | - update 18 | - patch 19 | - delete 20 | - apiGroups: 21 | - operators.coreos.com 22 | resources: 23 | - clusterserviceversions 24 | - catalogsources 25 | - installplans 26 | - subscriptions 27 | verbs: 28 | - delete 29 | - apiGroups: 30 | - operators.coreos.com 31 | resources: 32 | - clusterserviceversions 33 | - catalogsources 34 | - installplans 35 | - subscriptions 36 | - operatorgroups 37 | verbs: 38 | - get 39 | - list 40 | - watch 41 | - apiGroups: 42 | - packages.operators.coreos.com 43 | resources: 44 | - packagemanifests 45 | - packagemanifests/icon 46 | verbs: 47 | - get 48 | - list 49 | - watch 50 | - apiGroups: 51 | - packages.operators.coreos.com 52 | resources: 53 | - packagemanifests 54 | verbs: 55 | - create 56 | - update 57 | - patch 58 | - delete 59 | - apiGroups: 60 | - "" 61 | resources: 62 | - secrets 63 | - serviceaccounts 64 | verbs: 65 | - create 66 | - delete 67 | - deletecollection 68 | - get 69 | - list 70 | - patch 71 | - update 72 | - watch 73 | - apiGroups: 74 | - "" 75 | - image.openshift.io 76 | resources: 77 | - imagestreamimages 78 | - imagestreammappings 79 | - imagestreams 80 | - imagestreams/secrets 81 | - imagestreamtags 82 | verbs: 83 | - create 84 | - delete 85 | - deletecollection 86 | - get 87 | - list 88 | - patch 89 | - update 90 | - watch 91 | - apiGroups: 92 | - "" 93 | - image.openshift.io 94 | resources: 95 | - imagestreamimports 96 | verbs: 97 | - create 98 | - apiGroups: 99 | - "" 100 | - image.openshift.io 101 | resources: 102 | - imagestreams/layers 103 | verbs: 104 | - get 105 | - update 106 | - apiGroups: 107 | - "" 108 | resources: 109 | - namespaces 110 | verbs: 111 | - get 112 | - apiGroups: 113 | - "" 114 | - project.openshift.io 115 | resources: 116 | - projects 117 | verbs: 118 | - get 119 | - apiGroups: 120 | - "" 121 | resources: 122 | - pods/attach 123 | - pods/exec 124 | - pods/portforward 125 | - pods/proxy 126 | - secrets 127 | - services/proxy 128 | verbs: 129 | - get 130 | - list 131 | - watch 132 | - apiGroups: 133 | - "" 134 | resources: 135 | - serviceaccounts 136 | verbs: 137 | - impersonate 138 | - apiGroups: 139 | - "" 140 | resources: 141 | - pods 142 | - pods/attach 143 | - pods/exec 144 | - pods/portforward 145 | - pods/proxy 146 | verbs: 147 | - create 148 | - delete 149 | - deletecollection 150 | - patch 151 | - update 152 | - apiGroups: 153 | - "" 154 | resources: 155 | - configmaps 156 | - endpoints 157 | - persistentvolumeclaims 158 | - replicationcontrollers 159 | - replicationcontrollers/scale 160 | - secrets 161 | - serviceaccounts 162 | - services 163 | - services/proxy 164 | verbs: 165 | - create 166 | - delete 167 | - deletecollection 168 | - patch 169 | - update 170 | - apiGroups: 171 | - apps 172 | resources: 173 | - daemonsets 174 | - deployments 175 | - deployments/rollback 176 | - deployments/scale 177 | - replicasets 178 | - replicasets/scale 179 | - statefulsets 180 | - statefulsets/scale 181 | verbs: 182 | - create 183 | - delete 184 | - deletecollection 185 | - patch 186 | - update 187 | - apiGroups: 188 | - autoscaling 189 | resources: 190 | - horizontalpodautoscalers 191 | verbs: 192 | - create 193 | - delete 194 | - deletecollection 195 | - patch 196 | - update 197 | - apiGroups: 198 | - batch 199 | resources: 200 | - cronjobs 201 | - jobs 202 | verbs: 203 | - create 204 | - delete 205 | - deletecollection 206 | - patch 207 | - update 208 | - apiGroups: 209 | - extensions 210 | resources: 211 | - daemonsets 212 | - deployments 213 | - deployments/rollback 214 | - deployments/scale 215 | - ingresses 216 | - networkpolicies 217 | - replicasets 218 | - replicasets/scale 219 | - replicationcontrollers/scale 220 | verbs: 221 | - create 222 | - delete 223 | - deletecollection 224 | - patch 225 | - update 226 | - apiGroups: 227 | - policy 228 | resources: 229 | - poddisruptionbudgets 230 | verbs: 231 | - create 232 | - delete 233 | - deletecollection 234 | - patch 235 | - update 236 | - apiGroups: 237 | - networking.k8s.io 238 | resources: 239 | - ingresses 240 | - networkpolicies 241 | verbs: 242 | - create 243 | - delete 244 | - deletecollection 245 | - patch 246 | - update 247 | - apiGroups: 248 | - metrics.k8s.io 249 | resources: 250 | - pods 251 | verbs: 252 | - get 253 | - list 254 | - watch 255 | - apiGroups: 256 | - "" 257 | - image.openshift.io 258 | resources: 259 | - imagestreams 260 | verbs: 261 | - create 262 | # - apiGroups: 263 | # - "" 264 | # - build.openshift.io 265 | # resources: 266 | # - builds/details 267 | # verbs: 268 | # - update 269 | - apiGroups: 270 | - "" 271 | - build.openshift.io 272 | resources: 273 | - builds 274 | verbs: 275 | - get 276 | # - apiGroups: 277 | # - "" 278 | # - build.openshift.io 279 | # resources: 280 | # - buildconfigs 281 | # - buildconfigs/webhooks 282 | # - builds 283 | # verbs: 284 | # - create 285 | # - delete 286 | # - deletecollection 287 | # - get 288 | # - list 289 | # - patch 290 | # - update 291 | # - watch 292 | - apiGroups: 293 | - "" 294 | - build.openshift.io 295 | resources: 296 | - builds/log 297 | verbs: 298 | - get 299 | - list 300 | - watch 301 | # - apiGroups: 302 | # - "" 303 | # - build.openshift.io 304 | # resources: 305 | # - buildconfigs/instantiate 306 | # - buildconfigs/instantiatebinary 307 | # - builds/clone 308 | # verbs: 309 | # - create 310 | # - apiGroups: 311 | # - build.openshift.io 312 | # resources: 313 | # - jenkins 314 | # verbs: 315 | # - edit 316 | # - view 317 | - apiGroups: 318 | - "" 319 | - apps.openshift.io 320 | resources: 321 | - deploymentconfigs 322 | - deploymentconfigs/scale 323 | verbs: 324 | - create 325 | - delete 326 | - deletecollection 327 | - get 328 | - list 329 | - patch 330 | - update 331 | - watch 332 | - apiGroups: 333 | - "" 334 | - apps.openshift.io 335 | resources: 336 | - deploymentconfigrollbacks 337 | - deploymentconfigs/instantiate 338 | - deploymentconfigs/rollback 339 | verbs: 340 | - create 341 | - apiGroups: 342 | - "" 343 | - apps.openshift.io 344 | resources: 345 | - deploymentconfigs/log 346 | - deploymentconfigs/status 347 | verbs: 348 | - get 349 | - list 350 | - watch 351 | - apiGroups: 352 | - "" 353 | - image.openshift.io 354 | resources: 355 | - imagestreams/status 356 | verbs: 357 | - get 358 | - list 359 | - watch 360 | - apiGroups: 361 | - "" 362 | - quota.openshift.io 363 | resources: 364 | - appliedclusterresourcequotas 365 | verbs: 366 | - get 367 | - list 368 | - watch 369 | - apiGroups: 370 | - "" 371 | - route.openshift.io 372 | resources: 373 | - routes 374 | verbs: 375 | - create 376 | - delete 377 | - deletecollection 378 | - get 379 | - list 380 | - patch 381 | - update 382 | - watch 383 | - apiGroups: 384 | - "" 385 | - route.openshift.io 386 | resources: 387 | - routes/custom-host 388 | verbs: 389 | - create 390 | - apiGroups: 391 | - "" 392 | - route.openshift.io 393 | resources: 394 | - routes/status 395 | verbs: 396 | - get 397 | - list 398 | - watch 399 | - apiGroups: 400 | - "" 401 | - template.openshift.io 402 | resources: 403 | - processedtemplates 404 | - templateconfigs 405 | - templateinstances 406 | - templates 407 | verbs: 408 | - create 409 | - delete 410 | - deletecollection 411 | - get 412 | - list 413 | - patch 414 | - update 415 | - watch 416 | - apiGroups: 417 | - extensions 418 | - networking.k8s.io 419 | resources: 420 | - networkpolicies 421 | verbs: 422 | - create 423 | - delete 424 | - deletecollection 425 | - get 426 | - list 427 | - patch 428 | - update 429 | - watch 430 | - apiGroups: 431 | - "" 432 | - build.openshift.io 433 | resources: 434 | - buildlogs 435 | verbs: 436 | - create 437 | - delete 438 | - deletecollection 439 | - get 440 | - list 441 | - patch 442 | - update 443 | - watch 444 | - apiGroups: 445 | - "" 446 | resources: 447 | - resourcequotausages 448 | verbs: 449 | - get 450 | - list 451 | - watch 452 | - apiGroups: 453 | - packages.operators.coreos.com 454 | resources: 455 | - packagemanifests 456 | verbs: 457 | - get 458 | - list 459 | - watch 460 | - apiGroups: 461 | - "" 462 | - image.openshift.io 463 | resources: 464 | - imagestreamimages 465 | - imagestreammappings 466 | - imagestreams 467 | - imagestreamtags 468 | verbs: 469 | - get 470 | - list 471 | - watch 472 | - apiGroups: 473 | - "" 474 | - image.openshift.io 475 | resources: 476 | - imagestreams/layers 477 | verbs: 478 | - get 479 | - apiGroups: 480 | - "" 481 | resources: 482 | - configmaps 483 | - endpoints 484 | - persistentvolumeclaims 485 | - persistentvolumeclaims/status 486 | - pods 487 | - replicationcontrollers 488 | - replicationcontrollers/scale 489 | - serviceaccounts 490 | - services 491 | - services/status 492 | verbs: 493 | - get 494 | - list 495 | - watch 496 | - apiGroups: 497 | - "" 498 | resources: 499 | - bindings 500 | - events 501 | - limitranges 502 | - namespaces/status 503 | - pods/log 504 | - pods/status 505 | - replicationcontrollers/status 506 | - resourcequotas 507 | - resourcequotas/status 508 | verbs: 509 | - get 510 | - list 511 | - watch 512 | - apiGroups: 513 | - "" 514 | resources: 515 | - namespaces 516 | verbs: 517 | - get 518 | - list 519 | - watch 520 | - apiGroups: 521 | - apps 522 | resources: 523 | - controllerrevisions 524 | - daemonsets 525 | - daemonsets/status 526 | - deployments 527 | - deployments/scale 528 | - deployments/status 529 | - replicasets 530 | - replicasets/scale 531 | - replicasets/status 532 | - statefulsets 533 | - statefulsets/scale 534 | - statefulsets/status 535 | verbs: 536 | - get 537 | - list 538 | - watch 539 | - apiGroups: 540 | - autoscaling 541 | resources: 542 | - horizontalpodautoscalers 543 | - horizontalpodautoscalers/status 544 | verbs: 545 | - get 546 | - list 547 | - watch 548 | - apiGroups: 549 | - batch 550 | resources: 551 | - cronjobs 552 | - cronjobs/status 553 | - jobs 554 | - jobs/status 555 | verbs: 556 | - get 557 | - list 558 | - watch 559 | - apiGroups: 560 | - extensions 561 | resources: 562 | - daemonsets 563 | - daemonsets/status 564 | - deployments 565 | - deployments/scale 566 | - deployments/status 567 | - ingresses 568 | - ingresses/status 569 | - networkpolicies 570 | - replicasets 571 | - replicasets/scale 572 | - replicasets/status 573 | - replicationcontrollers/scale 574 | verbs: 575 | - get 576 | - list 577 | - watch 578 | - apiGroups: 579 | - policy 580 | resources: 581 | - poddisruptionbudgets 582 | - poddisruptionbudgets/status 583 | verbs: 584 | - get 585 | - list 586 | - watch 587 | - apiGroups: 588 | - networking.k8s.io 589 | resources: 590 | - ingresses 591 | - ingresses/status 592 | - networkpolicies 593 | verbs: 594 | - get 595 | - list 596 | - watch 597 | - apiGroups: 598 | - "" 599 | - build.openshift.io 600 | resources: 601 | - buildconfigs 602 | - buildconfigs/webhooks 603 | - builds 604 | verbs: 605 | - get 606 | - list 607 | - watch 608 | - apiGroups: 609 | - build.openshift.io 610 | resources: 611 | - jenkins 612 | verbs: 613 | - view 614 | - apiGroups: 615 | - "" 616 | - apps.openshift.io 617 | resources: 618 | - deploymentconfigs 619 | - deploymentconfigs/scale 620 | verbs: 621 | - get 622 | - list 623 | - watch 624 | - apiGroups: 625 | - "" 626 | - route.openshift.io 627 | resources: 628 | - routes 629 | verbs: 630 | - get 631 | - list 632 | - watch 633 | - apiGroups: 634 | - "" 635 | - template.openshift.io 636 | resources: 637 | - processedtemplates 638 | - templateconfigs 639 | - templateinstances 640 | - templates 641 | verbs: 642 | - get 643 | - list 644 | - watch 645 | - apiGroups: 646 | - "" 647 | - build.openshift.io 648 | resources: 649 | - buildlogs 650 | verbs: 651 | - get 652 | - list 653 | - watch 654 | - apiGroups: 655 | - packages.operators.coreos.com 656 | resources: 657 | - packagemanifests 658 | verbs: 659 | - '*' 660 | - apiGroups: 661 | - "" 662 | - authorization.openshift.io 663 | resources: 664 | - rolebindings 665 | - roles 666 | verbs: 667 | - create 668 | - delete 669 | - deletecollection 670 | - get 671 | - list 672 | - patch 673 | - update 674 | - watch 675 | - apiGroups: 676 | - rbac.authorization.k8s.io 677 | resources: 678 | - rolebindings 679 | - roles 680 | verbs: 681 | - create 682 | - delete 683 | - deletecollection 684 | - get 685 | - list 686 | - patch 687 | - update 688 | - watch 689 | - apiGroups: 690 | - "" 691 | - authorization.openshift.io 692 | resources: 693 | - localresourceaccessreviews 694 | - localsubjectaccessreviews 695 | - subjectrulesreviews 696 | verbs: 697 | - create 698 | - apiGroups: 699 | - authorization.k8s.io 700 | resources: 701 | - localsubjectaccessreviews 702 | verbs: 703 | - create 704 | - apiGroups: 705 | - "" 706 | - project.openshift.io 707 | resources: 708 | - projects 709 | verbs: 710 | - delete 711 | - get 712 | - apiGroups: 713 | - "" 714 | - authorization.openshift.io 715 | resources: 716 | - resourceaccessreviews 717 | - subjectaccessreviews 718 | verbs: 719 | - create 720 | - apiGroups: 721 | - "" 722 | - security.openshift.io 723 | resources: 724 | - podsecuritypolicyreviews 725 | - podsecuritypolicyselfsubjectreviews 726 | - podsecuritypolicysubjectreviews 727 | verbs: 728 | - create 729 | - apiGroups: 730 | - "" 731 | - authorization.openshift.io 732 | resources: 733 | - rolebindingrestrictions 734 | verbs: 735 | - get 736 | - list 737 | - watch 738 | # - apiGroups: 739 | # - build.openshift.io 740 | # resources: 741 | # - jenkins 742 | # verbs: 743 | # - admin 744 | # - edit 745 | # - view 746 | - apiGroups: 747 | - "" 748 | - project.openshift.io 749 | resources: 750 | - projects 751 | verbs: 752 | - delete 753 | - get 754 | - patch 755 | - update 756 | - apiGroups: 757 | - "" 758 | - route.openshift.io 759 | resources: 760 | - routes/status 761 | verbs: 762 | - update 763 | -------------------------------------------------------------------------------- /examples/team-onboarding/group-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: GroupConfig 3 | metadata: 4 | name: team-onboarding 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: devteam 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: Namespace 13 | metadata: 14 | name: {{ .Name }}-build 15 | labels: 16 | team: {{ .Name }} 17 | type: build 18 | annotations: 19 | egressip-ipam-operator.redhat-cop.io/egressipam=egressip-ipam 20 | - objectTemplate: | 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: {{ .Name }}-build 25 | namespace: {{ .Name }}-build 26 | roleRef: 27 | apiGroup: rbac.authorization.k8s.io 28 | kind: ClusterRole 29 | name: admin 30 | subjects: 31 | - kind: Group 32 | apiGroup: rbac.authorization.k8s.io 33 | name: {{ .Name }} 34 | - objectTemplate: | 35 | apiVersion: v1 36 | kind: Namespace 37 | metadata: 38 | name: {{ .Name }}-dev 39 | labels: 40 | team: {{ .Name }} 41 | type: run 42 | stage: dev 43 | annotations: 44 | egressip-ipam-operator.redhat-cop.io/egressipam=egressip-ipam 45 | - objectTemplate: | 46 | apiVersion: rbac.authorization.k8s.io/v1 47 | kind: RoleBinding 48 | metadata: 49 | name: {{ .Name }}-dev 50 | namespace: {{ .Name }}-dev 51 | roleRef: 52 | apiGroup: rbac.authorization.k8s.io 53 | kind: ClusterRole 54 | name: admin-no-build 55 | subjects: 56 | - kind: Group 57 | apiGroup: rbac.authorization.k8s.io 58 | name: {{ .Name }} 59 | - objectTemplate: | 60 | apiVersion: v1 61 | kind: Namespace 62 | metadata: 63 | name: {{ .Name }}-qa 64 | labels: 65 | team: {{ .Name }} 66 | type: run 67 | stage: qa 68 | annotations: 69 | egressip-ipam-operator.redhat-cop.io/egressipam=egressip-ipam 70 | - objectTemplate: | 71 | apiVersion: rbac.authorization.k8s.io/v1 72 | kind: RoleBinding 73 | metadata: 74 | name: {{ .Name }}-qa 75 | namespace: {{ .Name }}-qa 76 | roleRef: 77 | apiGroup: rbac.authorization.k8s.io 78 | kind: ClusterRole 79 | name: admin-no-build 80 | subjects: 81 | - kind: Group 82 | apiGroup: rbac.authorization.k8s.io 83 | name: {{ .Name }} 84 | - objectTemplate: | 85 | apiVersion: v1 86 | kind: Namespace 87 | metadata: 88 | name: {{ .Name }}-prod 89 | labels: 90 | team: {{ .Name }} 91 | type: run 92 | stage: prod 93 | annotations: 94 | egressip-ipam-operator.redhat-cop.io/egressipam=egressip-ipam 95 | - objectTemplate: | 96 | apiVersion: rbac.authorization.k8s.io/v1 97 | kind: RoleBinding 98 | metadata: 99 | name: {{ .Name }}-prod 100 | namespace: {{ .Name }}-prod 101 | roleRef: 102 | apiGroup: rbac.authorization.k8s.io 103 | kind: ClusterRole 104 | name: admin-no-build 105 | subjects: 106 | - kind: Group 107 | apiGroup: rbac.authorization.k8s.io 108 | name: {{ .Name }} 109 | - objectTemplate: | 110 | apiVersion: quota.openshift.io/v1 111 | kind: ClusterResourceQuota 112 | metadata: 113 | name: {{ .Name }}-quota 114 | spec: 115 | quota: 116 | hard: 117 | requests.cpu: "50" 118 | requests.memory: 50Gi 119 | requests.ephemeral-storage: 50Gi 120 | selector: 121 | labels: 122 | matchLabels: 123 | team: {{ .Name }} -------------------------------------------------------------------------------- /examples/team-onboarding/groups.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Group 3 | apiVersion: user.openshift.io/v1 4 | metadata: 5 | name: test-group 6 | labels: 7 | type: devteam 8 | users: 9 | - user1 10 | - user3 11 | --- 12 | kind: Group 13 | apiVersion: user.openshift.io/v1 14 | metadata: 15 | name: test-group2 16 | labels: 17 | type: devteam 18 | users: 19 | - user2 20 | - user4 -------------------------------------------------------------------------------- /examples/team-onboarding/namespace-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: build-network-policy 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: build 9 | templates: 10 | - objectTemplate: | 11 | kind: EgressNetworkPolicy 12 | apiVersion: v1 13 | metadata: 14 | name: build-egress-policy 15 | namespace: {{ .Name }} 16 | spec: 17 | egress: 18 | - type: Allow 19 | to: 20 | dnsName: quay.io 21 | - type: Allow 22 | to: 23 | dnsName: corporate.gitlab.com 24 | - type: Allow 25 | to: 26 | dnsName: mvnrepository.com 27 | - type: Deny 28 | to: 29 | cidrSelector: 0.0.0.0/0 30 | --- 31 | apiVersion: redhatcop.redhat.io/v1alpha1 32 | kind: NamespaceConfig 33 | metadata: 34 | name: run-network-policy 35 | spec: 36 | labelSelector: 37 | matchLabels: 38 | type: run 39 | templates: 40 | - objectTemplate: | 41 | kind: EgressNetworkPolicy 42 | apiVersion: v1 43 | metadata: 44 | name: run-egress-policy 45 | namespace: {{ .Name }} 46 | spec: 47 | egress: 48 | - type: Allow 49 | to: 50 | cidrSelector: 10.20.32.0/0 51 | - type: Allow 52 | to: 53 | cidrSelector: 10.20.0.0/0 54 | - type: Deny 55 | to: 56 | cidrSelector: 0.0.0.0/0 57 | --- 58 | apiVersion: redhatcop.redhat.io/v1alpha1 59 | kind: NamespaceConfig 60 | metadata: 61 | name: team-east-west-network-policy 62 | spec: 63 | labelSelector: 64 | matchExpressions: 65 | - {key: type, operator: In, values: [run,build]} 66 | templates: 67 | - objectTemplate: | 68 | apiVersion: networking.k8s.io/v1 69 | kind: NetworkPolicy 70 | metadata: 71 | name: allow-from-same-namespace 72 | namespace: {{ .Name }} 73 | spec: 74 | podSelector: 75 | ingress: 76 | - from: 77 | - podSelector: {} 78 | policyTypes: 79 | - Ingress 80 | - objectTemplate: | 81 | apiVersion: networking.k8s.io/v1 82 | kind: NetworkPolicy 83 | metadata: 84 | name: allow-from-openshift-ingress 85 | namespace: {{ .Name }} 86 | spec: 87 | ingress: 88 | - from: 89 | - namespaceSelector: 90 | matchLabels: 91 | network.openshift.io/policy-group: ingress 92 | podSelector: {} 93 | policyTypes: 94 | - Ingress 95 | 96 | -------------------------------------------------------------------------------- /examples/team-onboarding/readme.md: -------------------------------------------------------------------------------- 1 | # Team Onboarding 2 | 3 | This examples showcases a possible onboarding process for a team. We assume the following: 4 | 5 | 1. Teams are identified by a groups in an external Identity Provider System (such as LDAP). 6 | 2. There is a group sync job that between LDAP and OCP 7 | 3. Groups marked with a label `type: devteam` need to be on-boarded. (this assumption is needed to showcase some feature and not really required, another option can be that all synched teams need to be on-boarded). 8 | 9 | We have the following requirements: 10 | 11 | 1. Each team will get 4 namespaces: `-build`, `-dev`, `-qa` and `-prod`. 12 | 2. These four projects receive a multiproject quota, it is up to the team to manage it. 13 | 3. Builds can occur only in the `-project`. 14 | 4. Each project will have automatically assigned egress IPs (we assume the [egressip-ipam-operator](https://github.com/redhat-cop/egressip-ipam-operator) is installed). 15 | 5. Build projects can communicate only with a set of predefined endpoints (some of them out of the corporate network), but cannot communicate with the corporate network. 16 | 6. Run projects can communicate only with the corporate network (represented by this CIDR: `10.20.0.0/0`), with the exclusion of the OCP nodes (represented by this CIDR `10.20.32.0/0`). 17 | 7. By default each project cannot communicate with other projects in teh cluster, but the team is given the ability to manage their own network policies. 18 | 19 | For this scenario we will need to configure several resources. Let's start from the GroupConfig: 20 | 21 | ## Create the admin-no-build cluster role 22 | 23 | ```shell 24 | oc apply -f ./examples/team-onboarding/admin-no-build.yaml 25 | ``` 26 | 27 | ## Create the needed UserConfig and NamespaceConfig 28 | 29 | ```shell 30 | oc apply -f ./examples/team-onboarding/group-config.yaml 31 | oc apply -f ./examples/team-onboarding/namespace-config.yaml 32 | ``` 33 | 34 | ## Create users and groups 35 | 36 | ```shell 37 | oc apply -f ./examples/user-sandbox/users.yaml 38 | for username in user1 user2 ; do 39 | export username 40 | export uid=$(oc get user $username -o jsonpath='{.metadata.uid}') 41 | cat ./examples/user-sandbox/identities.yaml | envsubst | oc apply -f - 42 | done 43 | oc apply -f ./examples/team-onboarding/groups.yaml 44 | ``` 45 | 46 | now you can verify the result by impersonating user1 (impersonation will actually not work until this is resolved [MSTR-1000](https://issues.redhat.com/browse/MSTR-1000) ) 47 | 48 | ```shell 49 | oc projects --as=user1 50 | ``` 51 | -------------------------------------------------------------------------------- /examples/user-sandbox/identities.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: user.openshift.io/v1 2 | kind: Identity 3 | metadata: 4 | name: my-provider:${username} 5 | providerName: my-provider 6 | providerUserName: ${username} 7 | user: 8 | name: ${username} 9 | uid: ${uid} -------------------------------------------------------------------------------- /examples/user-sandbox/readme.md: -------------------------------------------------------------------------------- 1 | # Developer SandBox 2 | 3 | This example use case is about creating an onboarding process for a cluster in which users get their own sandbox and can freely experiment. 4 | The requirements for this onboarding process is the following: 5 | 6 | 1. user logging in with the provider `my-provider` get a sandbox, which is a namespace with name `-sandbox` 7 | 2. this namespace will have a resource quota defined on it to limit the resources usable by each user. 8 | 3. user cannot communicate with anything else within the corporate network (represented by this CIDR: `10.20.0.0/0`), but they can open connections to Internet services. 9 | 4. by default sandboxes cannot communicate with other sandboxes, but user are given the ability to connect different sandboxes by managing their own network policies. 10 | 11 | An `UserConfig` CR that would satisfy those requirements would look like this: 12 | 13 | ```yaml 14 | apiVersion: redhatcop.redhat.io/v1alpha1 15 | kind: UserConfig 16 | metadata: 17 | name: user-sandbox 18 | spec: 19 | providerName: my-provider 20 | templates: 21 | - objectTemplate: | 22 | apiVersion: v1 23 | kind: Namespace 24 | metadata: 25 | name: {{ .Name }}-sandbox 26 | - objectTemplate: | 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: RoleBinding 29 | metadata: 30 | name: {{ .Name }}-sandbox 31 | namespace: {{ .Name }}-sandbox 32 | roleRef: 33 | apiGroup: rbac.authorization.k8s.io 34 | kind: ClusterRole 35 | name: admin 36 | subjects: 37 | - kind: User 38 | apiGroup: rbac.authorization.k8s.io 39 | name: {{ .Name }} 40 | - objectTemplate: | 41 | apiVersion: v1 42 | kind: ResourceQuota 43 | metadata: 44 | name: standard-sandbox 45 | namespace: {{ .Name }}-sandbox 46 | spec: 47 | hard: 48 | requests.cpu: "1" 49 | requests.memory: 1Gi 50 | requests.ephemeral-storage: 2Gi 51 | - objectTemplate: | 52 | kind: EgressNetworkPolicy 53 | apiVersion: network.openshift.io/v1 54 | metadata: 55 | name: air-gapped-sandbox 56 | namespace: {{ .Name }}-sandbox 57 | spec: 58 | egress: 59 | - type: Deny 60 | to: 61 | cidrSelector: 10.20.0.0/0 62 | - objectTemplate: | 63 | apiVersion: networking.k8s.io/v1 64 | kind: NetworkPolicy 65 | metadata: 66 | name: allow-from-same-namespace 67 | namespace: {{ .Name }}-sandbox 68 | spec: 69 | podSelector: 70 | ingress: 71 | - from: 72 | - podSelector: {} 73 | policyTypes: 74 | - Ingress 75 | - objectTemplate: | 76 | apiVersion: networking.k8s.io/v1 77 | kind: NetworkPolicy 78 | metadata: 79 | name: allow-from-openshift-ingress 80 | namespace: {{ .Name }}-sandbox 81 | spec: 82 | ingress: 83 | - from: 84 | - namespaceSelector: 85 | matchLabels: 86 | network.openshift.io/policy-group: ingress 87 | podSelector: {} 88 | policyTypes: 89 | - Ingress 90 | ``` 91 | 92 | Here is how you can test it: 93 | 94 | ```shell 95 | oc apply -f ./examples/user-sandbox/user-config.yaml 96 | oc apply -f ./examples/user-sandbox/users.yaml 97 | for username in user1 user2 ; do 98 | export username 99 | export uid=$(oc get user $username -o jsonpath='{.metadata.uid}') 100 | cat ./examples/user-sandbox/identities.yaml | envsubst | oc apply -f - 101 | done 102 | ``` 103 | 104 | now impersonate either `user1` or `user2` and explore. 105 | 106 | ```shell 107 | oc get projects --as=user1 108 | ``` 109 | -------------------------------------------------------------------------------- /examples/user-sandbox/user-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: UserConfig 3 | metadata: 4 | name: user-sandbox 5 | spec: 6 | providerName: my-provider 7 | templates: 8 | - objectTemplate: | 9 | apiVersion: v1 10 | kind: Namespace 11 | metadata: 12 | name: {{ .Name }}-sandbox 13 | - objectTemplate: | 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: RoleBinding 16 | metadata: 17 | name: {{ .Name }}-sandbox 18 | namespace: {{ .Name }}-sandbox 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: admin 23 | subjects: 24 | - kind: User 25 | apiGroup: rbac.authorization.k8s.io 26 | name: {{ .Name }} 27 | - objectTemplate: | 28 | apiVersion: v1 29 | kind: ResourceQuota 30 | metadata: 31 | name: standard-sandbox 32 | namespace: {{ .Name }}-sandbox 33 | spec: 34 | hard: 35 | pods: "4" 36 | requests.cpu: "1" 37 | requests.memory: 1Gi 38 | requests.ephemeral-storage: 2Gi 39 | limits.cpu: "2" 40 | limits.memory: 2Gi 41 | limits.ephemeral-storage: 4Gi 42 | - objectTemplate: | 43 | kind: EgressNetworkPolicy 44 | apiVersion: network.openshift.io/v1 45 | metadata: 46 | name: air-gapped-sandbox 47 | namespace: {{ .Name }}-sandbox 48 | spec: 49 | egress: 50 | - type: Deny 51 | to: 52 | cidrSelector: 10.20.0.0/0 53 | - objectTemplate: | 54 | apiVersion: networking.k8s.io/v1 55 | kind: NetworkPolicy 56 | metadata: 57 | name: allow-from-same-namespace 58 | namespace: {{ .Name }}-sandbox 59 | spec: 60 | podSelector: 61 | ingress: 62 | - from: 63 | - podSelector: {} 64 | policyTypes: 65 | - Ingress 66 | - objectTemplate: | 67 | apiVersion: networking.k8s.io/v1 68 | kind: NetworkPolicy 69 | metadata: 70 | name: allow-from-openshift-ingress 71 | namespace: {{ .Name }}-sandbox 72 | spec: 73 | ingress: 74 | - from: 75 | - namespaceSelector: 76 | matchLabels: 77 | network.openshift.io/policy-group: ingress 78 | podSelector: {} 79 | policyTypes: 80 | - Ingress -------------------------------------------------------------------------------- /examples/user-sandbox/users.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: user.openshift.io/v1 3 | kind: User 4 | metadata: 5 | name: user1 6 | identities: 7 | - my-provider:test-user-config 8 | --- 9 | apiVersion: user.openshift.io/v1 10 | kind: User 11 | metadata: 12 | name: user2 13 | identities: 14 | - my-provider:test-user-config2 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/redhat-cop/namespace-configuration-operator 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.4 6 | 7 | require ( 8 | github.com/go-logr/logr v1.2.4 9 | github.com/onsi/ginkgo/v2 v2.11.0 10 | github.com/onsi/gomega v1.27.10 11 | github.com/openshift/api v0.0.0-20231020115248-f404f2bc3524 12 | github.com/redhat-cop/operator-utils v1.3.8 13 | github.com/redhat-cop/vault-config-operator v0.8.24 14 | github.com/scylladb/go-set v1.0.2 15 | k8s.io/api v0.28.2 16 | k8s.io/apimachinery v0.28.2 17 | k8s.io/client-go v0.28.2 18 | sigs.k8s.io/controller-runtime v0.15.2 19 | ) 20 | 21 | require ( 22 | github.com/BurntSushi/toml v1.3.2 // indirect 23 | github.com/Masterminds/goutils v1.1.1 // indirect 24 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 25 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 26 | github.com/agext/levenshtein v1.2.1 // indirect 27 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cenkalti/backoff/v3 v3.0.0 // indirect 30 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 33 | github.com/evanphx/json-patch v5.7.0+incompatible // indirect 34 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 35 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect 36 | github.com/fsnotify/fsnotify v1.6.0 // indirect 37 | github.com/go-errors/errors v1.4.2 // indirect 38 | github.com/go-jose/go-jose/v3 v3.0.0 // indirect 39 | github.com/go-logr/zapr v1.2.4 // indirect 40 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 41 | github.com/go-openapi/jsonreference v0.20.2 // indirect 42 | github.com/go-openapi/swag v0.22.3 // indirect 43 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 44 | github.com/gogo/protobuf v1.3.2 // indirect 45 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 46 | github.com/golang/protobuf v1.5.3 // indirect 47 | github.com/google/gnostic-models v0.6.8 // indirect 48 | github.com/google/go-cmp v0.5.9 // indirect 49 | github.com/google/gofuzz v1.2.0 // indirect 50 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 51 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 52 | github.com/google/uuid v1.3.0 // indirect 53 | github.com/hashicorp/errwrap v1.1.0 // indirect 54 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 55 | github.com/hashicorp/go-multierror v1.1.1 // indirect 56 | github.com/hashicorp/go-retryablehttp v0.6.6 // indirect 57 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 58 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect 59 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 60 | github.com/hashicorp/go-sockaddr v1.0.2 // indirect 61 | github.com/hashicorp/hcl v1.0.0 // indirect 62 | github.com/hashicorp/hcl/v2 v2.17.0 // indirect 63 | github.com/hashicorp/vault/api v1.9.2 // indirect 64 | github.com/huandu/xstrings v1.3.3 // indirect 65 | github.com/imdario/mergo v0.3.12 // indirect 66 | github.com/josharian/intern v1.0.0 // indirect 67 | github.com/json-iterator/go v1.1.12 // indirect 68 | github.com/mailru/easyjson v0.7.7 // indirect 69 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 70 | github.com/mitchellh/copystructure v1.0.0 // indirect 71 | github.com/mitchellh/go-homedir v1.1.0 // indirect 72 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 73 | github.com/mitchellh/mapstructure v1.5.0 // indirect 74 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 76 | github.com/modern-go/reflect2 v1.0.2 // indirect 77 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 78 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 79 | github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 // indirect 80 | github.com/pkg/errors v0.9.1 // indirect 81 | github.com/prometheus/client_golang v1.16.0 // indirect 82 | github.com/prometheus/client_model v0.4.0 // indirect 83 | github.com/prometheus/common v0.44.0 // indirect 84 | github.com/prometheus/procfs v0.10.1 // indirect 85 | github.com/ryanuber/go-glob v1.0.0 // indirect 86 | github.com/shopspring/decimal v1.2.0 // indirect 87 | github.com/spf13/cast v1.3.1 // indirect 88 | github.com/spf13/pflag v1.0.5 // indirect 89 | github.com/xlab/treeprint v1.2.0 // indirect 90 | github.com/zclconf/go-cty v1.13.0 // indirect 91 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect 92 | go.uber.org/atomic v1.10.0 // indirect 93 | go.uber.org/multierr v1.11.0 // indirect 94 | go.uber.org/zap v1.24.0 // indirect 95 | golang.org/x/crypto v0.11.0 // indirect 96 | golang.org/x/net v0.13.0 // indirect 97 | golang.org/x/oauth2 v0.8.0 // indirect 98 | golang.org/x/sync v0.2.0 // indirect 99 | golang.org/x/sys v0.10.0 // indirect 100 | golang.org/x/term v0.10.0 // indirect 101 | golang.org/x/text v0.11.0 // indirect 102 | golang.org/x/time v0.3.0 // indirect 103 | golang.org/x/tools v0.9.3 // indirect 104 | gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect 105 | google.golang.org/appengine v1.6.7 // indirect 106 | google.golang.org/protobuf v1.30.0 // indirect 107 | gopkg.in/inf.v0 v0.9.1 // indirect 108 | gopkg.in/yaml.v2 v2.4.0 // indirect 109 | gopkg.in/yaml.v3 v3.0.1 // indirect 110 | k8s.io/apiextensions-apiserver v0.27.2 // indirect 111 | k8s.io/cli-runtime v0.28.2 // indirect 112 | k8s.io/component-base v0.28.2 // indirect 113 | k8s.io/klog/v2 v2.100.1 // indirect 114 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 115 | k8s.io/kubectl v0.28.2 // indirect 116 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 117 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 118 | sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect 119 | sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect 120 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 121 | sigs.k8s.io/yaml v1.3.0 // indirect 122 | ) 123 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 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 | */ -------------------------------------------------------------------------------- /integration/cluster-kind.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: InitConfiguration 8 | nodeRegistration: 9 | kubeletExtraArgs: 10 | node-labels: "ingress-ready=true" 11 | extraPortMappings: 12 | - containerPort: 80 13 | hostPort: 8080 14 | protocol: TCP 15 | - containerPort: 443 16 | hostPort: 8443 17 | protocol: TCP -------------------------------------------------------------------------------- /integration/helm/ingress-nginx/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /integration/helm/ingress-nginx/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ingress-nginx 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 1.1.1 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.1.1" 25 | -------------------------------------------------------------------------------- /integration/helm/ingress-nginx/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "ingress-nginx.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "ingress-nginx.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "ingress-nginx.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "ingress-nginx.labels" -}} 37 | helm.sh/chart: {{ include "ingress-nginx.chart" . }} 38 | {{ include "ingress-nginx.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "ingress-nginx.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "ingress-nginx.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "ingress-nginx.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "ingress-nginx.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /integration/helm/ingress-nginx/values.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/namespace-configuration-operator/8eaa74be68e8a1489757ed50d41c2dfdfb95c988/integration/helm/ingress-nginx/values.yaml -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: prometheus-rbac 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "prometheus-rbac.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "prometheus-rbac.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "prometheus-rbac.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "prometheus-rbac.labels" -}} 37 | helm.sh/chart: {{ include "prometheus-rbac.chart" . }} 38 | {{ include "prometheus-rbac.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "prometheus-rbac.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "prometheus-rbac.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "prometheus-rbac.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "prometheus-rbac.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/templates/clusterrole-prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: prometheus 5 | rules: 6 | - apiGroups: [""] 7 | resources: 8 | - nodes 9 | - nodes/metrics 10 | - services 11 | - endpoints 12 | - pods 13 | verbs: ["get", "list", "watch"] 14 | - apiGroups: [""] 15 | resources: 16 | - configmaps 17 | verbs: ["get"] 18 | - apiGroups: 19 | - networking.k8s.io 20 | resources: 21 | - ingresses 22 | verbs: ["get", "list", "watch"] 23 | - nonResourceURLs: ["/metrics"] 24 | verbs: ["get"] 25 | -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/templates/clusterrolebinding-prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: prometheus 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: prometheus 9 | subjects: 10 | - kind: ServiceAccount 11 | name: kube-prometheus-stack-prometheus 12 | namespace: {{ .Release.Namespace }} 13 | -------------------------------------------------------------------------------- /integration/helm/prometheus-rbac/values.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/namespace-configuration-operator/8eaa74be68e8a1489757ed50d41c2dfdfb95c988/integration/helm/prometheus-rbac/values.yaml -------------------------------------------------------------------------------- /integration/kube-prometheus-stack-values.yaml: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml 2 | 3 | ## Configuration for alertmanager 4 | ## ref: https://prometheus.io/docs/alerting/alertmanager/ 5 | ## 6 | alertmanager: 7 | 8 | ## Deploy alertmanager 9 | ## 10 | enabled: false 11 | 12 | ## Using default values from https://github.com/grafana/helm-charts/blob/main/charts/grafana/values.yaml 13 | ## 14 | grafana: 15 | enabled: false 16 | 17 | ## Component scraping kube state metrics 18 | ## 19 | kubeStateMetrics: 20 | enabled: false 21 | 22 | ## Deploy node exporter as a daemonset to all nodes 23 | ## 24 | nodeExporter: 25 | enabled: false 26 | 27 | ## Deploy a Prometheus instance 28 | ## 29 | prometheus: 30 | 31 | ## Settings affecting prometheusSpec 32 | ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#prometheusspec 33 | ## 34 | prometheusSpec: 35 | 36 | ## If true, a nil or {} value for prometheus.prometheusSpec.serviceMonitorSelector will cause the 37 | ## prometheus resource to be created with selectors based on values in the helm deployment, 38 | ## which will also match the servicemonitors created 39 | ## 40 | serviceMonitorSelectorNilUsesHelmValues: false 41 | 42 | ## Containers allows injecting additional containers. This is meant to allow adding an authentication proxy to a Prometheus pod. 43 | ## if using proxy extraContainer update targetPort with proxy container port 44 | 45 | ## The test-metrics sidecar will become ready when metrics are available. 46 | containers: 47 | - image: busybox:latest 48 | name: test-metrics 49 | command: 50 | - /bin/sh 51 | - -c 52 | - | 53 | while true; do 54 | out=$(wget -O - --post-data="query=controller_runtime_active_workers%7Bnamespace%3D%22namespace-configuration-operator-local%22%7D%0A" localhost:9090/api/v1/query) 55 | if [[ -z "$(echo ${out})" || "$(echo $out | grep -F '{"status":"success","data":{"resultType":"vector","result":[]}}')" ]]; then 56 | echo "No Metrics yet..." 57 | echo "${out}" 58 | else 59 | echo "Metrics is working..." 60 | echo "${out}" > /tmp/ready 61 | cat /tmp/ready 62 | fi 63 | sleep 5s 64 | done 65 | readinessProbe: 66 | exec: 67 | command: 68 | - cat 69 | - /tmp/ready 70 | initialDelaySeconds: 5 71 | periodSeconds: 15 72 | failureThreshold: 30 73 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Red Hat Community of Practice. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "os" 23 | "strconv" 24 | "time" 25 | 26 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 27 | // to ensure that exec-entrypoint and run can make use of them. 28 | userv1 "github.com/openshift/api/user/v1" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | "k8s.io/apimachinery/pkg/runtime/schema" 31 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 32 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 33 | _ "k8s.io/client-go/plugin/pkg/client/auth" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | "sigs.k8s.io/controller-runtime/pkg/healthz" 36 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 37 | 38 | redhatcopv1alpha1 "github.com/redhat-cop/namespace-configuration-operator/api/v1alpha1" 39 | "github.com/redhat-cop/namespace-configuration-operator/controllers" 40 | "github.com/redhat-cop/operator-utils/pkg/util/discoveryclient" 41 | "github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller" 42 | // +kubebuilder:scaffold:imports 43 | ) 44 | 45 | const ( 46 | AllowSystemNamespacesEnvVarKey = "ALLOW_SYSTEM_NAMESPACES" 47 | ) 48 | 49 | var ( 50 | scheme = runtime.NewScheme() 51 | setupLog = ctrl.Log.WithName("setup") 52 | ) 53 | 54 | func init() { 55 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 56 | 57 | utilruntime.Must(redhatcopv1alpha1.AddToScheme(scheme)) 58 | utilruntime.Must(userv1.AddToScheme(scheme)) 59 | // +kubebuilder:scaffold:scheme 60 | } 61 | 62 | func main() { 63 | var metricsAddr string 64 | var enableLeaderElection bool 65 | var probeAddr string 66 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 67 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 68 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 69 | "Enable leader election for controller manager. "+ 70 | "Enabling this will ensure there is only one active controller manager.") 71 | opts := zap.Options{ 72 | Development: true, 73 | } 74 | opts.BindFlags(flag.CommandLine) 75 | flag.Parse() 76 | 77 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 78 | 79 | var syncPeriod = 36000 * time.Second //Defaults to every 10Hrs 80 | if syncPeriodSeconds, ok := os.LookupEnv("SYNC_PERIOD_SECONDS"); ok && syncPeriodSeconds != "" { 81 | if syncPeriodSecondsInt, err := strconv.ParseInt(syncPeriodSeconds, 10, 64); err == nil { 82 | syncPeriod = time.Duration(syncPeriodSecondsInt) * time.Second 83 | } else if err != nil { 84 | setupLog.Error(err, "unable to start manager") 85 | os.Exit(1) 86 | } 87 | } 88 | 89 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 90 | Scheme: scheme, 91 | MetricsBindAddress: metricsAddr, 92 | Port: 9443, 93 | HealthProbeBindAddress: probeAddr, 94 | LeaderElection: enableLeaderElection, 95 | LeaderElectionID: "b0b2f089.redhat.io", 96 | SyncPeriod: &syncPeriod, 97 | }) 98 | if err != nil { 99 | setupLog.Error(err, "unable to start manager") 100 | os.Exit(1) 101 | } 102 | 103 | if err = (&controllers.NamespaceConfigReconciler{ 104 | EnforcingReconciler: lockedresourcecontroller.NewEnforcingReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetAPIReader(), mgr.GetEventRecorderFor("NamespaceConfig_controller"), true, true), 105 | Log: ctrl.Log.WithName("controllers").WithName("NamespaceConfig"), 106 | AllowSystemNamespaces: checkNamespaceScope(), 107 | }).SetupWithManager(mgr); err != nil { 108 | setupLog.Error(err, "unable to create controller", "controller", "NamespaceConfig") 109 | os.Exit(1) 110 | } 111 | ctx := context.WithValue(context.TODO(), "restConfig", mgr.GetConfig()) 112 | 113 | userConfigController := &controllers.UserConfigReconciler{ 114 | EnforcingReconciler: lockedresourcecontroller.NewEnforcingReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetAPIReader(), mgr.GetEventRecorderFor("UserConfig_controller"), true, true), 115 | Log: ctrl.Log.WithName("controllers").WithName("UserConfig"), 116 | } 117 | 118 | if ok, err := discoveryclient.IsGVKDefined(ctx, schema.GroupVersionKind{ 119 | Group: "user.openshift.io", 120 | Version: "v1", 121 | Kind: "User", 122 | }); !ok || err != nil { 123 | if err != nil { 124 | setupLog.Error(err, "unable to set check whether resource User.user.openshift.io exists") 125 | os.Exit(1) 126 | } 127 | } else { 128 | if err = (userConfigController).SetupWithManager(mgr); err != nil { 129 | setupLog.Error(err, "unable to create controller", "controller", "UserConfig") 130 | os.Exit(1) 131 | } 132 | } 133 | 134 | groupConfigController := &controllers.GroupConfigReconciler{ 135 | EnforcingReconciler: lockedresourcecontroller.NewEnforcingReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetAPIReader(), mgr.GetEventRecorderFor("GroupConfig_controller"), true, true), 136 | Log: ctrl.Log.WithName("controllers").WithName("GroupConfig"), 137 | } 138 | 139 | if ok, err := discoveryclient.IsGVKDefined(ctx, schema.GroupVersionKind{ 140 | Group: "user.openshift.io", 141 | Version: "v1", 142 | Kind: "Group", 143 | }); !ok || err != nil { 144 | if err != nil { 145 | setupLog.Error(err, "unable to set check wheter resource Group.user.openshift.io exists") 146 | os.Exit(1) 147 | } 148 | } else { 149 | if err = (groupConfigController).SetupWithManager(mgr); err != nil { 150 | setupLog.Error(err, "unable to create controller", "controller", "GroupConfig") 151 | os.Exit(1) 152 | } 153 | } 154 | // +kubebuilder:scaffold:builder 155 | 156 | if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { 157 | setupLog.Error(err, "unable to set up health check") 158 | os.Exit(1) 159 | } 160 | if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { 161 | setupLog.Error(err, "unable to set up ready check") 162 | os.Exit(1) 163 | } 164 | 165 | setupLog.Info("starting manager") 166 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 167 | setupLog.Error(err, "problem running manager") 168 | os.Exit(1) 169 | } 170 | } 171 | 172 | func checkNamespaceScope() bool { 173 | value := os.Getenv(AllowSystemNamespacesEnvVarKey) 174 | if len(value) == 0 { 175 | return false 176 | } 177 | res, err := strconv.ParseBool(value) 178 | if err != nil { 179 | return false 180 | } 181 | return res 182 | } 183 | -------------------------------------------------------------------------------- /media/namespace-configuration-operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/namespace-configuration-operator/8eaa74be68e8a1489757ed50d41c2dfdfb95c988/media/namespace-configuration-operator.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices", 5 | "schedule:earlyMondays" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/advanced-group-config-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: GroupConfig 3 | metadata: 4 | name: advanced-test-groupconfig 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: advancedteam 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: Namespace 13 | metadata: 14 | annotations: 15 | operatorNamespaceCreatedOn: '{{ (lookup "v1" "Namespace" "" "namespace-configuration-operator").metadata.creationTimestamp }}' 16 | sourceGroup: "{{ toJson . | b64enc }}" 17 | url: '{{ required "URL annotation on the Group is required!" .Annotations.url }}' 18 | name: {{ index .Labels "projectname" | lower }}-dev -------------------------------------------------------------------------------- /test/group-config-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: GroupConfig 3 | metadata: 4 | name: test-groupconfig 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: team 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: Namespace 13 | metadata: 14 | name: {{ .Name }}-dev 15 | labels: 16 | group: {{ .Name }} 17 | - objectTemplate: | 18 | apiVersion: v1 19 | kind: Namespace 20 | metadata: 21 | name: {{ .Name }}-qa 22 | labels: 23 | group: {{ .Name }} 24 | - objectTemplate: | 25 | apiVersion: v1 26 | kind: Namespace 27 | metadata: 28 | name: {{ .Name }}-prod 29 | labels: 30 | group: {{ .Name }} 31 | - objectTemplate: | 32 | apiVersion: quota.openshift.io/v1 33 | kind: ClusterResourceQuota 34 | metadata: 35 | name: {{ .Name }}-quota 36 | spec: 37 | quota: 38 | hard: 39 | pods: "4" 40 | requests.cpu: "1" 41 | requests.memory: 1Gi 42 | requests.ephemeral-storage: 2Gi 43 | limits.cpu: "2" 44 | limits.memory: 2Gi 45 | limits.ephemeral-storage: 4Gi 46 | selector: 47 | labels: 48 | matchLabels: 49 | group: {{ .Name }} -------------------------------------------------------------------------------- /test/groups.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Group 3 | apiVersion: user.openshift.io/v1 4 | metadata: 5 | name: test-group 6 | labels: 7 | type: team 8 | users: 9 | - user1 10 | - user2 11 | --- 12 | kind: Group 13 | apiVersion: user.openshift.io/v1 14 | metadata: 15 | name: test-group2 16 | labels: 17 | type: team 18 | users: 19 | - user3 20 | - user4 21 | --- 22 | kind: Group 23 | apiVersion: user.openshift.io/v1 24 | metadata: 25 | name: test-group3 26 | annotations: 27 | url: "localhost" 28 | labels: 29 | type: advancedteam 30 | projectname: TESTGROUP3 31 | users: 32 | - user4 33 | - user5 -------------------------------------------------------------------------------- /test/identities.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: user.openshift.io/v1 2 | kind: Identity 3 | metadata: 4 | name: my-provider:${username} 5 | providerName: my-provider 6 | providerUserName: ${username} 7 | user: 8 | name: ${username} 9 | uid: ${uid} -------------------------------------------------------------------------------- /test/namespace-config-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: NamespaceConfig 3 | metadata: 4 | name: test-namespaceconfig 5 | spec: 6 | labelSelector: 7 | matchLabels: 8 | type: secure 9 | templates: 10 | - objectTemplate: | 11 | apiVersion: v1 12 | kind: ResourceQuota 13 | metadata: 14 | name: standard-sandbox 15 | namespace: {{ .Name }} 16 | spec: 17 | hard: 18 | pods: "4" 19 | requests.cpu: "1" 20 | requests.memory: 1Gi 21 | requests.ephemeral-storage: 2Gi 22 | limits.cpu: "2" 23 | limits.memory: 2Gi 24 | limits.ephemeral-storage: 4Gi -------------------------------------------------------------------------------- /test/namespaces.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: test-namespaceconfig 6 | labels: 7 | type: secure 8 | --- 9 | apiVersion: v1 10 | kind: Namespace 11 | metadata: 12 | name: test-namespaceconfig2 13 | labels: 14 | type: secure 15 | annotations: 16 | type: ciao 17 | -------------------------------------------------------------------------------- /test/user-config-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: redhatcop.redhat.io/v1alpha1 2 | kind: UserConfig 3 | metadata: 4 | name: test-user-config 5 | spec: 6 | providerName: my-provider 7 | templates: 8 | - objectTemplate: | 9 | apiVersion: v1 10 | kind: Namespace 11 | metadata: 12 | name: {{ .Name }}-sandbox 13 | - objectTemplate: | 14 | apiVersion: v1 15 | kind: ResourceQuota 16 | metadata: 17 | name: standard-sandbox 18 | namespace: {{ .Name }}-sandbox 19 | spec: 20 | hard: 21 | pods: "4" 22 | requests.cpu: "1" 23 | requests.memory: 1Gi 24 | requests.ephemeral-storage: 2Gi 25 | limits.cpu: "2" 26 | limits.memory: 2Gi 27 | limits.ephemeral-storage: 4Gi 28 | 29 | -------------------------------------------------------------------------------- /test/users.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: user.openshift.io/v1 3 | kind: User 4 | metadata: 5 | name: test-user-config 6 | identities: 7 | - my-provider:test-user-config 8 | --- 9 | apiVersion: user.openshift.io/v1 10 | kind: User 11 | metadata: 12 | name: test-user-config2 13 | identities: 14 | - my-provider:test-user-config2 15 | --------------------------------------------------------------------------------