├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .dockerignore ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── documentation-question.yml │ └── feature-request.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── apis └── grafana │ └── v1alpha1 │ ├── grafanauser_types.go │ ├── grafanauser_webhook.go │ ├── groupversion_info.go │ ├── webhook_suite_test.go │ └── zz_generated.deepcopy.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── grafana.snappcloud.io_grafanausers.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_grafana_grafanausers.yaml │ │ └── webhook_in_grafana_grafanausers.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── grafana_grafanauser_editor_role.yaml │ ├── grafana_grafanauser_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── grafana_v1alpha1_grafanauser.yaml │ └── kustomization.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── grafanauser │ ├── grafanauser_controller.go │ └── suite_test.go └── namespace │ ├── namespace_controller.go │ └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── main.go /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | 3 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) 4 | 5 | {{ range .CommitGroups -}} 6 | ### {{ .Title }} 7 | 8 | {{ range .Commits -}} 9 | * {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | 13 | {{- if .RevertCommits -}} 14 | ### Reverts 15 | 16 | {{ range .RevertCommits -}} 17 | * {{ .Revert.Header }} 18 | {{ end }} 19 | {{ end -}} 20 | 21 | {{- if .MergeCommits -}} 22 | ### Pull Requests 23 | 24 | {{ range .MergeCommits -}} 25 | * {{ .Header }} 26 | {{ end }} 27 | {{ end -}} 28 | 29 | {{- if .NoteGroups -}} 30 | {{ range .NoteGroups -}} 31 | ### {{ .Title }} 32 | 33 | {{ range .Notes }} 34 | {{ .Body }} 35 | {{ end }} 36 | {{ end -}} 37 | {{ end -}} 38 | {{ end -}} -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/snapp-incubator/event-exporter 6 | options: 7 | commits: 8 | # filters: 9 | # Type: 10 | # - feat 11 | # - fix 12 | # - perf 13 | # - refactor 14 | commit_groups: 15 | # title_maps: 16 | # feat: Features 17 | # fix: Bug Fixes 18 | # perf: Performance Improvements 19 | # refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Scope 25 | - Subject 26 | notes: 27 | keywords: 28 | - BREAKING CHANGE -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | # Uses editorconfig to maintain consistent coding styles 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | max_line_length = 120 15 | trim_trailing_whitespace = true 16 | tab_width = 8 17 | 18 | [*.{tf,tfvars}] 19 | indent_size = 2 20 | indent_style = space 21 | 22 | [*.md] 23 | max_line_length = 0 24 | trim_trailing_whitespace = false 25 | 26 | [*.py] 27 | max_line_length = 120 28 | indent_style = space 29 | indent_size = 4 30 | ignore_frosted_errors = E103 31 | skip = runtests.py,build 32 | balanced_wrapping = true 33 | not_skip = __init__.py 34 | 35 | [Makefile] 36 | tab_width = 2 37 | indent_style = tab 38 | 39 | [COMMIT_EDITMSG] 40 | max_line_length = 0 41 | 42 | [*.yml] 43 | indent_style = space 44 | indent_size = 2 45 | 46 | [*.go] 47 | indent_style = tab 48 | indent_size = 8 49 | 50 | [*.{rst,ini}] 51 | indent_style = space 52 | indent_size = 4 53 | 54 | [*.j2] 55 | end_of_line = lf 56 | 57 | [{Dockerfile,*.json,*.sh}] 58 | indent_style = space 59 | indent_size = 2 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | assignees: 6 | - m-yosefpor # TODO 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: textarea 13 | id: what-happened 14 | attributes: 15 | label: What happened? 16 | description: Also tell us, what did you expect to happen? 17 | placeholder: Tell us what you see! 18 | validations: 19 | required: true 20 | - type: input 21 | id: version 22 | attributes: 23 | label: App Version 24 | description: The version of the app you experience the bug in. 25 | placeholder: ex. v0.1.0 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: logs 30 | attributes: 31 | label: Relevant log output 32 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 33 | render: shell 34 | - type: checkboxes 35 | id: terms 36 | attributes: 37 | label: Code of Conduct 38 | description: By submitting this issue, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md). 39 | options: 40 | - label: I agree to follow this project's Code of Conduct 41 | required: true 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-question.yml: -------------------------------------------------------------------------------- 1 | name: 📝 Question 2 | description: Question 3 | title: "[Question]: " 4 | labels: ["question"] 5 | assignees: 6 | - m-yosefpor # TODO 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for your question. We try respond ASAP! 12 | - type: textarea 13 | id: question 14 | attributes: 15 | label: What's your question? 16 | description: What's your question? 17 | placeholder: Tell us your question! 18 | validations: 19 | required: true 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Enhancement 2 | description: Feature Request or Enhancement Proposal 3 | title: "[Enhancement]: " 4 | labels: ["enhancement"] 5 | assignees: 6 | - m-yosefpor # TODO 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for your suggestion. We try our best to prioritize it! 12 | - type: textarea 13 | id: proposal 14 | attributes: 15 | label: What's your proposal? 16 | description: Also tell us, how do you think it should be implemented? 17 | placeholder: Tell us your proposal! 18 | validations: 19 | required: true 20 | - type: checkboxes 21 | id: terms 22 | attributes: 23 | label: Code of Conduct 24 | description: By submitting this issue, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md). 25 | options: 26 | - label: I agree to follow this project's Code of Conduct 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | labels: 8 | - dependencies 9 | groups: 10 | dev-dependencies: 11 | patterns: 12 | - "*" 13 | - package-ecosystem: "docker" 14 | directory: "/" 15 | schedule: 16 | interval: "monthly" 17 | labels: 18 | - dependencies 19 | groups: 20 | dev-dependencies: 21 | patterns: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ v* ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | lint: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v2 18 | with: 19 | version: latest 20 | # test: 21 | # name: test 22 | # runs-on: ubuntu-latest 23 | # steps: 24 | # - uses: actions/checkout@v2 25 | # - uses: actions/setup-go@v2 26 | # with: 27 | # go-version: '^1.16' 28 | # - uses: RyanSiu1995/kubebuilder-action@v1.2.1 29 | # - run: make test 30 | # - uses: codecov/codecov-action@v1 31 | # with: 32 | # files: coverage.out 33 | docker: 34 | name: docker 35 | runs-on: ubuntu-latest 36 | needs: 37 | - lint 38 | # - test 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: docker/setup-qemu-action@v1 42 | - uses: docker/setup-buildx-action@v1 43 | - uses: docker/login-action@v1 44 | with: 45 | registry: ghcr.io 46 | username: ${{ github.repository_owner }} 47 | password: ${{ secrets.GITHUB_TOKEN }} 48 | - uses: docker/metadata-action@v3 49 | id: meta 50 | with: 51 | images: ghcr.io/${{ github.repository }} 52 | tags: | 53 | type=ref,event=branch 54 | type=ref,event=pr 55 | type=semver,pattern={{version}} 56 | type=semver,pattern={{major}}.{{minor}} 57 | - uses: docker/build-push-action@v2 58 | with: 59 | file: "Dockerfile" 60 | context: . 61 | platforms: linux/amd64 62 | push: true 63 | tags: ${{ steps.meta.outputs.tags }} 64 | labels: ${{ steps.meta.outputs.labels }} 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | grafana-complementary-operator 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 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # https://golangci-lint.run/usage/configuration/ 2 | run: 3 | timeout: 10m 4 | deadline: 5m 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sane defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | goarch: 15 | - amd64 16 | archives: 17 | - replacements: 18 | linux: Linux 19 | amd64: x86_64 20 | checksum: 21 | name_template: 'checksums.txt' 22 | snapshot: 23 | name_template: "{{ incpatch .Version }}-next" 24 | changelog: 25 | sort: asc 26 | filters: 27 | exclude: 28 | - '^docs:' 29 | - '^test:' 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [v0.2.1](https://github.com/snapp-incubator/event-exporter/compare/v0.2.0...v0.2.1) (2022-03-05) 4 | 5 | ### Chore 6 | 7 | * bump kustomize to 0.2.0 8 | 9 | ### Fix 10 | 11 | * change ns label 12 | 13 | 14 | 15 | ## [v0.2.0](https://github.com/snapp-incubator/event-exporter/compare/0.0.1...v0.2.0) (2022-03-04) 16 | 17 | ### Chore 18 | 19 | * update changelog for v0.2.0 20 | 21 | ### Feat 22 | 23 | * add releaser 24 | 25 | ### Fix 26 | 27 | * add svu version 28 | * trigger on changes on owned object 29 | * handle object already exits 30 | 31 | 32 | 33 | ## [0.0.1](https://github.com/snapp-incubator/event-exporter/compare/v0.1.0...0.0.1) (2022-03-04) 34 | 35 | ### Chore 36 | 37 | * update changelog for 0.0.1 38 | * add release files 39 | 40 | 41 | 42 | ## v0.1.0 (2022-02-28) 43 | 44 | ### Chore 45 | 46 | * fix lint 47 | * add golangci timeout 48 | * add ci 49 | 50 | ### Fix 51 | 52 | * add grafana crd to scheme 53 | * role 54 | * add rbac 55 | * issues 56 | * **kustomize:** manager image 57 | 58 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /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 apis/ apis/ 14 | COPY main.go main.go 15 | COPY controllers/ controllers/ 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 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 gcr.io/distroless/static:nonroot 23 | WORKDIR / 24 | COPY --from=builder /workspace/manager . 25 | USER 65532:65532 26 | 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # VERSION defines the project version for the bundle. 2 | # Update this value when you upgrade the version of your project. 3 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 4 | # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 5 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 6 | VERSION ?= $(shell svu next) 7 | 8 | # CHANNELS define the bundle channels used in the bundle. 9 | # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") 10 | # To re-generate a bundle for other specific channels without changing the standard setup, you can: 11 | # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) 12 | # - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") 13 | ifneq ($(origin CHANNELS), undefined) 14 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 15 | endif 16 | 17 | # DEFAULT_CHANNEL defines the default channel used in the bundle. 18 | # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") 19 | # To re-generate a bundle for any other default channel without changing the default setup, you can: 20 | # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) 21 | # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") 22 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 23 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 24 | endif 25 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 26 | 27 | # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. 28 | # This variable is used to construct full image tags for bundle and catalog images. 29 | # 30 | # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both 31 | # snappcloud.io/grafana-complementary-operator-bundle:$VERSION and snappcloud.io/grafana-complementary-operator-catalog:$VERSION. 32 | IMAGE_TAG_BASE ?= snappcloud.io/grafana-complementary-operator 33 | 34 | # BUNDLE_IMG defines the image:tag used for the bundle. 35 | # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) 36 | BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) 37 | 38 | # BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command 39 | BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 40 | 41 | # USE_IMAGE_DIGESTS defines if images are resolved via tags or digests 42 | # You can enable this value if you would like to use SHA Based Digests 43 | # To enable set flag to true 44 | USE_IMAGE_DIGESTS ?= false 45 | ifeq ($(USE_IMAGE_DIGESTS), true) 46 | BUNDLE_GEN_FLAGS += --use-image-digests 47 | endif 48 | 49 | # Image URL to use all building/pushing image targets 50 | IMG ?= controller:latest 51 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 52 | ENVTEST_K8S_VERSION = 1.23 53 | 54 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 55 | ifeq (,$(shell go env GOBIN)) 56 | GOBIN=$(shell go env GOPATH)/bin 57 | else 58 | GOBIN=$(shell go env GOBIN) 59 | endif 60 | 61 | # Setting SHELL to bash allows bash commands to be executed by recipes. 62 | # This is a requirement for 'setup-envtest.sh' in the test target. 63 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 64 | SHELL = /usr/bin/env bash -o pipefail 65 | .SHELLFLAGS = -ec 66 | 67 | .PHONY: all 68 | all: build 69 | 70 | ##@ General 71 | 72 | # The help target prints out all targets with their descriptions organized 73 | # beneath their categories. The categories are represented by '##@' and the 74 | # target descriptions by '##'. The awk commands is responsible for reading the 75 | # entire set of makefiles included in this invocation, looking for lines of the 76 | # file as xyz: ## something, and then pretty-format the target and help. Then, 77 | # if there's a line with ##@ something, that gets pretty-printed as a category. 78 | # More info on the usage of ANSI control characters for terminal formatting: 79 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 80 | # More info on the awk command: 81 | # http://linuxcommand.org/lc3_adv_awk.php 82 | 83 | .PHONY: help 84 | help: ## Display this help. 85 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 86 | 87 | ##@ Development 88 | 89 | .PHONY: manifests 90 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 91 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 92 | 93 | .PHONY: generate 94 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 95 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 96 | 97 | .PHONY: fmt 98 | fmt: ## Run go fmt against code. 99 | go fmt ./... 100 | 101 | .PHONY: vet 102 | vet: ## Run go vet against code. 103 | go vet ./... 104 | 105 | .PHONY: test 106 | test: manifests generate fmt vet envtest ## Run tests. 107 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out 108 | 109 | ##@ Build 110 | 111 | .PHONY: build 112 | build: generate fmt vet ## Build manager binary. 113 | go build -o bin/manager main.go 114 | 115 | .PHONY: run 116 | run: manifests generate fmt vet ## Run a controller from your host. 117 | go run ./main.go 118 | 119 | .PHONY: docker-build 120 | docker-build: test ## Build docker image with the manager. 121 | docker build -t ${IMG} . 122 | 123 | .PHONY: docker-push 124 | docker-push: ## Push docker image with the manager. 125 | docker push ${IMG} 126 | 127 | ##@ Deployment 128 | 129 | ifndef ignore-not-found 130 | ignore-not-found = false 131 | endif 132 | 133 | .PHONY: install 134 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 135 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 136 | 137 | .PHONY: uninstall 138 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 139 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 140 | 141 | .PHONY: deploy 142 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 143 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 144 | $(KUSTOMIZE) build config/default | kubectl apply -f - 145 | 146 | .PHONY: undeploy 147 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 148 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 149 | 150 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 151 | .PHONY: controller-gen 152 | controller-gen: ## Download controller-gen locally if necessary. 153 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0) 154 | 155 | KUSTOMIZE = $(shell pwd)/bin/kustomize 156 | .PHONY: kustomize 157 | kustomize: ## Download kustomize locally if necessary. 158 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 159 | 160 | ENVTEST = $(shell pwd)/bin/setup-envtest 161 | .PHONY: envtest 162 | envtest: ## Download envtest-setup locally if necessary. 163 | $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) 164 | 165 | # go-get-tool will 'go get' any package $2 and install it to $1. 166 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 167 | define go-get-tool 168 | @[ -f $(1) ] || { \ 169 | set -e ;\ 170 | TMP_DIR=$$(mktemp -d) ;\ 171 | cd $$TMP_DIR ;\ 172 | go mod init tmp ;\ 173 | echo "Downloading $(2)" ;\ 174 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 175 | rm -rf $$TMP_DIR ;\ 176 | } 177 | endef 178 | 179 | .PHONY: bundle 180 | bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. 181 | operator-sdk generate kustomize manifests -q 182 | cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) 183 | $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) 184 | operator-sdk bundle validate ./bundle 185 | 186 | .PHONY: bundle-build 187 | bundle-build: ## Build the bundle image. 188 | docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . 189 | 190 | .PHONY: bundle-push 191 | bundle-push: ## Push the bundle image. 192 | $(MAKE) docker-push IMG=$(BUNDLE_IMG) 193 | 194 | .PHONY: opm 195 | OPM = ./bin/opm 196 | opm: ## Download opm locally if necessary. 197 | ifeq (,$(wildcard $(OPM))) 198 | ifeq (,$(shell which opm 2>/dev/null)) 199 | @{ \ 200 | set -e ;\ 201 | mkdir -p $(dir $(OPM)) ;\ 202 | OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ 203 | curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.19.1/$${OS}-$${ARCH}-opm ;\ 204 | chmod +x $(OPM) ;\ 205 | } 206 | else 207 | OPM = $(shell which opm) 208 | endif 209 | endif 210 | 211 | # A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). 212 | # These images MUST exist in a registry and be pull-able. 213 | BUNDLE_IMGS ?= $(BUNDLE_IMG) 214 | 215 | # The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). 216 | CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) 217 | 218 | # Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. 219 | ifneq ($(origin CATALOG_BASE_IMG), undefined) 220 | FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) 221 | endif 222 | 223 | # Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. 224 | # This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: 225 | # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator 226 | .PHONY: catalog-build 227 | catalog-build: opm ## Build a catalog image. 228 | $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) 229 | 230 | # Push the catalog image. 231 | .PHONY: catalog-push 232 | catalog-push: ## Push a catalog image. 233 | $(MAKE) docker-push IMG=$(CATALOG_IMG) 234 | 235 | 236 | 237 | 238 | ############# 239 | ##@ Release 240 | 241 | 242 | .PHONY: changelog 243 | changelog: build ## Generate changelog 244 | echo $(VERSION) 245 | git-chglog --next-tag $(VERSION) -o CHANGELOG.md 246 | 247 | .PHONY: release 248 | release: changelog ## Release a new tag 249 | git add CHANGELOG.md 250 | git commit -m "chore: update changelog for $(VERSION)" 251 | git tag $(VERSION) -m "$(VERSION)" 252 | git push origin main $(VERSION) 253 | 254 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: snappcloud.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | multigroup: true 5 | plugins: 6 | manifests.sdk.operatorframework.io/v2: {} 7 | scorecard.sdk.operatorframework.io/v2: {} 8 | projectName: grafana-complementary-operator 9 | repo: github.com/snapp-cab/grafana-complementary-operator 10 | resources: 11 | - controller: true 12 | group: core 13 | kind: Namespace 14 | path: k8s.io/api/core/v1 15 | version: v1 16 | - api: 17 | crdVersion: v1 18 | namespaced: true 19 | controller: true 20 | domain: snappcloud.io 21 | group: grafana 22 | kind: GrafanaUser 23 | path: github.com/snapp-cab/grafana-complementary-operator/apis/grafana/v1alpha1 24 | version: v1alpha1 25 | webhooks: 26 | defaulting: true 27 | validation: true 28 | webhookVersion: v1 29 | version: "3" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grafana Complementary Operator 2 | 3 | A grafana which complements grafana-operator for custom features which are not feasible to be merged into core operator. 4 | 5 | ## Instructions 6 | 7 | ### Development 8 | 9 | * `make generate` update the generated code for that resource type. 10 | * `make manifests` Generating CRD manifests. 11 | * `make test` Run tests. 12 | 13 | ### Build 14 | 15 | Export your image name: 16 | 17 | ``` 18 | export IMG=ghcr.io/your-repo-path/image-name:latest 19 | ``` 20 | 21 | * `make build` builds golang app locally. 22 | * `make docker-build` build docker image locally. 23 | * `make docker-push` push container image to registry. 24 | 25 | ### Run, Deploy 26 | * `make run` run app locally 27 | * `make deploy` deploy to k8s. 28 | 29 | ### Clean up 30 | 31 | * `make undeploy` delete resouces in k8s. 32 | 33 | ### Release 34 | 35 | * `make changelog` generate changelog to check before release 36 | * `make release` create a new tag and release it to github 37 | 38 | ## Metrics 39 | 40 | | Metric | Notes 41 | |-----------------------------------------------------|------------------------------------ 42 | | controller_runtime_active_workers | Number of currently used workers per controller 43 | | controller_runtime_max_concurrent_reconciles | Maximum number of concurrent reconciles per controller 44 | | controller_runtime_reconcile_errors_total | Total number of reconciliation errors per controller 45 | | controller_runtime_reconcile_time_seconds | Length of time per reconciliation per controller 46 | | controller_runtime_reconcile_total | Total number of reconciliations per controller 47 | | rest_client_request_latency_seconds | Request latency in seconds. Broken down by verb and URL. 48 | | rest_client_requests_total | Number of HTTP requests, partitioned by status code, method, and host. 49 | | workqueue_adds_total | Total number of adds handled by workqueue 50 | | workqueue_depth | Current depth of workqueue 51 | | workqueue_longest_running_processor_seconds | How many seconds has the longest running processor for workqueue been running. 52 | | workqueue_queue_duration_seconds | How long in seconds an item stays in workqueue before being requested 53 | | workqueue_retries_total | Total number of retries handled by workqueue 54 | | workqueue_unfinished_work_seconds | How many seconds of work has been done that is in progress and hasn't been observed by work_duration. Large values indicate stuck threads. One can deduce the number of stuck threads by observing the rate at which this increases. 55 | | workqueue_work_duration_seconds | How long in seconds processing an item from workqueue takes. 56 | 57 | 58 | ## Security 59 | 60 | ### Reporting security vulnerabilities 61 | 62 | If you find a security vulnerability or any security related issues, please DO NOT file a public issue, instead send your report privately to cloud@snapp.cab. Security reports are greatly appreciated and we will publicly thank you for it. 63 | 64 | ## License 65 | 66 | Apache-2.0 License, see [LICENSE](LICENSE). 67 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | 4 | ## Reporting a Vulnerability 5 | 6 | If you find a security vulnerability or any security related issues, please DO NOT file a public issue. Do not create a Github issue. Instead, send your report privately to cloud@snapp.cab. Security reports are greatly appreciated and we will publicly thank you for it. 7 | -------------------------------------------------------------------------------- /apis/grafana/v1alpha1/grafanauser_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // GrafanaUserSpec defines the desired state of GrafanaUser 27 | type GrafanaUserSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | Admin []string `json:"admin,omitempty"` 32 | Edit []string `json:"edit,omitempty"` 33 | View []string `json:"view,omitempty"` 34 | } 35 | 36 | //+kubebuilder:object:root=true 37 | //+kubebuilder:subresource:status 38 | 39 | // GrafanaUser is the Schema for the grafanausers API 40 | type GrafanaUser struct { 41 | metav1.TypeMeta `json:",inline"` 42 | metav1.ObjectMeta `json:"metadata,omitempty"` 43 | Spec GrafanaUserSpec `json:"spec,omitempty"` 44 | } 45 | 46 | //+kubebuilder:object:root=true 47 | 48 | // GrafanaUserList contains a list of GrafanaUser 49 | type GrafanaUserList struct { 50 | metav1.TypeMeta `json:",inline"` 51 | metav1.ListMeta `json:"metadata,omitempty"` 52 | Items []GrafanaUser `json:"items"` 53 | } 54 | 55 | func init() { 56 | SchemeBuilder.Register(&GrafanaUser{}, &GrafanaUserList{}) 57 | } 58 | -------------------------------------------------------------------------------- /apis/grafana/v1alpha1/grafanauser_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "context" 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "github.com/grafana-tools/sdk" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | logf "sigs.k8s.io/controller-runtime/pkg/log" 29 | "sigs.k8s.io/controller-runtime/pkg/webhook" 30 | ) 31 | 32 | // log is for logging in this package. 33 | var grafanauserlog = logf.Log.WithName("grafanauser-resource") 34 | 35 | // Get Grafana URL and PassWord as a env. 36 | var grafanaPassword = os.Getenv("GRAFANA_PASSWORD") 37 | var grafanaUsername = os.Getenv("GRAFANA_USERNAME") 38 | var grafanaURL = os.Getenv("GRAFANA_URL") 39 | 40 | // Get Grafana URL and PassWord as a env. 41 | 42 | func (r *GrafanaUser) SetupWebhookWithManager(mgr ctrl.Manager) error { 43 | return ctrl.NewWebhookManagedBy(mgr). 44 | For(r). 45 | Complete() 46 | } 47 | 48 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 49 | 50 | var _ webhook.Defaulter = &GrafanaUser{} 51 | 52 | // Default implements webhook.Defaulter so a webhook will be registered for the type 53 | func (r *GrafanaUser) Default() { 54 | grafanauserlog.Info("default", "name", r.Name) 55 | 56 | // TODO(user): fill in your defaulting logic. 57 | } 58 | 59 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 60 | //+kubebuilder:webhook:path=/validate-grafana-snappcloud-io-v1alpha1-grafanauser,mutating=false,failurePolicy=fail,sideEffects=None,groups=grafana.snappcloud.io,resources=grafanausers,verbs=create;update,versions=v1alpha1,name=vgrafana.kb.io,admissionReviewVersions={v1,v1beta1} 61 | 62 | var _ webhook.Validator = &GrafanaUser{} 63 | 64 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 65 | func (r *GrafanaUser) ValidateCreate() error { 66 | grafanauserlog.Info("validate create", "name", r.Name) 67 | // TODO(user): fill in your validation logic upon object creation. 68 | var emaillist []string 69 | emaillist = append(r.Spec.Admin, r.Spec.Edit...) 70 | emaillist = append(emaillist, r.Spec.View...) 71 | str2 := strings.Join(emaillist, ", ") 72 | grafanauserlog.Info(str2) 73 | err := r.ValidateEmailExist(context.Background(), emaillist) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 81 | func (r *GrafanaUser) ValidateUpdate(old runtime.Object) error { 82 | grafanauserlog.Info("validate update", "name", r.Name) 83 | var emaillist []string 84 | emaillist = append(r.Spec.Admin, r.Spec.Edit...) 85 | emaillist = append(emaillist, r.Spec.View...) 86 | err := r.ValidateEmailExist(context.Background(), emaillist) 87 | if err != nil { 88 | return err 89 | } 90 | // TODO(user): fill in your validation logic upon object update. 91 | return nil 92 | } 93 | 94 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 95 | func (r *GrafanaUser) ValidateDelete() error { 96 | grafanauserlog.Info("validate delete", "name", r.Name) 97 | // TODO(user): fill in your validation logic upon object deletion. 98 | return nil 99 | } 100 | 101 | func Find(slice []sdk.User, val string) bool { 102 | for _, item := range slice { 103 | if item.Email == val { 104 | return true 105 | } 106 | } 107 | return false 108 | } 109 | 110 | func (r *GrafanaUser) ValidateEmailExist(ctx context.Context, emails []string) error { 111 | client, _ := sdk.NewClient(grafanaURL, fmt.Sprintf("%s:%s", grafanaUsername, grafanaPassword), sdk.DefaultHTTPClient) 112 | grafanalUsers, _ := client.GetAllUsers(ctx) 113 | var Users []string 114 | for _, email := range emails { 115 | found := Find(grafanalUsers, email) 116 | if !found { 117 | Users = append(Users, email) 118 | } 119 | } 120 | userlist := strings.Join(Users, ", ") 121 | if len(Users) > 0 { 122 | return fmt.Errorf("%q do NOT exist in grafana, please make sure the user name is correct, or they have login at least one time in grafana and then try again", userlist) 123 | 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /apis/grafana/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 grafana v1alpha1 API group 18 | //+kubebuilder:object:generate=true 19 | //+groupName=grafana.snappcloud.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: "grafana.snappcloud.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 | -------------------------------------------------------------------------------- /apis/grafana/v1alpha1/webhook_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "context" 21 | "crypto/tls" 22 | "fmt" 23 | "net" 24 | "path/filepath" 25 | "testing" 26 | "time" 27 | 28 | . "github.com/onsi/ginkgo" 29 | . "github.com/onsi/gomega" 30 | 31 | admissionv1beta1 "k8s.io/api/admission/v1beta1" 32 | //+kubebuilder:scaffold:imports 33 | "k8s.io/apimachinery/pkg/runtime" 34 | "k8s.io/client-go/rest" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/client" 37 | "sigs.k8s.io/controller-runtime/pkg/envtest" 38 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 39 | logf "sigs.k8s.io/controller-runtime/pkg/log" 40 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 41 | ) 42 | 43 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 44 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 45 | 46 | var cfg *rest.Config 47 | var k8sClient client.Client 48 | var testEnv *envtest.Environment 49 | var ctx context.Context 50 | var cancel context.CancelFunc 51 | 52 | func TestAPIs(t *testing.T) { 53 | RegisterFailHandler(Fail) 54 | 55 | RunSpecsWithDefaultAndCustomReporters(t, 56 | "Webhook Suite", 57 | []Reporter{printer.NewlineReporter{}}) 58 | } 59 | 60 | var _ = BeforeSuite(func() { 61 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 62 | 63 | ctx, cancel = context.WithCancel(context.TODO()) 64 | 65 | By("bootstrapping test environment") 66 | testEnv = &envtest.Environment{ 67 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, 68 | ErrorIfCRDPathMissing: false, 69 | WebhookInstallOptions: envtest.WebhookInstallOptions{ 70 | Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, 71 | }, 72 | } 73 | 74 | var err error 75 | // cfg is defined in this file globally. 76 | cfg, err = testEnv.Start() 77 | Expect(err).NotTo(HaveOccurred()) 78 | Expect(cfg).NotTo(BeNil()) 79 | 80 | scheme := runtime.NewScheme() 81 | err = AddToScheme(scheme) 82 | Expect(err).NotTo(HaveOccurred()) 83 | 84 | err = admissionv1beta1.AddToScheme(scheme) 85 | Expect(err).NotTo(HaveOccurred()) 86 | 87 | //+kubebuilder:scaffold:scheme 88 | 89 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 90 | Expect(err).NotTo(HaveOccurred()) 91 | Expect(k8sClient).NotTo(BeNil()) 92 | 93 | // start webhook server using Manager 94 | webhookInstallOptions := &testEnv.WebhookInstallOptions 95 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 96 | Scheme: scheme, 97 | Host: webhookInstallOptions.LocalServingHost, 98 | Port: webhookInstallOptions.LocalServingPort, 99 | CertDir: webhookInstallOptions.LocalServingCertDir, 100 | LeaderElection: false, 101 | MetricsBindAddress: "0", 102 | }) 103 | Expect(err).NotTo(HaveOccurred()) 104 | 105 | err = (&GrafanaUser{}).SetupWebhookWithManager(mgr) 106 | Expect(err).NotTo(HaveOccurred()) 107 | 108 | //+kubebuilder:scaffold:webhook 109 | 110 | go func() { 111 | defer GinkgoRecover() 112 | err = mgr.Start(ctx) 113 | Expect(err).NotTo(HaveOccurred()) 114 | }() 115 | 116 | // wait for the webhook server to get ready 117 | dialer := &net.Dialer{Timeout: time.Second} 118 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) 119 | Eventually(func() error { 120 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) 121 | if err != nil { 122 | return err 123 | } 124 | conn.Close() 125 | return nil 126 | }).Should(Succeed()) 127 | 128 | }, 60) 129 | 130 | var _ = AfterSuite(func() { 131 | cancel() 132 | By("tearing down the test environment") 133 | err := testEnv.Stop() 134 | Expect(err).NotTo(HaveOccurred()) 135 | }) 136 | -------------------------------------------------------------------------------- /apis/grafana/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2022. 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 | "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *GrafanaUser) DeepCopyInto(out *GrafanaUser) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaUser. 37 | func (in *GrafanaUser) DeepCopy() *GrafanaUser { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(GrafanaUser) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *GrafanaUser) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *GrafanaUserList) DeepCopyInto(out *GrafanaUserList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]GrafanaUser, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaUserList. 69 | func (in *GrafanaUserList) DeepCopy() *GrafanaUserList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(GrafanaUserList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *GrafanaUserList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *GrafanaUserSpec) DeepCopyInto(out *GrafanaUserSpec) { 88 | *out = *in 89 | if in.Admin != nil { 90 | in, out := &in.Admin, &out.Admin 91 | *out = make([]string, len(*in)) 92 | copy(*out, *in) 93 | } 94 | if in.Edit != nil { 95 | in, out := &in.Edit, &out.Edit 96 | *out = make([]string, len(*in)) 97 | copy(*out, *in) 98 | } 99 | if in.View != nil { 100 | in, out := &in.View, &out.View 101 | *out = make([]string, len(*in)) 102 | copy(*out, *in) 103 | } 104 | } 105 | 106 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrafanaUserSpec. 107 | func (in *GrafanaUserSpec) DeepCopy() *GrafanaUserSpec { 108 | if in == nil { 109 | return nil 110 | } 111 | out := new(GrafanaUserSpec) 112 | in.DeepCopyInto(out) 113 | return out 114 | } 115 | -------------------------------------------------------------------------------- /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/crd/bases/grafana.snappcloud.io_grafanausers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: grafanausers.grafana.snappcloud.io 9 | spec: 10 | group: grafana.snappcloud.io 11 | names: 12 | kind: GrafanaUser 13 | listKind: GrafanaUserList 14 | plural: grafanausers 15 | singular: grafanauser 16 | scope: Namespaced 17 | versions: 18 | - name: v1alpha1 19 | schema: 20 | openAPIV3Schema: 21 | description: GrafanaUser is the Schema for the grafanausers API 22 | properties: 23 | apiVersion: 24 | description: 'APIVersion defines the versioned schema of this representation 25 | of an object. Servers should convert recognized schemas to the latest 26 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 27 | type: string 28 | kind: 29 | description: 'Kind is a string value representing the REST resource this 30 | object represents. Servers may infer this from the endpoint the client 31 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | description: GrafanaUserSpec defines the desired state of GrafanaUser 37 | properties: 38 | admin: 39 | items: 40 | type: string 41 | type: array 42 | edit: 43 | items: 44 | type: string 45 | type: array 46 | view: 47 | items: 48 | type: string 49 | type: array 50 | type: object 51 | type: object 52 | served: true 53 | storage: true 54 | subresources: 55 | status: {} 56 | status: 57 | acceptedNames: 58 | kind: "" 59 | plural: "" 60 | conditions: [] 61 | storedVersions: [] 62 | -------------------------------------------------------------------------------- /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/grafana.snappcloud.io_grafanausers.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | - patches/webhook_in_grafana_grafanausers.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | - patches/cainjection_in_grafana_grafanausers.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafana_grafanausers.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: grafanausers.grafana.snappcloud.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafana_grafanausers.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: grafanausers.grafana.snappcloud.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: grafana-complementary-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: grafana-complementary-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | - ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | - manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | - webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | objref: 51 | kind: Certificate 52 | group: cert-manager.io 53 | version: v1 54 | name: serving-cert # this name should match the one in certificate.yaml 55 | fieldref: 56 | fieldpath: metadata.namespace 57 | - name: CERTIFICATE_NAME 58 | objref: 59 | kind: Certificate 60 | group: cert-manager.io 61 | version: v1 62 | name: serving-cert # this name should match the one in certificate.yaml 63 | - name: SERVICE_NAMESPACE # namespace of the service 64 | objref: 65 | kind: Service 66 | version: v1 67 | name: webhook-service 68 | fieldref: 69 | fieldpath: metadata.namespace 70 | - name: SERVICE_NAME 71 | objref: 72 | kind: Service 73 | version: v1 74 | name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=0" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | resources: 24 | limits: 25 | cpu: 500m 26 | memory: 128Mi 27 | requests: 28 | cpu: 5m 29 | memory: 64Mi 30 | - name: manager 31 | args: 32 | - "--health-probe-bind-address=:8081" 33 | - "--metrics-bind-address=127.0.0.1:8080" 34 | - "--leader-elect" 35 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | name: validating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | -------------------------------------------------------------------------------- /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: 5a5bab80.snappcloud.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: controller 16 | newTag: latest 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | containers: 23 | - command: 24 | - /manager 25 | args: 26 | - --leader-elect 27 | image: ghcr.io/snapp-incubator/grafana-complementary-operator:0.8.0 28 | env: 29 | - name: GRAFANA_PASSWORD 30 | valueFrom: 31 | secretKeyRef: 32 | name: grafana-operated-dashboard-credentials 33 | key: grafana-password 34 | - name: GRAFANA_USERNAME 35 | valueFrom: 36 | secretKeyRef: 37 | name: grafana-operated-dashboard-credentials 38 | key: grafana-username 39 | - name: GRAFANA_URL 40 | valueFrom: 41 | configMapKeyRef: 42 | name: grafana-complementary-config 43 | key: grafana-url 44 | - name: PROMETHEUS_URL 45 | valueFrom: 46 | configMapKeyRef: 47 | name: grafana-complementary-config 48 | key: prometheus-url 49 | imagePullPolicy: Always 50 | name: manager 51 | securityContext: 52 | allowPrivilegeEscalation: false 53 | livenessProbe: 54 | httpGet: 55 | path: /healthz 56 | port: 8081 57 | initialDelaySeconds: 15 58 | periodSeconds: 20 59 | readinessProbe: 60 | httpGet: 61 | path: /readyz 62 | port: 8081 63 | initialDelaySeconds: 5 64 | periodSeconds: 10 65 | # TODO(user): Configure the resources accordingly based on the project requirements. 66 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 67 | resources: 68 | limits: 69 | cpu: 2 70 | memory: 800Mi 71 | requests: 72 | cpu: 1 73 | memory: 800Mi 74 | serviceAccountName: controller-manager 75 | terminationGracePeriodSeconds: 10 76 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/grafana-complementary-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: ctrl-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/grafana_grafanauser_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanausers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanauser-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.snappcloud.io 9 | resources: 10 | - grafanausers 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.snappcloud.io 21 | resources: 22 | - grafanausers/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafana_grafanauser_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanausers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanauser-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.snappcloud.io 9 | resources: 10 | - grafanausers 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.snappcloud.io 17 | resources: 18 | - grafanausers/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - configmaps 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - secrets 24 | verbs: 25 | - create 26 | - delete 27 | - get 28 | - list 29 | - patch 30 | - update 31 | - watch 32 | - apiGroups: 33 | - "" 34 | resources: 35 | - namespaces 36 | verbs: 37 | - get 38 | - list 39 | - watch 40 | - apiGroups: 41 | - "" 42 | resources: 43 | - namespaces/finalizers 44 | verbs: 45 | - update 46 | - apiGroups: 47 | - "" 48 | resources: 49 | - namespaces/status 50 | verbs: 51 | - get 52 | - patch 53 | - update 54 | - apiGroups: 55 | - "" 56 | resources: 57 | - secrets 58 | verbs: 59 | - get 60 | - list 61 | - watch 62 | - apiGroups: 63 | - "" 64 | resources: 65 | - serviceaccounts 66 | verbs: 67 | - get 68 | - list 69 | - watch 70 | - apiGroups: 71 | - grafana.snappcloud.io 72 | resources: 73 | - grafanausers 74 | verbs: 75 | - create 76 | - delete 77 | - get 78 | - list 79 | - patch 80 | - update 81 | - watch 82 | - apiGroups: 83 | - grafana.snappcloud.io 84 | resources: 85 | - grafanausers/finalizers 86 | verbs: 87 | - update 88 | - apiGroups: 89 | - grafana.snappcloud.io 90 | resources: 91 | - grafanausers/status 92 | verbs: 93 | - get 94 | - patch 95 | - update 96 | - apiGroups: 97 | - integreatly.org 98 | resources: 99 | - grafanadatasources 100 | verbs: 101 | - create 102 | - delete 103 | - get 104 | - list 105 | - patch 106 | - update 107 | - watch 108 | - apiGroups: 109 | - user.openshift.io 110 | resources: 111 | - '*' 112 | verbs: 113 | - create 114 | - delete 115 | - get 116 | - list 117 | - patch 118 | - update 119 | - watch 120 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/grafana_v1alpha1_grafanauser.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.snappcloud.io/v1alpha1 2 | kind: GrafanaUser 3 | metadata: 4 | name: grafanauser-sample 5 | namespace: test 6 | spec: 7 | admin: 8 | - user1 9 | view: 10 | - user2 11 | edit: 12 | - user3 13 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - core_v1_namespace.yaml 4 | - grafana_v1alpha1_grafanauser.yaml 5 | #+kubebuilder:scaffold:manifestskustomizesamples 6 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.18.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.18.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.18.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.18.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.18.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.18.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: ValidatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: ValidatingWebhookConfiguration 13 | group: admissionregistration.k8s.io 14 | path: webhooks/clientConfig/service/namespace 15 | create: true 16 | 17 | varReference: 18 | - path: metadata/annotations 19 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | creationTimestamp: null 6 | name: validating-webhook-configuration 7 | webhooks: 8 | - admissionReviewVersions: 9 | - v1 10 | - v1beta1 11 | clientConfig: 12 | service: 13 | name: webhook-service 14 | namespace: system 15 | path: /validate-grafana-snappcloud-io-v1alpha1-grafanauser 16 | failurePolicy: Fail 17 | name: vgrafana.kb.io 18 | rules: 19 | - apiGroups: 20 | - grafana.snappcloud.io 21 | apiVersions: 22 | - v1alpha1 23 | operations: 24 | - CREATE 25 | - UPDATE 26 | resources: 27 | - grafanausers 28 | sideEffects: None 29 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | protocol: TCP 11 | targetPort: 9443 12 | selector: 13 | control-plane: controller-manager 14 | -------------------------------------------------------------------------------- /controllers/grafanauser/grafanauser_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 grafanauser 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "strings" 24 | 25 | "k8s.io/apimachinery/pkg/api/errors" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "k8s.io/apimachinery/pkg/types" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/log" 31 | 32 | "github.com/grafana-tools/sdk" 33 | grafanauserv1alpha1 "github.com/snapp-cab/grafana-complementary-operator/apis/grafana/v1alpha1" 34 | corev1 "k8s.io/api/core/v1" 35 | ) 36 | 37 | const ( 38 | teamLabel = "snappcloud.io/team" 39 | ) 40 | 41 | // Get Grafana URL and PassWord as a env. 42 | var grafanaPassword = os.Getenv("GRAFANA_PASSWORD") 43 | var grafanaUsername = os.Getenv("GRAFANA_USERNAME") 44 | var grafanaURL = os.Getenv("GRAFANA_URL") 45 | 46 | // GrafanaReconciler reconciles a Grafana object 47 | type GrafanaUserReconciler struct { 48 | client.Client 49 | Scheme *runtime.Scheme 50 | } 51 | 52 | //+kubebuilder:rbac:groups=grafana.snappcloud.io,resources=grafanausers,verbs=get;list;watch;create;update;patch;delete 53 | //+kubebuilder:rbac:groups=grafana.snappcloud.io,resources=grafanausers/status,verbs=get;update;patch 54 | //+kubebuilder:rbac:groups=grafana.snappcloud.io,resources=grafanausers/finalizers,verbs=update 55 | //+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete 56 | //+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete 57 | //+kubebuilder:rbac:groups=user.openshift.io,resources=*,verbs=get;list;watch;create;update;patch;delete 58 | 59 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 60 | // move the current state of the cluster closer to the desired state. 61 | // TODO(user): Modify the Reconcile function to compare the state specified by 62 | // the Grafana object against the actual cluster state, and then 63 | // perform operations to make the cluster state reflect the state specified by 64 | // the user. 65 | // 66 | // For more details, check Reconcile and its Result here: 67 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile 68 | func (r *GrafanaUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 69 | log := log.FromContext(ctx) 70 | reqLogger := log.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name) 71 | // Getting namespace 72 | ns := &corev1.Namespace{} 73 | err := r.Client.Get(context.TODO(), types.NamespacedName{Name: req.Namespace}, ns) 74 | if err != nil { 75 | log.Error(err, "Failed to get namespace") 76 | return ctrl.Result{}, err 77 | } 78 | // Ignore namespaces which does not have team label 79 | org, ok := ns.Labels[teamLabel] 80 | if !ok { 81 | reqLogger.Info("Namespace does not have team label. Ignoring", "namespace", ns.Name, "team name ", org) 82 | return ctrl.Result{}, nil 83 | } 84 | //Connecting to the Grafana API 85 | grafanaclient, err := sdk.NewClient(grafanaURL, fmt.Sprintf("%s:%s", grafanaUsername, grafanaPassword), sdk.DefaultHTTPClient) 86 | if err != nil { 87 | reqLogger.Error(err, "Unable to create Grafana client") 88 | return ctrl.Result{}, err 89 | } 90 | //Retrieving the Organization Info 91 | retrievedOrg, err := grafanaclient.GetOrgByOrgName(ctx, org) 92 | if err != nil { 93 | if strings.Contains(err.Error(), "Organization not found") { 94 | reqLogger.Error(err, "Unable to get organization") 95 | return ctrl.Result{}, err 96 | } 97 | } 98 | reqLogger.Info("Reconciling grafana") 99 | grafana := &grafanauserv1alpha1.GrafanaUser{} 100 | err = r.Client.Get(context.TODO(), req.NamespacedName, grafana) 101 | if err != nil { 102 | if errors.IsNotFound(err) { 103 | // Request object not found, could have been deleted after reconcile request. 104 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 105 | // Return and don't requeue 106 | return ctrl.Result{}, nil 107 | } 108 | // Error reading the object - requeue the request. 109 | return ctrl.Result{}, err 110 | } else { 111 | log.Info("grafana_org is found and orgName is : " + org) 112 | 113 | } 114 | 115 | _, err = r.AddUsersToGrafanaOrgByEmail(ctx, req, org, grafanaclient, retrievedOrg, grafana.Spec.Admin, "admin") 116 | if err != nil { 117 | return ctrl.Result{}, err 118 | } 119 | 120 | _, err = r.AddUsersToGrafanaOrgByEmail(ctx, req, org, grafanaclient, retrievedOrg, grafana.Spec.Edit, "editor") 121 | if err != nil { 122 | return ctrl.Result{}, err 123 | } 124 | _, err = r.AddUsersToGrafanaOrgByEmail(ctx, req, org, grafanaclient, retrievedOrg, grafana.Spec.View, "viewer") 125 | if err != nil { 126 | return ctrl.Result{}, err 127 | } 128 | 129 | return ctrl.Result{}, nil 130 | } 131 | func (r *GrafanaUserReconciler) AddUsersToGrafanaOrgByEmail(ctx context.Context, req ctrl.Request, org string, client *sdk.Client, retrievedOrg sdk.Org, emails []string, role string) (ctrl.Result, error) { 132 | log := log.FromContext(ctx) 133 | reqLogger := log.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name) 134 | orgID := retrievedOrg.ID 135 | orgName := retrievedOrg.Name 136 | getallUser, _ := client.GetAllUsers(ctx) 137 | getuserOrg, _ := client.GetOrgUsers(ctx, orgID) 138 | for _, email := range emails { 139 | var orguserfound bool 140 | for _, orguser := range getuserOrg { 141 | UserOrg := orguser.Email 142 | if email == UserOrg { 143 | orguserfound = true 144 | reqLogger.Info(orguser.Email, "is already in", orgName) 145 | break 146 | } 147 | } 148 | if orguserfound { 149 | continue 150 | } 151 | for _, user := range getallUser { 152 | UserEmail := user.Email 153 | if email == UserEmail { 154 | newuser := sdk.UserRole{LoginOrEmail: email, Role: role} 155 | _, err := client.AddOrgUser(ctx, newuser, orgID) 156 | if err != nil { 157 | return ctrl.Result{}, err 158 | } else { 159 | log.Info(UserEmail, "is added to", orgName) 160 | } 161 | break 162 | } 163 | } 164 | } 165 | return ctrl.Result{}, nil 166 | } 167 | 168 | // SetupWithManager sets up the controller with the Manager. 169 | func (r *GrafanaUserReconciler) SetupWithManager(mgr ctrl.Manager) error { 170 | return ctrl.NewControllerManagedBy(mgr). 171 | For(&grafanauserv1alpha1.GrafanaUser{}). 172 | Complete(r) 173 | } 174 | -------------------------------------------------------------------------------- /controllers/grafanauser/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 grafanauser 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/envtest" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | logf "sigs.k8s.io/controller-runtime/pkg/log" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | 32 | grafanauserv1alpha1 "github.com/snapp-cab/grafana-complementary-operator/apis/grafana/v1alpha1" 33 | //+kubebuilder:scaffold:imports 34 | ) 35 | 36 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 37 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 38 | 39 | var k8sClient client.Client 40 | var testEnv *envtest.Environment 41 | 42 | func TestAPIs(t *testing.T) { 43 | RegisterFailHandler(Fail) 44 | 45 | RunSpecsWithDefaultAndCustomReporters(t, 46 | "Controller Suite", 47 | []Reporter{printer.NewlineReporter{}}) 48 | } 49 | 50 | var _ = BeforeSuite(func() { 51 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 52 | 53 | By("bootstrapping test environment") 54 | testEnv = &envtest.Environment{ 55 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 56 | ErrorIfCRDPathMissing: true, 57 | } 58 | 59 | cfg, err := testEnv.Start() 60 | Expect(err).NotTo(HaveOccurred()) 61 | Expect(cfg).NotTo(BeNil()) 62 | 63 | err = grafanauserv1alpha1.AddToScheme(scheme.Scheme) 64 | Expect(err).NotTo(HaveOccurred()) 65 | 66 | //+kubebuilder:scaffold:scheme 67 | 68 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Expect(k8sClient).NotTo(BeNil()) 71 | 72 | }, 60) 73 | 74 | var _ = AfterSuite(func() { 75 | By("tearing down the test environment") 76 | err := testEnv.Stop() 77 | Expect(err).NotTo(HaveOccurred()) 78 | }) 79 | -------------------------------------------------------------------------------- /controllers/namespace/namespace_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | "fmt" 22 | "os" 23 | "reflect" 24 | "strings" 25 | 26 | grafanav1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1" 27 | "github.com/grafana-tools/sdk" 28 | corev1 "k8s.io/api/core/v1" 29 | "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/apimachinery/pkg/types" 33 | ctrl "sigs.k8s.io/controller-runtime" 34 | "sigs.k8s.io/controller-runtime/pkg/client" 35 | "sigs.k8s.io/controller-runtime/pkg/log" 36 | ) 37 | 38 | const ( 39 | baseNs = "snappcloud-monitoring" 40 | baseSa = "monitoring-datasource" 41 | nsMonitoringLabel = "monitoring.snappcloud.io/grafana-datasource" 42 | teamLabel = "snappcloud.io/team" 43 | ) 44 | 45 | // Get Grafana URL and PassWord as a env. 46 | var grafanaPassword = os.Getenv("GRAFANA_PASSWORD") 47 | var grafanaUsername = os.Getenv("GRAFANA_USERNAME") 48 | var grafanaURL = os.Getenv("GRAFANA_URL") 49 | var prometheusURL = os.Getenv("PROMETHEUS_URL") 50 | 51 | // NamespaceReconciler reconciles a Namespace object 52 | type NamespaceReconciler struct { 53 | client.Client 54 | Scheme *runtime.Scheme 55 | } 56 | 57 | //+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch 58 | //+kubebuilder:rbac:groups=core,resources=namespaces/status,verbs=get;update;patch 59 | //+kubebuilder:rbac:groups=core,resources=namespaces/finalizers,verbs=update 60 | //+kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch 61 | //+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch 62 | 63 | //+kubebuilder:rbac:groups=integreatly.org,resources=grafanadatasources,verbs=get;list;watch;create;update;patch;delete 64 | 65 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 66 | // move the current state of the cluster closer to the desired state. 67 | // TODO(user): Modify the Reconcile function to compare the state specified by 68 | // the Namespace object against the actual cluster state, and then 69 | // perform operations to make the cluster state reflect the state specified by 70 | // the user. 71 | // 72 | // For more details, check Reconcile and its Result here: 73 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile 74 | func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 75 | logger := log.FromContext(ctx) 76 | ns := &corev1.Namespace{} 77 | err := r.Get(ctx, req.NamespacedName, ns) 78 | if err != nil { 79 | if errors.IsNotFound(err) { 80 | // Request object not found, could have been deleted after reconcile request. 81 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 82 | // Return and don't requeue 83 | logger.Info("Resource not found. Ignoring since object must be deleted") 84 | return ctrl.Result{}, nil 85 | } 86 | // Error reading the object - requeue the request. 87 | logger.Error(err, "Failed to get Namespace") 88 | return ctrl.Result{}, err 89 | } 90 | 91 | // Ignore namespaces which does not have special label 92 | if _, ok := ns.Labels[nsMonitoringLabel]; !ok { 93 | logger.Info("Namespace does not have monitoring label. Ignoring", "namespace", ns.Name) 94 | return ctrl.Result{}, nil 95 | } 96 | 97 | // Ignore namespaces which does not have team label 98 | team, ok := ns.Labels[teamLabel] 99 | if !ok { 100 | logger.Info("Namespace does not have team label. Ignoring", "namespace", ns.Name, "team name ", team) 101 | return ctrl.Result{}, nil 102 | } 103 | 104 | logger.Info("Reconciling Namespace", "Namespace.Name", req.NamespacedName, "Team", team) 105 | 106 | // Getting serviceAccount 107 | logger.Info("Getting serviceAccount", "serviceAccount.Name", baseSa, "Namespace.Name", req.NamespacedName) 108 | sa := &corev1.ServiceAccount{} 109 | err = r.Get(ctx, types.NamespacedName{Name: baseSa, Namespace: req.Name}, sa) 110 | if err != nil { 111 | logger.Error(err, "Unable to get ServiceAccount") 112 | return ctrl.Result{}, err 113 | } 114 | 115 | // Getting serviceaccount token 116 | secret := &corev1.Secret{} 117 | var token string 118 | for _, ref := range sa.Secrets { 119 | 120 | logger.Info("Getting secret", "secret.Name", ref.Name, "Namespace.Name", req.Name) 121 | // get secret 122 | err = r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: req.Name}, secret) 123 | if err != nil { 124 | logger.Error(err, "Unable to get Secret") 125 | return ctrl.Result{}, err 126 | } 127 | 128 | // Check if secret is a token for the serviceaccount 129 | if secret.Type != corev1.SecretTypeServiceAccountToken { 130 | continue 131 | } 132 | name := secret.Annotations[corev1.ServiceAccountNameKey] 133 | uid := secret.Annotations[corev1.ServiceAccountUIDKey] 134 | tokenData := secret.Data[corev1.ServiceAccountTokenKey] 135 | //tmp 136 | logger.Info("Token data", "token", string(tokenData)) 137 | logger.Info("Token meta", "name", name, "uid", uid, "ref.Name", ref.Name, "ref.UID", ref.UID) 138 | if name == sa.Name && uid == string(sa.UID) && len(tokenData) > 0 { 139 | // found token, the first token found is used 140 | token = string(tokenData) 141 | logger.Info("Found token", "token", token) 142 | break 143 | } 144 | 145 | } 146 | // if no token found 147 | if token == "" { 148 | logger.Error(fmt.Errorf("did not found service account token for service account %q", sa.Name), "") 149 | return ctrl.Result{}, err 150 | } 151 | gfDs, err := r.generateGfDataSource(ctx, req.Name, team, token, ns) 152 | if err != nil { 153 | logger.Error(err, "Error generating grafanaDatasource manifest") 154 | return ctrl.Result{}, err 155 | } 156 | 157 | // Check if grafanaDatasource does not exist and create a new one 158 | found := &grafanav1alpha1.GrafanaDataSource{} 159 | err = r.Get(ctx, types.NamespacedName{Name: req.Name, Namespace: baseNs}, found) 160 | if err != nil && errors.IsNotFound(err) { 161 | logger.Info("Creating grafana datasource", "grafanaDatasource.Name", gfDs.Name) 162 | err = r.Create(ctx, gfDs) 163 | if err != nil { 164 | logger.Error(err, "Unable to create GrafanaDataSource") 165 | return ctrl.Result{}, err 166 | } 167 | // grafanaDatasource created successfully - return and requeue 168 | return ctrl.Result{Requeue: true}, nil 169 | } else if err != nil { 170 | logger.Error(err, "Failed to get grafanaDatasource") 171 | return ctrl.Result{}, err 172 | } 173 | 174 | // If GrafanaDatasource already exist, check if it is deeply equal with desrired state 175 | if !reflect.DeepEqual(gfDs.Spec, found.Spec) { 176 | logger.Info("Updating grafanaDatasource", "grafanaDatasource.Namespace", found.Namespace, "grafanaDatasource.Name", found.Name) 177 | found.Spec = gfDs.Spec 178 | err := r.Update(ctx, found) 179 | if err != nil { 180 | logger.Error(err, "Failed to update grafanaDatasource", "grafanaDatasource.Namespace", found.Namespace, "grafanaDatasource.Name", found.Name) 181 | return ctrl.Result{}, err 182 | } 183 | } 184 | 185 | return ctrl.Result{}, nil 186 | } 187 | 188 | // SetupWithManager sets up the controller with the Manager. 189 | func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { 190 | return ctrl.NewControllerManagedBy(mgr). 191 | For(&corev1.Namespace{}). 192 | Owns(&grafanav1alpha1.GrafanaDataSource{}). 193 | Complete(r) 194 | } 195 | 196 | func (r *NamespaceReconciler) generateGfDataSource(ctx context.Context, name, team, token string, nsOwner *corev1.Namespace) (*grafanav1alpha1.GrafanaDataSource, error) { 197 | logger := log.FromContext(ctx) 198 | 199 | // Connecting to the Grafana API 200 | client, err := sdk.NewClient(grafanaURL, fmt.Sprintf("%s:%s", grafanaUsername, grafanaPassword), sdk.DefaultHTTPClient) 201 | if err != nil { 202 | logger.Error(err, "Unable to create Grafana client") 203 | return nil, err 204 | } 205 | 206 | // Retrieving the Organization Info 207 | retrievedOrg, err := client.GetOrgByOrgName(ctx, team) 208 | if err != nil { 209 | if strings.Contains(err.Error(), "Organization not found") { 210 | logger.Info("Creating organization", "team name is", team) 211 | // Create the organization 212 | 213 | neworg := sdk.Org{Name: team} 214 | _, err := client.CreateOrg(ctx, neworg) 215 | 216 | if err != nil { 217 | logger.Error(err, "Failed to create organization") 218 | return nil, err 219 | } 220 | retrievedOrg = neworg 221 | logger.Info("Organization created", "for team", team, "organization name is", retrievedOrg.Name) 222 | } else { 223 | logger.Error(err, "Unable to get organization") 224 | return nil, err 225 | } 226 | } 227 | // Generating the datasource 228 | logger.Info("Start creating Datasource", "Team name:", team, "Team ID:", retrievedOrg.ID, "Namespace", name) 229 | grafanaDatasource := &grafanav1alpha1.GrafanaDataSource{ 230 | ObjectMeta: metav1.ObjectMeta{ 231 | Name: name, 232 | Namespace: baseNs, 233 | }, 234 | Spec: grafanav1alpha1.GrafanaDataSourceSpec{ 235 | Name: name + ".yaml", 236 | Datasources: []grafanav1alpha1.GrafanaDataSourceFields{{ 237 | Access: "proxy", 238 | Editable: false, 239 | IsDefault: false, 240 | Name: name, 241 | OrgId: int(retrievedOrg.ID), 242 | Type: "prometheus", 243 | Url: prometheusURL, 244 | Version: 1, 245 | JsonData: grafanav1alpha1.GrafanaDataSourceJsonData{ 246 | HTTPMethod: "POST", 247 | TlsSkipVerify: true, 248 | HTTPHeaderName1: "Authorization", 249 | HTTPHeaderName2: "namespace", 250 | }, 251 | SecureJsonData: grafanav1alpha1.GrafanaDataSourceSecureJsonData{ 252 | HTTPHeaderValue1: "Bearer " + token, 253 | HTTPHeaderValue2: name, 254 | }, 255 | }}, 256 | }, 257 | } 258 | 259 | // Set target Namespace as the owner of GrafanaDatasource in another Namespace 260 | err = ctrl.SetControllerReference(nsOwner, grafanaDatasource, r.Scheme) 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | return grafanaDatasource, nil 266 | } 267 | -------------------------------------------------------------------------------- /controllers/namespace/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | corev1 "k8s.io/api/core/v1" 26 | "k8s.io/client-go/kubernetes/scheme" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | //+kubebuilder:scaffold:imports 33 | ) 34 | 35 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 36 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 37 | 38 | var k8sClient client.Client 39 | var testEnv *envtest.Environment 40 | 41 | func TestAPIs(t *testing.T) { 42 | RegisterFailHandler(Fail) 43 | 44 | RunSpecsWithDefaultAndCustomReporters(t, 45 | "Controller Suite", 46 | []Reporter{printer.NewlineReporter{}}) 47 | } 48 | 49 | var _ = BeforeSuite(func() { 50 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 51 | 52 | By("bootstrapping test environment") 53 | testEnv = &envtest.Environment{ 54 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 55 | ErrorIfCRDPathMissing: false, 56 | } 57 | 58 | cfg, err := testEnv.Start() 59 | Expect(err).NotTo(HaveOccurred()) 60 | Expect(cfg).NotTo(BeNil()) 61 | 62 | err = corev1.AddToScheme(scheme.Scheme) 63 | Expect(err).NotTo(HaveOccurred()) 64 | 65 | //+kubebuilder:scaffold:scheme 66 | 67 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 68 | Expect(err).NotTo(HaveOccurred()) 69 | Expect(k8sClient).NotTo(BeNil()) 70 | 71 | }, 60) 72 | 73 | var _ = AfterSuite(func() { 74 | By("tearing down the test environment") 75 | err := testEnv.Stop() 76 | Expect(err).NotTo(HaveOccurred()) 77 | }) 78 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/snapp-cab/grafana-complementary-operator 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/grafana-operator/grafana-operator/v4 v4.10.1 7 | github.com/grafana-tools/sdk v0.0.0-20220402173226-77f22ba83269 8 | github.com/onsi/ginkgo v1.16.5 9 | github.com/onsi/gomega v1.29.0 10 | k8s.io/api v0.28.3 11 | k8s.io/apimachinery v0.28.3 12 | k8s.io/client-go v0.28.3 13 | sigs.k8s.io/controller-runtime v0.16.3 14 | ) 15 | 16 | require ( 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/blang/semver v3.5.1+incompatible // indirect 19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 23 | github.com/fsnotify/fsnotify v1.6.0 // indirect 24 | github.com/go-logr/logr v1.2.4 // indirect 25 | github.com/go-logr/zapr v1.2.4 // indirect 26 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 27 | github.com/go-openapi/jsonreference v0.20.2 // indirect 28 | github.com/go-openapi/swag v0.22.3 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/golang/protobuf v1.5.3 // indirect 32 | github.com/google/gnostic-models v0.6.8 // indirect 33 | github.com/google/go-cmp v0.6.0 // indirect 34 | github.com/google/gofuzz v1.2.0 // indirect 35 | github.com/google/uuid v1.3.0 // indirect 36 | github.com/gosimple/slug v1.12.0 // indirect 37 | github.com/gosimple/unidecode v1.0.1 // indirect 38 | github.com/imdario/mergo v0.3.13 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/mailru/easyjson v0.7.7 // indirect 42 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 44 | github.com/modern-go/reflect2 v1.0.2 // indirect 45 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 46 | github.com/nxadm/tail v1.4.8 // indirect 47 | github.com/openshift/api v3.9.0+incompatible // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/prometheus/client_golang v1.16.0 // indirect 50 | github.com/prometheus/client_model v0.4.0 // indirect 51 | github.com/prometheus/common v0.44.0 // indirect 52 | github.com/prometheus/procfs v0.10.1 // indirect 53 | github.com/spf13/pflag v1.0.5 // indirect 54 | go.uber.org/multierr v1.11.0 // indirect 55 | go.uber.org/zap v1.25.0 // indirect 56 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect 57 | golang.org/x/net v0.17.0 // indirect 58 | golang.org/x/oauth2 v0.8.0 // indirect 59 | golang.org/x/sys v0.13.0 // indirect 60 | golang.org/x/term v0.13.0 // indirect 61 | golang.org/x/text v0.13.0 // indirect 62 | golang.org/x/time v0.3.0 // indirect 63 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 64 | google.golang.org/appengine v1.6.7 // indirect 65 | google.golang.org/protobuf v1.30.0 // indirect 66 | gopkg.in/inf.v0 v0.9.1 // indirect 67 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 68 | gopkg.in/yaml.v2 v2.4.0 // indirect 69 | gopkg.in/yaml.v3 v3.0.1 // indirect 70 | k8s.io/apiextensions-apiserver v0.28.3 // indirect 71 | k8s.io/component-base v0.28.3 // indirect 72 | k8s.io/klog/v2 v2.100.1 // indirect 73 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 74 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 75 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 76 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 77 | sigs.k8s.io/yaml v1.3.0 // indirect 78 | ) 79 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 2 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 6 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 7 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 8 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/chromedp/cdproto v0.0.0-20210526005521-9e51b9051fd0/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= 10 | github.com/chromedp/cdproto v0.0.0-20210706234513-2bc298e8be7f h1:lg5k1KAxmknil6Z19LaaeiEs5Pje7hPzRfyWSSnWLP0= 11 | github.com/chromedp/cdproto v0.0.0-20210706234513-2bc298e8be7f/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= 12 | github.com/chromedp/chromedp v0.7.3 h1:FvgJICfjvXtDX+miuMUY0NHuY8zQvjS/TcEQEG6Ldzs= 13 | github.com/chromedp/chromedp v0.7.3/go.mod h1:9gC521Yzgrk078Ulv6KIgG7hJ2x9aWrxMBBobTFk30A= 14 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 15 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 16 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 21 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 22 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 23 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 24 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 25 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 26 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 27 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 28 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 29 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 30 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 31 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 32 | github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= 33 | github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= 34 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 35 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 36 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 37 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 38 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 39 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 40 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 41 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 42 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 43 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 44 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 45 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 46 | github.com/gobwas/ws v1.1.0-rc.5 h1:QOAag7FoBaBYYHRqzqkhhd8fq5RTubvI4v3Ft/gDVVQ= 47 | github.com/gobwas/ws v1.1.0-rc.5/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= 48 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 49 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 50 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 51 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 52 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 55 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 56 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 57 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 58 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 59 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 60 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 61 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 62 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 63 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 64 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 65 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 66 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 67 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 68 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 69 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 70 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 71 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 72 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 73 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 74 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 75 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 76 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 77 | github.com/gosimple/slug v1.1.1/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0= 78 | github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc= 79 | github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= 80 | github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= 81 | github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= 82 | github.com/grafana-operator/grafana-operator/v4 v4.10.1 h1:9TSZhuMh6b64frhTa8eb+jBEw0oZp076Bh990Ts2WqU= 83 | github.com/grafana-operator/grafana-operator/v4 v4.10.1/go.mod h1:k69wJcXVrqAcZBoGuh5LSqz0ak8LlVOxxqp0W3f/4V8= 84 | github.com/grafana-tools/sdk v0.0.0-20220402173226-77f22ba83269 h1:730mFhwDnWHkimN3URriW84LJs8k0PJccN1lDn7cxHA= 85 | github.com/grafana-tools/sdk v0.0.0-20220402173226-77f22ba83269/go.mod h1:AHHlOEv1+GGQ3ktHMlhuTUwo3zljV3QJbC0+8o2kn+4= 86 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 87 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 88 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 89 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 90 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 91 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 92 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 93 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 94 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 95 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 96 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 97 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 98 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 99 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 100 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 101 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 102 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 103 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 104 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 105 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 106 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 107 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 108 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 109 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 110 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 111 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 112 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 113 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 114 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 115 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 116 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 117 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 118 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 119 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 120 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 121 | github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= 122 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 123 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 124 | github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= 125 | github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 126 | github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= 127 | github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= 128 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 129 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 130 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 131 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 132 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 133 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 134 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 135 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 136 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 137 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 138 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 139 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 140 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 141 | github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= 142 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 143 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 144 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 145 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 146 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 147 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 148 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 149 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 150 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 151 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 152 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 153 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 154 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 155 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 156 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 157 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 158 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 159 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 160 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 161 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 162 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 163 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 164 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 165 | go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= 166 | go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= 167 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 168 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 169 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 170 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= 171 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= 172 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 173 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 174 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 175 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 176 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 177 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 178 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 179 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 180 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 181 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 182 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 183 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 184 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 185 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 186 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 187 | golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= 188 | golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 189 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 190 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 191 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 192 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 196 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 197 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 | golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 210 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 211 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 212 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 213 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 214 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 215 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 216 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 217 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 218 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 219 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 220 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 221 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 222 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 223 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 224 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 225 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 226 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 227 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 228 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 229 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 230 | golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= 231 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 232 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 233 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 234 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 235 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 236 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 237 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 238 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 239 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 240 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 241 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 242 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 243 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 244 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 245 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 246 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 247 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 248 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 249 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 250 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 251 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 252 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 253 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 254 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 255 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 256 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 257 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 258 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 259 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 260 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 261 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 262 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 263 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 264 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 265 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 266 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 267 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 268 | k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= 269 | k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= 270 | k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= 271 | k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= 272 | k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= 273 | k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= 274 | k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= 275 | k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= 276 | k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= 277 | k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= 278 | k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= 279 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 280 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= 281 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= 282 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= 283 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 284 | sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= 285 | sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= 286 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 287 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 288 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 289 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 290 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 291 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 292 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 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 | */ -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 24 | // to ensure that exec-entrypoint and run can make use of them. 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | 27 | grafanav1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 30 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 31 | ctrl "sigs.k8s.io/controller-runtime" 32 | "sigs.k8s.io/controller-runtime/pkg/healthz" 33 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 34 | 35 | grafanauserv1alpha1 "github.com/snapp-cab/grafana-complementary-operator/apis/grafana/v1alpha1" 36 | grafanausercontrollers "github.com/snapp-cab/grafana-complementary-operator/controllers/grafanauser" 37 | namesapcecontrollers "github.com/snapp-cab/grafana-complementary-operator/controllers/namespace" 38 | //+kubebuilder:scaffold:imports 39 | ) 40 | 41 | var ( 42 | scheme = runtime.NewScheme() 43 | setupLog = ctrl.Log.WithName("setup") 44 | ) 45 | 46 | func init() { 47 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 48 | 49 | utilruntime.Must(grafanav1alpha1.AddToScheme(scheme)) 50 | utilruntime.Must(grafanauserv1alpha1.AddToScheme(scheme)) 51 | //+kubebuilder:scaffold:scheme 52 | } 53 | 54 | func main() { 55 | var metricsAddr string 56 | var enableLeaderElection bool 57 | var probeAddr string 58 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 59 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 60 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 61 | "Enable leader election for controller manager. "+ 62 | "Enabling this will ensure there is only one active controller manager.") 63 | opts := zap.Options{ 64 | Development: true, 65 | } 66 | opts.BindFlags(flag.CommandLine) 67 | flag.Parse() 68 | 69 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 70 | 71 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 72 | Scheme: scheme, 73 | MetricsBindAddress: metricsAddr, 74 | Port: 9443, 75 | HealthProbeBindAddress: probeAddr, 76 | LeaderElection: enableLeaderElection, 77 | LeaderElectionID: "5a5bab80.snappcloud.io", 78 | }) 79 | if err != nil { 80 | setupLog.Error(err, "unable to start manager") 81 | os.Exit(1) 82 | } 83 | 84 | if err = (&namesapcecontrollers.NamespaceReconciler{ 85 | Client: mgr.GetClient(), 86 | Scheme: mgr.GetScheme(), 87 | }).SetupWithManager(mgr); err != nil { 88 | setupLog.Error(err, "unable to create controller", "controller", "Namespace") 89 | os.Exit(1) 90 | } 91 | if err = (&grafanausercontrollers.GrafanaUserReconciler{ 92 | Client: mgr.GetClient(), 93 | Scheme: mgr.GetScheme(), 94 | }).SetupWithManager(mgr); err != nil { 95 | setupLog.Error(err, "unable to create controller", "controller", "GrafanaUser") 96 | os.Exit(1) 97 | } 98 | if err = (&grafanauserv1alpha1.GrafanaUser{}).SetupWebhookWithManager(mgr); err != nil { 99 | setupLog.Error(err, "unable to create webhook", "webhook", "GrafanaUser") 100 | os.Exit(1) 101 | } 102 | //+kubebuilder:scaffold:builder 103 | 104 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 105 | setupLog.Error(err, "unable to set up health check") 106 | os.Exit(1) 107 | } 108 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 109 | setupLog.Error(err, "unable to set up ready check") 110 | os.Exit(1) 111 | } 112 | 113 | setupLog.Info("starting manager") 114 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 115 | setupLog.Error(err, "problem running manager") 116 | os.Exit(1) 117 | } 118 | } 119 | --------------------------------------------------------------------------------