├── .chainsaw.yaml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── labeler.yml ├── release.yml └── workflows │ ├── e2e.yaml │ ├── hugo.yaml │ ├── labeler.yaml │ ├── pr-hugo.yaml │ ├── pr-validation.yaml │ ├── release.yaml │ └── stale.yml ├── .gitignore ├── .golangci.yaml ├── .ko.yaml ├── .pre-commit-config.yaml ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── PREPARE_RELEASE.md ├── PROJECT ├── README.md ├── Toolchain.mk ├── api ├── folderreferencer.go └── v1beta1 │ ├── common.go │ ├── common_test.go │ ├── content.go │ ├── grafana_types.go │ ├── grafanaalertrulegroup_types.go │ ├── grafanaalertrulegroup_types_test.go │ ├── grafanacontactpoint_types.go │ ├── grafanacontactpoint_types_test.go │ ├── grafanadashboard_types.go │ ├── grafanadashboard_types_test.go │ ├── grafanadatasource_types.go │ ├── grafanadatasource_types_test.go │ ├── grafanafolder_types.go │ ├── grafanafolder_types_test.go │ ├── grafanalibrarypanel_types.go │ ├── grafanalibrarypanel_types_test.go │ ├── grafanamutetiming_types.go │ ├── grafanamutetiming_types_test.go │ ├── grafananotificationpolicy_types.go │ ├── grafananotificationpolicy_types_test.go │ ├── grafananotificationpolicyroute_types.go │ ├── grafananotificationtemplate_types.go │ ├── grafananotificationtemplate_types_test.go │ ├── groupversion_info.go │ ├── namespaced_resource.go │ ├── plugin_list.go │ ├── plugin_list_test.go │ ├── suite_test.go │ ├── typeoverrides.go │ └── zz_generated.deepcopy.go ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── grafana-operator-manager-config_v1_configmap.yaml │ ├── grafana-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── grafana-operator-operator-metrics-service_v1_service.yaml │ ├── grafana-operator.clusterserviceversion.yaml │ ├── grafana.integreatly.org_grafanaalertrulegroups.yaml │ ├── grafana.integreatly.org_grafanacontactpoints.yaml │ ├── grafana.integreatly.org_grafanadashboards.yaml │ ├── grafana.integreatly.org_grafanadatasources.yaml │ ├── grafana.integreatly.org_grafanafolders.yaml │ └── grafana.integreatly.org_grafanas.yaml └── metadata │ └── annotations.yaml ├── catalog-info.yaml ├── config ├── crd │ ├── bases │ │ ├── grafana.integreatly.org_grafanaalertrulegroups.yaml │ │ ├── grafana.integreatly.org_grafanacontactpoints.yaml │ │ ├── grafana.integreatly.org_grafanadashboards.yaml │ │ ├── grafana.integreatly.org_grafanadatasources.yaml │ │ ├── grafana.integreatly.org_grafanafolders.yaml │ │ ├── grafana.integreatly.org_grafanalibrarypanels.yaml │ │ ├── grafana.integreatly.org_grafanamutetimings.yaml │ │ ├── grafana.integreatly.org_grafananotificationpolicies.yaml │ │ ├── grafana.integreatly.org_grafananotificationpolicyroutes.yaml │ │ ├── grafana.integreatly.org_grafananotificationtemplates.yaml │ │ └── grafana.integreatly.org_grafanas.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_grafanaalertrulegroups.yaml │ │ ├── cainjection_in_grafanacontactpoints.yaml │ │ ├── cainjection_in_grafanadashboards.yaml │ │ ├── cainjection_in_grafanadatasources.yaml │ │ ├── cainjection_in_grafanafolders.yaml │ │ ├── cainjection_in_grafananotificationpolicyroutes.yaml │ │ ├── cainjection_in_grafanas.yaml │ │ ├── webhook_in_grafanaalertrulegroups.yaml │ │ ├── webhook_in_grafanacontactpoints.yaml │ │ ├── webhook_in_grafanadashboards.yaml │ │ ├── webhook_in_grafanadatasources.yaml │ │ ├── webhook_in_grafanafolders.yaml │ │ ├── webhook_in_grafananotificationpolicyroutes.yaml │ │ └── webhook_in_grafanas.yaml ├── default │ ├── kustomization.yaml │ ├── manager_config_patch.yaml │ └── metrics_service.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── grafana-operator.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── grafana_editor_role.yaml │ ├── grafana_viewer_role.yaml │ ├── grafanaalertrulegroup_editor_role.yaml │ ├── grafanaalertrulegroup_viewer_role.yaml │ ├── grafanacontactpoint_editor_role.yaml │ ├── grafanacontactpoint_viewer_role.yaml │ ├── grafanadashboard_editor_role.yaml │ ├── grafanadashboard_viewer_role.yaml │ ├── grafanadatasource_editor_role.yaml │ ├── grafanadatasource_viewer_role.yaml │ ├── grafanafolder_editor_role.yaml │ ├── grafanafolder_viewer_role.yaml │ ├── grafanalibrarypanel_editor_role.yaml │ ├── grafanalibrarypanel_viewer_role.yaml │ ├── grafananotificationpolicyroute_editor_role.yaml │ ├── grafananotificationpolicyroute_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── grafana_v1beta1_grafana.yaml │ ├── grafana_v1beta1_grafanaalertrulegroup.yaml │ ├── grafana_v1beta1_grafanacontactpoint.yaml │ ├── grafana_v1beta1_grafanadashboard.yaml │ ├── grafana_v1beta1_grafanadatasource.yaml │ ├── grafana_v1beta1_grafanafolder.yaml │ ├── grafana_v1beta1_grafanalibrarypanel.yaml │ ├── grafana_v1beta1_grafanamutetiming.yaml │ ├── grafana_v1beta1_grafananotificationpolicy.yaml │ ├── grafana_v1beta1_grafananotificationpolicyroute.yaml │ ├── grafana_v1beta1_grafananotificationtemplate.yaml │ └── kustomization.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── alertrulegroup_controller.go ├── alertrulegroup_controller_test.go ├── autodetect │ ├── main.go │ └── main_test.go ├── client │ ├── common.go │ ├── grafana_client.go │ ├── grafana_client_test.go │ ├── http_client.go │ ├── round_tripper.go │ └── tls.go ├── config │ ├── grafana_ini.go │ ├── grafana_ini_test.go │ └── operator_constants.go ├── contactpoint_controller.go ├── contactpoint_controller_test.go ├── content │ ├── cache │ │ ├── cache.go │ │ └── cache_test.go │ ├── fetchers │ │ ├── configmap_fetcher.go │ │ ├── grafana_com_fetcher.go │ │ ├── grafana_com_fetcher_test.go │ │ ├── jsonnet_fetcher.go │ │ ├── jsonnet_fetcher_test.go │ │ ├── suite_test.go │ │ ├── url_fetcher.go │ │ └── url_fetcher_test.go │ ├── resolver.go │ ├── resolver_test.go │ ├── source.go │ └── suite_test.go ├── controller_shared.go ├── controller_shared_test.go ├── dashboard_controller.go ├── dashboard_controller_test.go ├── datasource_controller.go ├── datasource_controller_test.go ├── folder_controller.go ├── folder_controller_test.go ├── grafana_controller.go ├── librarypanel_controller.go ├── librarypanel_controller_test.go ├── metrics │ └── metrics.go ├── model │ ├── dashboard_resources.go │ ├── grafana_resources.go │ └── utils.go ├── mutetiming_controller.go ├── mutetiming_controller_test.go ├── notificationpolicy_controller.go ├── notificationpolicy_controller_test.go ├── notificationtemplate_controller.go ├── notificationtemplate_controller_test.go ├── reconcilers │ ├── grafana │ │ ├── admin_secret_reconciler.go │ │ ├── admin_secret_reconciler_test.go │ │ ├── complete_reconciler.go │ │ ├── config_reconciler.go │ │ ├── deployment_reconciler.go │ │ ├── deployment_reconciler_test.go │ │ ├── ingress_reconciler.go │ │ ├── plugins_reconciler.go │ │ ├── pvc_reconciler.go │ │ ├── service_account_reconciler.go │ │ ├── service_reconciler.go │ │ ├── service_reconciler_test.go │ │ └── suite_test.go │ └── reconciler.go └── suite_test.go ├── deploy ├── helm │ ├── cr.yaml │ └── grafana-operator │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── README.md.gotmpl │ │ ├── crds │ │ ├── grafana.integreatly.org_grafanaalertrulegroups.yaml │ │ ├── grafana.integreatly.org_grafanacontactpoints.yaml │ │ ├── grafana.integreatly.org_grafanadashboards.yaml │ │ ├── grafana.integreatly.org_grafanadatasources.yaml │ │ ├── grafana.integreatly.org_grafanafolders.yaml │ │ ├── grafana.integreatly.org_grafanalibrarypanels.yaml │ │ ├── grafana.integreatly.org_grafanamutetimings.yaml │ │ ├── grafana.integreatly.org_grafananotificationpolicies.yaml │ │ ├── grafana.integreatly.org_grafananotificationpolicyroutes.yaml │ │ ├── grafana.integreatly.org_grafananotificationtemplates.yaml │ │ └── grafana.integreatly.org_grafanas.yaml │ │ ├── files │ │ ├── dashboard.json │ │ ├── rbac-openshift.yaml │ │ └── rbac.yaml │ │ ├── templates │ │ ├── _helpers.tpl │ │ ├── dashboard.yaml │ │ ├── deployment.yaml │ │ ├── extraobjects.yaml │ │ ├── rbac.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── servicemonitor.yaml │ │ └── values.yaml └── kustomize │ ├── README.md │ ├── base │ ├── crds.yaml │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── namespace.yaml │ ├── role.yaml │ ├── rolebinding.yaml │ ├── service.yaml │ └── serviceaccount.yaml │ └── overlays │ ├── chainsaw │ ├── deployment-patch.yaml │ └── kustomization.yaml │ ├── cluster_scoped │ └── kustomization.yaml │ └── namespace_scoped │ ├── deployment.yaml │ └── kustomization.yaml ├── docs ├── README.md ├── _index.html ├── about │ └── _index.html ├── blog │ ├── _index.md │ ├── flux-gitops │ │ ├── grafana-operator.yaml │ │ ├── grafana.yaml │ │ └── grafana │ │ │ ├── dashboard.yaml │ │ │ ├── grafana.yaml │ │ │ └── kustomization.yaml │ ├── history-and-operator-reach.md │ ├── kustomize-installation.md │ ├── operator-migration-to-upstream-grafana.md │ ├── v4-v5-migration.md │ ├── v5-getting-started.md │ └── v5-intro.md └── docs │ ├── _index.md │ ├── alerting │ ├── _index.md │ ├── alert-rule-groups.md │ ├── contact-points.md │ ├── dynamic-notification-policy.png │ ├── notification-policies.md │ ├── notification-policy-tree.png │ ├── notification-routing.png │ └── overview-page.png │ ├── api.md │ ├── dashboards.md │ ├── datasources.md │ ├── folder.md │ ├── grafana.md │ ├── installation │ ├── _index.md │ ├── kustomize.md │ └── ops-and-monitoring.md │ ├── library-panels.md │ ├── overview.md │ ├── proposals │ ├── 001 external grafana instance.md │ ├── 002-alerting-support.md │ ├── 003-grafanaserviceaccount-crd.md │ ├── 004-grafanafolder-parent-folder-management.md │ ├── 005-grafanadashboard-folder-management-strategy-update.md │ ├── 006-ssl-specification-in-grafanaexternal.md │ ├── 007-grafanalibrarypanel-crd.md │ └── _index.md │ ├── quick-start.md │ └── security.md ├── embeds ├── README.md ├── grafana_embeds.go ├── grafonnet-lib │ ├── grafonnet-7.0 │ │ ├── DOCS.md │ │ ├── dashboard.libsonnet │ │ ├── grafana.libsonnet │ │ ├── panel │ │ │ ├── gauge.libsonnet │ │ │ ├── graph.libsonnet │ │ │ ├── row.libsonnet │ │ │ ├── stat.libsonnet │ │ │ ├── table.libsonnet │ │ │ └── text.libsonnet │ │ ├── target │ │ │ └── prometheus.libsonnet │ │ └── template │ │ │ ├── custom.libsonnet │ │ │ ├── datasource.libsonnet │ │ │ └── query.libsonnet │ └── grafonnet │ │ ├── alert_condition.libsonnet │ │ ├── alertlist.libsonnet │ │ ├── annotation.libsonnet │ │ ├── bar_gauge_panel.libsonnet │ │ ├── cloudmonitoring.libsonnet │ │ ├── cloudwatch.libsonnet │ │ ├── dashboard.libsonnet │ │ ├── dashlist.libsonnet │ │ ├── elasticsearch.libsonnet │ │ ├── gauge_panel.libsonnet │ │ ├── grafana.libsonnet │ │ ├── graph_panel.libsonnet │ │ ├── graphite.libsonnet │ │ ├── heatmap_panel.libsonnet │ │ ├── influxdb.libsonnet │ │ ├── link.libsonnet │ │ ├── log_panel.libsonnet │ │ ├── loki.libsonnet │ │ ├── pie_chart_panel.libsonnet │ │ ├── pluginlist.libsonnet │ │ ├── prometheus.libsonnet │ │ ├── row.libsonnet │ │ ├── singlestat.libsonnet │ │ ├── sql.libsonnet │ │ ├── stat_panel.libsonnet │ │ ├── table_panel.libsonnet │ │ ├── template.libsonnet │ │ ├── text.libsonnet │ │ ├── timepicker.libsonnet │ │ └── transformation.libsonnet └── testing │ ├── dashboard.json │ ├── dashboard.jsonnet │ ├── dashboard_with_envs.jsonnet │ ├── dashboard_with_provided_envs.json │ ├── jsonnetProjectWithRuntimeRaw.tar.gz │ └── jsonnetProjectWithRuntimeRaw │ └── dashboard_with_envs.jsonnet ├── examples ├── _index.md ├── alertrulegroups │ ├── README.md │ └── resources.yaml ├── basic │ ├── README.md │ └── resources.yaml ├── configmaps_sidecar │ ├── README.md │ └── resources.yaml ├── contactpoint_override │ ├── README.md │ └── resources.yaml ├── credential_config │ ├── README.md │ └── resources.yaml ├── credential_secret │ ├── README.md │ └── resources.yaml ├── crossnamespace │ ├── README.md │ └── resources.yaml ├── dashboard_from_configmap │ ├── README.md │ └── resources.yaml ├── dashboard_from_grafana_com │ ├── README.md │ └── resources.yaml ├── dashboard_from_url │ ├── README.md │ ├── dashboard.json │ └── resources.yaml ├── dashboard_gzipped │ ├── README.md │ ├── dashboard.json │ └── resources.yaml ├── dashboard_with_custom_folder │ ├── README.md │ └── resources.yaml ├── dashboard_with_external_dependencies │ ├── README.md │ ├── dashboard_project │ │ ├── dashboard.jsonnet │ │ ├── jsonnetfile.json │ │ ├── jsonnetfile.lock.json │ │ └── util │ │ │ ├── datasources.libsonnet │ │ │ ├── envs.libsonnet │ │ │ ├── g.libsonnet │ │ │ ├── panels.libsonnet │ │ │ ├── queries.libsonnet │ │ │ ├── styles.libsonnet │ │ │ └── variables.libsonnet │ └── resources.yaml ├── datasource_mapping │ ├── README.md │ └── resources.yaml ├── datasource_types │ ├── README.md │ ├── postgresql.yaml │ └── prometheus.yaml ├── datasource_variables │ ├── README.md │ └── resources.yaml ├── external_grafana │ ├── README.md │ └── resources.yaml ├── folder │ ├── README.md │ └── resources.yaml ├── grafana_deployment │ ├── README.md │ └── resources.yaml ├── grafana_google_sso │ ├── README.md │ └── resources.yaml ├── grafana_keycloak_sso │ ├── README.md │ └── resources.yaml ├── ingress_http │ ├── README.md │ └── resources.yaml ├── ingress_https │ ├── README.md │ ├── cert.yaml │ ├── dashboard.yaml │ └── resources.yaml ├── jsonnet │ ├── README.md │ └── resources.yaml ├── k3d │ ├── README.md │ └── resources.yaml ├── ldap │ ├── README.md │ └── resources.yaml ├── multiple_replicas │ ├── README.md │ └── resources.yaml ├── mute_timing │ ├── README.md │ └── resources.yaml ├── notification-policy │ ├── resources.yaml │ └── routes.yaml ├── notification_template │ ├── README.md │ └── resources.yaml ├── notifications-full │ ├── contact-points.yaml │ ├── folder.yaml │ ├── kubernetes-alert-rules.yaml │ ├── notification-policy.yaml │ └── security-alert-rules.yaml ├── oauth_proxy │ ├── README.md │ └── resources.yaml ├── openshift │ ├── README.md │ └── resources.yaml ├── persistent_volume │ ├── README.md │ └── resources.yaml └── plugins │ ├── README.md │ ├── dashboard.yaml │ └── datasource.yaml ├── go.mod ├── go.sum ├── hack ├── add-openshift-annotations.sh ├── boilerplate.go.txt └── kind │ ├── populate-kind-cluster.sh │ ├── resources │ ├── cluster.yaml │ ├── crd-ns │ │ ├── grafana-dashboard.yaml │ │ ├── grafana-datasource.yaml │ │ └── kustomization.yaml │ └── default │ │ ├── deployment-thanos.yaml │ │ ├── grafana-contactpoint.yaml │ │ ├── grafana-dashboard.yaml │ │ ├── grafana-datasource-thanos.yaml │ │ ├── grafana-datasource.yaml │ │ ├── grafana-notification-policy.yaml │ │ ├── grafana.yaml │ │ ├── kustomization.yaml │ │ ├── secret.yaml │ │ └── serviceaccount.yaml │ └── start-kind.sh ├── hugo ├── .gitignore ├── Makefile ├── README.md ├── archetypes │ └── default.md ├── assets │ └── scss │ │ ├── _styles_project.scss │ │ └── _variables_project.scss ├── config.yaml ├── go.mod ├── go.sum ├── package-lock.json ├── package.json └── templates │ └── frontmatter-grafana-operator.tmpl ├── labs ├── audit │ ├── README.md │ ├── audit-policy.yaml │ ├── collect-audit-stats.sh │ └── kind-config.yaml └── benchmark │ ├── Makefile │ ├── create_resources.lua │ ├── dashboards │ └── .gitkeep │ ├── datasources │ └── .gitkeep │ └── prometheus.yml ├── main.go ├── media ├── logo.svg ├── logo_small.svg └── slack.png └── tests ├── e2e ├── conditions │ ├── 02-apply-failed-assertions.yaml │ ├── 03-additional-invalid-spec.yaml │ ├── 03-invalid-spec-assertions.yaml │ ├── 03-testdata-invalid-specs.yaml │ └── chainsaw-test.yaml ├── example-test │ ├── 00-assert.yaml │ ├── 00-create-external-grafana.yaml │ ├── 00-create-grafana.yaml │ ├── 00-create-versioned-grafana.yaml │ ├── 01-assert.yaml │ ├── 01-datasource.yaml │ ├── 02-assert.yaml │ ├── 03-assert.yaml │ ├── 03-dashboard.yaml │ ├── 04-assert.yaml │ ├── 04-dashboard.yaml │ ├── 05-assert.yaml │ ├── 05-dashboard.yaml │ ├── 06-assert.yaml │ ├── 06-dashboard.yaml │ ├── 07-assert.yaml │ ├── 07-jsonnet.yaml │ ├── 08-alert-folder.yaml │ ├── 08-alert-rule-group.yaml │ ├── 08-assert.yaml │ ├── 08-folder-assert.yaml │ ├── 09-assert.yaml │ ├── 09-contactpoint.yaml │ ├── 10-assert-contact-points.yaml │ ├── 10-assert.yaml │ ├── 10-contact-points.yaml │ ├── 10-notification-policy.yaml │ ├── 11-assert.yaml │ ├── 11-tls.yaml │ ├── 12-assert.yaml │ ├── 12-notification-template.yaml │ ├── 13-assert.yaml │ ├── 13-mute-timing.yaml │ ├── 14-assert.yaml │ ├── 14-library-panel.yaml │ ├── 15-assert.yaml │ ├── 15-library-panel.yaml │ └── chainsaw-test.yaml ├── examples │ ├── basic │ │ ├── assertions.yaml │ │ ├── chainsaw-test.yaml │ │ └── resources.yaml │ ├── crossnamespace │ │ ├── assertions.yaml │ │ ├── chainsaw-test.yaml │ │ └── resources.yaml │ └── dashboard_immutable_uid │ │ ├── base-resources.yaml │ │ └── chainsaw-test.yaml.disabled ├── force_delete_folder │ └── chainsaw-test.yaml ├── testdata-assertions.yaml └── testdata-resources.yaml └── example-resources.yaml /.chainsaw.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/configuration-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha2 3 | kind: Configuration 4 | metadata: 5 | name: configuration 6 | spec: 7 | error: 8 | catch: 9 | - describe: 10 | apiVersion: grafana.integreatly.org/v1beta1 11 | kind: grafana-operator 12 | - podLogs: 13 | namespace: grafana-operator-system 14 | tail: 100 15 | timeouts: 16 | assert: 2m0s 17 | cleanup: 3m0s 18 | delete: 2m0s 19 | error: 2m0s 20 | exec: 2m0s 21 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us triage the problem and find a fix. 4 | labels: needs triage 5 | type: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Version** 14 | Full semver version of the operator being used e.g. v4.10.0, v5.0.0-rc0 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | etc. 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Suspect component/Location where the bug might be occurring** 29 | Please provide this if you know where this bug might occur otherwise leave as `unknown` 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Runtime (please complete the following information):** 35 | 36 | 37 | 38 | - OS: [e.g. Linux,Fedora,Mac] 39 | - Grafana Operator Version [e.g. v5.0.0] 40 | - Environment: [e.g Openshift,Kubernetes,minikube etc. please specify versions] 41 | - Deployment type: [e.g Openshift OLM/Helm/kustomize] 42 | - Other: [Other variables/things that might be relevant to this bug, versions of other services e.g. operator-sdk] 43 | 44 | **Additional context** 45 | Add any other context about the problem here. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: needs triage 5 | type: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **(If applicable)If your feature request solves a bug please provide a link to the community issue** 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | 24 | **Existing solutions** 25 | If applicable please provide a link to an existing solution from a different project 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | groups: 9 | gha: 10 | update-types: 11 | - minor 12 | - patch 13 | 14 | - package-ecosystem: "gomod" 15 | directory: "/" 16 | schedule: 17 | interval: "weekly" 18 | groups: 19 | gomod: 20 | update-types: 21 | - minor 22 | - patch 23 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | documentation: 2 | - changed-files: 3 | - any-glob-to-any-file: docs/** 4 | - any-glob-to-any-file: examples/** 5 | 6 | feature: 7 | - head-branch: ['^feature', 'feature', '^feat'] 8 | 9 | bugfix: 10 | - head-branch: ['^fix', 'fix', '^bugfix'] 11 | 12 | chore: 13 | - head-branch: ['^chore', '^ci', ^refactor] 14 | 15 | test: 16 | - head-branch: ['^test'] 17 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - documentation 5 | - chore 6 | - refactor 7 | - release-ignore 8 | categories: 9 | - title: Features 10 | labels: 11 | - 'feature' 12 | - title: Fixes 13 | labels: 14 | - 'bugfix' 15 | - title: Dependencies 16 | labels: 17 | - dependencies 18 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yaml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: # zizmor: ignore[dangerous-triggers] pull_request target used here as per recommendation from GitHub in actions/labeler 3 | - pull_request_target 4 | 5 | jobs: 6 | labeler: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 13 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: "0 1 * * *" 11 | 12 | jobs: 13 | stale: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | issues: write 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 21 | with: 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | stale-issue-message: "This issue hasn't been updated for a while, marking as stale, please respond within the next 7 days to remove this label" 24 | stale-pr-message: "This PR hasn't been updated for a while, marking as stale" 25 | stale-issue-label: "stale" 26 | stale-pr-label: "stale" 27 | # mark issues and PR's as stale after this many days 28 | days-before-stale: 30 29 | # close issues and PR's that are marked as stale after this many days 30 | days-before-close: 7 31 | # don't mark triaged issues as stale 32 | exempt-issue-labels: "triage/accepted" 33 | # unmark as stale if someone responds 34 | remove-issue-stale-when-updated: true 35 | remove-pr-stale-when-updated: true 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | vendor 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | *.swp 25 | *.swo 26 | *~ 27 | 28 | .vscode/ 29 | .DS_Store 30 | 31 | # Audit lab 32 | kube-apiserver-audit.log 33 | labs/benchmark/dashboards 34 | labs/benchmark/datasources 35 | 36 | # kind artifacts 37 | kubeconfig 38 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - id: grafana-operator 3 | dir: . 4 | main: . 5 | flags: 6 | - '-trimpath' 7 | ldflags: 8 | - -X github.com/grafana/grafana-operator/v5/embeds.Version={{.Git.Tag}} 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-shebang-scripts-are-executable 6 | 7 | - id: end-of-file-fixer 8 | 9 | - id: mixed-line-ending 10 | args: [--fix=lf] 11 | 12 | - id: trailing-whitespace 13 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @NissesSenap @weisdd @ishanjainn @theSuess @hubeadmin @pb82 @Baarsgaard 2 | -------------------------------------------------------------------------------- /api/folderreferencer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | type FolderReferencer interface { 6 | FolderRef() string 7 | FolderUID() string 8 | FolderNamespace() string 9 | ConditionsResource 10 | } 11 | 12 | type ConditionsResource interface { 13 | Conditions() *[]metav1.Condition 14 | CurrentGeneration() int64 15 | } 16 | -------------------------------------------------------------------------------- /api/v1beta1/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 v1beta1 contains API Schema definitions for the grafana v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=grafana.integreatly.org 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "grafana.integreatly.org", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1beta1/plugin_list_test.go: -------------------------------------------------------------------------------- 1 | package v1beta1 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "testing/quick" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPluginString(t *testing.T) { 12 | err := quick.Check(func(a string, b string, c string) bool { 13 | if strings.Contains(a, ",") || strings.Contains(b, ",") || strings.Contains(c, ",") { 14 | return true // skip plugins with , 15 | } 16 | pl := PluginList{ 17 | { 18 | Name: a, 19 | Version: "7.2", 20 | }, 21 | { 22 | Name: b, 23 | Version: "2.2", 24 | }, 25 | { 26 | Name: c, 27 | Version: "6.7", 28 | }, 29 | } 30 | out := pl.String() 31 | split := strings.Split(out, ",") 32 | if len(split) != 3 { 33 | return false 34 | } 35 | if split[0] > split[1] { 36 | return false 37 | } 38 | if split[1] > split[2] { 39 | return false 40 | } 41 | return true 42 | }, nil) 43 | if err != nil { 44 | t.Errorf("plugin list was not sorted: %s", err.Error()) 45 | } 46 | } 47 | 48 | func TestPluginSanitize(t *testing.T) { 49 | pl := PluginList{ 50 | { 51 | Name: "plugin-a", 52 | Version: "1.0.0", 53 | }, 54 | { 55 | Name: "plugin-b", 56 | Version: "2.0.0", 57 | }, 58 | { 59 | Name: "plugin-a", 60 | Version: "3.0.0", 61 | }, 62 | } 63 | sanitized := pl.Sanitize() 64 | assert.Len(t, sanitized, 2) 65 | } 66 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=grafana-operator 8 | LABEL operators.operatorframework.io.bundle.channels.v1=v5 9 | LABEL operators.operatorframework.io.bundle.channel.default.v1=v5 10 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.32.0 11 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 12 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 13 | 14 | # Copy files to locations specified by labels. 15 | COPY bundle/manifests /manifests/ 16 | COPY bundle/metadata /metadata/ 17 | LABEL com.redhat.openshift.versions="v4.11-v4.15" 18 | -------------------------------------------------------------------------------- /bundle/manifests/grafana-operator-manager-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | controller_manager_config.yaml: | 4 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 5 | kind: ControllerManagerConfig 6 | health: 7 | healthProbeBindAddress: :8081 8 | metrics: 9 | bindAddress: 127.0.0.1:8080 10 | webhook: 11 | port: 9443 12 | leaderElection: 13 | leaderElect: true 14 | resourceName: f75f3bba.integreatly.org 15 | kind: ConfigMap 16 | metadata: 17 | name: grafana-operator-manager-config 18 | -------------------------------------------------------------------------------- /bundle/manifests/grafana-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | name: grafana-operator-metrics-reader 6 | rules: 7 | - nonResourceURLs: 8 | - /metrics 9 | verbs: 10 | - get 11 | -------------------------------------------------------------------------------- /bundle/manifests/grafana-operator-operator-metrics-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app.kubernetes.io/managed-by: olm 7 | app.kubernetes.io/name: grafana-operator 8 | name: grafana-operator-operator-metrics-service 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 8443 13 | protocol: TCP 14 | targetPort: metrics 15 | - port: 8888 16 | targetPort: pprof 17 | protocol: TCP 18 | name: pprof 19 | selector: 20 | app.kubernetes.io/managed-by: olm 21 | app.kubernetes.io/name: grafana-operator 22 | status: 23 | loadBalancer: {} 24 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: grafana-operator 7 | operators.operatorframework.io.bundle.channels.v1: v5 8 | operators.operatorframework.io.bundle.channel.default.v1: v5 9 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.32.0 10 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 11 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 12 | 13 | # OpenShift specific annotations 14 | com.redhat.openshift.versions: "v4.11-v4.15" 15 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: grafana-operator 5 | title: grafana-operator 6 | description: | 7 | An operator for Grafana that installs and manages Grafana instances, Dashboards and Datasources through Kubernetes/OpenShift CRs 8 | links: 9 | - title: Internal Slack Channel 10 | url: https://raintank-corp.slack.com/archives/C06K5TJ469W 11 | - title: Community Slack Channel 12 | url: https://grafana.slack.com/archives/C0692KN29K3 13 | - title: Kubernetes Slack Channel 14 | url: https://kubernetes.slack.com/archives/C019A1KTYKC 15 | annotations: 16 | backstage.io/techdocs-ref: dir:. 17 | github.com/project-slug: grafana/grafana-operator 18 | spec: 19 | type: tool 20 | owner: group:default/devex 21 | lifecycle: production 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_grafanaalertrulegroups.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: grafanaalertrulegroups.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafanacontactpoints.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: grafanacontactpoints.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafanadashboards.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: grafanadashboards.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafanadatasources.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: grafanadatasources.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafanafolders.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: grafanafolders.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafananotificationpolicyroutes.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: grafananotificationpolicyroutes.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_grafanas.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: grafanas.grafana.integreatly.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanaalertrulegroups.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: grafanaalertrulegroups.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanacontactpoints.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: grafanacontactpoints.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanadashboards.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: grafanadashboards.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanadatasources.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: grafanadatasources.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanafolders.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: grafanafolders.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafananotificationpolicyroutes.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: grafananotificationpolicyroutes.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_grafanas.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: grafanas.grafana.integreatly.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /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/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: grafana-operator 6 | app.kubernetes.io/managed-by: olm 7 | name: operator-metrics-service 8 | namespace: system 9 | spec: 10 | ports: 11 | - name: metrics 12 | port: 9090 13 | protocol: TCP 14 | targetPort: metrics 15 | - port: 8888 16 | targetPort: pprof 17 | protocol: TCP 18 | name: pprof 19 | selector: 20 | app.kubernetes.io/name: grafana-operator 21 | app.kubernetes.io/managed-by: olm 22 | -------------------------------------------------------------------------------- /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: f75f3bba.integreatly.org 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - manager.yaml 6 | 7 | generatorOptions: 8 | disableNameSuffixHash: true 9 | 10 | configMapGenerator: 11 | - files: 12 | - controller_manager_config.yaml 13 | name: manager-config 14 | 15 | images: 16 | - name: controller 17 | newName: ghcr.io/grafana/grafana-operator 18 | newTag: v5.8.1 19 | -------------------------------------------------------------------------------- /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-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 8 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 9 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 10 | #patchesJson6902: 11 | #- target: 12 | # group: apps 13 | # version: v1 14 | # kind: Deployment 15 | # name: controller-manager 16 | # namespace: system 17 | # patch: |- 18 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 19 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 20 | # - op: remove 21 | # path: /spec/template/spec/containers/1/volumeMounts/0 22 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 23 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 24 | # - op: remove 25 | # path: /spec/template/spec/volumes/0 26 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: grafana-operator 7 | app.kubernetes.io/managed-by: olm 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: metrics 14 | interval: 60s 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/name: grafana-operator 18 | app.kubernetes.io/managed-by: olm 19 | -------------------------------------------------------------------------------- /config/rbac/grafana_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanas. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafana-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanas 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanas/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafana_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanas. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafana-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanas 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanas/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafanaalertrulegroup_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanaalertrulegroups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanaalertrulegroup-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanaalertrulegroups 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanaalertrulegroups/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafanaalertrulegroup_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanaalertrulegroups. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanaalertrulegroup-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanaalertrulegroups 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanaalertrulegroups/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafanacontactpoint_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanacontactpoints. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: grafanacontactpoint-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: grafana-operator 10 | app.kubernetes.io/part-of: grafana-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: grafanacontactpoint-editor-role 13 | rules: 14 | - apiGroups: 15 | - grafana.integreatly.org 16 | resources: 17 | - grafanacontactpoints 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - grafana.integreatly.org 28 | resources: 29 | - grafanacontactpoints/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/grafanacontactpoint_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanacontactpoints. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: grafanacontactpoint-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: grafana-operator 10 | app.kubernetes.io/part-of: grafana-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: grafanacontactpoint-viewer-role 13 | rules: 14 | - apiGroups: 15 | - grafana.integreatly.org 16 | resources: 17 | - grafanacontactpoints 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - grafana.integreatly.org 24 | resources: 25 | - grafanacontactpoints/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/grafanadashboard_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanadashboards. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanadashboard-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanadashboards 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanadashboards/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafanadashboard_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanadashboards. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanadashboard-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanadashboards 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanadashboards/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafanadatasource_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanadatasources. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanadatasource-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanadatasources 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanadatasources/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafanadatasource_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanadatasources. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanadatasource-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanadatasources 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanadatasources/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafanafolder_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanafolders. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanafolder-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanafolders 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanafolders/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafanafolder_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanafolders. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanafolder-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanafolders 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanafolders/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafanalibrarypanel_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafanalibrarypanels. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanalibrarypanel-editor-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanalibrarypanels 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - grafana.integreatly.org 21 | resources: 22 | - grafanalibrarypanels/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/grafanalibrarypanel_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafanalibrarypanels. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: grafanalibrarypanel-viewer-role 6 | rules: 7 | - apiGroups: 8 | - grafana.integreatly.org 9 | resources: 10 | - grafanalibrarypanels 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - grafana.integreatly.org 17 | resources: 18 | - grafanalibrarypanels/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/grafananotificationpolicyroute_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit grafananotificationpolicyroutes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: grafananotificationpolicyroute-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: grafana-operator 10 | app.kubernetes.io/part-of: grafana-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: grafananotificationpolicyroute-editor-role 13 | rules: 14 | - apiGroups: 15 | - grafana.integreatly.org 16 | resources: 17 | - grafananotificationpolicyroutes 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - grafana.integreatly.org 28 | resources: 29 | - grafananotificationpolicyroutes/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/grafananotificationpolicyroute_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view grafananotificationpolicyroutes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: grafananotificationpolicyroute-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: grafana-operator 10 | app.kubernetes.io/part-of: grafana-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: grafananotificationpolicyroute-viewer-role 13 | rules: 14 | - apiGroups: 15 | - grafana.integreatly.org 16 | resources: 17 | - grafananotificationpolicyroutes 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - grafana.integreatly.org 24 | resources: 25 | - grafananotificationpolicyroutes/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | -------------------------------------------------------------------------------- /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_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_v1beta1_grafana.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana-a 6 | labels: 7 | dashboards: "grafana-a" 8 | folders: "grafana-a" 9 | spec: 10 | config: 11 | security: 12 | admin_user: root 13 | admin_password: start 14 | log: 15 | mode: "console" 16 | auth: 17 | disable_login_form: "false" 18 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanacontactpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: grafanacontactpoint 6 | app.kubernetes.io/instance: grafanacontactpoint-sample 7 | app.kubernetes.io/part-of: grafana-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: grafana-operator 10 | name: grafanacontactpoint-sample 11 | spec: 12 | name: grafanacontactpoint-sample 13 | type: "email" 14 | instanceSelector: 15 | matchLabels: 16 | dashboards: "grafana-a" 17 | settings: 18 | email: 19 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanadashboard.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | name: grafanadashboard-sample 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana-a" 10 | json: > 11 | { 12 | "id": null, 13 | "title": "Simple Dashboard", 14 | "tags": [], 15 | "style": "dark", 16 | "timezone": "browser", 17 | "editable": true, 18 | "hideControls": false, 19 | "graphTooltip": 1, 20 | "panels": [], 21 | "time": { 22 | "from": "now-6h", 23 | "to": "now" 24 | }, 25 | "timepicker": { 26 | "time_options": [], 27 | "refresh_intervals": [] 28 | }, 29 | "templating": { 30 | "list": [] 31 | }, 32 | "annotations": { 33 | "list": [] 34 | }, 35 | "refresh": "5s", 36 | "schemaVersion": 17, 37 | "version": 0, 38 | "links": [] 39 | } 40 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanadatasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: grafanadatasource-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana-a" 9 | plugins: 10 | - name: grafana-clock-panel 11 | version: 1.3.0 12 | datasource: 13 | name: prometheus 14 | type: prometheus 15 | access: proxy 16 | url: http://prometheus-service:9090 17 | isDefault: true 18 | jsonData: 19 | "tlsSkipVerify": true 20 | "timeInterval": "5s" 21 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanafolder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaFolder 3 | metadata: 4 | name: grafanafolder-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana-a" 9 | title: "Example Folder" 10 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanalibrarypanel.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaLibraryPanel 3 | metadata: 4 | name: grafana-library-panel-inline-envs 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | envs: 10 | - name: CUSTOM_RANGE_ENV 11 | value: "now - 12h" 12 | plugins: 13 | - name: grafana-piechart-panel 14 | version: 1.3.9 15 | jsonnet: > 16 | local myRange = std.extVar('CUSTOM_RANGE_ENV'); 17 | { 18 | model: {} 19 | } 20 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafanamutetiming.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaMuteTiming 3 | metadata: 4 | name: mutetiming-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | name: mutetiming-sample 10 | editable: false 11 | time_intervals: 12 | - times: 13 | - start_time: "00:00" 14 | end_time: "06:00" 15 | weekdays: [saturday] 16 | days_of_month: ["1", "15"] 17 | location: Asia/Shanghai 18 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafananotificationpolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaNotificationPolicy 3 | metadata: 4 | name: grafananotificationpolicy-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | route: 10 | receiver: Grafana Cloud OnCall 11 | group_by: 12 | - grafana_folder 13 | - alertname 14 | routes: 15 | - receiver: grafana-default-email 16 | object_matchers: 17 | - - foo 18 | - = 19 | - bar 20 | routes: 21 | - receiver: Grafana Cloud OnCall 22 | object_matchers: 23 | - - severity 24 | - = 25 | - critical 26 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafananotificationpolicyroute.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaNotificationPolicyRoute 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: grafananotificationpolicyroute 6 | app.kubernetes.io/instance: grafananotificationpolicyroute-sample 7 | app.kubernetes.io/part-of: grafana-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: grafana-operator 10 | name: grafananotificationpolicyroute-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /config/samples/grafana_v1beta1_grafananotificationtemplate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationTemplate 4 | metadata: 5 | name: test 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | name: test 11 | template: | 12 | {{ define "SlackAlert" }} 13 | [{{.Status}}] {{ .Labels.alertname }} 14 | {{ .Annotations.AlertValues }} 15 | {{ end }} 16 | 17 | {{ define "SlackAlertMessage" }} 18 | {{ if gt (len .Alerts.Firing) 0 }} 19 | {{ len .Alerts.Firing }} firing: 20 | {{ range .Alerts.Firing }} {{ template "SlackAlert" . }} {{ end }} 21 | {{ end }} 22 | {{ if gt (len .Alerts.Resolved) 0 }} 23 | {{ len .Alerts.Resolved }} resolved: 24 | {{ range .Alerts.Resolved }} {{ template "SlackAlert" . }} {{ end }} 25 | {{ end }} 26 | {{ end }} 27 | 28 | {{ template "SlackAlertMessage" . }} 29 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - grafana_v1beta1_grafana.yaml 4 | - grafana_v1beta1_grafanadashboard.yaml 5 | - grafana_v1beta1_grafanadatasource.yaml 6 | - grafana_v1beta1_grafanafolder.yaml 7 | - grafana_v1beta1_grafanaalertrulegroup.yaml 8 | - grafana_v1beta1_grafanacontactpoint.yaml 9 | - grafana_v1beta1_grafananotificationpolicy.yaml 10 | - grafana_v1beta1_grafananotificationtemplate.yaml 11 | - grafana_v1beta1_grafananotificationpolicyroute.yaml 12 | - grafana_v1beta1_grafanalibrarypanel.yaml 13 | - grafana_v1beta1_grafanamutetiming.yaml 14 | #+kubebuilder:scaffold:manifestskustomizesamples 15 | -------------------------------------------------------------------------------- /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 | patches: 4 | - path: patches/olm.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | kind: Configuration 8 | name: config 9 | version: v1alpha3 10 | -------------------------------------------------------------------------------- /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.15.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.15.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.15.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.15.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.15.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.15.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/autodetect/main.go: -------------------------------------------------------------------------------- 1 | // Package autodetect is for auto-detecting traits from the environment (platform, APIs, ...). 2 | package autodetect 3 | 4 | import ( 5 | "k8s.io/client-go/discovery" 6 | "k8s.io/client-go/rest" 7 | ) 8 | 9 | var _ AutoDetect = (*autoDetect)(nil) 10 | 11 | // AutoDetect provides an assortment of routines that auto-detect traits based on the runtime. 12 | type AutoDetect interface { 13 | IsOpenshift() (bool, error) 14 | } 15 | 16 | type autoDetect struct { 17 | dcl discovery.DiscoveryInterface 18 | } 19 | 20 | // New creates a new auto-detection worker, using the given client when talking to the current cluster. 21 | func New(restConfig *rest.Config) (AutoDetect, error) { 22 | dcl, err := discovery.NewDiscoveryClientForConfig(restConfig) 23 | if err != nil { 24 | // it's pretty much impossible to get into this problem, as most of the 25 | // code branches from the previous call just won't fail at all, 26 | // but let's handle this error anyway... 27 | return nil, err 28 | } 29 | 30 | return &autoDetect{ 31 | dcl: dcl, 32 | }, nil 33 | } 34 | 35 | // Platform returns the detected platform this operator is running on. Possible values: Kubernetes, OpenShift. 36 | func (a *autoDetect) IsOpenshift() (bool, error) { 37 | apiList, err := a.dcl.ServerGroups() 38 | if err != nil { 39 | return false, err 40 | } 41 | 42 | apiGroups := apiList.Groups 43 | for i := range apiGroups { 44 | if apiGroups[i].Name == "route.openshift.io" { 45 | return true, nil 46 | } 47 | } 48 | 49 | return false, nil 50 | } 51 | -------------------------------------------------------------------------------- /controllers/autodetect/main_test.go: -------------------------------------------------------------------------------- 1 | package autodetect_test 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/grafana/grafana-operator/v5/controllers/autodetect" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/client-go/rest" 14 | ) 15 | 16 | func TestDetectPlatformBasedOnAvailableAPIGroups(t *testing.T) { 17 | for _, tt := range []struct { 18 | apiGroupList *metav1.APIGroupList 19 | expected bool 20 | }{ 21 | { 22 | &metav1.APIGroupList{}, 23 | false, 24 | }, 25 | { 26 | &metav1.APIGroupList{ 27 | Groups: []metav1.APIGroup{ 28 | { 29 | Name: "route.openshift.io", 30 | }, 31 | }, 32 | }, 33 | true, 34 | }, 35 | } { 36 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 37 | output, err := json.Marshal(tt.apiGroupList) 38 | require.NoError(t, err) 39 | 40 | w.Header().Set("Content-Type", "application/json") 41 | w.WriteHeader(http.StatusOK) 42 | _, err = w.Write(output) 43 | require.NoError(t, err) 44 | })) 45 | defer server.Close() 46 | 47 | autoDetect, err := autodetect.New(&rest.Config{Host: server.URL}) 48 | require.NoError(t, err) 49 | 50 | // test 51 | plt, err := autoDetect.IsOpenshift() 52 | 53 | // verify 54 | assert.NoError(t, err) 55 | assert.Equal(t, tt.expected, plt) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /controllers/client/common.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | v1 "k8s.io/api/core/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | func GetValueFromSecretKey(ctx context.Context, ref *v1.SecretKeySelector, c client.Client, namespace string) ([]byte, error) { 13 | if ref == nil { 14 | return nil, errors.New("empty secret key selector") 15 | } 16 | 17 | secret := &v1.Secret{} 18 | selector := client.ObjectKey{ 19 | Name: ref.Name, 20 | Namespace: namespace, 21 | } 22 | err := c.Get(ctx, selector, secret) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if secret.Data == nil { 28 | return nil, fmt.Errorf("empty credential secret: %v/%v", namespace, ref.Name) 29 | } 30 | 31 | if val, ok := secret.Data[ref.Key]; ok { 32 | return val, nil 33 | } 34 | 35 | return nil, fmt.Errorf("credentials not found in secret: %v/%v", namespace, ref.Name) 36 | } 37 | -------------------------------------------------------------------------------- /controllers/client/http_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 9 | "github.com/grafana/grafana-operator/v5/controllers/metrics" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | func NewHTTPClient(ctx context.Context, c client.Client, grafana *v1beta1.Grafana) (*http.Client, error) { 15 | var timeout time.Duration 16 | if grafana.Spec.Client != nil && grafana.Spec.Client.TimeoutSeconds != nil { 17 | timeout = max(time.Duration(*grafana.Spec.Client.TimeoutSeconds), 0) 18 | } else { 19 | timeout = 10 20 | } 21 | 22 | tlsConfig, err := buildTLSConfiguration(ctx, c, grafana) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | transport := NewInstrumentedRoundTripper(grafana.IsExternal(), tlsConfig, metrics.GrafanaAPIRequests.MustCurryWith(prometheus.Labels{ 28 | "instance_namespace": grafana.Namespace, 29 | "instance_name": grafana.Name, 30 | })) 31 | if grafana.Spec.Client != nil && grafana.Spec.Client.Headers != nil { 32 | transport.(*instrumentedRoundTripper).addHeaders(grafana.Spec.Client.Headers) //nolint:errcheck 33 | } 34 | 35 | return &http.Client{ 36 | Transport: transport, 37 | Timeout: time.Second * timeout, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /controllers/content/fetchers/configmap_fetcher.go: -------------------------------------------------------------------------------- 1 | package fetchers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 8 | v1 "k8s.io/api/core/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | func FetchDashboardFromConfigMap(cr v1beta1.GrafanaContentResource, c client.Client) ([]byte, error) { 13 | spec := cr.GrafanaContentSpec() 14 | if spec == nil { 15 | return nil, nil // TODO 16 | } 17 | ref := spec.ConfigMapRef 18 | dashboardConfigMap := &v1.ConfigMap{} 19 | selector := client.ObjectKey{ 20 | Namespace: cr.GetNamespace(), 21 | Name: ref.Name, 22 | } 23 | 24 | err := c.Get(context.Background(), selector, dashboardConfigMap) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | if content, ok := dashboardConfigMap.Data[ref.Key]; ok { 30 | return []byte(content), nil 31 | } 32 | 33 | return nil, fmt.Errorf("cannot find key '%v' in config map '%v' for dashboard %v/%v", 34 | ref.Key, ref.Name, cr.GetNamespace(), cr.GetName()) 35 | } 36 | -------------------------------------------------------------------------------- /controllers/content/fetchers/grafana_com_fetcher_test.go: -------------------------------------------------------------------------------- 1 | package fetchers 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFetchDashboardFromGrafanaCom(t *testing.T) { 12 | dashboard := &v1beta1.GrafanaDashboard{ 13 | Spec: v1beta1.GrafanaDashboardSpec{ 14 | GrafanaContentSpec: v1beta1.GrafanaContentSpec{ 15 | GrafanaCom: &v1beta1.GrafanaComContentReference{ 16 | ID: 1860, 17 | }, 18 | }, 19 | }, 20 | Status: v1beta1.GrafanaDashboardStatus{}, 21 | } 22 | 23 | fetchedDashboard, err := FetchFromGrafanaCom(context.Background(), dashboard, k8sClient) 24 | assert.Nil(t, err) 25 | assert.NotNil(t, fetchedDashboard, "Fetched dashboard shouldn't be empty") 26 | assert.GreaterOrEqual(t, *dashboard.Spec.GrafanaCom.Revision, 30, "At least 30 revisions exist for dashboard 1860 as of 2023-03-29") 27 | 28 | assert.False(t, dashboard.Status.ContentTimestamp.Time.IsZero(), "ContentTimestamp should have been set") 29 | assert.NotEmpty(t, dashboard.Status.ContentURL, "ContentURL should have been set") 30 | } 31 | -------------------------------------------------------------------------------- /controllers/content/fetchers/suite_test.go: -------------------------------------------------------------------------------- 1 | package fetchers 2 | 3 | import ( 4 | "testing" 5 | 6 | grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | 8 | "k8s.io/client-go/kubernetes/scheme" 9 | "k8s.io/client-go/rest" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/envtest" 12 | logf "sigs.k8s.io/controller-runtime/pkg/log" 13 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 14 | 15 | . "github.com/onsi/ginkgo/v2" 16 | . "github.com/onsi/gomega" 17 | ) 18 | 19 | var ( 20 | cfg *rest.Config 21 | k8sClient client.Client 22 | testEnv *envtest.Environment 23 | ) 24 | 25 | func TestAPIs(t *testing.T) { 26 | RegisterFailHandler(Fail) 27 | 28 | RunSpecs(t, "Fetchers Suite") 29 | } 30 | 31 | var _ = BeforeSuite(func() { 32 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 33 | 34 | By("bootstrapping test environment") 35 | testEnv = &envtest.Environment{} 36 | 37 | cfg, err := testEnv.Start() 38 | Expect(err).NotTo(HaveOccurred()) 39 | Expect(cfg).NotTo(BeNil()) 40 | 41 | err = grafanav1beta1.AddToScheme(scheme.Scheme) 42 | Expect(err).NotTo(HaveOccurred()) 43 | 44 | //+kubebuilder:scaffold:scheme 45 | 46 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 47 | Expect(err).NotTo(HaveOccurred()) 48 | Expect(k8sClient).NotTo(BeNil()) 49 | }) 50 | 51 | var _ = AfterSuite(func() { 52 | By("tearing down the test environment") 53 | err := testEnv.Stop() 54 | Expect(err).NotTo(HaveOccurred()) 55 | }) 56 | -------------------------------------------------------------------------------- /controllers/folder_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var _ = Describe("Folder: Reconciler", func() { 14 | It("Results in NoMatchingInstances Condition", func() { 15 | // Create object 16 | cr := &v1beta1.GrafanaFolder{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: "no-match", 19 | Namespace: "default", 20 | }, 21 | Spec: v1beta1.GrafanaFolderSpec{ 22 | GrafanaCommonSpec: instanceSelectorNoMatchingInstances, 23 | }, 24 | } 25 | ctx := context.Background() 26 | err := k8sClient.Create(ctx, cr) 27 | Expect(err).ToNot(HaveOccurred()) 28 | 29 | // Reconciliation Request 30 | req := requestFromMeta(cr.ObjectMeta) 31 | 32 | // Reconcile 33 | r := GrafanaFolderReconciler{Client: k8sClient} 34 | _, err = r.Reconcile(ctx, req) 35 | Expect(err).ShouldNot(HaveOccurred()) // NoMatchingInstances is a valid reconciliation result 36 | 37 | resultCr := &v1beta1.GrafanaFolder{} 38 | Expect(r.Get(ctx, req.NamespacedName, resultCr)).Should(Succeed()) // NoMatchingInstances is a valid status 39 | 40 | // Verify NoMatchingInstances condition 41 | Expect(resultCr.Status.Conditions).Should(ContainElement(HaveField("Type", conditionNoMatchingInstance))) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /controllers/librarypanel_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var _ = Describe("LibraryPanel: Reconciler", func() { 14 | It("Results in NoMatchingInstances Condition", func() { 15 | // Create object 16 | cr := &v1beta1.GrafanaLibraryPanel{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: "no-match", 19 | Namespace: "default", 20 | }, 21 | Spec: v1beta1.GrafanaLibraryPanelSpec{ 22 | GrafanaCommonSpec: instanceSelectorNoMatchingInstances, 23 | GrafanaContentSpec: v1beta1.GrafanaContentSpec{JSON: "{}"}, 24 | }, 25 | } 26 | ctx := context.Background() 27 | err := k8sClient.Create(ctx, cr) 28 | Expect(err).ToNot(HaveOccurred()) 29 | 30 | // Reconciliation Request 31 | req := requestFromMeta(cr.ObjectMeta) 32 | 33 | // Reconcile 34 | r := GrafanaLibraryPanelReconciler{Client: k8sClient} 35 | _, err = r.Reconcile(ctx, req) 36 | Expect(err).ShouldNot(HaveOccurred()) // NoMatchingInstances is a valid reconciliation result 37 | 38 | resultCr := &v1beta1.GrafanaLibraryPanel{} 39 | Expect(r.Get(ctx, req.NamespacedName, resultCr)).Should(Succeed()) // NoMatchingInstances is a valid status 40 | 41 | // Verify NoMatchingInstances condition 42 | Expect(resultCr.Status.Conditions).Should(ContainElement(HaveField("Type", conditionNoMatchingInstance))) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /controllers/model/dashboard_resources.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | 6 | grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | v1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 11 | ) 12 | 13 | func GetPluginsConfigMap(cr *grafanav1beta1.Grafana, scheme *runtime.Scheme) *v1.ConfigMap { 14 | config := &v1.ConfigMap{ 15 | ObjectMeta: metav1.ObjectMeta{ 16 | Name: fmt.Sprintf("%s-plugins", cr.Name), 17 | Namespace: cr.Namespace, 18 | Labels: GetCommonLabels(), 19 | }, 20 | } 21 | controllerutil.SetControllerReference(cr, config, scheme) //nolint:errcheck 22 | return config 23 | } 24 | -------------------------------------------------------------------------------- /controllers/model/utils.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "maps" 7 | 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func generateRandomBytes(n int) []byte { 12 | b := make([]byte, n) 13 | _, err := rand.Read(b) 14 | if err != nil { 15 | panic(err) 16 | } 17 | return b 18 | } 19 | 20 | func RandStringRunes(s int) string { 21 | b := generateRandomBytes(s) 22 | return base64.URLEncoding.EncodeToString(b) 23 | } 24 | 25 | func MergeAnnotations(requested map[string]string, existing map[string]string) map[string]string { 26 | if existing == nil { 27 | return requested 28 | } 29 | 30 | maps.Copy(existing, requested) 31 | return existing 32 | } 33 | 34 | func BoolPtr(b bool) *bool { return &b } 35 | 36 | func IntPtr(b int64) *int64 { return &b } 37 | 38 | func SetInheritedLabels(obj metav1.ObjectMetaAccessor, extraLabels map[string]string) { 39 | meta := obj.GetObjectMeta() 40 | labels := meta.GetLabels() 41 | if labels == nil { 42 | labels = make(map[string]string) 43 | } 44 | // Inherit labels from the parent grafana instance if any 45 | maps.Copy(labels, extraLabels) 46 | // Ensure default CommonLabels for child resources 47 | maps.Copy(labels, GetCommonLabels()) 48 | meta.SetLabels(labels) 49 | } 50 | -------------------------------------------------------------------------------- /controllers/notificationtemplate_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var _ = Describe("NotificationTemplate: Reconciler", func() { 14 | It("Results in NoMatchingInstances Condition", func() { 15 | // Create object 16 | cr := &v1beta1.GrafanaNotificationTemplate{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: "no-match", 19 | Namespace: "default", 20 | }, 21 | Spec: v1beta1.GrafanaNotificationTemplateSpec{ 22 | GrafanaCommonSpec: instanceSelectorNoMatchingInstances, 23 | Name: "NoMatch", 24 | }, 25 | } 26 | ctx := context.Background() 27 | err := k8sClient.Create(ctx, cr) 28 | Expect(err).ToNot(HaveOccurred()) 29 | 30 | // Reconciliation Request 31 | req := requestFromMeta(cr.ObjectMeta) 32 | 33 | // Reconcile 34 | r := GrafanaNotificationTemplateReconciler{Client: k8sClient} 35 | _, err = r.Reconcile(ctx, req) 36 | Expect(err).ShouldNot(HaveOccurred()) // NoMatchingInstances is a valid reconciliation result 37 | 38 | resultCr := &v1beta1.GrafanaNotificationTemplate{} 39 | Expect(r.Get(ctx, req.NamespacedName, resultCr)).Should(Succeed()) // NoMatchingInstances is a valid status 40 | 41 | // Verify NoMatchingInstances condition 42 | Expect(resultCr.Status.Conditions).Should(ContainElement(HaveField("Type", conditionNoMatchingInstance))) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /controllers/reconcilers/grafana/admin_secret_reconciler_test.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | "k8s.io/client-go/kubernetes/scheme" 10 | ) 11 | 12 | var _ = Describe("Reconcile AdminSecret", func() { 13 | It("runs successfully with disabled default admin secret", func() { 14 | r := NewAdminSecretReconciler(k8sClient) 15 | cr := &v1beta1.Grafana{ 16 | Spec: v1beta1.GrafanaSpec{ 17 | DisableDefaultAdminSecret: true, 18 | }, 19 | } 20 | 21 | vars := &v1beta1.OperatorReconcileVars{} 22 | status, err := r.Reconcile(context.Background(), cr, vars, scheme.Scheme) 23 | 24 | Expect(err).ToNot(HaveOccurred()) 25 | Expect(status).To(Equal(v1beta1.OperatorStageResultSuccess)) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /controllers/reconcilers/grafana/deployment_reconciler_test.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 8 | config2 "github.com/grafana/grafana-operator/v5/controllers/config" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func Test_getGrafanaImage(t *testing.T) { 14 | cr := &v1beta1.Grafana{ 15 | Spec: v1beta1.GrafanaSpec{ 16 | Version: "", 17 | }, 18 | } 19 | 20 | expectedDeploymentImage := fmt.Sprintf("%s:%s", config2.GrafanaImage, config2.GrafanaVersion) 21 | 22 | assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr)) 23 | } 24 | 25 | func Test_getGrafanaImage_specificVersion(t *testing.T) { 26 | cr := &v1beta1.Grafana{ 27 | Spec: v1beta1.GrafanaSpec{ 28 | Version: "10.4.0", 29 | }, 30 | } 31 | 32 | expectedDeploymentImage := fmt.Sprintf("%s:10.4.0", config2.GrafanaImage) 33 | 34 | assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr)) 35 | } 36 | 37 | func Test_getGrafanaImage_withImageInVersion(t *testing.T) { 38 | expectedDeploymentImage := "docker.io/grafana/grafana@sha256:b7fcb534f7b3512801bb3f4e658238846435804deb479d105b5cdc680847c272" 39 | cr := &v1beta1.Grafana{ 40 | Spec: v1beta1.GrafanaSpec{ 41 | Version: expectedDeploymentImage, 42 | }, 43 | } 44 | 45 | assert.Equal(t, expectedDeploymentImage, getGrafanaImage(cr)) 46 | } 47 | -------------------------------------------------------------------------------- /controllers/reconcilers/grafana/service_account_reconciler.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | "github.com/grafana/grafana-operator/v5/controllers/model" 8 | "github.com/grafana/grafana-operator/v5/controllers/reconcilers" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 12 | ) 13 | 14 | type ServiceAccountReconciler struct { 15 | client client.Client 16 | } 17 | 18 | func NewServiceAccountReconciler(client client.Client) reconcilers.OperatorGrafanaReconciler { 19 | return &ServiceAccountReconciler{ 20 | client: client, 21 | } 22 | } 23 | 24 | func (r *ServiceAccountReconciler) Reconcile(ctx context.Context, cr *v1beta1.Grafana, vars *v1beta1.OperatorReconcileVars, scheme *runtime.Scheme) (v1beta1.OperatorStageStatus, error) { 25 | sa := model.GetGrafanaServiceAccount(cr, scheme) 26 | 27 | _, err := controllerutil.CreateOrUpdate(ctx, r.client, sa, func() error { 28 | err := v1beta1.Merge(sa, cr.Spec.ServiceAccount) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | if scheme != nil { 34 | err = controllerutil.SetControllerReference(cr, sa, scheme) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | 40 | model.SetInheritedLabels(sa, cr.Labels) 41 | 42 | return nil 43 | }) 44 | if err != nil { 45 | return v1beta1.OperatorStageResultFailed, err 46 | } 47 | 48 | return v1beta1.OperatorStageResultSuccess, nil 49 | } 50 | -------------------------------------------------------------------------------- /controllers/reconcilers/reconciler.go: -------------------------------------------------------------------------------- 1 | package reconcilers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grafana/grafana-operator/v5/api/v1beta1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | ) 9 | 10 | type OperatorGrafanaReconciler interface { 11 | Reconcile(ctx context.Context, cr *v1beta1.Grafana, vars *v1beta1.OperatorReconcileVars, scheme *runtime.Scheme) (v1beta1.OperatorStageStatus, error) 12 | } 13 | -------------------------------------------------------------------------------- /deploy/helm/cr.yaml: -------------------------------------------------------------------------------- 1 | git-repo: helm-charts 2 | owner: grafana 3 | skip-existing: true 4 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: grafana-operator 3 | description: Helm chart for the Grafana Operator 4 | # A chart can be either an 'application' or a 'library' chart. 5 | # 6 | # Application charts are a collection of templates that can be packaged into versioned archives 7 | # to be deployed. 8 | # 9 | # Library charts provide useful utilities or functions for the chart developer. They're included as 10 | # a dependency of application charts to inject those utilities and functions into the rendering 11 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 12 | type: application 13 | # We keep the version and appVersion in sync as most updates also include 14 | # changes to the CRDs which are bundled with the helm resources 15 | version: v5.18.0 16 | appVersion: "v5.18.0" 17 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/files/rbac-openshift.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - route.openshift.io 9 | resources: 10 | - routes 11 | - routes/custom-host 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - update 18 | - watch 19 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/templates/dashboard.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.dashboard.enabled -}} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "grafana-operator.fullname" . }}-dashboard 6 | namespace: {{ include "grafana-operator.namespace" . }} 7 | labels: 8 | {{- include "grafana-operator.labels" . | nindent 4 }} 9 | app.kubernetes.io/component: operator 10 | {{- with .Values.dashboard.labels }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- with .Values.dashboard.annotations }} 14 | annotations: 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | data: 18 | grafana-operator.json: |- 19 | {{- .Files.Get "files/dashboard.json" | nindent 4 }} 20 | {{- end -}} 21 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/templates/extraobjects.yaml: -------------------------------------------------------------------------------- 1 | {{ range .Values.extraObjects }} 2 | --- 3 | {{ tpl (toYaml .) $ }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "grafana-operator.fullname" . }}-metrics-service 5 | namespace: {{ include "grafana-operator.namespace" . }} 6 | labels: 7 | {{- include "grafana-operator.labels" . | nindent 4 }} 8 | app.kubernetes.io/component: operator 9 | spec: 10 | type: {{ .Values.metricsService.type }} 11 | ports: 12 | - port: {{ .Values.metricsService.metricsPort }} 13 | targetPort: metrics 14 | protocol: TCP 15 | name: metrics 16 | - port: {{ .Values.metricsService.pprofPort }} 17 | targetPort: pprof 18 | protocol: TCP 19 | name: pprof 20 | selector: 21 | {{- include "grafana-operator.selectorLabels" . | nindent 4 }} 22 | -------------------------------------------------------------------------------- /deploy/helm/grafana-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "grafana-operator.serviceAccountName" . }} 6 | namespace: {{ include "grafana-operator.namespace" . }} 7 | labels: 8 | {{- include "grafana-operator.labels" . | nindent 4 }} 9 | app.kubernetes.io/component: operator 10 | {{- with .Values.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: true 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /deploy/kustomize/README.md: -------------------------------------------------------------------------------- 1 | # Deploy with kustomize 2 | 3 | Two overlays are provided, for namespace scoped and cluster scoped installation. 4 | To install the Grafana operator, select one of the overlays and edit its `kustomization.yaml` file. 5 | Make sure `namespace` is set to the namespace where you want to install the operator. 6 | Then run: 7 | 8 | ```shell 9 | kustomize build deploy/kustomize/overlays/cluster_scoped --load-restrictor LoadRestrictionsNone | kubectl apply -f - 10 | ``` 11 | 12 | for a cluster scoped installation, or: 13 | 14 | ```shell 15 | kustomize build deploy/kustomize/overlays/namespace_scoped --load-restrictor LoadRestrictionsNone | kubectl apply -f - 16 | ``` 17 | 18 | for a namespace scoped installation. 19 | 20 | When you want to patch the grafana operator instead of using `kubectl apply` you need to use `kubectl replace`. 21 | Else you will get the following error `invalid: metadata.annotations: Too long: must have at most 262144 bytes`. 22 | 23 | For example 24 | 25 | ```shell 26 | kustomize build deploy/kustomize/overlays/namespace_scoped --load-restrictor LoadRestrictionsNone | kubectl replace -f - 27 | ``` 28 | -------------------------------------------------------------------------------- /deploy/kustomize/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - crds.yaml 3 | - namespace.yaml 4 | - serviceaccount.yaml 5 | - service.yaml 6 | - deployment.yaml 7 | - rolebinding.yaml 8 | - role.yaml 9 | patches: 10 | - target: 11 | kind: ClusterRole 12 | patch: | 13 | - op: replace 14 | path: /metadata/name 15 | value: grafana-operator-permissions 16 | images: 17 | - name: ghcr.io/grafana/grafana-operator 18 | newTag: v5.18.0 19 | -------------------------------------------------------------------------------- /deploy/kustomize/base/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: default 6 | -------------------------------------------------------------------------------- /deploy/kustomize/base/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: grafana-operator-permissions 5 | subjects: 6 | - kind: ServiceAccount 7 | name: grafana-operator-controller-manager 8 | namespace: default 9 | roleRef: 10 | kind: ClusterRole 11 | name: grafana-operator-permissions 12 | apiGroup: rbac.authorization.k8s.io 13 | -------------------------------------------------------------------------------- /deploy/kustomize/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grafana-operator-metrics-service 5 | labels: 6 | app.kubernetes.io/name: grafana-operator 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - port: 9090 11 | targetPort: metrics 12 | protocol: TCP 13 | name: metrics 14 | - port: 8888 15 | targetPort: pprof 16 | protocol: TCP 17 | name: pprof 18 | selector: 19 | app.kubernetes.io/name: grafana-operator 20 | -------------------------------------------------------------------------------- /deploy/kustomize/base/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: grafana-operator-controller-manager 6 | namespace: default 7 | automountServiceAccountToken: true 8 | -------------------------------------------------------------------------------- /deploy/kustomize/overlays/chainsaw/deployment-patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: manager 10 | imagePullPolicy: Never 11 | resources: 12 | limits: 13 | cpu: 400m 14 | memory: 1024Mi 15 | requests: 16 | cpu: 100m 17 | memory: 200Mi 18 | volumeMounts: 19 | - name: dashboards-dir 20 | mountPath: /tmp/dashboards 21 | volumes: 22 | - name: dashboards-dir 23 | emptyDir: {} 24 | -------------------------------------------------------------------------------- /deploy/kustomize/overlays/chainsaw/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../base 3 | 4 | patches: 5 | - path: deployment-patch.yaml 6 | target: 7 | group: apps 8 | kind: Deployment 9 | version: v1 10 | apiVersion: kustomize.config.k8s.io/v1beta1 11 | kind: Kustomization 12 | images: 13 | - name: ghcr.io/grafana/grafana-operator 14 | newName: ko.local/grafana/grafana-operator 15 | newTag: latest 16 | -------------------------------------------------------------------------------- /deploy/kustomize/overlays/cluster_scoped/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: grafana 2 | 3 | resources: 4 | - ../../base 5 | -------------------------------------------------------------------------------- /deploy/kustomize/overlays/namespace_scoped/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grafana-operator-controller-manager 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: manager 10 | env: 11 | - name: WATCH_NAMESPACE 12 | valueFrom: 13 | fieldRef: 14 | fieldPath: metadata.namespace 15 | -------------------------------------------------------------------------------- /deploy/kustomize/overlays/namespace_scoped/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: grafana 2 | 3 | resources: 4 | - ../../base 5 | 6 | patches: 7 | - path: deployment.yaml 8 | target: 9 | kind: Deployment 10 | version: v1 11 | group: apps 12 | name: grafana-operator-controller-manager 13 | - target: 14 | kind: ClusterRole 15 | patch: | 16 | - op: replace 17 | path: /kind 18 | value: Role 19 | - target: 20 | kind: ClusterRoleBinding 21 | patch: | 22 | - op: replace 23 | path: /kind 24 | value: RoleBinding 25 | - op: replace 26 | path: /roleRef/kind 27 | value: Role 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Grafana-operator 2 | 3 | The docs folder contain all the grafana-operator documentation. 4 | It's a soft link to our hugo docs and our homepage [https://grafana-operator.github.io/grafana-operator/](https://grafana-operator.github.io/grafana-operator/) which automatically gets update when any changes is done in these files. 5 | 6 | Feel free to contribute to the docs if you feel that something is missing, you can find more information on how to contribute under [CONTRIBUTING.md](../CONTRIBUTING.md). 7 | -------------------------------------------------------------------------------- /docs/about/_index.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: About Grafana-Operator 3 | linkTitle: About 4 | menu: 5 | main: 6 | weight: 10 7 | --- 8 | 9 | {{< blocks/section >}} 10 | 11 |
12 |

About Grafana-Operator

13 | 14 |

Creation

15 |

16 | The Grafana-Operator was initially created by a number of RedHat employees as an internal project to manage their grafana instance. 17 | But the project was never supposed to be a part of a RedHat product and was made open-source within an external github organization. 18 | And was mostly maintained by few people from the original team that created it. 19 |

20 |
21 | 22 |

Current

23 |

24 | Currently the operator is maintained by Grafana Labs and the original creators/maintainers. 25 | While Grafana Labs maintains the operator, we do not offer any commercial support for it. 26 | 27 | Support & Issue triage is done on a best-effort basis. 28 |

29 | 30 |
31 | {{< /blocks/section >}} 32 | -------------------------------------------------------------------------------- /docs/blog/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Docsy Blog" 3 | linkTitle: "Blog" 4 | menu: 5 | main: 6 | weight: 30 7 | --- 8 | 9 | 10 | This is the **blog** section. It has two categories: News and Releases. 11 | 12 | Files in these directories will be listed in reverse chronological order. 13 | -------------------------------------------------------------------------------- /docs/blog/flux-gitops/grafana-operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: OCIRepository 3 | metadata: 4 | name: grafana-operator 5 | namespace: flux-system 6 | spec: 7 | interval: 10m 8 | url: oci://ghcr.io/grafana/kustomize/grafana-operator 9 | ref: 10 | tag: v5.0.0-rc3 11 | --- 12 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 13 | kind: Kustomization 14 | metadata: 15 | name: grafana-operator 16 | namespace: flux-system 17 | spec: 18 | interval: 10m 19 | targetNamespace: grafana 20 | images: 21 | - name: ghcr.io/grafana/grafana-operator 22 | newTag: v5.0.0-rc3 23 | prune: true 24 | sourceRef: 25 | kind: OCIRepository 26 | name: grafana-operator 27 | path: ./overlays/namespace_scoped 28 | -------------------------------------------------------------------------------- /docs/blog/flux-gitops/grafana.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 2 | kind: Kustomization 3 | metadata: 4 | name: grafana 5 | namespace: flux-system 6 | spec: 7 | force: false 8 | dependsOn: 9 | - name: grafana-operator # Depends on the grafana-operator being synced 10 | healthChecks: # Check that grafana-deployment comes up 11 | - apiVersion: apps/v1 12 | kind: Deployment 13 | name: grafana-deployment 14 | namespace: grafana 15 | interval: 10m0s 16 | targetNamespace: grafana 17 | path: ./clusters/my-cluster/grafana 18 | prune: true 19 | sourceRef: 20 | kind: GitRepository 21 | name: flux-system 22 | -------------------------------------------------------------------------------- /docs/blog/flux-gitops/grafana/dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDashboard 3 | metadata: 4 | name: grafanadashboard-sample 5 | namespace: grafana 6 | spec: 7 | resyncPeriod: 30s 8 | instanceSelector: 9 | matchLabels: 10 | dashboards: "grafana" 11 | json: > 12 | { 13 | "id": null, 14 | "title": "Simple Dashboard", 15 | "tags": [], 16 | "style": "dark", 17 | "timezone": "browser", 18 | "editable": true, 19 | "hideControls": false, 20 | "graphTooltip": 1, 21 | "panels": [], 22 | "time": { 23 | "from": "now-6h", 24 | "to": "now" 25 | }, 26 | "timepicker": { 27 | "time_options": [], 28 | "refresh_intervals": [] 29 | }, 30 | "templating": { 31 | "list": [] 32 | }, 33 | "annotations": { 34 | "list": [] 35 | }, 36 | "refresh": "5s", 37 | "schemaVersion": 17, 38 | "version": 0, 39 | "links": [] 40 | } 41 | -------------------------------------------------------------------------------- /docs/blog/flux-gitops/grafana/grafana.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | namespace: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | deployment: 15 | spec: 16 | template: 17 | spec: 18 | containers: 19 | - name: grafana 20 | env: 21 | - name: GF_SECURITY_ADMIN_USER 22 | valueFrom: 23 | secretKeyRef: 24 | key: GF_SECURITY_ADMIN_USER 25 | name: credentials 26 | - name: GF_SECURITY_ADMIN_PASSWORD 27 | valueFrom: 28 | secretKeyRef: 29 | key: GF_SECURITY_ADMIN_PASSWORD 30 | name: credentials 31 | ingress: 32 | spec: 33 | ingressClassName: nginx 34 | rules: 35 | - host: grafana.127.0.0.1.nip.io 36 | http: 37 | paths: 38 | - backend: 39 | service: 40 | name: grafana-service 41 | port: 42 | number: 3000 43 | path: / 44 | pathType: Prefix 45 | -------------------------------------------------------------------------------- /docs/blog/flux-gitops/grafana/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - grafana.yaml 5 | - dashboard.yaml 6 | -------------------------------------------------------------------------------- /docs/docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction" 3 | linkTitle: "Documentation" 4 | weight: 20 5 | menu: 6 | main: 7 | weight: 20 8 | --- 9 | 10 | The Grafana operator allows you to: 11 | * ⚙️ Deploy & Manage Grafana Instances inside of Kubernetes with ease 12 | * 🌐 Manage externally hosted instances using Kubernetes resources (for example Grafana Cloud) 13 | 14 | To install the Grafana Operator in your Kubernetes cluster, Run the following command in your terminal: 15 | 16 | ```bash 17 | helm upgrade -i grafana-operator oci://ghcr.io/grafana/helm-charts/grafana-operator --version {{}} 18 | ``` 19 | 20 | For a detailed installation guide, refer to [the installation documentation]({{}}). 21 | 22 | To get started, take a look at the [quick start guide]({{}}). 23 | -------------------------------------------------------------------------------- /docs/docs/alerting/alert-rule-groups.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Alert Rule Groups 3 | --- 4 | 5 | Alert Rule Groups contain a list of alerts which should evaluate at the same interval. 6 | Every rule group must belong to a folder and contain at least one rule. 7 | 8 | The easiest way to get the YAML specification for an alert rule is to use the [modify export feature](https://grafana.com/docs/grafana/latest/alerting/set-up/provision-alerting-resources/export-alerting-resources/), introduced in Grafana 10. 9 | 10 | The following snippet shows an example alert rule group with a single alert that fires when the temperature is below zero degrees. 11 | 12 | {{< readfile file="../examples/alertrulegroups/resources.yaml" code="true" lang="yaml" >}} 13 | -------------------------------------------------------------------------------- /docs/docs/alerting/contact-points.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contact Points 3 | --- 4 | 5 | Contact points contain the configuration for sending alert notifications. You can assign a contact point either in the alert rule or notification policy options. 6 | For a complete explanation on notification policies, refer to the [upstream Grafana documentation](https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/contact-points/). 7 | 8 | {{% alert title="Note" color="secondary" %}} 9 | The Grafana operator currently only supports a single receiver per contact point definition. 10 | As a workaround you can create multiple contact points with the same `spec.name` value. 11 | Follow issue [#1529](https://github.com/grafana/grafana-operator/issues/1529) for further updates on this topic. 12 | {{% /alert %}} 13 | 14 | The following snippet shows an example contact point which notifies a specific email address. 15 | It also highlights how secrets and config maps can utilized to externalize some of the configuration. 16 | This is especially useful for contact points which contain sensitive information. 17 | 18 | {{< readfile file="../examples/contactpoint_override/resources.yaml" code="true" lang="yaml" >}} 19 | -------------------------------------------------------------------------------- /docs/docs/alerting/dynamic-notification-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/docs/docs/alerting/dynamic-notification-policy.png -------------------------------------------------------------------------------- /docs/docs/alerting/notification-policy-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/docs/docs/alerting/notification-policy-tree.png -------------------------------------------------------------------------------- /docs/docs/alerting/notification-routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/docs/docs/alerting/notification-routing.png -------------------------------------------------------------------------------- /docs/docs/alerting/overview-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/docs/docs/alerting/overview-page.png -------------------------------------------------------------------------------- /docs/docs/installation/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Installation" 3 | linkTitle: "Installation" 4 | weight: 10 5 | --- 6 | 7 | The grafana-operator supports multiple different installation methods. 8 | 9 | - Helm 10 | - Kustomize 11 | - Openshift OLM 12 | -------------------------------------------------------------------------------- /docs/docs/proposals/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Proposals" 3 | linkTitle: "Proposals" 4 | weight: 300 5 | --- 6 | -------------------------------------------------------------------------------- /docs/docs/security.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Security 3 | weight: 50 4 | --- 5 | 6 | ## Verification of container images 7 | 8 | Grafana-operator container images are signed by cosign using identity-based ("keyless") signing and transparency. Executing the following command can be used to verify the signature of a container image: 9 | 10 | To verify the grafana-operator run 11 | 12 | Pre-requirement 13 | 14 | - cosign v2.0.0 or higher [installation instructions](https://docs.sigstore.dev/system_config/installation/). 15 | 16 | ```shell 17 | cosign verify ghcr.io/grafana/grafana-operator@ \ 18 | --certificate-identity-regexp 'https://github\.com/grafana/grafana-operator/\.github/workflows/.+' \ 19 | --certificate-oidc-issuer https://token.actions.githubusercontent.com | jq 20 | ``` 21 | 22 | For example 23 | 24 | ```shell 25 | cosign verify ghcr.io/grafana/grafana-operator@v5.6.1 \ 26 | --certificate-identity-regexp 'https://github\.com/grafana/grafana-operator/\.github/workflows/.+' \ 27 | --certificate-oidc-issuer https://token.actions.githubusercontent.com | jq 28 | ``` 29 | 30 | ## SBOM 31 | 32 | As a part of our release cycle we also generate SBOMs. 33 | You can find them as artifacts in our supported repositories. 34 | 35 | To download the sbom you can run 36 | 37 | ```shell 38 | cosign download sbom --platform linux/amd64 ghcr.io/grafana/grafana-operator: 39 | ``` 40 | 41 | example: 42 | 43 | ```shell 44 | cosign download sbom --platform linux/amd64 ghcr.io/grafana/grafana-operator:v5.6.1 45 | ``` 46 | -------------------------------------------------------------------------------- /embeds/README.md: -------------------------------------------------------------------------------- 1 | # Embeds 2 | 3 | This package contains static information compiled into the binary at build time. 4 | 5 | # Grafonnet 6 | 7 | The Grafonnet Jsonnet is used to allow users to define their dashboards using the jsonnet framework. 8 | 9 | # Version 10 | 11 | The `Version` variable is set during production builds by `ko`. Configuration for this can be found in `.ko.yaml` 12 | -------------------------------------------------------------------------------- /embeds/grafana_embeds.go: -------------------------------------------------------------------------------- 1 | package embeds 2 | 3 | import "embed" 4 | 5 | //go:embed grafonnet-lib 6 | var GrafonnetEmbed embed.FS 7 | 8 | //go:embed testing/dashboard.jsonnet 9 | var TestDashboardEmbed []byte 10 | 11 | //go:embed testing/dashboard.json 12 | var TestDashboardEmbedExpectedJSON []byte 13 | 14 | //go:embed testing/dashboard_with_envs.jsonnet 15 | var TestDashboardEmbedWithEnv []byte 16 | 17 | //go:embed testing/dashboard_with_provided_envs.json 18 | var TestDashboardEmbedWithEnvExpectedJSON []byte 19 | 20 | //go:embed testing/jsonnetProjectWithRuntimeRaw.tar.gz 21 | var TestJsonnetProjectBuildFolderGzip []byte 22 | 23 | // this variable is replaced during production builds 24 | var Version = "dev" 25 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet-7.0/grafana.libsonnet: -------------------------------------------------------------------------------- 1 | // This file was generated by https://github.com/grafana/dashboard-spec 2 | 3 | { 4 | dashboard:: import 'dashboard.libsonnet', 5 | panel:: { 6 | gauge:: import 'panel/gauge.libsonnet', 7 | graph:: import 'panel/graph.libsonnet', 8 | row:: import 'panel/row.libsonnet', 9 | stat:: import 'panel/stat.libsonnet', 10 | table:: import 'panel/table.libsonnet', 11 | text:: import 'panel/text.libsonnet', 12 | }, 13 | target:: { 14 | prometheus:: import 'target/prometheus.libsonnet', 15 | }, 16 | template:: { 17 | custom:: import 'template/custom.libsonnet', 18 | datasource:: import 'template/datasource.libsonnet', 19 | query:: import 'template/query.libsonnet', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet-7.0/panel/row.libsonnet: -------------------------------------------------------------------------------- 1 | // This file was generated by https://github.com/grafana/dashboard-spec 2 | 3 | { 4 | new( 5 | collapse=true, 6 | collapsed=true, 7 | datasource=null, 8 | repeat=null, 9 | repeatIteration=null, 10 | showTitle=true, 11 | title=null, 12 | titleSize='h6', 13 | ):: { 14 | [if collapse != null then 'collapse']: collapse, 15 | [if collapsed != null then 'collapsed']: collapsed, 16 | [if datasource != null then 'datasource']: datasource, 17 | [if repeat != null then 'repeat']: repeat, 18 | [if repeatIteration != null then 'repeatIteration']: repeatIteration, 19 | [if showTitle != null then 'showTitle']: showTitle, 20 | [if title != null then 'title']: title, 21 | [if titleSize != null then 'titleSize']: titleSize, 22 | type: 'row', 23 | 24 | setGridPos( 25 | h=8, 26 | w=12, 27 | x=null, 28 | y=null, 29 | ):: self {} 30 | + { gridPos+: { [if h != null then 'h']: h } } 31 | + { gridPos+: { [if w != null then 'w']: w } } 32 | + { gridPos+: { [if x != null then 'x']: x } } 33 | + { gridPos+: { [if y != null then 'y']: y } } 34 | , 35 | 36 | 37 | addPanel( 38 | panel 39 | ):: self {} 40 | + { panels+: [ 41 | panel, 42 | ] }, 43 | 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet-7.0/target/prometheus.libsonnet: -------------------------------------------------------------------------------- 1 | // This file was generated by https://github.com/grafana/dashboard-spec 2 | 3 | { 4 | new( 5 | datasource='default', 6 | expr=null, 7 | format='time_series', 8 | instant=null, 9 | interval=null, 10 | intervalFactor=null, 11 | legendFormat=null, 12 | ):: { 13 | [if datasource != null then 'datasource']: datasource, 14 | [if expr != null then 'expr']: expr, 15 | [if format != null then 'format']: format, 16 | [if instant != null then 'instant']: instant, 17 | [if interval != null then 'interval']: interval, 18 | [if intervalFactor != null then 'intervalFactor']: intervalFactor, 19 | [if legendFormat != null then 'legendFormat']: legendFormat, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet-7.0/template/custom.libsonnet: -------------------------------------------------------------------------------- 1 | // This file was generated by https://github.com/grafana/dashboard-spec 2 | 3 | { 4 | new( 5 | allValue=null, 6 | hide=0, 7 | includeAll=false, 8 | label=null, 9 | multi=false, 10 | name=null, 11 | query=null, 12 | queryValue='', 13 | skipUrlSync=false, 14 | ):: { 15 | [if allValue != null then 'allValue']: allValue, 16 | [if hide != null then 'hide']: hide, 17 | [if includeAll != null then 'includeAll']: includeAll, 18 | [if label != null then 'label']: label, 19 | [if multi != null then 'multi']: multi, 20 | [if name != null then 'name']: name, 21 | [if query != null then 'query']: query, 22 | [if queryValue != null then 'queryValue']: queryValue, 23 | [if skipUrlSync != null then 'skipUrlSync']: skipUrlSync, 24 | type: 'custom', 25 | 26 | setCurrent( 27 | selected=false, 28 | text=null, 29 | value=null, 30 | ):: self {} 31 | + { current+: { [if selected != null then 'selected']: selected } } 32 | + { current+: { [if text != null then 'text']: text } } 33 | + { current+: { [if value != null then 'value']: value } }, 34 | 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet-7.0/template/datasource.libsonnet: -------------------------------------------------------------------------------- 1 | // This file was generated by https://github.com/grafana/dashboard-spec 2 | 3 | { 4 | new( 5 | hide=0, 6 | includeAll=false, 7 | label=null, 8 | multi=false, 9 | name=null, 10 | query=null, 11 | refresh=1, 12 | regex=null, 13 | skipUrlSync=false, 14 | ):: { 15 | [if hide != null then 'hide']: hide, 16 | [if includeAll != null then 'includeAll']: includeAll, 17 | [if label != null then 'label']: label, 18 | [if multi != null then 'multi']: multi, 19 | [if name != null then 'name']: name, 20 | [if query != null then 'query']: query, 21 | [if refresh != null then 'refresh']: refresh, 22 | [if regex != null then 'regex']: regex, 23 | [if skipUrlSync != null then 'skipUrlSync']: skipUrlSync, 24 | type: 'datasource', 25 | 26 | setCurrent( 27 | selected=false, 28 | text=null, 29 | value=null, 30 | ):: self {} 31 | + { current+: { [if selected != null then 'selected']: selected } } 32 | + { current+: { [if text != null then 'text']: text } } 33 | + { current+: { [if value != null then 'value']: value } }, 34 | 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/annotation.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | default:: 3 | { 4 | builtIn: 1, 5 | datasource: '-- Grafana --', 6 | enable: true, 7 | hide: true, 8 | iconColor: 'rgba(0, 211, 255, 1)', 9 | name: 'Annotations & Alerts', 10 | type: 'dashboard', 11 | }, 12 | 13 | /** 14 | * @name annotation.datasource 15 | */ 16 | 17 | datasource( 18 | name, 19 | datasource, 20 | expr=null, 21 | enable=true, 22 | hide=false, 23 | iconColor='rgba(255, 96, 96, 1)', 24 | tags=[], 25 | type='tags', 26 | builtIn=null, 27 | ):: 28 | { 29 | datasource: datasource, 30 | enable: enable, 31 | [if expr != null then 'expr']: expr, 32 | hide: hide, 33 | iconColor: iconColor, 34 | name: name, 35 | showIn: 0, 36 | tags: tags, 37 | type: type, 38 | [if builtIn != null then 'builtIn']: builtIn, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/bar_gauge_panel.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Create a [bar gauge panel](https://grafana.com/docs/grafana/latest/panels/visualizations/bar-gauge-panel/), 4 | * 5 | * @name barGaugePanel.new 6 | * 7 | * @param title Panel title. 8 | * @param description (optional) Panel description. 9 | * @param datasource (optional) Panel datasource. 10 | * @param unit (optional) The unit of the data. 11 | * @param thresholds (optional) An array of threashold values. 12 | * 13 | * @method addTarget(target) Adds a target object. 14 | * @method addTargets(targets) Adds an array of targets. 15 | */ 16 | new( 17 | title, 18 | description=null, 19 | datasource=null, 20 | unit=null, 21 | thresholds=[], 22 | ):: { 23 | type: 'bargauge', 24 | title: title, 25 | [if description != null then 'description']: description, 26 | datasource: datasource, 27 | targets: [ 28 | ], 29 | fieldConfig: { 30 | defaults: { 31 | unit: unit, 32 | thresholds: { 33 | mode: 'absolute', 34 | steps: thresholds, 35 | }, 36 | }, 37 | }, 38 | _nextTarget:: 0, 39 | addTarget(target):: self { 40 | // automatically ref id in added targets. 41 | local nextTarget = super._nextTarget, 42 | _nextTarget: nextTarget + 1, 43 | targets+: [target { refId: std.char(std.codepoint('A') + nextTarget) }], 44 | }, 45 | addTargets(targets):: std.foldl(function(p, t) p.addTarget(t), targets, self), 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/cloudwatch.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates a [CloudWatch target](https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) 4 | * 5 | * @name cloudwatch.target 6 | * 7 | * @param region 8 | * @param namespace 9 | * @param metric 10 | * @param datasource (optional) 11 | * @param statistic (default: `'Average'`) 12 | * @param alias (optional) 13 | * @param highResolution (default: `false`) 14 | * @param period (default: `'auto'`) 15 | * @param dimensions (optional) 16 | * @param id (optional) 17 | * @param expression (optional) 18 | * @param hide (optional) 19 | 20 | * @return Panel target 21 | */ 22 | 23 | target( 24 | region, 25 | namespace, 26 | metric, 27 | datasource=null, 28 | statistic='Average', 29 | alias=null, 30 | highResolution=false, 31 | period='auto', 32 | dimensions={}, 33 | id=null, 34 | expression=null, 35 | hide=null 36 | ):: { 37 | region: region, 38 | namespace: namespace, 39 | metricName: metric, 40 | [if datasource != null then 'datasource']: datasource, 41 | statistics: [statistic], 42 | [if alias != null then 'alias']: alias, 43 | highResolution: highResolution, 44 | period: period, 45 | dimensions: dimensions, 46 | [if id != null then 'id']: id, 47 | [if expression != null then 'expression']: expression, 48 | [if hide != null then 'hide']: hide, 49 | 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/dashlist.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates a [dashlist panel](https://grafana.com/docs/grafana/latest/panels/visualizations/dashboard-list-panel/). 4 | * It requires the dashlist panel plugin in grafana, which is built-in. 5 | * 6 | * @name dashlist.new 7 | * 8 | * @param title The title of the dashlist panel. 9 | * @param description (optional) Description of the panel 10 | * @param query (optional) Query to search by 11 | * @param tags (optional) Array of tag(s) to search by 12 | * @param recent (default `true`) Displays recently viewed dashboards 13 | * @param search (default `false`) Description of the panel 14 | * @param starred (default `false`) Displays starred dashboards 15 | * @param headings (default `true`) Chosen list selection(starred, recently Viewed, search) is shown as a heading 16 | * @param limit (default `10`) Set maximum items in a list 17 | * @return A json that represents a dashlist panel 18 | */ 19 | new( 20 | title, 21 | description=null, 22 | query=null, 23 | tags=[], 24 | recent=true, 25 | search=false, 26 | starred=false, 27 | headings=true, 28 | limit=10, 29 | ):: { 30 | type: 'dashlist', 31 | title: title, 32 | query: if query != null then query else '', 33 | tags: tags, 34 | recent: recent, 35 | search: search, 36 | starred: starred, 37 | headings: headings, 38 | limit: limit, 39 | [if description != null then 'description']: description, 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/elasticsearch.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates an [Elasticsearch target](https://grafana.com/docs/grafana/latest/datasources/elasticsearch/) 4 | * 5 | * @name elasticsearch.target 6 | * 7 | * @param query 8 | * @param timeField 9 | * @param id (optional) 10 | * @param datasource (optional) 11 | * @param metrics (optional) 12 | * @param bucketAggs (optional) 13 | * @param alias (optional) 14 | */ 15 | target( 16 | query, 17 | timeField, 18 | id=null, 19 | datasource=null, 20 | metrics=[{ 21 | field: 'value', 22 | id: null, 23 | type: 'percentiles', 24 | settings: { 25 | percents: [ 26 | '90', 27 | ], 28 | }, 29 | }], 30 | bucketAggs=[{ 31 | field: 'timestamp', 32 | id: null, 33 | type: 'date_histogram', 34 | settings: { 35 | interval: '1s', 36 | min_doc_count: 0, 37 | trimEdges: 0, 38 | }, 39 | }], 40 | alias=null, 41 | ):: { 42 | [if datasource != null then 'datasource']: datasource, 43 | query: query, 44 | id: id, 45 | timeField: timeField, 46 | bucketAggs: bucketAggs, 47 | metrics: metrics, 48 | alias: alias, 49 | // TODO: generate bucket ids 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/grafana.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | alertlist:: import 'alertlist.libsonnet', 3 | dashboard:: import 'dashboard.libsonnet', 4 | template:: import 'template.libsonnet', 5 | text:: import 'text.libsonnet', 6 | timepicker:: import 'timepicker.libsonnet', 7 | row:: import 'row.libsonnet', 8 | link:: import 'link.libsonnet', 9 | annotation:: import 'annotation.libsonnet', 10 | graphPanel:: import 'graph_panel.libsonnet', 11 | logPanel:: import 'log_panel.libsonnet', 12 | tablePanel:: import 'table_panel.libsonnet', 13 | singlestat:: import 'singlestat.libsonnet', 14 | pieChartPanel:: import 'pie_chart_panel.libsonnet', 15 | influxdb:: import 'influxdb.libsonnet', 16 | prometheus:: import 'prometheus.libsonnet', 17 | loki:: import 'loki.libsonnet', 18 | sql:: import 'sql.libsonnet', 19 | graphite:: import 'graphite.libsonnet', 20 | alertCondition:: import 'alert_condition.libsonnet', 21 | cloudmonitoring:: import 'cloudmonitoring.libsonnet', 22 | cloudwatch:: import 'cloudwatch.libsonnet', 23 | elasticsearch:: import 'elasticsearch.libsonnet', 24 | heatmapPanel:: import 'heatmap_panel.libsonnet', 25 | dashlist:: import 'dashlist.libsonnet', 26 | pluginlist:: import 'pluginlist.libsonnet', 27 | gauge:: error 'gauge is removed, migrate to gaugePanel', 28 | gaugePanel:: import 'gauge_panel.libsonnet', 29 | barGaugePanel:: import 'bar_gauge_panel.libsonnet', 30 | statPanel:: import 'stat_panel.libsonnet', 31 | transformation:: import 'transformation.libsonnet', 32 | } 33 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/graphite.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates a [Graphite target](https://grafana.com/docs/grafana/latest/datasources/graphite/) 4 | * 5 | * @name graphite.target 6 | * 7 | * @param target Graphite Query. Nested queries are possible by adding the query reference (refId). 8 | * @param targetFull (optional) Expanding the @target. Used in nested queries. 9 | * @param hide (default `false`) Disable query on graph. 10 | * @param textEditor (default `false`) Enable raw query mode. 11 | * @param datasource (optional) Datasource. 12 | 13 | * @return Panel target 14 | */ 15 | target( 16 | target, 17 | targetFull=null, 18 | hide=false, 19 | textEditor=false, 20 | datasource=null, 21 | ):: { 22 | target: target, 23 | hide: hide, 24 | textEditor: textEditor, 25 | 26 | [if targetFull != null then 'targetFull']: targetFull, 27 | [if datasource != null then 'datasource']: datasource, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/loki.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates a [Loki target](https://grafana.com/docs/grafana/latest/datasources/loki/) 4 | * 5 | * @name loki.target 6 | * 7 | * @param expr 8 | * @param hide (optional) Disable query on graph. 9 | * @param legendFormat (optional) Defines the legend. Defaults to ''. 10 | */ 11 | target( 12 | expr, 13 | hide=null, 14 | legendFormat='', 15 | instant=null, 16 | ):: { 17 | [if hide != null then 'hide']: hide, 18 | expr: expr, 19 | legendFormat: legendFormat, 20 | [if instant != null then 'instant']: instant, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/pluginlist.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Returns a new pluginlist panel that can be added in a row. 4 | * It requires the pluginlist panel plugin in grafana, which is built-in. 5 | * 6 | * @name pluginlist.new 7 | * 8 | * @param title The title of the pluginlist panel. 9 | * @param description (optional) Description of the panel 10 | * @param limit (optional) Set maximum items in a list 11 | * @return A json that represents a pluginlist panel 12 | */ 13 | new( 14 | title, 15 | description=null, 16 | limit=null, 17 | ):: { 18 | type: 'pluginlist', 19 | title: title, 20 | [if limit != null then 'limit']: limit, 21 | [if description != null then 'description']: description, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/sql.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates an SQL target. 4 | * 5 | * @name sql.target 6 | * 7 | * @param rawSql The SQL query 8 | * @param datasource (optional) 9 | * @param format (default `'time_series'`) 10 | * @param alias (optional) 11 | */ 12 | target( 13 | rawSql, 14 | datasource=null, 15 | format='time_series', 16 | alias=null, 17 | ):: { 18 | [if datasource != null then 'datasource']: datasource, 19 | format: format, 20 | [if alias != null then 'alias']: alias, 21 | rawSql: rawSql, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/timepicker.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Creates a Timepicker 4 | * 5 | * @name timepicker.new 6 | * 7 | * @param refresh_intervals (default: `['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d']`) Array of time durations 8 | * @param time_options (default: `['5m','15m','1h','6h','12h','24h','2d','7d','30d']`) Array of time durations 9 | */ 10 | new( 11 | refresh_intervals=[ 12 | '5s', 13 | '10s', 14 | '30s', 15 | '1m', 16 | '5m', 17 | '15m', 18 | '30m', 19 | '1h', 20 | '2h', 21 | '1d', 22 | ], 23 | time_options=[ 24 | '5m', 25 | '15m', 26 | '1h', 27 | '6h', 28 | '12h', 29 | '24h', 30 | '2d', 31 | '7d', 32 | '30d', 33 | ], 34 | nowDelay=null, 35 | ):: { 36 | refresh_intervals: refresh_intervals, 37 | time_options: time_options, 38 | [if nowDelay != null then 'nowDelay']: nowDelay, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /embeds/grafonnet-lib/grafonnet/transformation.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * @name transformation.new 4 | */ 5 | new( 6 | id='', 7 | options={} 8 | ):: { 9 | id: id, 10 | options: options, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /embeds/testing/jsonnetProjectWithRuntimeRaw.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/embeds/testing/jsonnetProjectWithRuntimeRaw.tar.gz -------------------------------------------------------------------------------- /embeds/testing/jsonnetProjectWithRuntimeRaw/dashboard_with_envs.jsonnet: -------------------------------------------------------------------------------- 1 | local env = std.extVar('TEST_ENV'); 2 | { 3 | "env" : env 4 | } 5 | -------------------------------------------------------------------------------- /examples/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Examples" 3 | linkTitle: "Examples" 4 | weight: 20 5 | --- 6 | -------------------------------------------------------------------------------- /examples/alertrulegroups/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Alert rule groups" 3 | linkTitle: "Alert rule groups" 4 | --- 5 | 6 | Shows how to create an alert rule group, contained in a folder. 7 | 8 | The easiest way to build alerts is using the Grafana UI. 9 | After creating the rule, use the [Modify Export](https://grafana.com/docs/grafana/latest/alerting/set-up/provision-alerting-resources/export-alerting-resources/#modify-and-export-alert-rules-without-saving-changes) feature to get the correct YAML from your resource. 10 | 11 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 12 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Basic example" 3 | linkTitle: "Basic example" 4 | --- 5 | 6 | A basic deployment of Grafana with a dashboard. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/basic/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | config: 9 | log: 10 | mode: "console" 11 | auth: 12 | disable_login_form: "false" 13 | security: 14 | admin_user: root 15 | admin_password: secret 16 | --- 17 | apiVersion: grafana.integreatly.org/v1beta1 18 | kind: GrafanaDashboard 19 | metadata: 20 | name: grafanadashboard-sample 21 | spec: 22 | resyncPeriod: 30s 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | json: > 27 | { 28 | "id": null, 29 | "title": "Simple Dashboard", 30 | "tags": [], 31 | "style": "dark", 32 | "timezone": "browser", 33 | "editable": true, 34 | "hideControls": false, 35 | "graphTooltip": 1, 36 | "panels": [], 37 | "time": { 38 | "from": "now-6h", 39 | "to": "now" 40 | }, 41 | "timepicker": { 42 | "time_options": [], 43 | "refresh_intervals": [] 44 | }, 45 | "templating": { 46 | "list": [] 47 | }, 48 | "annotations": { 49 | "list": [] 50 | }, 51 | "refresh": "5s", 52 | "schemaVersion": 17, 53 | "version": 0, 54 | "links": [] 55 | } 56 | -------------------------------------------------------------------------------- /examples/configmaps_sidecar/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard from configmap via sidecar example" 3 | linkTitle: "Dashboard from configmap via sidecar example" 4 | --- 5 | 6 | Using the [kiwigrid/k8s-sidecar](https://github.com/kiwigrid/k8s-sidecar) to watch for configmaps and import their 7 | contents as dashboards. 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/contactpoint_override/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Contact point overrides" 3 | linkTitle: "Contact point overrides" 4 | --- 5 | 6 | Contact point overrides allow you to set configuration values using secrets or configmaps instead of directly in the resource 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/contactpoint_override/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: contact-mails 5 | stringData: 6 | alert-mails: "foo@example.com" 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaContactPoint 10 | metadata: 11 | name: grafanacontactpoint-sample 12 | spec: 13 | name: grafanacontactpoint-sample 14 | type: "email" 15 | instanceSelector: 16 | matchLabels: 17 | instance: my-grafana-stack 18 | settings: 19 | subject: 'Grafana Alert' 20 | valuesFrom: 21 | - targetPath: addresses 22 | valueFrom: 23 | secretKeyRef: 24 | name: contact-mails 25 | key: alert-mails 26 | -------------------------------------------------------------------------------- /examples/credential_config/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Credential config" 3 | linkTitle: "Credential config" 4 | --- 5 | 6 | This example shows how to set the admin account credentials via configuration. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/credential_secret/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Credential Secret" 3 | linkTitle: "Credential Secret" 4 | --- 5 | 6 | This example shows how to provide you own Grafana admin credentials from an 7 | existing secret. 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/credential_secret/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Secret 3 | apiVersion: v1 4 | metadata: 5 | name: credentials 6 | namespace: grafana 7 | stringData: 8 | GF_SECURITY_ADMIN_PASSWORD: secret 9 | GF_SECURITY_ADMIN_USER: root 10 | type: Opaque 11 | --- 12 | apiVersion: grafana.integreatly.org/v1beta1 13 | kind: Grafana 14 | metadata: 15 | name: grafana 16 | labels: 17 | dashboards: "grafana" 18 | spec: 19 | config: 20 | log: 21 | mode: "console" 22 | auth: 23 | disable_login_form: "false" 24 | disableDefaultAdminSecret: true 25 | deployment: 26 | spec: 27 | template: 28 | spec: 29 | containers: 30 | - name: grafana 31 | env: 32 | - name: GF_SECURITY_ADMIN_USER 33 | valueFrom: 34 | secretKeyRef: 35 | key: GF_SECURITY_ADMIN_USER 36 | name: credentials 37 | - name: GF_SECURITY_ADMIN_PASSWORD 38 | valueFrom: 39 | secretKeyRef: 40 | key: GF_SECURITY_ADMIN_PASSWORD 41 | name: credentials 42 | -------------------------------------------------------------------------------- /examples/crossnamespace/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Cross namespace" 3 | linkTitle: "Cross namespace" 4 | --- 5 | 6 | 7 | If you want your dashboard or datasource to be able to be used by a grafana instance in another namespace you need to set `spec.allowCrossNamespaceImport: true`. 8 | 9 | In the resources file you will find examples how to do this. 10 | 11 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 12 | -------------------------------------------------------------------------------- /examples/crossnamespace/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: grafana-a 5 | --- 6 | apiVersion: v1 7 | kind: Namespace 8 | metadata: 9 | name: grafana-b 10 | --- 11 | apiVersion: grafana.integreatly.org/v1beta1 12 | kind: Grafana 13 | metadata: 14 | name: grafana 15 | namespace: grafana-a 16 | labels: 17 | dashboards: "grafana" 18 | spec: 19 | config: 20 | log: 21 | mode: "console" 22 | auth: 23 | disable_login_form: "false" 24 | security: 25 | admin_user: root 26 | admin_password: secret 27 | deployment: 28 | spec: 29 | template: 30 | spec: 31 | containers: 32 | - name: grafana 33 | securityContext: 34 | allowPrivilegeEscalation: true 35 | readOnlyRootFilesystem: false 36 | readinessProbe: 37 | failureThreshold: 3 38 | --- 39 | apiVersion: grafana.integreatly.org/v1beta1 40 | kind: GrafanaDatasource 41 | metadata: 42 | name: example-grafanadatasource 43 | namespace: grafana-b 44 | spec: 45 | allowCrossNamespaceImport: true 46 | datasource: 47 | access: proxy 48 | database: prometheus 49 | jsonData: 50 | timeInterval: 5s 51 | tlsSkipVerify: true 52 | name: Prometheus 53 | url: http://prometheus-service:9090 54 | instanceSelector: 55 | matchLabels: 56 | dashboards: grafana 57 | -------------------------------------------------------------------------------- /examples/dashboard_from_configmap/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard from ConfigMap" 3 | linkTitle: "Dashboard from ConfigMap" 4 | --- 5 | 6 | Shows how to obtain the dashboard definition (json) from a key in a ConfigMap in the same namespace as the dashboard CR. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/dashboard_from_configmap/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: v1 19 | kind: ConfigMap 20 | metadata: 21 | name: dashboard-definition 22 | data: 23 | json: > 24 | { 25 | "id": null, 26 | "title": "Simple Dashboard from ConfigMap", 27 | "tags": [], 28 | "style": "dark", 29 | "timezone": "browser", 30 | "editable": true, 31 | "hideControls": false, 32 | "graphTooltip": 1, 33 | "panels": [], 34 | "time": { 35 | "from": "now-6h", 36 | "to": "now" 37 | }, 38 | "timepicker": { 39 | "time_options": [], 40 | "refresh_intervals": [] 41 | }, 42 | "templating": { 43 | "list": [] 44 | }, 45 | "annotations": { 46 | "list": [] 47 | }, 48 | "refresh": "5s", 49 | "schemaVersion": 17, 50 | "version": 0, 51 | "links": [] 52 | } 53 | --- 54 | apiVersion: grafana.integreatly.org/v1beta1 55 | kind: GrafanaDashboard 56 | metadata: 57 | name: grafanadashboard-from-configmap 58 | spec: 59 | instanceSelector: 60 | matchLabels: 61 | dashboards: "grafana" 62 | configMapRef: 63 | name: dashboard-definition 64 | key: json 65 | -------------------------------------------------------------------------------- /examples/dashboard_from_grafana_com/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard from grafana.com/dashboards" 3 | linkTitle: "Dashboard from grafana.com/dashboards" 4 | --- 5 | 6 | Shows how to obtain the dashboard definition from [grafana.com/dashboards](https://grafana.com/dashboards). 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/dashboard_from_grafana_com/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaDashboard 20 | metadata: 21 | name: node-exporter-latest 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | grafanaCom: 27 | id: 1860 28 | --- 29 | apiVersion: grafana.integreatly.org/v1beta1 30 | kind: GrafanaDashboard 31 | metadata: 32 | name: instio-control-plane-pinned-revision 33 | spec: 34 | instanceSelector: 35 | matchLabels: 36 | dashboards: "grafana" 37 | grafanaCom: 38 | id: 7645 39 | revision: 161 40 | -------------------------------------------------------------------------------- /examples/dashboard_from_url/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard from URL" 3 | linkTitle: "Dashboard from URL" 4 | --- 5 | 6 | Shows how to obtain the dashboard definition (json) from an external url. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/dashboard_from_url/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": null, 3 | "title": "Simple Dashboard-url", 4 | "tags": [], 5 | "style": "dark", 6 | "timezone": "browser", 7 | "editable": true, 8 | "hideControls": false, 9 | "graphTooltip": 1, 10 | "panels": [], 11 | "time": { 12 | "from": "now-6h", 13 | "to": "now" 14 | }, 15 | "timepicker": { 16 | "time_options": [], 17 | "refresh_intervals": [] 18 | }, 19 | "templating": { 20 | "list": [] 21 | }, 22 | "annotations": { 23 | "list": [] 24 | }, 25 | "refresh": "5s", 26 | "schemaVersion": 17, 27 | "version": 0, 28 | "links": [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/dashboard_from_url/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaDashboard 20 | metadata: 21 | name: grafanadashboard-from-url 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | url: "https://raw.githubusercontent.com/grafana-operator/grafana-operator/master/examples/dashboard_from_url/dashboard.json" 27 | -------------------------------------------------------------------------------- /examples/dashboard_gzipped/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard gzipped" 3 | linkTitle: "Dashboard gzipped" 4 | --- 5 | 6 | Shows how to store the dashboard definition (json) as gzip compressed. 7 | 8 | ```shell 9 | cat dashboard.json | gzip | base64 -w0 10 | ``` 11 | 12 | {{< readfile file="dashboard.json" code="true" lang="json" >}} 13 | 14 | Should provide 15 | 16 | ```txt 17 | H4sIAAAAAAAAA4WQQU/DMAyF7/0VVc9MggMgcYV/AOKC0OQubmM1jSPH28Sm/XfSNJ1WcaA3f+/l+dXnqk5fQ6Z5qf3eubt5VlKHCTXvNAaH9RtE2zKI2fQnCgFNsxihj8n39V3mqD/zQwMyXE004ol95q3wMaIsEhpSaPMTlT0WasngK3sVdlN6By4uUi8Q7AezUwpJeig4gEe3ajItTfM5T5l0wuNUwfNx82RLg9nLhTeZXW4iAu2GVHcVNPEtByX2tyuzJtgJRrslrygHKJ3WsZhuCkq+X8c6ivrXDd6zwrLrX3vZP/3PY1yuHHcWR/hEiSlmutpzEQ5XdF+IIz+Uzpeq+gWtMMT1HwIAAA== 18 | ``` 19 | 20 | And simply add this output to your dashboard CR 21 | 22 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 23 | -------------------------------------------------------------------------------- /examples/dashboard_gzipped/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": null, 3 | "title": "Simple Dashboard-gzipped", 4 | "tags": [], 5 | "style": "dark", 6 | "timezone": "browser", 7 | "editable": true, 8 | "hideControls": false, 9 | "graphTooltip": 1, 10 | "panels": [], 11 | "time": { 12 | "from": "now-6h", 13 | "to": "now" 14 | }, 15 | "timepicker": { 16 | "time_options": [], 17 | "refresh_intervals": [] 18 | }, 19 | "templating": { 20 | "list": [] 21 | }, 22 | "annotations": { 23 | "list": [] 24 | }, 25 | "refresh": "5s", 26 | "schemaVersion": 17, 27 | "version": 0, 28 | "links": [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/dashboard_gzipped/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaDashboard 20 | metadata: 21 | name: grafanadashboard-gzipped 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | gzipJson: |- 27 | H4sIAAAAAAAAA4WQQU/DMAyF7/0VVc9MggMgcYV/AOKC0OQubmM1jSPH28Sm/XfSNJ1WcaA3f+/l+dXnqk5fQ6Z5qf3eubt5VlKHCTXvNAaH9RtE2zKI2fQnCgFNsxihj8n39V3mqD/zQwMyXE004ol95q3wMaIsEhpSaPMTlT0WasngK3sVdlN6By4uUi8Q7AezUwpJeig4gEe3ajItTfM5T5l0wuNUwfNx82RLg9nLhTeZXW4iAu2GVHcVNPEtByX2tyuzJtgJRrslrygHKJ3WsZhuCkq+X8c6ivrXDd6zwrLrX3vZP/3PY1yuHHcWR/hEiSlmutpzEQ5XdF+IIz+Uzpeq+gWtMMT1HwIAAA== 28 | -------------------------------------------------------------------------------- /examples/dashboard_with_custom_folder/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Custom dashboard folder" 3 | linkTitle: "Custom dashboard folder" 4 | --- 5 | 6 | This example shows how to add assign a dashboard to a folder through the dashboard resource, 7 | avoiding the use of the GrafanaFolder resource. 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/dashboard_with_custom_folder/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | client: 10 | preferIngress: true 11 | config: 12 | log: 13 | mode: "console" 14 | auth: 15 | disable_login_form: "false" 16 | security: 17 | admin_user: root 18 | admin_password: secret 19 | --- 20 | apiVersion: grafana.integreatly.org/v1beta1 21 | kind: GrafanaDashboard 22 | metadata: 23 | name: grafanadashboard-with-custom-folder 24 | spec: 25 | folder: "Custom Folder" 26 | instanceSelector: 27 | matchLabels: 28 | dashboards: "grafana" 29 | url: "https://raw.githubusercontent.com/grafana-operator/grafana-operator/master/examples/dashboard_from_url/dashboard.json" 30 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dashboard with external dependencies" 3 | linkTitle: "Dashboard with external dependencies" 4 | --- 5 | 6 | This example shows how to obtain the dashboard definition (json) from provided gzip archived project with dependencies: 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/jsonnetfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/grafana/grafonnet.git", 8 | "subdir": "gen/grafonnet-latest" 9 | } 10 | }, 11 | "version": "main" 12 | } 13 | ], 14 | "legacyImports": true 15 | } 16 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/util/datasources.libsonnet: -------------------------------------------------------------------------------- 1 | local g = import './g.libsonnet'; 2 | local envs = import './envs.libsonnet'; 3 | 4 | local datasource = g.dashboard.annotation.datasource; 5 | 6 | { 7 | base(type, uid): 8 | datasource.withType(type) 9 | + datasource.withUid(uid), 10 | 11 | dashboardDatasource: 12 | self.base('datasource', 'grafana'), 13 | 14 | prometheusDatasource: 15 | self.base(envs.PROMETHEUS_DS_TYPE, envs.PROMETHEUS_DS_UID), 16 | } 17 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/util/envs.libsonnet: -------------------------------------------------------------------------------- 1 | { 2 | // Prometheus datasource type 3 | PROMETHEUS_DS_TYPE: 4 | 'prometheus', 5 | 6 | // Prometheus datasource uid 7 | PROMETHEUS_DS_UID: 8 | std.extVar('PROMETHEUS_DS_UID'), 9 | 10 | //Target namespace where service is deployed 11 | K8S_NAMESPACE: 12 | std.extVar('K8S_NAMESPACE') 13 | } 14 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/util/g.libsonnet: -------------------------------------------------------------------------------- 1 | import 'github.com/grafana/grafonnet/gen/grafonnet-latest/main.libsonnet' 2 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/util/queries.libsonnet: -------------------------------------------------------------------------------- 1 | local datasources = import './datasources.libsonnet'; 2 | local g = import './g.libsonnet'; 3 | local envs = import './envs.libsonnet'; 4 | 5 | local query = g.query; 6 | local prometheusQuery = query.prometheus; 7 | 8 | { 9 | readyPods: 10 | prometheusQuery.new( 11 | envs.PROMETHEUS_DS_UID, 12 | ||| 13 | avg by (container) (100 * kube_pod_container_status_ready{namespace="$namespace", pod=~"pod-.*", job="kube-state-metrics"})[1m] 14 | ||| 15 | ) + prometheusQuery.withLegendFormat('{{`{{ pod }}`}}'), 16 | 17 | totalRestartsPerContainer: 18 | prometheusQuery.new( 19 | envs.PROMETHEUS_DS_UID, 20 | ||| 21 | sum by (container) (kube_pod_container_status_restarts_total{namespace="$namespace", pod=~"pod-.*", job="kube-state-metrics"}) 22 | ||| 23 | ) + prometheusQuery.withLegendFormat('{{`{{ container }}`}}') 24 | + prometheusQuery.withEditorMode('code'), 25 | } 26 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/dashboard_project/util/variables.libsonnet: -------------------------------------------------------------------------------- 1 | local g = import './g.libsonnet'; 2 | local datasources = import './datasources.libsonnet'; 3 | local envs = import './envs.libsonnet'; 4 | 5 | local var = g.dashboard.variable; 6 | 7 | { 8 | namespace: 9 | var.constant.new('namespace', [envs.K8S_NAMESPACE]), 10 | 11 | promteheus_metrics_aggegation_time: 12 | var.interval.new('agg_time', ['1m', '5m', '10m', '15m', '30m', '1h', '3h', '6h', '12h', '1d', '3d', '1w', '1m']) 13 | + var.interval.generalOptions.withLabel('Metrics aggregation time') 14 | } 15 | -------------------------------------------------------------------------------- /examples/dashboard_with_external_dependencies/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaDashboard 20 | metadata: 21 | name: grafana-dashboard-with-dependencies 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | envs: 27 | - name: PROMETHEUS_DS_UID 28 | value: "uuid" 29 | - name: K8S_NAMESPACE 30 | value: "k8s_namespace" 31 | - name: DASHBOARD_JSONNET_RECURSIVE 32 | value: "true" 33 | jsonnetLib: 34 | jPath: 35 | - "vendor" 36 | fileName: "dashboard.jsonnet" 37 | gzipJsonnetProject: |- 38 | {{- (.Files.Get "dashboards.tar.gz") | b64enc | nindent 6 }} 39 | -------------------------------------------------------------------------------- /examples/datasource_mapping/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Datasource mapping" 3 | linkTitle: "Datasource mapping" 4 | --- 5 | 6 | This example shows how to map an internal data source to an input from an exported dashboard. 7 | 8 | There are two datasources in this example. 9 | This to visualize that we can use multiple datasources and specify which `datasourceName` to use when overwriting the `DS_PROMETHEUS` config in the grafana dashboard json. 10 | 11 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 12 | -------------------------------------------------------------------------------- /examples/datasource_types/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Datasource types" 3 | linkTitle: "Datasource types" 4 | --- 5 | 6 | This example shows different types of datasources and how to configure them. 7 | 8 | {{< readfile file="prometheus.yaml" code="true" lang="yaml" >}} 9 | {{< readfile file="postgresql.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/datasource_types/postgresql.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: postgresql 5 | namespace: grafana 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | datasource: 11 | name: postgresql 12 | type: postgres 13 | jsonData: 14 | database: postgres 15 | connMaxLifetime: 14400 16 | maxIdleConns: 2 17 | maxOpenConns: 0 18 | postgresVersion: 1400 19 | sslmode: disable 20 | timescaledb: false 21 | access: proxy 22 | secureJsonData: 23 | password: postgres 24 | url: postgresql.namespace.svc:5432 25 | user: postgres 26 | -------------------------------------------------------------------------------- /examples/datasource_types/prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: prometheus 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | datasource: 10 | name: prom1 11 | type: prometheus 12 | access: proxy 13 | url: http://prometheus-service:9090 14 | isDefault: true 15 | jsonData: 16 | "tlsSkipVerify": true 17 | "timeInterval": "5s" 18 | -------------------------------------------------------------------------------- /examples/datasource_variables/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Datasource variable" 3 | linkTitle: "Datasource variable" 4 | --- 5 | 6 | This example shows how to reference values from a secret in data source fields. 7 | Values can be assigned using `spec.valuesFrom`: 8 | 9 | ```yaml 10 | valuesFrom: 11 | - targetPath: "secureJsonData.httpHeaderValue1" 12 | valueFrom: 13 | secretKeyRef: 14 | name: "credentials" 15 | key: "PROMETHEUS_TOKEN" 16 | ``` 17 | 18 | The Operator will look for a key with the name `PROMETHEUS_TOKEN` in a secret with the name `credentials`. 19 | It will then inject the value into `secureJsonData.httpHeaderValue1`: 20 | 21 | ```yaml 22 | datasource: 23 | secureJsonData: 24 | "httpHeaderValue1": "Bearer ${PROMETHEUS_TOKEN}" 25 | ``` 26 | 27 | The Operator expects a string to be present with the replacement pattern. 28 | 29 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 30 | -------------------------------------------------------------------------------- /examples/datasource_variables/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | kind: Secret 19 | apiVersion: v1 20 | metadata: 21 | name: credentials 22 | namespace: grafana 23 | stringData: 24 | PROMETHEUS_TOKEN: secret_token 25 | type: Opaque 26 | --- 27 | apiVersion: grafana.integreatly.org/v1beta1 28 | kind: GrafanaDatasource 29 | metadata: 30 | name: grafanadatasource-sample 31 | spec: 32 | valuesFrom: 33 | - targetPath: "secureJsonData.httpHeaderValue1" 34 | valueFrom: 35 | secretKeyRef: 36 | name: "credentials" 37 | key: "PROMETHEUS_TOKEN" 38 | instanceSelector: 39 | matchLabels: 40 | dashboards: "grafana" 41 | datasource: 42 | name: prometheus 43 | type: prometheus 44 | access: proxy 45 | basicAuth: true 46 | url: http://prometheus-service:9090 47 | isDefault: true 48 | jsonData: 49 | "tlsSkipVerify": true 50 | "timeInterval": "5s" 51 | httpHeaderName1: "Authorization" 52 | secureJsonData: 53 | "httpHeaderValue1": "Bearer ${PROMETHEUS_TOKEN}" 54 | -------------------------------------------------------------------------------- /examples/external_grafana/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "External grafana" 3 | linkTitle: "External grafana" 4 | --- 5 | A basic example configuring an external grafana instance. 6 | 7 | In this case we create a grafana instance through the operator just to showcase that it can be done. 8 | 9 | Notice the `instanceSelector` of the grafana dashboard and the grafana instances. 10 | You will notice that it's only the grafana instance called `external-grafana` that will match with the grafana dashboard called `external-grafanadashboard-sample`. 11 | 12 | If you look in the status of external-grafana you will also see the hash of the dashboard that have been applied. 13 | 14 | ```shell 15 | kubectl get grafanas.grafana.integreatly.org external-grafana -o yaml 16 | # redacted output 17 | status: 18 | adminUrl: http://test.io 19 | dashboards: 20 | - grafana-operator-system/external-grafanadashboard-sample/cb1688d2-547a-465b-bc49-df3ccf3da883 21 | stage: complete 22 | stageStatus: success 23 | ``` 24 | 25 | ## FYI 26 | 27 | If you want run the same test locally on your computer remember to ether update the ingres host to match your settings or change `client.preferIngress` to false assuming that you run the operator within the cluster. 28 | 29 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 30 | -------------------------------------------------------------------------------- /examples/folder/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Folder with permissions" 3 | linkTitle: "Folder with permissions" 4 | --- 5 | 6 | Shows how to create a folder with custom title and [permissions](https://grafana.com/docs/grafana/v8.4/http_api/folder_permissions/#update-permissions-for-a-folder). 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/folder/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaFolder 20 | metadata: 21 | name: test-folder 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | 27 | # If title is not defined, the value will be taken from metadata.name 28 | title: custom title 29 | 30 | # When permissions value is empty/absent, a folder is created with default permissions 31 | # When empty JSON is passed ("{}"), the access is stripped for everyone except for Admin (default Grafana behaviour) 32 | permissions: | 33 | { 34 | "items": [ 35 | { 36 | "role": "Admin", 37 | "permission": 4 38 | }, 39 | { 40 | "role": "Editor", 41 | "permission": 2 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /examples/grafana_deployment/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Grafana deployment config" 3 | linkTitle: "Grafana deployment config" 4 | --- 5 | 6 | A basic deployment of Grafana that overrides the existing image, readiness and securityContext. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/grafana_deployment/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | security: 11 | admin_user: root 12 | admin_password: secret 13 | deployment: 14 | spec: 15 | template: 16 | spec: 17 | containers: 18 | - name: grafana 19 | securityContext: 20 | # Customize this in case your volume provider needs specific UIDs/GIDs 21 | runAsUser: 1001 22 | runAsGroup: 1001 23 | runAsNonRoot: true 24 | allowPrivilegeEscalation: false 25 | capabilities: 26 | drop: ["ALL"] 27 | readinessProbe: 28 | failureThreshold: 3 29 | -------------------------------------------------------------------------------- /examples/ingress_http/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ingress http" 3 | linkTitle: "Ingress http" 4 | --- 5 | 6 | A very basic ingress that should only be used for testing. 7 | Always always use https. 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/ingress_http/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | ingress: 18 | spec: 19 | ingressClassName: nginx 20 | rules: 21 | - host: test.io 22 | http: 23 | paths: 24 | - backend: 25 | service: 26 | name: grafana-service 27 | port: 28 | number: 3000 29 | path: / 30 | pathType: Prefix 31 | -------------------------------------------------------------------------------- /examples/ingress_https/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ingress https" 3 | linkTitle: "Ingress https" 4 | --- 5 | Assumes that you have [cert-manager](https://github.com/cert-manager/cert-manager) running in your cluster and have a ClusterIssuer called letsencrypt. 6 | 7 | It also assumes that you have `ingressClassName: nginx`. 8 | 9 | You can of course have added a certificate to a secret manually. 10 | 11 | {{< readfile file="cert.yaml" code="true" lang="yaml" >}} 12 | 13 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 14 | 15 | {{< readfile file="dashboard.yaml" code="true" lang="yaml" >}} 16 | -------------------------------------------------------------------------------- /examples/ingress_https/cert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: cert 5 | spec: 6 | issuerRef: 7 | group: cert-manager.io 8 | kind: ClusterIssuer 9 | name: letsencrypt 10 | dnsNames: 11 | - example.com 12 | secretName: core-cert 13 | revisionHistoryLimit: 1 14 | -------------------------------------------------------------------------------- /examples/ingress_https/dashboard.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | name: grafana-dashboard 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | json: > 11 | { 12 | "id": null, 13 | "title": "Simple Dashboard", 14 | "tags": [], 15 | "style": "dark", 16 | "timezone": "browser", 17 | "editable": true, 18 | "hideControls": false, 19 | "graphTooltip": 1, 20 | "panels": [], 21 | "time": { 22 | "from": "now-6h", 23 | "to": "now" 24 | }, 25 | "timepicker": { 26 | "time_options": [], 27 | "refresh_intervals": [] 28 | }, 29 | "templating": { 30 | "list": [] 31 | }, 32 | "annotations": { 33 | "list": [] 34 | }, 35 | "refresh": "5s", 36 | "schemaVersion": 17, 37 | "version": 0, 38 | "links": [] 39 | } 40 | -------------------------------------------------------------------------------- /examples/ingress_https/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | ingress: 18 | spec: 19 | ingressClassName: nginx 20 | rules: 21 | - host: example.com 22 | http: 23 | paths: 24 | - backend: 25 | service: 26 | name: grafana-service 27 | port: 28 | number: 3000 29 | path: / 30 | pathType: Prefix 31 | tls: 32 | - hosts: 33 | - example.com 34 | secretName: core-cert 35 | -------------------------------------------------------------------------------- /examples/jsonnet/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Jsonnet" 3 | linkTitle: "Jsonnet" 4 | --- 5 | 6 | A basic deployment of Grafana with a dashboard fom jsonnet. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/k3d/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "k3d example" 3 | linkTitle: "k3d example" 4 | --- 5 | 6 | A basic deployment of Grafana in k3d/k3d making use of the built-in Ingress controller. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/ldap/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "LDAP configmap auth" 3 | linkTitle: "LDAP configmap auth" 4 | --- 5 | 6 | A basic example of a Grafana Deployment with LDAP integration. The LDAP integration in Grafana allows your Grafana users to login with their LDAP credentials 7 | For cusomize you ldap-config configmap read [this](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/ldap/) 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/multiple_replicas/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Multiple replicas" 3 | linkTitle: "Multiple replicas" 4 | --- 5 | 6 | This example shows how to run multiple replicas of Grafana sharing a PostgreSQL database. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/mute_timing/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mute timing" 3 | linkTitle: "Mute timing" 4 | --- 5 | 6 | Shows how to create a mute timing. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/mute_timing/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaMuteTiming 4 | metadata: 5 | name: mutetiming-sample 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | name: mutetiming-sample 11 | editable: false 12 | time_intervals: 13 | - times: 14 | - start_time: "00:00" 15 | end_time: "06:00" 16 | weekdays: ["saturday", "sunday"] 17 | days_of_month: ["1", "10"] 18 | location: "Asia/Shanghai" 19 | -------------------------------------------------------------------------------- /examples/notification-policy/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaNotificationPolicy 3 | metadata: 4 | name: grafananotificationpolicy-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | route: 10 | receiver: grafana-email-default 11 | group_by: 12 | - grafana_folder 13 | - alertname 14 | routes: 15 | - receiver: grafana-email-operations 16 | object_matchers: 17 | - - team 18 | - = 19 | - operations 20 | - receiver: grafana-email-security 21 | object_matchers: 22 | - - team 23 | - = 24 | - security 25 | -------------------------------------------------------------------------------- /examples/notification_template/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Notification template" 3 | linkTitle: "Notification template" 4 | --- 5 | 6 | Shows how to create a notification template. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/notification_template/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationTemplate 4 | metadata: 5 | name: notificationtemplate-sample 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | name: notificationtemplate-sample 11 | editable: false 12 | template: | 13 | {{ define "SlackAlert" }} 14 | [{{.Status}}] {{ .Labels.alertname }} 15 | {{ .Annotations.AlertValues }} 16 | {{ end }} 17 | 18 | {{ define "SlackAlertMessage" }} 19 | {{ if gt (len .Alerts.Firing) 0 }} 20 | {{ len .Alerts.Firing }} firing: 21 | {{ range .Alerts.Firing }} {{ template "SlackAlert" . }} {{ end }} 22 | {{ end }} 23 | {{ if gt (len .Alerts.Resolved) 0 }} 24 | {{ len .Alerts.Resolved }} resolved: 25 | {{ range .Alerts.Resolved }} {{ template "SlackAlert" . }} {{ end }} 26 | {{ end }} 27 | {{ end }} 28 | -------------------------------------------------------------------------------- /examples/notifications-full/contact-points.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaContactPoint 4 | metadata: 5 | name: operations-team 6 | spec: 7 | name: operations-team 8 | type: "email" 9 | instanceSelector: 10 | matchLabels: 11 | instance: my-grafana-stack 12 | settings: 13 | addresses: 'operations@example.com' 14 | --- 15 | apiVersion: grafana.integreatly.org/v1beta1 16 | kind: GrafanaContactPoint 17 | metadata: 18 | name: security-team 19 | spec: 20 | name: security-team 21 | type: "email" 22 | instanceSelector: 23 | matchLabels: 24 | instance: my-grafana-stack 25 | settings: 26 | addresses: 'security@example.com' 27 | -------------------------------------------------------------------------------- /examples/notifications-full/folder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaFolder 3 | metadata: 4 | name: alerts-demo 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | instance: "my-grafana-stack" 9 | -------------------------------------------------------------------------------- /examples/notifications-full/notification-policy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationPolicy 4 | metadata: 5 | name: test 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | instance: "my-grafana-stack" 10 | route: 11 | receiver: grafana-default-email 12 | group_by: 13 | - grafana_folder 14 | - alertname 15 | routes: 16 | - receiver: operations-team 17 | object_matchers: 18 | - - team 19 | - = 20 | - operations 21 | routes: 22 | - object_matchers: 23 | - - severity 24 | - = 25 | - high 26 | repeat_interval: 5m 27 | - receiver: security-team 28 | object_matchers: 29 | - - team 30 | - = 31 | - security 32 | -------------------------------------------------------------------------------- /examples/oauth_proxy/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Oauth proxy" 3 | linkTitle: "Oauth proxy" 4 | --- 5 | An example that uses an auth proxy to enforce authentication. 6 | 7 | NOTE: this example currently only works on OpenShift 8 | 9 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/openshift/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OpenShift example" 3 | linkTitle: "OpenShift example" 4 | --- 5 | 6 | A basic deployment that makes use of OpenShift routes. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/openshift/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | # An empty route spec is enough to signal the creation of a default 9 | # route to the operator. This can also be used to override defaults 10 | # in the route spec. 11 | route: 12 | spec: {} 13 | config: 14 | log: 15 | mode: "console" 16 | auth: 17 | disable_login_form: "false" 18 | security: 19 | admin_user: root 20 | admin_password: secret 21 | -------------------------------------------------------------------------------- /examples/persistent_volume/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Grafana deployment with a Persistent Volume" 3 | linkTitle: "Grafana deployment with a Persistent Volume" 4 | --- 5 | 6 | A basic deployment of Grafana with a persistent volume attached using existing Storage Class. 7 | 8 | {{< readfile file="resources.yaml" code="true" lang="yaml" >}} 9 | -------------------------------------------------------------------------------- /examples/persistent_volume/resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana 6 | labels: 7 | dashboards: "grafana" 8 | spec: 9 | persistentVolumeClaim: 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | resources: 14 | requests: 15 | storage: 10Gi 16 | # storageClassName: "" # Customize storage class if needed 17 | config: 18 | log: 19 | mode: "console" 20 | auth: 21 | disable_login_form: "false" 22 | security: 23 | admin_user: root 24 | admin_password: secret 25 | deployment: 26 | spec: 27 | template: 28 | spec: 29 | containers: 30 | - name: grafana 31 | readinessProbe: 32 | failureThreshold: 3 33 | volumes: 34 | - name: grafana-data 35 | persistentVolumeClaim: 36 | claimName: grafana-pvc 37 | strategy: 38 | type: Recreate 39 | -------------------------------------------------------------------------------- /examples/plugins/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Grafana plugins" 3 | linkTitle: "Grafana plugins" 4 | --- 5 | This won't work on external grafana instances. 6 | Due to the operator don't own and thus we can't set the environment variable that we use to make grafana install plugins for us. 7 | 8 | {{< readfile file="dashboard.yaml" code="true" lang="yaml" >}} 9 | {{< readfile file="datasource.yaml" code="true" lang="yaml" >}} 10 | -------------------------------------------------------------------------------- /examples/plugins/datasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: example-grafanadatasource 5 | spec: 6 | datasource: 7 | access: proxy 8 | type: prometheus 9 | jsonData: 10 | timeInterval: 5s 11 | tlsSkipVerify: true 12 | name: Prometheus 13 | url: http://prometheus-service:9090 14 | instanceSelector: 15 | matchLabels: 16 | dashboards: grafana 17 | plugins: 18 | - name: grafana-clock-panel 19 | version: 1.3.0 20 | -------------------------------------------------------------------------------- /hack/add-openshift-annotations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OPENSHIFT_VERSIONS="\"v4.11\"" 4 | 5 | { 6 | echo "" 7 | echo " # OpenShift specific annotations" 8 | echo " com.redhat.openshift.versions: $OPENSHIFT_VERSIONS" 9 | } >> bundle/metadata/annotations.yaml 10 | 11 | echo "LABEL com.redhat.openshift.versions=$OPENSHIFT_VERSIONS" >> bundle.Dockerfile 12 | -------------------------------------------------------------------------------- /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 | */ 16 | -------------------------------------------------------------------------------- /hack/kind/resources/cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: InitConfiguration 8 | nodeRegistration: 9 | kubeletExtraArgs: 10 | node-labels: "ingress-ready=true" 11 | extraPortMappings: 12 | - containerPort: 80 13 | hostPort: 80 14 | protocol: TCP 15 | - containerPort: 443 16 | hostPort: 443 17 | protocol: TCP 18 | -------------------------------------------------------------------------------- /hack/kind/resources/crd-ns/grafana-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDashboard 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | folder: my-folder-name 9 | instanceSelector: 10 | matchLabels: 11 | dashboards: "grafana" 12 | json: | 13 | { 14 | "id": null, 15 | "title": "Simple Dashboard in CRD NS", 16 | "tags": [], 17 | "style": "dark", 18 | "timezone": "browser", 19 | "editable": true, 20 | "hideControls": false, 21 | "graphTooltip": 1, 22 | "panels": [], 23 | "time": { 24 | "from": "now-6h", 25 | "to": "now" 26 | }, 27 | "timepicker": { 28 | "time_options": [], 29 | "refresh_intervals": [] 30 | }, 31 | "templating": { 32 | "list": [] 33 | }, 34 | "annotations": { 35 | "list": [] 36 | }, 37 | "refresh": "5s", 38 | "schemaVersion": 17, 39 | "version": 0, 40 | "links": [] 41 | } 42 | -------------------------------------------------------------------------------- /hack/kind/resources/crd-ns/grafana-datasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: example-grafanadatasource 5 | spec: 6 | datasource: 7 | access: proxy 8 | type: prometheus 9 | jsonData: 10 | timeInterval: 5s 11 | tlsSkipVerify: true 12 | name: Prometheus-in-crd-nd 13 | url: http://prometheus-service:9090 14 | instanceSelector: 15 | matchLabels: 16 | dashboards: grafana 17 | plugins: 18 | - name: grafana-clock-panel 19 | version: 1.3.0 20 | -------------------------------------------------------------------------------- /hack/kind/resources/crd-ns/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - grafana-dashboard.yaml 3 | - grafana-datasource.yaml 4 | -------------------------------------------------------------------------------- /hack/kind/resources/default/deployment-thanos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: thanos 5 | labels: 6 | app: thanos 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: thanos 11 | template: 12 | metadata: 13 | labels: 14 | app: thanos 15 | spec: 16 | terminationGracePeriodSeconds: 3 17 | containers: 18 | - name: netcat 19 | image: alpine 20 | command: 21 | - sh 22 | - -c 23 | - | 24 | set -eu 25 | echo "Starting pod" 26 | while true; do echo -e 'HTTP/1.1 200 OK\n\n{"asdf":"date"}' | nc -l -p 8080; done 27 | ports: 28 | - containerPort: 8080 29 | name: http 30 | protocol: TCP 31 | --- 32 | apiVersion: v1 33 | kind: Service 34 | metadata: 35 | name: thanos 36 | spec: 37 | selector: 38 | app: thanos 39 | ports: 40 | - port: 8080 41 | name: http 42 | protocol: TCP 43 | targetPort: 8080 44 | -------------------------------------------------------------------------------- /hack/kind/resources/default/grafana-contactpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: grafanacontactpoint 6 | app.kubernetes.io/instance: grafanacontactpoint-sample 7 | app.kubernetes.io/part-of: grafana-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: grafana-operator 10 | name: grafanacontactpoint-sample 11 | spec: 12 | name: grafanacontactpoint-sample 13 | type: "email" 14 | instanceSelector: 15 | matchLabels: 16 | dashboards: "grafana" 17 | settings: 18 | addresses: "email@email.com" 19 | -------------------------------------------------------------------------------- /hack/kind/resources/default/grafana-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDashboard 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | folder: my-folder-name 9 | instanceSelector: 10 | matchLabels: 11 | dashboards: "grafana" 12 | json: | 13 | { 14 | "id": null, 15 | "title": "Simple Dashboard", 16 | "tags": [], 17 | "style": "dark", 18 | "timezone": "browser", 19 | "editable": true, 20 | "hideControls": false, 21 | "graphTooltip": 1, 22 | "panels": [], 23 | "time": { 24 | "from": "now-6h", 25 | "to": "now" 26 | }, 27 | "timepicker": { 28 | "time_options": [], 29 | "refresh_intervals": [] 30 | }, 31 | "templating": { 32 | "list": [] 33 | }, 34 | "annotations": { 35 | "list": [] 36 | }, 37 | "refresh": "5s", 38 | "schemaVersion": 17, 39 | "version": 0, 40 | "links": [] 41 | } 42 | -------------------------------------------------------------------------------- /hack/kind/resources/default/grafana-datasource-thanos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: thanos 5 | spec: 6 | datasource: 7 | access: proxy 8 | basicAuth: false 9 | isDefault: true 10 | jsonData: 11 | httpHeaderName1: Authorization 12 | timeInterval: 5s 13 | tlsSkipVerify: true 14 | name: Thanos 15 | secureJsonData: 16 | httpHeaderValue1: Bearer ${token} 17 | type: prometheus 18 | url: http://thanos.default.svc:8080 19 | instanceSelector: 20 | matchLabels: 21 | dashboards: grafana 22 | valuesFrom: 23 | - targetPath: "secureJsonData.httpHeaderValue1" 24 | valueFrom: 25 | secretKeyRef: 26 | name: grafana-instance-sa-token 27 | key: token 28 | -------------------------------------------------------------------------------- /hack/kind/resources/default/grafana-datasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: example-grafanadatasource 5 | spec: 6 | datasource: 7 | access: proxy 8 | type: prometheus 9 | jsonData: 10 | timeInterval: 5s 11 | tlsSkipVerify: true 12 | name: Prometheus 13 | url: http://prometheus-service:9090 14 | instanceSelector: 15 | matchLabels: 16 | dashboards: grafana 17 | plugins: 18 | - name: grafana-clock-panel 19 | version: 1.3.0 20 | -------------------------------------------------------------------------------- /hack/kind/resources/default/grafana.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | client: 9 | preferIngress: true 10 | config: 11 | log: 12 | mode: "console" 13 | feature_toggles: 14 | enable: nestedFolders 15 | auth: 16 | disable_login_form: "false" 17 | security: 18 | admin_user: root 19 | admin_password: secret 20 | ingress: 21 | spec: 22 | ingressClassName: nginx 23 | rules: 24 | - host: grafana.127.0.0.1.nip.io 25 | http: 26 | paths: 27 | - backend: 28 | service: 29 | name: grafana-service 30 | port: 31 | number: 3000 32 | path: / 33 | pathType: Prefix 34 | -------------------------------------------------------------------------------- /hack/kind/resources/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - grafana.yaml 3 | - grafana-dashboard.yaml 4 | - grafana-datasource.yaml 5 | - grafana-contactpoint.yaml 6 | - grafana-notification-policy.yaml 7 | 8 | - grafana-datasource-thanos.yaml 9 | - secret.yaml 10 | - serviceaccount.yaml 11 | - deployment-thanos.yaml 12 | -------------------------------------------------------------------------------- /hack/kind/resources/default/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: grafana-instance-sa-token 5 | annotations: 6 | kubernetes.io/service-account.name: grafana-instance-sa 7 | type: kubernetes.io/service-account-token 8 | -------------------------------------------------------------------------------- /hack/kind/resources/default/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: grafana-instance-sa 5 | secrets: 6 | - name: grafana-instance-sa-token 7 | -------------------------------------------------------------------------------- /hack/kind/start-kind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | KIND=${KIND:-kind} 4 | KIND_NODE_VERSION=${KIND_NODE_VERSION:-v1.33.0} 5 | KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-kind-grafana} 6 | KUBECONFIG=${KUBECONFIG:-~/.kube/kind-grafana-operator} 7 | set -eu 8 | 9 | # Find the script directory 10 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 11 | 12 | # Check if named kind cluster already exists 13 | if [[ "$($KIND get clusters)" =~ "$KIND_CLUSTER_NAME" ]]; then 14 | exit 0 15 | fi 16 | 17 | # Start kind cluster 18 | echo "" 19 | echo "###############################" 20 | echo "# 1. Start kind cluster #" 21 | echo "###############################" 22 | ${KIND} --kubeconfig "${KUBECONFIG}" create cluster \ 23 | --name "${KIND_CLUSTER_NAME}" \ 24 | --wait 120s \ 25 | --config="${SCRIPT_DIR}/resources/cluster.yaml" \ 26 | --image="kindest/node:${KIND_NODE_VERSION}" 27 | -------------------------------------------------------------------------------- /hugo/.gitignore: -------------------------------------------------------------------------------- 1 | /public 2 | resources/ 3 | node_modules/ 4 | .hugo_build.lock 5 | -------------------------------------------------------------------------------- /hugo/Makefile: -------------------------------------------------------------------------------- 1 | # Setting SHELL to bash allows bash commands to be executed by recipes. 2 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 3 | SHELL = /usr/bin/env bash -o pipefail 4 | .SHELLFLAGS = -ec 5 | 6 | DOCS_URL ?= http://localhost:1313 7 | 8 | .PHONY: detect-broken-links 9 | detect-broken-links: 10 | go install github.com/raviqqe/muffet/v2@latest 11 | muffet --rate-limit=3 \ 12 | --max-connections=2 \ 13 | --buffer-size=8192 \ 14 | --exclude=github.com \ 15 | --exclude=grafana.127.0.0.1.nip.io \ 16 | $(DOCS_URL) 17 | -------------------------------------------------------------------------------- /hugo/README.md: -------------------------------------------------------------------------------- 1 | # Hugo 2 | 3 | ## Installing and running Hugo 4 | 5 | When installing hugo install the `extended` edition at version `v0.134.0`. You 6 | can find it on the [Hugo release 7 | page](https://github.com/gohugoio/hugo/releases/tag/v0.134.0) or build it with a 8 | working Go toolchain: 9 | 10 | ```shell 11 | CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@v0.134.0 12 | ``` 13 | 14 | To develop locally you need to also follow the docsy [pre-req](https://github.com/google/docsy#prerequisites). 15 | But in most cases it should work to just run 16 | 17 | ```shell 18 | cd hugo 19 | # Install npm dependencies 20 | npm ci 21 | # Download hugo module 22 | hugo mod get 23 | ``` 24 | 25 | To look at your changes with hot reload. 26 | 27 | ```shell 28 | hugo serve 29 | ``` 30 | 31 | ## Detecting broken links in documentation 32 | 33 | To detect broken links in documentation using [muffet](https://github.com/raviqqe/muffet): 34 | 35 | 1. Make sure that hugo is running and serving documentation as specified in the abbove steps. 36 | 2. Run the following command in the hugo directory: 37 | 38 | ```shell 39 | make detect-broken-links 40 | ``` 41 | -------------------------------------------------------------------------------- /hugo/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | -------------------------------------------------------------------------------- /hugo/assets/scss/_styles_project.scss: -------------------------------------------------------------------------------- 1 | @import 'td/code-dark' 2 | -------------------------------------------------------------------------------- /hugo/assets/scss/_variables_project.scss: -------------------------------------------------------------------------------- 1 | $primary: #fd7e14; 2 | -------------------------------------------------------------------------------- /hugo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grafana/grafana-operator/hugo 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/google/docsy v0.11.0 // indirect 7 | github.com/google/docsy/dependencies v0.7.2 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /hugo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/FortAwesome/Font-Awesome v0.0.0-20230327165841-0698449d50f2/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= 2 | github.com/FortAwesome/Font-Awesome v0.0.0-20240716171331-37eff7fa00de/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= 3 | github.com/google/docsy v0.11.0 h1:QnV40cc28QwS++kP9qINtrIv4hlASruhC/K3FqkHAmM= 4 | github.com/google/docsy v0.11.0/go.mod h1:hGGW0OjNuG5ZbH5JRtALY3yvN8ybbEP/v2iaK4bwOUI= 5 | github.com/google/docsy/dependencies v0.7.2 h1:+t5ufoADQAj4XneFphz4A+UU0ICAxmNaRHVWtMYXPSI= 6 | github.com/google/docsy/dependencies v0.7.2/go.mod h1:gihhs5gmgeO+wuoay4FwOzob+jYJVyQbNaQOh788lD4= 7 | github.com/twbs/bootstrap v5.2.3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= 8 | github.com/twbs/bootstrap v5.3.3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= 9 | -------------------------------------------------------------------------------- /hugo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "autoprefixer": "^10.4.13", 4 | "postcss": "^8.4.21", 5 | "postcss-cli": "^10.1.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /labs/audit/README.md: -------------------------------------------------------------------------------- 1 | # Audit stats lab 2 | 3 | The goal of the lab is to get stats for interactions with kube-apiserver. 4 | 5 | ## Technical requirements 6 | 7 | - bash 8 | - docker 9 | - jq 10 | - kind 11 | 12 | ## Instructions 13 | 14 | Create kind cluster: 15 | 16 | ```shell 17 | kind create cluster --config kind-config.yaml 18 | ``` 19 | 20 | Deploy the operator and related resources from the root of the git folder: 21 | 22 | ```shell 23 | export IMG= 24 | make install 25 | make deploy 26 | kubectl apply -f config/samples/grafana_v1beta1_grafana.yaml 27 | kubectl apply -f config/samples/grafana_v1beta1_grafanadashboard.yaml 28 | kubectl apply -f config/samples/grafana_v1beta1_grafanadatasource.yaml 29 | ``` 30 | 31 | NOTE: at the time of writing, kubebuilder lacks full definition for cluster-scope RBAC, you need to take care of that. 32 | 33 | Collect stats after a few minutes: 34 | 35 | ```shell 36 | ./collect-audit-stats.sh 37 | ``` 38 | 39 | Example: 40 | 41 | ```shell 42 | ./collect-audit-stats.sh 43 | 3 "get - /api/v1/namespaces/grafana-operator-system/configmaps/f75f3bba.integreatly.org" 44 | 6 "get - /apis/coordination.k8s.io/v1/namespaces/grafana-operator-system/leases/f75f3bba.integreatly.org" 45 | 3 "update - /api/v1/namespaces/grafana-operator-system/configmaps/f75f3bba.integreatly.org" 46 | 3 "update - /apis/coordination.k8s.io/v1/namespaces/grafana-operator-system/leases/f75f3bba.integreatly.org" 47 | ``` 48 | -------------------------------------------------------------------------------- /labs/audit/audit-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: audit.k8s.io/v1 2 | kind: Policy 3 | rules: 4 | - level: Metadata 5 | namespaces: ["grafana-operator-system"] 6 | -------------------------------------------------------------------------------- /labs/audit/collect-audit-stats.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | docker cp kind-control-plane:/var/log/kubernetes/kube-apiserver-audit.log . 5 | docker exec kind-control-plane truncate -s 0 /var/log/kubernetes/kube-apiserver-audit.log 6 | grep RequestReceived kube-apiserver-audit.log | grep "system:serviceaccount:grafana-operator-system:grafana-operator-controller-manager" | jq '. | { verb, requestURI } | join(" - ")' | sort | uniq -c 7 | -------------------------------------------------------------------------------- /labs/audit/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: ClusterConfiguration 8 | apiServer: 9 | # enable auditing flags on the API server 10 | extraArgs: 11 | audit-log-path: /var/log/kubernetes/kube-apiserver-audit.log 12 | audit-log-maxsize: "100" 13 | audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml 14 | # mount new files / directories on the control plane 15 | extraVolumes: 16 | - name: audit-policies 17 | hostPath: /etc/kubernetes/policies 18 | mountPath: /etc/kubernetes/policies 19 | readOnly: true 20 | pathType: "DirectoryOrCreate" 21 | - name: "audit-logs" 22 | hostPath: "/var/log/kubernetes" 23 | mountPath: "/var/log/kubernetes" 24 | readOnly: false 25 | pathType: DirectoryOrCreate 26 | # mount the local file on the control plane 27 | extraMounts: 28 | - hostPath: ./audit-policy.yaml 29 | containerPath: /etc/kubernetes/policies/audit-policy.yaml 30 | readOnly: true 31 | -------------------------------------------------------------------------------- /labs/benchmark/Makefile: -------------------------------------------------------------------------------- 1 | prom: 2 | docker run --network=host -p 9090:9090 -v $(shell pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus --config.file=/etc/prometheus/prometheus.yml 3 | -------------------------------------------------------------------------------- /labs/benchmark/dashboards/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/labs/benchmark/dashboards/.gitkeep -------------------------------------------------------------------------------- /labs/benchmark/datasources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/labs/benchmark/datasources/.gitkeep -------------------------------------------------------------------------------- /labs/benchmark/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 10s 3 | evaluation_interval: 30s 4 | scrape_configs: 5 | - job_name: "grafana" 6 | static_configs: 7 | - targets: ["localhost:8080"] 8 | -------------------------------------------------------------------------------- /media/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/grafana-operator/953272667a20556c0a6d4fcd63f403170a160040/media/slack.png -------------------------------------------------------------------------------- /tests/e2e/conditions/03-additional-invalid-spec.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationPolicy 4 | metadata: 5 | name: additional-testdata-policy 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | test: ($test.metadata.name) 10 | resyncPeriod: 3s 11 | route: 12 | receiver: testdata 13 | group_by: 14 | - grafana_folder 15 | - alertname 16 | routes: 17 | - receiver: grafana-default-email 18 | object_matchers: 19 | - - type 20 | - = 21 | - static 22 | routes: 23 | - receiver: grafana-default-email 24 | object_matchers: 25 | - - type 26 | - = 27 | - static_child 28 | routeSelector: 29 | matchLabels: 30 | dynamic: "child" 31 | --- 32 | apiVersion: grafana.integreatly.org/v1beta1 33 | kind: GrafanaDashboard 34 | metadata: 35 | name: no-resolvable-model 36 | spec: 37 | resyncPeriod: 3s 38 | instanceSelector: 39 | matchLabels: 40 | test: "($test.metadata.name)" 41 | -------------------------------------------------------------------------------- /tests/e2e/conditions/03-testdata-invalid-specs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Make folder reference itself as parent 3 | apiVersion: grafana.integreatly.org/v1beta1 4 | kind: GrafanaFolder 5 | metadata: 6 | name: testdata 7 | spec: 8 | parentFolderUID: testdata-uid 9 | --- 10 | # Reference non-existing secret 11 | apiVersion: grafana.integreatly.org/v1beta1 12 | kind: GrafanaContactPoint 13 | metadata: 14 | name: testdata 15 | spec: 16 | # Override settings 17 | settings: {} 18 | valuesFrom: 19 | - targetPath: addresses 20 | valueFrom: 21 | secretKeyRef: 22 | name: contact-mails 23 | key: alert-mails 24 | --- 25 | # Introduce a loop and trigger loop detected 26 | apiVersion: grafana.integreatly.org/v1beta1 27 | kind: GrafanaNotificationPolicyRoute 28 | metadata: 29 | name: team-c 30 | spec: 31 | routeSelector: 32 | matchLabels: 33 | team-b: "child" 34 | --- 35 | # Model is not valid JSON 36 | apiVersion: grafana.integreatly.org/v1beta1 37 | kind: GrafanaDashboard 38 | metadata: 39 | name: testdata 40 | spec: 41 | json: "{" 42 | --- 43 | # Reference non-existing secret 44 | apiVersion: grafana.integreatly.org/v1beta1 45 | kind: GrafanaDatasource 46 | metadata: 47 | name: testdata 48 | spec: 49 | valuesFrom: 50 | - targetPath: secureJsonData.httpHeaderValue1 51 | valueFrom: 52 | secretKeyRef: 53 | name: credentials 54 | key: "PROMETHEUS_TOKEN" 55 | 56 | # TODO Grafana when InvalidSpec is implemented for external Grafana admin secret 57 | # Reference non-existing secret 58 | -------------------------------------------------------------------------------- /tests/e2e/example-test/00-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | spec: 6 | version: 11.3.0 7 | status: 8 | (wildcard('http://grafana-service.*:3000', adminUrl || '')): true 9 | stage: complete 10 | stageStatus: success 11 | version: 11.3.0 12 | --- 13 | apiVersion: grafana.integreatly.org/v1beta1 14 | kind: Grafana 15 | metadata: 16 | name: external-grafana 17 | status: 18 | adminUrl: (join('',['http://grafana-internal-service.',$namespace,':3000'])) 19 | stage: complete 20 | stageStatus: success 21 | version: 11.3.0 22 | --- 23 | apiVersion: grafana.integreatly.org/v1beta1 24 | kind: Grafana 25 | metadata: 26 | name: grafana-versioned 27 | status: 28 | stage: complete 29 | stageStatus: success 30 | version: 10.3.5 31 | -------------------------------------------------------------------------------- /tests/e2e/example-test/00-create-external-grafana.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana-internal 6 | labels: 7 | dashboards: "grafana-internal" 8 | spec: 9 | client: 10 | preferIngress: false 11 | config: 12 | log: 13 | mode: "console" 14 | auth: 15 | disable_login_form: "false" 16 | security: 17 | admin_user: root 18 | admin_password: secret 19 | ingress: 20 | spec: 21 | ingressClassName: nginx 22 | rules: 23 | - host: test.io 24 | http: 25 | paths: 26 | - backend: 27 | service: 28 | name: grafana-internal-service 29 | port: 30 | number: 3000 31 | path: / 32 | pathType: Prefix 33 | --- 34 | # Use the same grafana instance that we just created, notice the ingress config 35 | apiVersion: grafana.integreatly.org/v1beta1 36 | kind: Grafana 37 | metadata: 38 | name: external-grafana 39 | labels: 40 | dashboards: "external-grafana" 41 | spec: 42 | external: 43 | url: (join('',['http://grafana-internal-service.',$namespace,':3000'])) 44 | adminPassword: 45 | name: grafana-internal-admin-credentials 46 | key: GF_SECURITY_ADMIN_PASSWORD 47 | adminUser: 48 | name: grafana-internal-admin-credentials 49 | key: GF_SECURITY_ADMIN_USER 50 | -------------------------------------------------------------------------------- /tests/e2e/example-test/00-create-grafana.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | client: 9 | preferIngress: false 10 | config: 11 | log: 12 | mode: "console" 13 | auth: 14 | disable_login_form: "false" 15 | security: 16 | admin_user: root 17 | admin_password: secret 18 | ## Required to test creation of Grafana-managed recording rules 19 | feature_toggles: 20 | grafanaManagedRecordingRules: "true" 21 | recording_rules: 22 | enabled: "true" 23 | url: http://prometheus:9090/api/prom/push 24 | -------------------------------------------------------------------------------- /tests/e2e/example-test/00-create-versioned-grafana.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana-versioned 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | client: 9 | preferIngress: false 10 | version: 10.3.5 11 | config: 12 | log: 13 | mode: "console" 14 | auth: 15 | disable_login_form: "false" 16 | security: 17 | admin_user: root 18 | admin_password: secret 19 | -------------------------------------------------------------------------------- /tests/e2e/example-test/01-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grafana-deployment 5 | status: 6 | availableReplicas: 1 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDatasource 10 | metadata: 11 | name: grafanadatasource-sample 12 | status: 13 | conditions: 14 | - type: DatasourceSynchronized 15 | status: "True" 16 | -------------------------------------------------------------------------------- /tests/e2e/example-test/01-datasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDatasource 3 | metadata: 4 | name: grafanadatasource-sample 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | plugins: 10 | - name: grafana-clock-panel 11 | version: 1.3.0 12 | datasource: 13 | name: prometheus 14 | type: prometheus 15 | access: proxy 16 | url: http://prometheus-service:9090 17 | isDefault: true 18 | jsonData: 19 | "tlsSkipVerify": true 20 | "timeInterval": "5s" 21 | -------------------------------------------------------------------------------- /tests/e2e/example-test/02-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafanadatasource-sample-datasource: W3sibmFtZSI6ImdyYWZhbmEtY2xvY2stcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjAifV0= 7 | -------------------------------------------------------------------------------- /tests/e2e/example-test/03-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-dashboard-dashboard: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | -------------------------------------------------------------------------------- /tests/e2e/example-test/03-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDashboard 3 | metadata: 4 | name: grafana-dashboard 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | plugins: 10 | - name: grafana-piechart-panel 11 | version: 1.3.9 12 | json: > 13 | { 14 | "id": null, 15 | "title": "Simple Dashboard", 16 | "tags": [], 17 | "style": "dark", 18 | "timezone": "browser", 19 | "editable": true, 20 | "hideControls": false, 21 | "graphTooltip": 1, 22 | "panels": [], 23 | "time": { 24 | "from": "now-6h", 25 | "to": "now" 26 | }, 27 | "timepicker": { 28 | "time_options": [], 29 | "refresh_intervals": [] 30 | }, 31 | "templating": { 32 | "list": [] 33 | }, 34 | "annotations": { 35 | "list": [] 36 | }, 37 | "refresh": "5s", 38 | "schemaVersion": 17, 39 | "version": 0, 40 | "links": [] 41 | } 42 | -------------------------------------------------------------------------------- /tests/e2e/example-test/04-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-dashboard-dashboard: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDashboard 10 | metadata: 11 | name: grafana-dashboard-inline-envs 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/04-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaDashboard 3 | metadata: 4 | name: grafana-dashboard-inline-envs 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | envs: 10 | - name: CUSTOM_RANGE_ENV 11 | value: "now - 12h" 12 | plugins: 13 | - name: grafana-piechart-panel 14 | version: 1.3.9 15 | jsonnet: > 16 | local myRange = std.extVar('CUSTOM_RANGE_ENV'); 17 | { 18 | id: null, 19 | title: "Simple Dashboard With Inline Envs", 20 | tags: [], 21 | style: "dark", 22 | timezone: "browser", 23 | editable: true, 24 | hideControls: false, 25 | graphTooltip: 1, 26 | panels: [], 27 | time: { 28 | from: myRange, 29 | to: "now" 30 | }, 31 | timepicker: { 32 | time_options: [], 33 | refresh_intervals: [] 34 | }, 35 | templating: { 36 | list: [] 37 | }, 38 | annotations: { 39 | list: [] 40 | }, 41 | refresh: "5s", 42 | schemaVersion: 17, 43 | version: 0, 44 | links: [] 45 | } 46 | -------------------------------------------------------------------------------- /tests/e2e/example-test/05-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-dashboard-dashboard: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDashboard 10 | metadata: 11 | name: grafana-dashboard-cm-envs 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/05-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-user-envs 5 | data: 6 | CUSTOM_RANGE_ENV: "now-1h" 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDashboard 10 | metadata: 11 | name: grafana-dashboard-cm-envs 12 | spec: 13 | instanceSelector: 14 | matchLabels: 15 | dashboards: "grafana" 16 | envFrom: 17 | - configMapKeyRef: 18 | name: grafana-user-envs 19 | key: "CUSTOM_RANGE_ENV" 20 | plugins: 21 | - name: grafana-piechart-panel 22 | version: 1.3.9 23 | jsonnet: > 24 | local myRange = std.extVar('CUSTOM_RANGE_ENV'); 25 | { 26 | id: null, 27 | title: "Simple Dashboard with CM envs", 28 | tags: [], 29 | style: "dark", 30 | timezone: "browser", 31 | editable: true, 32 | hideControls: false, 33 | graphTooltip: 1, 34 | panels: [], 35 | time: { 36 | from: myRange, 37 | to: "now" 38 | }, 39 | timepicker: { 40 | time_options: [], 41 | refresh_intervals: [] 42 | }, 43 | templating: { 44 | list: [] 45 | }, 46 | annotations: { 47 | list: [] 48 | }, 49 | refresh: "5s", 50 | schemaVersion: 17, 51 | version: 0, 52 | links: [] 53 | } 54 | -------------------------------------------------------------------------------- /tests/e2e/example-test/06-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-dashboard-dashboard: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDashboard 10 | metadata: 11 | name: grafana-dashboard-secret-envs 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/06-dashboard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: grafana-user-secrets 5 | stringData: 6 | CUSTOM_RANGE_ENV: "now-2h" 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaDashboard 10 | metadata: 11 | name: grafana-dashboard-secret-envs 12 | spec: 13 | instanceSelector: 14 | matchLabels: 15 | dashboards: "grafana" 16 | envFrom: 17 | - secretKeyRef: 18 | name: grafana-user-secrets 19 | key: "CUSTOM_RANGE_ENV" 20 | plugins: 21 | - name: grafana-piechart-panel 22 | version: 1.3.9 23 | jsonnet: > 24 | local myRange = std.extVar('CUSTOM_RANGE_ENV'); 25 | { 26 | id: null, 27 | title: "Simple Dashboard with Envs from secret", 28 | tags: [], 29 | style: "dark", 30 | timezone: "browser", 31 | editable: true, 32 | hideControls: false, 33 | graphTooltip: 1, 34 | panels: [], 35 | time: { 36 | from: myRange, 37 | to: "now" 38 | }, 39 | timepicker: { 40 | time_options: [], 41 | refresh_intervals: [] 42 | }, 43 | templating: { 44 | list: [] 45 | }, 46 | annotations: { 47 | list: [] 48 | }, 49 | refresh: "5s", 50 | schemaVersion: 17, 51 | version: 0, 52 | links: [] 53 | } 54 | -------------------------------------------------------------------------------- /tests/e2e/example-test/07-assert.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaDashboard 4 | metadata: 5 | name: grafana-dashboard-jsonnet-project 6 | --- 7 | apiVersion: grafana.integreatly.org/v1beta1 8 | kind: GrafanaDashboard 9 | metadata: 10 | name: jsonnet-env-vars 11 | status: 12 | conditions: 13 | - type: DashboardSynchronized 14 | status: "True" 15 | -------------------------------------------------------------------------------- /tests/e2e/example-test/08-alert-folder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaFolder 3 | metadata: 4 | name: test-folder-from-operator 5 | labels: 6 | folder: "test-folder" 7 | spec: 8 | resyncPeriod: 30s 9 | instanceSelector: 10 | matchLabels: 11 | dashboards: "grafana" 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/08-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaAlertRuleGroup 3 | metadata: 4 | name: test 5 | status: 6 | conditions: 7 | - type: AlertGroupSynchronized 8 | status: "True" 9 | -------------------------------------------------------------------------------- /tests/e2e/example-test/08-folder-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaFolder 3 | metadata: 4 | name: test-folder-from-operator 5 | status: 6 | conditions: 7 | - reason: ApplySuccessful 8 | status: "True" 9 | type: FolderSynchronized 10 | -------------------------------------------------------------------------------- /tests/e2e/example-test/09-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | name: test 5 | status: 6 | conditions: 7 | - type: ContactPointSynchronized 8 | status: "True" 9 | -------------------------------------------------------------------------------- /tests/e2e/example-test/09-contactpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | name: test 5 | spec: 6 | name: test 7 | type: "email" 8 | instanceSelector: 9 | matchLabels: 10 | dashboards: "grafana" 11 | settings: 12 | addresses: "email@email.com" 13 | -------------------------------------------------------------------------------- /tests/e2e/example-test/10-assert-contact-points.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | name: first-test 5 | status: 6 | conditions: 7 | - reason: ApplySuccessful 8 | status: "True" 9 | type: ContactPointSynchronized 10 | --- 11 | apiVersion: grafana.integreatly.org/v1beta1 12 | kind: GrafanaContactPoint 13 | metadata: 14 | name: second-test 15 | status: 16 | conditions: 17 | - reason: ApplySuccessful 18 | status: "True" 19 | type: ContactPointSynchronized 20 | -------------------------------------------------------------------------------- /tests/e2e/example-test/10-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaNotificationPolicy 3 | metadata: 4 | name: test 5 | status: 6 | conditions: 7 | - type: NotificationPolicySynchronized 8 | status: "True" 9 | -------------------------------------------------------------------------------- /tests/e2e/example-test/10-contact-points.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaContactPoint 3 | metadata: 4 | name: first-test 5 | spec: 6 | name: first-test 7 | type: "email" 8 | resyncPeriod: 30s 9 | instanceSelector: 10 | matchLabels: 11 | dashboards: "grafana" 12 | settings: 13 | addresses: "email@email.com" 14 | --- 15 | apiVersion: grafana.integreatly.org/v1beta1 16 | kind: GrafanaContactPoint 17 | metadata: 18 | name: second-test 19 | spec: 20 | name: second-test 21 | type: "email" 22 | resyncPeriod: 30s 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | settings: 27 | addresses: "email@email.com" 28 | -------------------------------------------------------------------------------- /tests/e2e/example-test/10-notification-policy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationPolicy 4 | metadata: 5 | name: test 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | route: 11 | receiver: grafana-default-email 12 | group_by: 13 | - grafana_folder 14 | - alertname 15 | routes: 16 | - receiver: first-test 17 | object_matchers: 18 | - - foo 19 | - = 20 | - bar 21 | routes: 22 | - receiver: second-test 23 | object_matchers: 24 | - - severity 25 | - = 26 | - critical 27 | -------------------------------------------------------------------------------- /tests/e2e/example-test/11-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana-tls 5 | spec: 6 | version: 11.3.0 7 | status: 8 | (wildcard('https://grafana-tls-service.*:3000', adminUrl || '')): true 9 | stage: complete 10 | stageStatus: success 11 | version: 11.3.0 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/12-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaNotificationTemplate 3 | metadata: 4 | name: test 5 | status: 6 | conditions: 7 | - type: NotificationTemplateSynchronized 8 | status: "True" 9 | -------------------------------------------------------------------------------- /tests/e2e/example-test/12-notification-template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaNotificationTemplate 4 | metadata: 5 | name: test 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | name: test 11 | template: | 12 | {{ define "SlackAlert" }} 13 | [{{.Status}}] {{ .Labels.alertname }} 14 | {{ .Annotations.AlertValues }} 15 | {{ end }} 16 | 17 | {{ define "SlackAlertMessage" }} 18 | {{ if gt (len .Alerts.Firing) 0 }} 19 | {{ len .Alerts.Firing }} firing: 20 | {{ range .Alerts.Firing }} {{ template "SlackAlert" . }} {{ end }} 21 | {{ end }} 22 | {{ if gt (len .Alerts.Resolved) 0 }} 23 | {{ len .Alerts.Resolved }} resolved: 24 | {{ range .Alerts.Resolved }} {{ template "SlackAlert" . }} {{ end }} 25 | {{ end }} 26 | {{ end }} 27 | 28 | {{ template "SlackAlertMessage" . }} 29 | -------------------------------------------------------------------------------- /tests/e2e/example-test/13-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaMuteTiming 3 | metadata: 4 | name: mutetiming-sample 5 | status: 6 | conditions: 7 | - type: MuteTimingSynchronized 8 | status: "True" 9 | -------------------------------------------------------------------------------- /tests/e2e/example-test/13-mute-timing.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: GrafanaMuteTiming 4 | metadata: 5 | name: mutetiming-sample 6 | spec: 7 | instanceSelector: 8 | matchLabels: 9 | dashboards: "grafana" 10 | name: mutetiming-sample 11 | editable: false 12 | time_intervals: 13 | - times: 14 | - start_time: "00:00" 15 | end_time: "06:00" 16 | weekdays: [saturday] 17 | days_of_month: ["1", "15"] 18 | location: Asia/Shanghai 19 | -------------------------------------------------------------------------------- /tests/e2e/example-test/14-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-library-panel-librarypanel: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | -------------------------------------------------------------------------------- /tests/e2e/example-test/14-library-panel.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaLibraryPanel 3 | metadata: 4 | name: grafana-library-panel 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | plugins: 10 | - name: grafana-piechart-panel 11 | version: 1.3.9 12 | json: > 13 | { 14 | "uid": "V--OrYHnz", 15 | "name": "API docs Example", 16 | "kind": 1, 17 | "type": "text", 18 | "description": "", 19 | "model": {}, 20 | "version": 1 21 | } 22 | -------------------------------------------------------------------------------- /tests/e2e/example-test/15-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grafana-plugins 5 | binaryData: 6 | grafana-library-panel-inline-envs-librarypanel: W3sibmFtZSI6ImdyYWZhbmEtcGllY2hhcnQtcGFuZWwiLCJ2ZXJzaW9uIjoiMS4zLjkifV0= 7 | --- 8 | apiVersion: grafana.integreatly.org/v1beta1 9 | kind: GrafanaLibraryPanel 10 | metadata: 11 | name: grafana-library-panel-inline-envs 12 | -------------------------------------------------------------------------------- /tests/e2e/example-test/15-library-panel.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: GrafanaLibraryPanel 3 | metadata: 4 | name: grafana-library-panel-inline-envs 5 | spec: 6 | instanceSelector: 7 | matchLabels: 8 | dashboards: "grafana" 9 | envs: 10 | - name: CUSTOM_RANGE_ENV 11 | value: "now - 12h" 12 | plugins: 13 | - name: grafana-piechart-panel 14 | version: 1.3.9 15 | jsonnet: > 16 | local myRange = std.extVar('CUSTOM_RANGE_ENV'); 17 | { 18 | model: {} 19 | } 20 | -------------------------------------------------------------------------------- /tests/e2e/examples/basic/assertions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grafana-deployment 5 | ownerReferences: 6 | - apiVersion: grafana.integreatly.org/v1beta1 7 | kind: Grafana 8 | name: grafana 9 | spec: {} 10 | --- 11 | apiVersion: v1 12 | kind: Service 13 | metadata: 14 | name: grafana-service 15 | ownerReferences: 16 | - apiVersion: grafana.integreatly.org/v1beta1 17 | kind: Grafana 18 | name: grafana 19 | spec: {} 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: grafana-alerting 25 | ownerReferences: 26 | - apiVersion: grafana.integreatly.org/v1beta1 27 | kind: Grafana 28 | name: grafana 29 | spec: {} 30 | --- 31 | apiVersion: v1 32 | kind: ConfigMap 33 | metadata: 34 | name: grafana-ini 35 | ownerReferences: 36 | - apiVersion: grafana.integreatly.org/v1beta1 37 | kind: Grafana 38 | name: grafana 39 | --- 40 | apiVersion: v1 41 | kind: ConfigMap 42 | metadata: 43 | name: grafana-plugins 44 | ownerReferences: 45 | - apiVersion: grafana.integreatly.org/v1beta1 46 | kind: Grafana 47 | name: grafana 48 | -------------------------------------------------------------------------------- /tests/e2e/examples/basic/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: basic 6 | spec: 7 | steps: 8 | - name: step-00 9 | try: 10 | - apply: 11 | file: resources.yaml 12 | - assert: 13 | file: assertions.yaml 14 | -------------------------------------------------------------------------------- /tests/e2e/examples/basic/resources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: grafana.integreatly.org/v1beta1 2 | kind: Grafana 3 | metadata: 4 | name: grafana 5 | labels: 6 | dashboards: "grafana" 7 | spec: 8 | config: 9 | log: 10 | mode: "console" 11 | auth: 12 | disable_login_form: "false" 13 | security: 14 | admin_user: root 15 | admin_password: secret 16 | --- 17 | apiVersion: grafana.integreatly.org/v1beta1 18 | kind: GrafanaDashboard 19 | metadata: 20 | name: grafanadashboard-sample 21 | spec: 22 | resyncPeriod: 30s 23 | instanceSelector: 24 | matchLabels: 25 | dashboards: "grafana" 26 | json: > 27 | { 28 | "id": null, 29 | "title": "Simple Dashboard", 30 | "tags": [], 31 | "style": "dark", 32 | "timezone": "browser", 33 | "editable": true, 34 | "hideControls": false, 35 | "graphTooltip": 1, 36 | "panels": [], 37 | "time": { 38 | "from": "now-6h", 39 | "to": "now" 40 | }, 41 | "timepicker": { 42 | "time_options": [], 43 | "refresh_intervals": [] 44 | }, 45 | "templating": { 46 | "list": [] 47 | }, 48 | "annotations": { 49 | "list": [] 50 | }, 51 | "refresh": "5s", 52 | "schemaVersion": 17, 53 | "version": 0, 54 | "links": [] 55 | } 56 | -------------------------------------------------------------------------------- /tests/e2e/examples/crossnamespace/chainsaw-test.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json 2 | apiVersion: chainsaw.kyverno.io/v1alpha1 3 | kind: Test 4 | metadata: 5 | name: crossnamespace 6 | spec: 7 | concurrent: false 8 | steps: 9 | - name: step-00 10 | try: 11 | - apply: 12 | template: true 13 | file: resources.yaml 14 | - assert: 15 | template: true 16 | file: assertions.yaml 17 | -------------------------------------------------------------------------------- /tests/e2e/examples/dashboard_immutable_uid/base-resources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: grafana.integreatly.org/v1beta1 3 | kind: Grafana 4 | metadata: 5 | name: grafana-immutable 6 | labels: 7 | immutable: "grafana" 8 | spec: 9 | config: 10 | log: 11 | mode: "console" 12 | auth: 13 | disable_login_form: "false" 14 | security: 15 | admin_user: root 16 | admin_password: secret 17 | --- 18 | apiVersion: grafana.integreatly.org/v1beta1 19 | kind: GrafanaDashboard 20 | metadata: 21 | name: dashboard-uid 22 | spec: 23 | instanceSelector: 24 | matchLabels: 25 | immutable: "grafana" 26 | resyncPeriod: 3s 27 | json: ($dashboardModel) 28 | --- 29 | apiVersion: grafana.integreatly.org/v1beta1 30 | kind: GrafanaDashboard 31 | metadata: 32 | name: metadata-uid 33 | spec: 34 | instanceSelector: 35 | matchLabels: 36 | immutable: "grafana" 37 | resyncPeriod: 3s 38 | json: ($dashboardModel) 39 | --- 40 | apiVersion: grafana.integreatly.org/v1beta1 41 | kind: GrafanaDashboard 42 | metadata: 43 | name: spec-uid 44 | spec: 45 | instanceSelector: 46 | matchLabels: 47 | immutable: "grafana" 48 | resyncPeriod: 3s 49 | json: ($dashboardModel) 50 | uid: SpecUID 51 | --------------------------------------------------------------------------------