From 78596a6756b58676837731b3297488c34b71c626 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 25 Aug 2021 15:11:22 +0200 Subject: [PATCH] Migrate to Wire for dependency injection (#32289) Fixes #30144 Co-authored-by: dsotirakis Co-authored-by: Marcus Efraimsson Co-authored-by: Ida Furjesova Co-authored-by: Jack Westbrook Co-authored-by: Will Browne Co-authored-by: Leon Sorokin Co-authored-by: Andrej Ocenas Co-authored-by: spinillos Co-authored-by: Karl Persson Co-authored-by: Leonard Gram --- .bingo/.gitignore | 12 + .bingo/README.md | 14 ++ .bingo/Variables.mk | 25 ++ .bingo/go.mod | 1 + .bingo/variables.env | 12 + .bingo/wire.mod | 5 + .bra.toml | 1 + .drone.yml | 49 ++-- .gitignore | 4 + Makefile | 9 +- contribute/architecture/backend/services.md | 141 ++++++++--- .../style-guides/documentation-style-guide.md | 21 +- go.mod | 1 + go.sum | 4 + .../grafana-ui/src/components/Forms/Form.mdx | 3 +- pkg/api/apierrors/folder.go | 44 ++++ pkg/api/common_test.go | 41 +--- pkg/api/dashboard_test.go | 7 +- pkg/api/datasources.go | 4 +- pkg/api/folder.go | 50 +--- pkg/api/folder_permission.go | 11 +- pkg/api/http_server.go | 182 +++++++++----- pkg/api/metrics.go | 6 +- pkg/api/routing/route_register.go | 64 ++--- pkg/bus/bus.go | 32 +-- pkg/cmd/grafana-cli/commands/commands.go | 19 +- pkg/cmd/grafana-server/main.go | 24 +- .../imguploader/azureblobuploader_test.go | 3 +- .../imguploader/imguploader_test.go | 10 +- pkg/components/imguploader/s3uploader_test.go | 2 +- pkg/expr/service.go | 4 +- pkg/expr/service_test.go | 22 +- pkg/extensions/main.go | 12 - .../http_client_provider.go | 3 +- pkg/infra/localcache/cache.go | 4 + pkg/infra/metrics/service.go | 15 +- pkg/infra/remotecache/remotecache.go | 33 ++- pkg/infra/remotecache/remotecache_test.go | 12 +- pkg/infra/remotecache/testing.go | 13 +- pkg/infra/serverlock/serverlock.go | 16 +- pkg/infra/tracing/tracing.go | 32 ++- pkg/infra/tracing/tracing_test.go | 18 +- pkg/infra/usagestats/service.go | 38 +-- pkg/login/social/social.go | 36 ++- pkg/middleware/middleware_test.go | 37 +-- pkg/middleware/quota.go | 3 + pkg/middleware/request_metrics.go | 2 +- pkg/models/jwt.go | 4 - pkg/models/user_token.go | 6 + pkg/plugins/backendplugin/ifaces.go | 2 +- pkg/plugins/backendplugin/manager/manager.go | 60 ++--- .../backendplugin/manager/manager_test.go | 7 +- pkg/plugins/frontend_plugin_test.go | 12 +- pkg/plugins/ifaces.go | 1 - pkg/plugins/manager/dashboard_import_test.go | 8 +- pkg/plugins/manager/dashboards_test.go | 24 +- pkg/plugins/manager/manager.go | 46 ++-- pkg/plugins/manager/manager_test.go | 48 ++-- pkg/plugins/plugincontext/plugincontext.go | 34 ++- pkg/plugins/plugindashboards/service.go | 35 +-- pkg/registry/di.go | 45 ---- pkg/registry/registry.go | 119 +-------- .../backgroundsvcs/background_services.go | 76 ++++++ pkg/server/server.go | 206 +++++----------- pkg/server/server_test.go | 93 +++---- pkg/server/test_env.go | 12 + pkg/server/wire.go | 159 ++++++++++++ pkg/server/wireexts_oss.go | 56 +++++ .../ossaccesscontrol/ossaccesscontrol.go | 27 ++- .../ossaccesscontrol/ossaccesscontrol_test.go | 24 +- pkg/services/accesscontrol/roles.go | 5 + pkg/services/alerting/alerting_usage_test.go | 5 - pkg/services/alerting/engine.go | 30 ++- .../alerting/engine_integration_test.go | 4 +- pkg/services/alerting/engine_test.go | 4 +- pkg/services/auth/auth_token.go | 31 ++- pkg/services/auth/jwt/auth.go | 42 ++-- pkg/services/auth/jwt/auth_test.go | 27 +-- pkg/services/cleanup/cleanup.go | 27 ++- .../contexthandler/auth_proxy_test.go | 46 +--- pkg/services/contexthandler/contexthandler.go | 34 ++- .../datasourceproxy/datasourceproxy.go | 38 +-- pkg/services/datasources/cache.go | 24 +- pkg/services/hooks/hooks.go | 9 +- .../libraryelements/libraryelements.go | 31 ++- .../libraryelements/libraryelements_test.go | 3 - pkg/services/librarypanels/librarypanels.go | 30 +-- .../librarypanels/librarypanels_test.go | 3 - pkg/services/licensing/oss.go | 21 +- pkg/services/live/database/tests/setup.go | 26 -- pkg/services/live/live.go | 190 +++++++-------- pkg/services/live/pushhttp/push.go | 23 +- pkg/services/login/authinfoservice/service.go | 28 +-- .../login/authinfoservice/user_auth_test.go | 7 +- .../login/authinfoservice/userprotection.go | 11 +- .../login/loginservice/loginservice.go | 30 +-- pkg/services/ngalert/api/api.go | 2 +- pkg/services/ngalert/api/api_ruler.go | 4 +- pkg/services/ngalert/api/lotex_prom.go | 2 +- pkg/services/ngalert/api/lotex_ruler.go | 2 +- pkg/services/ngalert/api/util.go | 2 +- pkg/services/ngalert/metrics/metrics.go | 26 +- pkg/services/ngalert/ngalert.go | 81 ++++--- .../ngalert/schedule/schedule_test.go | 18 +- .../ngalert/schedule/schedule_unit_test.go | 8 +- pkg/services/ngalert/state/manager_test.go | 5 +- .../ngalert/store/instance_database_test.go | 4 +- pkg/services/ngalert/tests/util.go | 50 +--- pkg/services/notifications/codes.go | 8 +- pkg/services/notifications/codes_test.go | 9 +- pkg/services/notifications/email.go | 4 +- pkg/services/notifications/mailer.go | 5 +- pkg/services/notifications/notifications.go | 48 ++-- .../notifications/notifications_test.go | 2 +- .../send_email_integration_test.go | 3 - pkg/services/oauthtoken/oauth_token.go | 13 +- pkg/services/provisioning/provisioning.go | 61 ++--- .../provisioning/provisioning_test.go | 2 +- pkg/services/quota/quota.go | 16 +- pkg/services/rendering/rendering.go | 50 ++-- pkg/services/rendering/rendering_test.go | 3 +- pkg/services/schemaloader/schemaloader.go | 33 ++- pkg/services/search/service.go | 28 +-- pkg/services/shorturls/short_url_service.go | 13 +- .../migrations/live_mig.go} | 4 +- .../sqlstore/migrations/migrations.go | 12 +- .../sqlstore/migrations/migrations_test.go | 5 +- .../sqlstore/migrator/mysql_dialect.go | 2 + .../sqlstore/migrator/postgres_dialect.go | 7 +- .../sqlstore/migrator/sqlite_dialect.go | 1 + .../sqlstore/searchstore/search_test.go | 12 +- pkg/services/sqlstore/sqlstore.go | 229 +++++++++++------- pkg/services/validations/oss.go | 8 +- pkg/setting/dynamic_settings_test.go | 2 +- pkg/setting/provider.go | 10 +- pkg/setting/setting.go | 75 ++++-- pkg/setting/setting_session_test.go | 13 +- pkg/setting/setting_test.go | 40 +-- .../alerting/api_admin_configuration_test.go | 19 +- .../api_alertmanager_configuration_test.go | 17 +- .../api/alerting/api_alertmanager_test.go | 25 +- .../alerting/api_available_channel_test.go | 3 +- .../alerting/api_notification_channel_test.go | 28 ++- pkg/tests/api/alerting/api_prometheus_test.go | 8 +- pkg/tests/api/alerting/api_ruler_test.go | 7 +- pkg/tests/api/metrics/api_metrics_test.go | 17 +- pkg/tests/api/plugins/api_install_test.go | 4 +- pkg/tests/testinfra/testinfra.go | 44 +--- pkg/tests/web/index_view_test.go | 6 +- pkg/tsdb/azuremonitor/azuremonitor.go | 69 +++--- pkg/tsdb/cloudmonitoring/cloudmonitoring.go | 27 +-- pkg/tsdb/cloudwatch/cloudwatch.go | 35 ++- pkg/tsdb/cloudwatch/logs.go | 17 +- pkg/tsdb/data_plugin_adapter.go | 2 +- pkg/tsdb/elasticsearch/elasticsearch.go | 28 +-- pkg/tsdb/graphite/graphite.go | 42 ++-- pkg/tsdb/influxdb/flux/flux.go | 6 +- pkg/tsdb/influxdb/influxdb.go | 55 ++--- pkg/tsdb/influxdb/influxdb_test.go | 2 +- pkg/tsdb/influxdb/response_parser.go | 6 +- pkg/tsdb/loki/loki.go | 48 ++-- pkg/tsdb/opentsdb/opentsdb.go | 64 ++--- pkg/tsdb/opentsdb/opentsdb_test.go | 5 +- pkg/tsdb/postgres/postgres.go | 22 +- pkg/tsdb/prometheus/prometheus.go | 50 ++-- pkg/tsdb/service.go | 60 ++--- pkg/tsdb/service_test.go | 26 +- pkg/tsdb/tempo/tempo.go | 57 ++--- pkg/tsdb/tempo/tempo_test.go | 3 +- pkg/tsdb/testdatasource/csv_data.go | 10 +- pkg/tsdb/testdatasource/csv_data_test.go | 4 +- pkg/tsdb/testdatasource/flight_path.go | 2 +- pkg/tsdb/testdatasource/flight_path_test.go | 4 +- pkg/tsdb/testdatasource/resource_handler.go | 10 +- pkg/tsdb/testdatasource/scenarios.go | 36 +-- pkg/tsdb/testdatasource/scenarios_test.go | 2 +- pkg/tsdb/testdatasource/testdata.go | 48 ++-- pkg/tsdb/testdatasource/usa_stats.go | 2 +- pkg/tsdb/testdatasource/usa_stats_test.go | 4 +- scripts/lib.star | 5 +- 180 files changed, 2384 insertions(+), 2401 deletions(-) create mode 100644 .bingo/.gitignore create mode 100644 .bingo/README.md create mode 100644 .bingo/Variables.mk create mode 100644 .bingo/go.mod create mode 100644 .bingo/variables.env create mode 100644 .bingo/wire.mod create mode 100644 pkg/api/apierrors/folder.go delete mode 100644 pkg/registry/di.go create mode 100644 pkg/server/backgroundsvcs/background_services.go create mode 100644 pkg/server/test_env.go create mode 100644 pkg/server/wire.go create mode 100644 pkg/server/wireexts_oss.go rename pkg/services/{live/database/migrations.go => sqlstore/migrations/live_mig.go} (93%) diff --git a/.bingo/.gitignore b/.bingo/.gitignore new file mode 100644 index 00000000000..4f2055b6e4c --- /dev/null +++ b/.bingo/.gitignore @@ -0,0 +1,12 @@ + +# Ignore everything +* + +# But not these files: +!.gitignore +!*.mod +!README.md +!Variables.mk +!variables.env + +*tmp.mod diff --git a/.bingo/README.md b/.bingo/README.md new file mode 100644 index 00000000000..7a5c2d4f6da --- /dev/null +++ b/.bingo/README.md @@ -0,0 +1,14 @@ +# Project Development Dependencies. + +This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo. + +* Run `bingo get` to install all tools having each own module file in this directory. +* Run `bingo get ` to install that have own module file in this directory. +* For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. +* For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. +* For go: Import `.bingo/variables.go` to for variable names. +* See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. + +## Requirements + +* Go 1.14+ diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk new file mode 100644 index 00000000000..856cb86263b --- /dev/null +++ b/.bingo/Variables.mk @@ -0,0 +1,25 @@ +# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.4.3. DO NOT EDIT. +# All tools are designed to be build inside $GOBIN. +BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +GOPATH ?= $(shell go env GOPATH) +GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin +GO ?= $(shell which go) + +# Below generated variables ensure that every time a tool under each variable is invoked, the correct version +# will be used; reinstalling only if needed. +# For example for wire variable: +# +# In your main Makefile (for non array binaries): +# +#include .bingo/Variables.mk # Assuming -dir was set to .bingo . +# +#command: $(WIRE) +# @echo "Running wire" +# @$(WIRE) +# +WIRE := $(GOBIN)/wire-v0.5.0 +$(WIRE): $(BINGO_DIR)/wire.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/wire-v0.5.0" + @cd $(BINGO_DIR) && $(GO) build -mod=mod -modfile=wire.mod -o=$(GOBIN)/wire-v0.5.0 "github.com/google/wire/cmd/wire" + diff --git a/.bingo/go.mod b/.bingo/go.mod new file mode 100644 index 00000000000..610249af0b0 --- /dev/null +++ b/.bingo/go.mod @@ -0,0 +1 @@ +module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files. \ No newline at end of file diff --git a/.bingo/variables.env b/.bingo/variables.env new file mode 100644 index 00000000000..c474b18411d --- /dev/null +++ b/.bingo/variables.env @@ -0,0 +1,12 @@ +# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.4.3. DO NOT EDIT. +# All tools are designed to be build inside $GOBIN. +# Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk. +GOBIN=${GOBIN:=$(go env GOBIN)} + +if [ -z "$GOBIN" ]; then + GOBIN="$(go env GOPATH)/bin" +fi + + +WIRE="${GOBIN}/wire-v0.5.0" + diff --git a/.bingo/wire.mod b/.bingo/wire.mod new file mode 100644 index 00000000000..fc39b30da16 --- /dev/null +++ b/.bingo/wire.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.16 + +require github.com/google/wire v0.5.0 // cmd/wire diff --git a/.bra.toml b/.bra.toml index 2e222e4549c..3a7357ce64a 100644 --- a/.bra.toml +++ b/.bra.toml @@ -1,5 +1,6 @@ [run] init_cmds = [ + ["make", "gen-go"], ["go", "run", "build.go", "-dev", "build-cli"], ["go", "run", "build.go", "-dev", "build-server"], ["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"] diff --git a/.drone.yml b/.drone.yml index 9f995b1ea6a..7b0bfcfd85a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,7 +17,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -45,6 +45,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition oss environment: CGO_ENABLED: 1 @@ -253,7 +254,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -293,6 +294,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition oss environment: CGO_ENABLED: 1 @@ -596,7 +598,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - name: build-windows-installer image: grafana/ci-wix:0.1.1 @@ -645,7 +647,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone environment: @@ -733,7 +735,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version ${DRONE_TAG} @@ -762,6 +764,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition oss environment: CGO_ENABLED: 1 @@ -1036,7 +1039,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - name: build-windows-installer image: grafana/ci-wix:0.1.1 @@ -1086,7 +1089,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -1134,6 +1137,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise environment: CGO_ENABLED: 1 @@ -1195,6 +1199,7 @@ steps: - name: lint-backend-enterprise2 image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise2 environment: CGO_ENABLED: 1 @@ -1506,7 +1511,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout ${DRONE_TAG} @@ -1574,7 +1579,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version ${DRONE_TAG} @@ -1682,7 +1687,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version v7.3.0-test @@ -1711,6 +1716,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition oss environment: CGO_ENABLED: 1 @@ -1974,7 +1980,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - name: build-windows-installer image: grafana/ci-wix:0.1.1 @@ -2024,7 +2030,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2072,6 +2078,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise environment: CGO_ENABLED: 1 @@ -2133,6 +2140,7 @@ steps: - name: lint-backend-enterprise2 image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise2 environment: CGO_ENABLED: 1 @@ -2438,7 +2446,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout main @@ -2506,7 +2514,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version v7.3.0-test @@ -2614,7 +2622,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -2642,6 +2650,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition oss environment: CGO_ENABLED: 1 @@ -2881,7 +2890,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - name: build-windows-installer image: grafana/ci-wix:0.1.1 @@ -2927,7 +2936,7 @@ steps: image: grafana/build-container:1.4.1 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2974,6 +2983,7 @@ steps: - name: lint-backend image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise environment: CGO_ENABLED: 1 @@ -3032,6 +3042,7 @@ steps: - name: lint-backend-enterprise2 image: grafana/build-container:1.4.1 commands: + - make gen-go - ./bin/grabpl lint-backend --edition enterprise2 environment: CGO_ENABLED: 1 @@ -3344,7 +3355,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.3.3/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.4.0/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout $$env:DRONE_BRANCH @@ -3488,6 +3499,6 @@ get: --- kind: signature -hmac: 136823e64c71ad2b4b447ef869ba3c896800f7df8111083665753a9db243dcb6 +hmac: 56e9f30fa716aff37a2d3db8534dc0ad489270905f740f3772499a837beaa85c ... diff --git a/.gitignore b/.gitignore index 85378169e21..134faa1e7ee 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ profile.cov /pkg/cmd/grafana-server/grafana-server /pkg/cmd/grafana-server/debug /pkg/extensions/* +/pkg/server/wireexts_enterprise.go !/pkg/extensions/main.go /public/app/extensions debug.test @@ -127,3 +128,6 @@ compilation-stats.json # auto generated frontend docs /docs/sources/packages_api + +# auto generated Go files +*_gen.go diff --git a/Makefile b/Makefile index 6f4e32ae1f0..605a3f7fd6a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,10 @@ ## ## For more information, refer to https://suva.sh/posts/well-documented-makefiles/ +WIRE_TAGS = "oss" + -include local/Makefile +include .bingo/Variables.mk .PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go golangci-lint test-go test-js test run run-frontend clean devenv devenv-down protobuf drone help @@ -27,7 +30,11 @@ node_modules: package.json yarn.lock ## Install node modules. ##@ Building -build-go: ## Build all Go binaries. +gen-go: $(WIRE) + @echo "generate go files" + $(WIRE) gen -tags $(WIRE_TAGS) ./pkg/server + +build-go: gen-go ## Build all Go binaries. @echo "build go files" $(GO) run build.go build diff --git a/contribute/architecture/backend/services.md b/contribute/architecture/backend/services.md index d428de6258c..d5d7accf427 100644 --- a/contribute/architecture/backend/services.md +++ b/contribute/architecture/backend/services.md @@ -2,71 +2,136 @@ A Grafana _service_ encapsulates and exposes application logic to the rest of the application, through a set of related operations. -Before a service can start communicating with the rest of Grafana, it needs to be registered in the _service registry_. - -The service registry keeps track of all available services during runtime. On start-up, Grafana uses the registry to build a dependency graph of services, a _service graph_. +Grafana uses [Wire](https://github.com/google/wire), which is a code generation tool that automates connecting components using [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). Dependencies between components are represented in Wire as function parameters, encouraging explicit initialization instead of global variables. Even though the services in Grafana do different things, they share a number of patterns. To better understand how a service works, let's build one from scratch! -## Create a service +Before a service can start communicating with the rest of Grafana, it needs to be registered with Wire, see `ProvideService` factory function/method in the service example below and how it's being referenced in the wire.go example below. -To start building a service: +When Wire is run it will inspect the parameters of `ProvideService` and make sure that all it's dependencies has been wired up and initialized properly. -- Create a new Go package `mysvc` in the [pkg/services](/pkg/services) directory. -- Create a `service.go` file inside your new directory. - -All services need to implement the [Service](https://godoc.org/github.com/grafana/grafana/pkg/registry#Service) interface: +**Service example:** ```go -type MyService struct { +package example + +// Service service is the service responsible for X, Y and Z. +type Service struct { + logger log.Logger + cfg *setting.Cfg + sqlStore *sqlstore.SQLStore } -func (s *MyService) Init() error { +// ProvideService provides Service as dependency for other services. +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore) (*Service, error) { + s := &Service{ + logger: log.New("service"), + cfg: cfg, + sqlStore: sqlStore, + } + + if s.IsDisabled() { + // skip certain initialization logic + return s, nil + } + + if err := s.init(); err != nil { + return nil, err + } + + return s, nil +} + +func (s *Service) init() error { + // additional initialization logic... return nil } -``` -The `Init` method is used to initialize and configure the service to make it ready to use. Services that return an error halt Grafana's startup process and cause the error to be logged as it exits. +// IsDisabled returns true if the service is disabled. +// +// Satisfies the registry.CanBeDisabled interface which will guarantee +// that Run() is not called if the service is disabled. +func (s *Service) IsDisabled() bool { + return !s.cfg.IsServiceEnabled() +} -## Register a service - -Every service needs to be registered with the application for it to be included in the service graph. - -To register a service, call the `registry.RegisterService` function in an `init` function within your package. - -```go -func init() { - registry.RegisterService(&MyService{}) +// Run runs the service in the background. +// +// Satisfies the registry.BackgroundService interface which will +// guarantee that the service can be registered as a background service. +func (s *Service) Run(ctx context.Context) error { + // background service logic... + <-ctx.Done() + return ctx.Err() } ``` -`init` functions are only run whenever a package is imported, so we also need to import the package in the application. In the `server.go` file under `pkg/server`, import the package we just created: +[wire.go](/pkg/server/wire.go) ```go -import _ "github.com/grafana/grafana/pkg/services/mysvc" -``` +// +build wireinject -## Dependencies +package server -Grafana uses the [inject](https://github.com/facebookgo/inject) package to inject dependencies during runtime. +import ( + "github.com/google/wire" + "github.com/grafana/grafana/pkg/example" + "github.com/grafana/grafana/pkg/services/sqlstore" +) -For example, to access the [bus](communication.md), add it to the `MyService` struct: +var wireBasicSet = wire.NewSet( + example.ProvideService, -```go -type MyService struct { - Bus bus.Bus `inject:""` +) + +var wireSet = wire.NewSet( + wireBasicSet, + sqlstore.ProvideService, +) + +var wireTestSet = wire.NewSet( + wireBasicSet, +) + +func Initialize(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions) (*Server, error) { + wire.Build(wireExtsSet) + return &Server{}, nil } -``` -You can also inject other services in the same way: - -```go -type MyService struct { - Service other.Service `inject:""` +func InitializeForTest(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions, sqlStore *sqlstore.SQLStore) (*Server, error) { + wire.Build(wireExtsTestSet) + return &Server{}, nil } + ``` -> **Note:** Any injected dependency needs to be an exported field. Any unexported fields result in a runtime error. +## Background services + +A background service is a service that runs in the background of the lifecycle between Grafana starts up and shutdown. If you want a service to be run in the background your Service should satisfy the `registry.BackgroundService` interface and add it as argument to the [ProvideBackgroundServiceRegistry](/pkg/server/backgroundsvcs/background_services.go) function and add it as argument to `NewBackgroundServiceRegistry` to register it as a background service. + +You can see an example implementation above of the Run method. + +## Disabled services + +If you want to guarantee that a background service is not run by Grafana when certain criteria is met/service is disabled your service should satisfy the `registry.CanBeDisabled` interface. When the service.IsDisabled method return false Grafana would not call the service.Run method. + +If you want to run certain initialization code if service is disabled or not, you need to handle this in the service factory method. + +You can see an example implementation above of the IsDisabled method and custom initialization code when service is disabled. + +## Run Wire / generate code + +When running `make run` it will call `make gen-go` on the first run. `gen-go` in turn will call the wire binary and generate the code in [wire_gen.go](/pkg/server/wire_gen.go). The wire binary is installed using [bingo](https://github.com/bwplotka/bingo) which will make sure to download and install all the tools needed, including the Wire binary at using a specific version. + +## OSS vs Enterprise + +Grafana OSS and Grafana Enterprise shares code and dependencies. Grafana Enterprise might need to override/extend certain OSS services. + +There's a [wireexts_oss.go](/pkg/server/wireexts_oss.go) that has the `wireinject` and `oss` build tags as requirements. Here services that might have other implementations, e.g. Grafana Enterprise, can be registered. + +Similarly, there's a wireexts_enterprise.go file in the Enterprise source code repository where other service implementations can be overridden/be registered. + +To extend oss background service create a specific background interface for that type and inject that type to [ProvideBackgroundServiceRegistry](/pkg/server/backgroundsvcs/background_services.go) instead of the concrete type. Then add a wire binding for that interface in [wireexts_oss.go](/pkg/server/wireexts_oss.go) and in the enterprise wireexts file. ## Methods diff --git a/contribute/style-guides/documentation-style-guide.md b/contribute/style-guides/documentation-style-guide.md index 4d07e8d3e2b..f96f2c1ecc5 100644 --- a/contribute/style-guides/documentation-style-guide.md +++ b/contribute/style-guides/documentation-style-guide.md @@ -4,13 +4,13 @@ This style guide applies to all documentation created for Grafana products. For information about how to write technical documentation, refer to the following resources: -* [Google Technical Writing courses](https://developers.google.com/tech-writing) -* [Divio documentation system](https://documentation.divio.com/) -* [Vue writing principles](https://v3.vuejs.org/guide/contributing/writing-guide.html#principles) +- [Google Technical Writing courses](https://developers.google.com/tech-writing) +- [Divio documentation system](https://documentation.divio.com/) +- [Vue writing principles](https://v3.vuejs.org/guide/contributing/writing-guide.html#principles) ## Contributing -The *Documentation style guide* is a living document. Add to it whenever a style decision is made or a question is answered regarding style, grammar, or word choice. +The _Documentation style guide_ is a living document. Add to it whenever a style decision is made or a question is answered regarding style, grammar, or word choice. ## Published guides @@ -46,7 +46,7 @@ Avoid _master_ or _slave_. ## Grafana-specific style -The following guidelines are specific to Grafana documentation. For the most part, these are *guidelines* are not rigid rules. If you have questions, then please ask in the #docs channel of Grafana Slack. +The following guidelines are specific to Grafana documentation. For the most part, these are _guidelines_ are not rigid rules. If you have questions, then please ask in the #docs channel of Grafana Slack. ### General @@ -108,6 +108,7 @@ However, sometimes we need to use headings as numbered steps. This is mostly in If that is the case, then use the following format for headings: ##### Step 1. Install the software + ##### Step 2. Run the software ### Images @@ -153,6 +154,7 @@ In general, "integration" is not capitalized. Only capitalize it if it is capita The first letter of the name of an integration is always capitalized, even if the original named source is lowercase. **Examples:** + - MySQL Integration - CockroachDB Integration - Etcd Integration @@ -216,16 +218,20 @@ Warnings tell the user not to do something. For example: - Do not assume everyone is using Linux. Make sure instructions include enough information for Windows and Mac users to successfully complete procedures. - Do not add `$` before commands. Make it easy for users to copy and paste commands. + - **Right:** `sudo yum install grafana` - **Wrong:** `$ sudo yum install grafana` - Include `sudo` before commands that require `sudo` to work. For terminal examples and Grafana configuration, use a `bash` code block: + ```bash sudo yum install grafana ``` + For HTTP request/response, use an `http` code block: + ```http GET /api/dashboards/id/1/permissions HTTP/1.1 Accept: application/json @@ -263,6 +269,7 @@ Two words if used as a verb, one word if used as a noun. Two words, not one. **Exceptions:** + - "datasource" used as an identifier - "datasource" in a URL - Use "data source" instead of "datasource" unless used as an identifier, in code, or as part of a URL. @@ -271,7 +278,8 @@ Two words, not one. #### display (verb) -*Display* is a transitive verb, which means it always needs a direct object. +_Display_ is a transitive verb, which means it always needs a direct object. + - Correct, active voice: Grafana displays your list of active alarms. - Correct, but passive voice: Your list of active alarms is displayed. - Incorrect: The list of active alarms displays. @@ -331,6 +339,7 @@ Two words, not one. **Incorrect:** webserver ### MS SQL Server + Always use "MS SQL" when referring to MS SQL Server application. Incorrect UI spellings will be corrected in a later version of Grafana. diff --git a/go.mod b/go.mod index e4e80a99102..c90a3a00f67 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/gobwas/glob v0.2.3 github.com/gofrs/uuid v4.0.0+incompatible github.com/golang/mock v1.5.0 + github.com/google/wire v0.5.0 github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 6519e029347..a7d7d915f1e 100644 --- a/go.sum +++ b/go.sum @@ -935,12 +935,15 @@ github.com/google/pprof v0.0.0-20210323184331-8eee2492667d/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= @@ -2504,6 +2507,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/packages/grafana-ui/src/components/Forms/Form.mdx b/packages/grafana-ui/src/components/Forms/Form.mdx index 3cfc01aaf10..088fc0f4fef 100644 --- a/packages/grafana-ui/src/components/Forms/Form.mdx +++ b/packages/grafana-ui/src/components/Forms/Form.mdx @@ -275,8 +275,7 @@ In a nutshell, the two most important changes are: - register method is no longer passed as a `ref`, but instead its result is spread onto the input component: ```jsx -- -+ ; +-() + ; ``` - `InputControl`'s `as` prop has been replaced with `render`, which has `field` and `fieldState` objects as arguments. `onChange`, `onBlur`, `value`, `name`, and `ref` are parts of `field`. diff --git a/pkg/api/apierrors/folder.go b/pkg/api/apierrors/folder.go new file mode 100644 index 00000000000..da6fd4fa124 --- /dev/null +++ b/pkg/api/apierrors/folder.go @@ -0,0 +1,44 @@ +package apierrors + +import ( + "errors" + + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/util" +) + +// ToFolderErrorResponse returns a different response status according to the folder error type +func ToFolderErrorResponse(err error) response.Response { + var dashboardErr models.DashboardErr + if ok := errors.As(err, &dashboardErr); ok { + return response.Error(dashboardErr.StatusCode, err.Error(), err) + } + + if errors.Is(err, models.ErrFolderTitleEmpty) || + errors.Is(err, models.ErrDashboardTypeMismatch) || + errors.Is(err, models.ErrDashboardInvalidUid) || + errors.Is(err, models.ErrDashboardUidTooLong) || + errors.Is(err, models.ErrFolderContainsAlertRules) { + return response.Error(400, err.Error(), nil) + } + + if errors.Is(err, models.ErrFolderAccessDenied) { + return response.Error(403, "Access denied", err) + } + + if errors.Is(err, models.ErrFolderNotFound) { + return response.JSON(404, util.DynMap{"status": "not-found", "message": models.ErrFolderNotFound.Error()}) + } + + if errors.Is(err, models.ErrFolderSameNameExists) || + errors.Is(err, models.ErrFolderWithSameUIDExists) { + return response.Error(409, err.Error(), nil) + } + + if errors.Is(err, models.ErrFolderVersionMismatch) { + return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": models.ErrFolderVersionMismatch.Error()}) + } + + return response.Error(500, "Folder API error", err) +} diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 5145c2965c0..657a5935012 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -7,23 +7,23 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/auth" - "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" - "github.com/stretchr/testify/require" - "gopkg.in/macaron.v1" ) func loggedInUserScenario(t *testing.T, desc string, url string, fn scenarioFunc) { @@ -168,35 +168,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa userAuthTokenSvc := auth.NewFakeUserAuthTokenService() renderSvc := &fakeRenderService{} authJWTSvc := models.NewFakeJWTService() - ctxHdlr := &contexthandler.ContextHandler{} - - err := registry.BuildServiceGraph([]interface{}{cfg}, []*registry.Descriptor{ - { - Name: sqlstore.ServiceName, - Instance: sqlStore, - }, - { - Name: remotecache.ServiceName, - Instance: remoteCacheSvc, - }, - { - Name: auth.ServiceName, - Instance: userAuthTokenSvc, - }, - { - Name: rendering.ServiceName, - Instance: renderSvc, - }, - { - Name: jwt.ServiceName, - Instance: authJWTSvc, - }, - { - Name: contexthandler.ServiceName, - Instance: ctxHdlr, - }, - }) - require.NoError(t, err) + ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore) return ctxHdlr } @@ -236,6 +208,9 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin hs := &HTTPServer{ Cfg: cfg, RouteRegister: routing.NewRouteRegister(), + QuotaService: "a.QuotaService{ + Cfg: cfg, + }, AccessControl: accesscontrolmock.New().WithPermissions(permissions), } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 1f22c7c318b..53f9b680b52 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/quota" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -86,10 +87,8 @@ type testState struct { } func newTestLive(t *testing.T) *live.GrafanaLive { - gLive := live.NewGrafanaLive() - gLive.RouteRegister = routing.NewRouteRegister() - gLive.Cfg = &setting.Cfg{AppURL: "http://localhost:3000/"} - err := gLive.Init() + cfg := &setting.Cfg{AppURL: "http://localhost:3000/"} + gLive, err := live.ProvideService(nil, cfg, routing.NewRouteRegister(), nil, nil, nil, nil, sqlstore.InitTestDB(t)) require.NoError(t, err) return gLive } diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index b80654d59f9..6befa9e6b60 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -363,7 +363,7 @@ func GetDataSourceIdByName(c *models.ReqContext) response.Response { // /api/datasources/:id/resources/* func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) { datasourceID := c.ParamsInt64(":id") - ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) + ds, err := hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) if err != nil { if errors.Is(err, models.ErrDataSourceAccessDenied) { c.JsonApiErr(403, "Access denied to datasource", err) @@ -431,7 +431,7 @@ func convertModelToDtos(ds *models.DataSource) dtos.DataSource { func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Response { datasourceID := c.ParamsInt64("id") - ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) + ds, err := hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) if err != nil { if errors.Is(err, models.ErrDataSourceAccessDenied) { return response.Error(403, "Access denied to datasource", err) diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 68136cde55c..5fd1cf37628 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" @@ -19,7 +20,7 @@ func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response { folders, err := s.GetFolders(c.QueryInt64("limit"), c.QueryInt64("page")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } result := make([]dtos.FolderSearchHit, 0) @@ -39,7 +40,7 @@ func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response { s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) folder, err := s.GetFolderByUID(c.Params(":uid")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } g := guardian.New(folder.Id, c.OrgId, c.SignedInUser) @@ -50,7 +51,7 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response { s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) folder, err := s.GetFolderByID(c.ParamsInt64(":id")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } g := guardian.New(folder.Id, c.OrgId, c.SignedInUser) @@ -61,7 +62,7 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolder s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) folder, err := s.CreateFolder(cmd.Title, cmd.Uid) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } if hs.Cfg.EditorsCanAdmin { @@ -79,7 +80,7 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolder s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) err := s.UpdateFolder(c.Params(":uid"), &cmd) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser) @@ -93,12 +94,12 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // if errors.Is(err, libraryelements.ErrFolderHasConnectedLibraryElements) { return response.Error(403, "Folder could not be deleted because it contains library elements in use", err) } - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } f, err := s.DeleteFolder(c.Params(":uid"), c.QueryBool("forceDeleteRules")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } return response.JSON(200, util.DynMap{ @@ -138,38 +139,3 @@ func toFolderDto(ctx context.Context, g guardian.DashboardGuardian, folder *mode Version: folder.Version, } } - -// ToFolderErrorResponse returns a different response status according to the folder error type -func ToFolderErrorResponse(err error) response.Response { - var dashboardErr models.DashboardErr - if ok := errors.As(err, &dashboardErr); ok { - return response.Error(dashboardErr.StatusCode, err.Error(), err) - } - - if errors.Is(err, models.ErrFolderTitleEmpty) || - errors.Is(err, models.ErrDashboardTypeMismatch) || - errors.Is(err, models.ErrDashboardInvalidUid) || - errors.Is(err, models.ErrDashboardUidTooLong) || - errors.Is(err, models.ErrFolderContainsAlertRules) { - return response.Error(400, err.Error(), nil) - } - - if errors.Is(err, models.ErrFolderAccessDenied) { - return response.Error(403, "Access denied", err) - } - - if errors.Is(err, models.ErrFolderNotFound) { - return response.JSON(404, util.DynMap{"status": "not-found", "message": models.ErrFolderNotFound.Error()}) - } - - if errors.Is(err, models.ErrFolderSameNameExists) || - errors.Is(err, models.ErrFolderWithSameUIDExists) { - return response.Error(409, err.Error(), nil) - } - - if errors.Is(err, models.ErrFolderVersionMismatch) { - return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": models.ErrFolderVersionMismatch.Error()}) - } - - return response.Error(500, "Folder API error", err) -} diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index 4fdb6a69dba..9009da442c4 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" @@ -17,13 +18,13 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res folder, err := s.GetFolderByUID(c.Params(":uid")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } g := guardian.New(folder.Id, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { - return ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) } acl, err := g.GetAcl() @@ -64,17 +65,17 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos. s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) folder, err := s.GetFolderByUID(c.Params(":uid")) if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } g := guardian.New(folder.Id, c.OrgId, c.SignedInUser) canAdmin, err := g.CanAdmin() if err != nil { - return ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } if !canAdmin { - return ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) } var items []*models.DashboardAcl diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 58862e989cd..5f6c3e117e1 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -14,6 +14,10 @@ import ( "sync" "github.com/grafana/grafana/pkg/login/social" + "github.com/grafana/grafana/pkg/services/cleanup" + "github.com/grafana/grafana/pkg/services/ngalert" + "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/oauthtoken" @@ -24,15 +28,16 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" _ "github.com/grafana/grafana/pkg/plugins/backendplugin/manager" "github.com/grafana/grafana/pkg/plugins/plugincontext" - "github.com/grafana/grafana/pkg/plugins/plugindashboards" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/contexthandler" @@ -58,14 +63,6 @@ import ( "gopkg.in/macaron.v1" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "HTTPServer", - Instance: &HTTPServer{}, - InitPriority: registry.High, - }) -} - type HTTPServer struct { log log.Logger macaron *macaron.Macaron @@ -73,49 +70,128 @@ type HTTPServer struct { httpSrv *http.Server middlewares []macaron.Handler - PluginContextProvider *plugincontext.Provider `inject:""` - RouteRegister routing.RouteRegister `inject:""` - Bus bus.Bus `inject:""` - RenderService rendering.Service `inject:""` - Cfg *setting.Cfg `inject:""` - SettingsProvider setting.Provider `inject:""` - HooksService *hooks.HooksService `inject:""` - CacheService *localcache.CacheService `inject:""` - DatasourceCache datasources.CacheService `inject:""` - AuthTokenService models.UserTokenService `inject:""` - QuotaService *quota.QuotaService `inject:""` - RemoteCacheService *remotecache.RemoteCache `inject:""` - ProvisioningService provisioning.ProvisioningService `inject:""` - Login login.Service `inject:""` - License models.Licensing `inject:""` - AccessControl accesscontrol.AccessControl `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - DataProxy *datasourceproxy.DatasourceProxyService `inject:""` - PluginRequestValidator models.PluginRequestValidator `inject:""` - PluginManager plugins.Manager `inject:""` - SearchService *search.SearchService `inject:""` - ShortURLService shorturls.Service `inject:""` - Live *live.GrafanaLive `inject:""` - LivePushGateway *pushhttp.Gateway `inject:""` - ContextHandler *contexthandler.ContextHandler `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - DataService *tsdb.Service `inject:""` - PluginDashboardService *plugindashboards.Service `inject:""` - AlertEngine *alerting.AlertEngine `inject:""` - LoadSchemaService *schemaloader.SchemaLoaderService `inject:""` - LibraryPanelService librarypanels.Service `inject:""` - LibraryElementService libraryelements.Service `inject:""` - SocialService social.Service `inject:""` - OAuthTokenService *oauthtoken.Service `inject:""` + UsageStatsService usagestats.UsageStats + PluginContextProvider *plugincontext.Provider + RouteRegister routing.RouteRegister + Bus bus.Bus + RenderService rendering.Service + Cfg *setting.Cfg + SettingsProvider setting.Provider + HooksService *hooks.HooksService + CacheService *localcache.CacheService + DataSourceCache datasources.CacheService + AuthTokenService models.UserTokenService + QuotaService *quota.QuotaService + RemoteCacheService *remotecache.RemoteCache + ProvisioningService provisioning.ProvisioningService + Login login.Service + License models.Licensing + AccessControl accesscontrol.AccessControl + BackendPluginManager backendplugin.Manager + DataProxy *datasourceproxy.DataSourceProxyService + PluginRequestValidator models.PluginRequestValidator + PluginManager plugins.Manager + SearchService *search.SearchService + ShortURLService shorturls.Service + Live *live.GrafanaLive + LivePushGateway *pushhttp.Gateway + ContextHandler *contexthandler.ContextHandler + SQLStore *sqlstore.SQLStore + DataService *tsdb.Service + AlertEngine *alerting.AlertEngine + LoadSchemaService *schemaloader.SchemaLoaderService + AlertNG *ngalert.AlertNG + LibraryPanelService librarypanels.Service + LibraryElementService libraryelements.Service + notificationService *notifications.NotificationService + SocialService social.Service + OAuthTokenService oauthtoken.OAuthTokenService Listener net.Listener + cleanUpService *cleanup.CleanUpService + tracingService *tracing.TracingService + internalMetricsSvc *metrics.InternalMetricsService } -func (hs *HTTPServer) Init() error { - hs.log = log.New("http.server") +type ServerOptions struct { + Listener net.Listener +} - hs.macaron = hs.newMacaron() +func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routing.RouteRegister, bus bus.Bus, + renderService rendering.Service, licensing models.Licensing, hooksService *hooks.HooksService, + cacheService *localcache.CacheService, sqlStore *sqlstore.SQLStore, + dataService *tsdb.Service, alertEngine *alerting.AlertEngine, + usageStatsService *usagestats.UsageStatsService, pluginRequestValidator models.PluginRequestValidator, + pluginManager plugins.Manager, backendPM backendplugin.Manager, settingsProvider setting.Provider, + dataSourceCache datasources.CacheService, userTokenService models.UserTokenService, + cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, + remoteCache *remotecache.RemoteCache, provisioningService provisioning.ProvisioningService, + loginService login.Service, accessControl accesscontrol.AccessControl, + dataSourceProxy *datasourceproxy.DataSourceProxyService, searchService *search.SearchService, + live *live.GrafanaLive, livePushGateway *pushhttp.Gateway, plugCtxProvider *plugincontext.Provider, + contextHandler *contexthandler.ContextHandler, + schemaService *schemaloader.SchemaLoaderService, alertNG *ngalert.AlertNG, + libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service, + notificationService *notifications.NotificationService, tracingService *tracing.TracingService, + internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService, + socialService social.Service, oauthTokenService oauthtoken.OAuthTokenService) (*HTTPServer, error) { + macaron.Env = cfg.Env + m := macaron.New() + // automatically set HEAD for every GET + m.SetAutoHead(true) + + hs := &HTTPServer{ + Cfg: cfg, + RouteRegister: routeRegister, + Bus: bus, + RenderService: renderService, + License: licensing, + HooksService: hooksService, + CacheService: cacheService, + SQLStore: sqlStore, + DataService: dataService, + AlertEngine: alertEngine, + UsageStatsService: usageStatsService, + PluginRequestValidator: pluginRequestValidator, + PluginManager: pluginManager, + BackendPluginManager: backendPM, + SettingsProvider: settingsProvider, + DataSourceCache: dataSourceCache, + AuthTokenService: userTokenService, + cleanUpService: cleanUpService, + ShortURLService: shortURLService, + RemoteCacheService: remoteCache, + ProvisioningService: provisioningService, + Login: loginService, + AccessControl: accessControl, + DataProxy: dataSourceProxy, + SearchService: searchService, + Live: live, + LivePushGateway: livePushGateway, + PluginContextProvider: plugCtxProvider, + ContextHandler: contextHandler, + LoadSchemaService: schemaService, + AlertNG: alertNG, + LibraryPanelService: libraryPanelService, + LibraryElementService: libraryElementService, + QuotaService: quotaService, + notificationService: notificationService, + tracingService: tracingService, + internalMetricsSvc: internalMetricsSvc, + log: log.New("http.server"), + macaron: m, + Listener: opts.Listener, + SocialService: socialService, + OAuthTokenService: oauthTokenService, + } + if hs.Listener != nil { + hs.log.Debug("Using provided listener") + } hs.registerRoutes() - return hs.declareFixedRoles() + + if err := hs.declareFixedRoles(); err != nil { + return nil, err + } + return hs, nil } func (hs *HTTPServer) AddMiddleware(middleware macaron.Handler) { @@ -304,16 +380,6 @@ func (hs *HTTPServer) configureHttp2() error { return nil } -func (hs *HTTPServer) newMacaron() *macaron.Macaron { - macaron.Env = hs.Cfg.Env - m := macaron.New() - - // automatically set HEAD for every GET - m.SetAutoHead(true) - - return m -} - func (hs *HTTPServer) applyRoutes() { // start with middlewares & static routes hs.addMiddlewaresAndStaticRoutes() diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index b8a1a76c4b5..6a60543fa46 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -52,7 +52,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq // So only the datasource from the first query is needed. As all requests // should be the same data source. if i == 0 { - ds, err = hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) + ds, err = hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) if err != nil { return hs.handleGetDataSourceError(err, datasourceID) } @@ -122,7 +122,7 @@ func (hs *HTTPServer) handleExpressions(c *models.ReqContext, reqDTO dtos.Metric if name != expr.DatasourceName { // Expression requests have everything in one request, so need to check // all data source queries for possible permission / not found issues. - if _, err = hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache); err != nil { + if _, err = hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache); err != nil { return hs.handleGetDataSourceError(err, datasourceID) } } @@ -170,7 +170,7 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque return response.Error(http.StatusBadRequest, "Query missing datasourceId", nil) } - ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) + ds, err := hs.DataSourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) if err != nil { return hs.handleGetDataSourceError(err, datasourceId) } diff --git a/pkg/api/routing/route_register.go b/pkg/api/routing/route_register.go index 54523e44cbb..e699733071c 100644 --- a/pkg/api/routing/route_register.go +++ b/pkg/api/routing/route_register.go @@ -4,6 +4,8 @@ import ( "net/http" "strings" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/setting" "gopkg.in/macaron.v1" ) @@ -50,13 +52,17 @@ type RouteRegister interface { type RegisterNamedMiddleware func(name string) macaron.Handler +func ProvideRegister(cfg *setting.Cfg) *RouteRegisterImpl { + return NewRouteRegister(middleware.ProvideRouteOperationName, middleware.RequestMetrics(cfg)) +} + // NewRouteRegister creates a new RouteRegister with all middlewares sent as params -func NewRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister { - return &routeRegister{ - prefix: "", - routes: []route{}, - subfixHandlers: []macaron.Handler{}, - namedMiddleware: namedMiddleware, +func NewRouteRegister(namedMiddlewares ...RegisterNamedMiddleware) *RouteRegisterImpl { + return &RouteRegisterImpl{ + prefix: "", + routes: []route{}, + subfixHandlers: []macaron.Handler{}, + namedMiddlewares: namedMiddlewares, } } @@ -66,15 +72,15 @@ type route struct { handlers []macaron.Handler } -type routeRegister struct { - prefix string - subfixHandlers []macaron.Handler - namedMiddleware []RegisterNamedMiddleware - routes []route - groups []*routeRegister +type RouteRegisterImpl struct { + prefix string + subfixHandlers []macaron.Handler + namedMiddlewares []RegisterNamedMiddleware + routes []route + groups []*RouteRegisterImpl } -func (rr *routeRegister) Reset() { +func (rr *RouteRegisterImpl) Reset() { if rr == nil { return } @@ -84,7 +90,7 @@ func (rr *routeRegister) Reset() { rr.subfixHandlers = nil } -func (rr *routeRegister) Insert(pattern string, fn func(RouteRegister), handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Insert(pattern string, fn func(RouteRegister), handlers ...macaron.Handler) { // loop over all groups at current level for _, g := range rr.groups { // apply routes if the prefix matches the pattern @@ -100,19 +106,19 @@ func (rr *routeRegister) Insert(pattern string, fn func(RouteRegister), handlers } } -func (rr *routeRegister) Group(pattern string, fn func(rr RouteRegister), handlers ...macaron.Handler) { - group := &routeRegister{ - prefix: rr.prefix + pattern, - subfixHandlers: append(rr.subfixHandlers, handlers...), - routes: []route{}, - namedMiddleware: rr.namedMiddleware, +func (rr *RouteRegisterImpl) Group(pattern string, fn func(rr RouteRegister), handlers ...macaron.Handler) { + group := &RouteRegisterImpl{ + prefix: rr.prefix + pattern, + subfixHandlers: append(rr.subfixHandlers, handlers...), + routes: []route{}, + namedMiddlewares: rr.namedMiddlewares, } fn(group) rr.groups = append(rr.groups, group) } -func (rr *routeRegister) Register(router Router) { +func (rr *RouteRegisterImpl) Register(router Router) { for _, r := range rr.routes { // GET requests have to be added to macaron routing using Get() // Otherwise HEAD requests will not be allowed. @@ -129,11 +135,11 @@ func (rr *routeRegister) Register(router Router) { } } -func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) route(pattern, method string, handlers ...macaron.Handler) { h := make([]macaron.Handler, 0) fullPattern := rr.prefix + pattern - for _, fn := range rr.namedMiddleware { + for _, fn := range rr.namedMiddlewares { h = append(h, fn(fullPattern)) } @@ -153,26 +159,26 @@ func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handl }) } -func (rr *routeRegister) Get(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Get(pattern string, handlers ...macaron.Handler) { rr.route(pattern, http.MethodGet, handlers...) } -func (rr *routeRegister) Post(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Post(pattern string, handlers ...macaron.Handler) { rr.route(pattern, http.MethodPost, handlers...) } -func (rr *routeRegister) Delete(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Delete(pattern string, handlers ...macaron.Handler) { rr.route(pattern, http.MethodDelete, handlers...) } -func (rr *routeRegister) Put(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Put(pattern string, handlers ...macaron.Handler) { rr.route(pattern, http.MethodPut, handlers...) } -func (rr *routeRegister) Patch(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Patch(pattern string, handlers ...macaron.Handler) { rr.route(pattern, http.MethodPatch, handlers...) } -func (rr *routeRegister) Any(pattern string, handlers ...macaron.Handler) { +func (rr *RouteRegisterImpl) Any(pattern string, handlers ...macaron.Handler) { rr.route(pattern, "*", handlers...) } diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go index 6dd8a745604..af4ae7c0396 100644 --- a/pkg/bus/bus.go +++ b/pkg/bus/bus.go @@ -47,11 +47,6 @@ type Bus interface { SetTransactionManager(tm TransactionManager) } -// InTransaction defines an in transaction function -func (b *InProcBus) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { - return b.txMng.InTransaction(ctx, fn) -} - // InProcBus defines the bus structure type InProcBus struct { logger log.Logger @@ -61,20 +56,27 @@ type InProcBus struct { txMng TransactionManager } +func ProvideBus() *InProcBus { + return globalBus +} + +// InTransaction defines an in transaction function +func (b *InProcBus) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { + return b.txMng.InTransaction(ctx, fn) +} + // temp stuff, not sure how to handle bus instance, and init yet var globalBus = New() // New initialize the bus -func New() Bus { - bus := &InProcBus{ - logger: log.New("bus"), +func New() *InProcBus { + return &InProcBus{ + logger: log.New("bus"), + handlers: make(map[string]HandlerFunc), + handlersWithCtx: make(map[string]HandlerFunc), + listeners: make(map[string][]HandlerFunc), + txMng: &noopTransactionManager{}, } - bus.handlers = make(map[string]HandlerFunc) - bus.handlersWithCtx = make(map[string]HandlerFunc) - bus.listeners = make(map[string][]HandlerFunc) - bus.txMng = &noopTransactionManager{} - - return bus } // Want to get rid of global bus @@ -234,7 +236,7 @@ func Publish(msg Msg) error { } func GetHandlerCtx(name string) HandlerFunc { - return globalBus.(*InProcBus).GetHandlerCtx(name) + return globalBus.GetHandlerCtx(name) } func ClearBusHandlers() { diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index 4175c119831..8bb298bc4fc 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -4,13 +4,13 @@ import ( "strings" "github.com/fatih/color" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/datamigrations" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" "github.com/urfave/cli/v2" @@ -21,14 +21,13 @@ func runDbCommand(command func(commandLine utils.CommandLine, sqlStore *sqlstore cmd := &utils.ContextCommandLine{Context: context} debug := cmd.Bool("debug") - cfg := setting.NewCfg() - configOptions := strings.Split(cmd.String("configOverrides"), " ") - if err := cfg.Load(&setting.CommandLineArgs{ + cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ Config: cmd.ConfigFile(), HomePath: cmd.HomePath(), Args: append(configOptions, cmd.Args().Slice()...), // tailing arguments have precedence over the options string - }); err != nil { + }) + if err != nil { return errutil.Wrap("failed to load configuration", err) } @@ -36,14 +35,12 @@ func runDbCommand(command func(commandLine utils.CommandLine, sqlStore *sqlstore cfg.LogConfigSources() } - engine := &sqlstore.SQLStore{} - engine.Cfg = cfg - engine.Bus = bus.GetBus() - if err := engine.Init(); err != nil { - return errutil.Wrap("failed to initialize SQL engine", err) + sqlStore, err := sqlstore.ProvideService(cfg, nil, bus.GetBus(), &migrations.OSSMigrations{}) + if err != nil { + return errutil.Wrap("failed to initialize SQL store", err) } - if err := command(cmd, engine); err != nil { + if err := command(cmd, sqlStore); err != nil { return err } diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 3ba31b35366..c01fe691cd6 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -16,6 +16,7 @@ import ( "syscall" "time" + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -23,19 +24,6 @@ import ( _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" - _ "github.com/grafana/grafana/pkg/tsdb/azuremonitor" - _ "github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" - _ "github.com/grafana/grafana/pkg/tsdb/cloudwatch" - _ "github.com/grafana/grafana/pkg/tsdb/elasticsearch" - _ "github.com/grafana/grafana/pkg/tsdb/graphite" - _ "github.com/grafana/grafana/pkg/tsdb/influxdb" - _ "github.com/grafana/grafana/pkg/tsdb/loki" - _ "github.com/grafana/grafana/pkg/tsdb/mysql" - _ "github.com/grafana/grafana/pkg/tsdb/opentsdb" - _ "github.com/grafana/grafana/pkg/tsdb/postgres" - _ "github.com/grafana/grafana/pkg/tsdb/prometheus" - _ "github.com/grafana/grafana/pkg/tsdb/tempo" - _ "github.com/grafana/grafana/pkg/tsdb/testdatasource" ) // The following variables cannot be constants, since they can be overridden through the -X link flag @@ -160,11 +148,13 @@ func executeServer(configFile, homePath, pidFile, packaging string, traceDiagnos metrics.SetBuildInformation(version, commit, buildBranch) - s, err := server.New(server.Config{ - ConfigFile: configFile, HomePath: homePath, PidFile: pidFile, - Version: version, Commit: commit, BuildBranch: buildBranch, - }) + s, err := server.Initialize(setting.CommandLineArgs{ + Config: configFile, HomePath: homePath, Args: flag.Args(), + }, server.Options{ + PidFile: pidFile, Version: version, Commit: commit, BuildBranch: buildBranch, + }, api.ServerOptions{}) if err != nil { + fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error()) return err } diff --git a/pkg/components/imguploader/azureblobuploader_test.go b/pkg/components/imguploader/azureblobuploader_test.go index 33c14b5d001..ca7a663ab0f 100644 --- a/pkg/components/imguploader/azureblobuploader_test.go +++ b/pkg/components/imguploader/azureblobuploader_test.go @@ -12,7 +12,8 @@ func TestUploadToAzureBlob(t *testing.T) { t.Run("[Integration test] for external_image_store.azure_blob", func(t *testing.T) { t.Skip("Skipping testing for external_image_store.azure_blob") cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) diff --git a/pkg/components/imguploader/imguploader_test.go b/pkg/components/imguploader/imguploader_test.go index 8c7f7beffc9..11d9dff4fe6 100644 --- a/pkg/components/imguploader/imguploader_test.go +++ b/pkg/components/imguploader/imguploader_test.go @@ -12,7 +12,7 @@ func TestImageUploaderFactory(t *testing.T) { t.Run("Can create image uploader for ", func(t *testing.T) { t.Run("S3ImageUploader config", func(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) @@ -85,7 +85,7 @@ func TestImageUploaderFactory(t *testing.T) { t.Run("Webdav uploader", func(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) @@ -113,7 +113,7 @@ func TestImageUploaderFactory(t *testing.T) { t.Run("GCS uploader", func(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) @@ -138,7 +138,7 @@ func TestImageUploaderFactory(t *testing.T) { t.Run("AzureBlobUploader config", func(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) @@ -168,7 +168,7 @@ func TestImageUploaderFactory(t *testing.T) { t.Run("Local uploader", func(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) diff --git a/pkg/components/imguploader/s3uploader_test.go b/pkg/components/imguploader/s3uploader_test.go index 3ff061da639..8d5bc4a64e7 100644 --- a/pkg/components/imguploader/s3uploader_test.go +++ b/pkg/components/imguploader/s3uploader_test.go @@ -12,7 +12,7 @@ func TestUploadToS3(t *testing.T) { t.Run("[Integration test] for external_image_store.s3", func(t *testing.T) { t.Skip("Skip test [Integration test] for external_image_store.s3") cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.NoError(t, err) diff --git a/pkg/expr/service.go b/pkg/expr/service.go index fbc44e8f8e2..1ec8f54bb8e 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -4,8 +4,8 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb" ) // DatasourceName is the string constant used as the datasource name in requests @@ -23,7 +23,7 @@ const DatasourceUID = "-100" // Service is service representation for expression handling. type Service struct { Cfg *setting.Cfg - DataService *tsdb.Service + DataService plugins.DataRequestHandler } func (s *Service) isDisabled() bool { diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 255b5f4d7be..7df9d3d7fb7 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -13,9 +13,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/plugins/backendplugin" - "github.com/grafana/grafana/pkg/plugins/manager" - "github.com/grafana/grafana/pkg/tsdb" "github.com/stretchr/testify/require" ) @@ -25,17 +22,10 @@ func TestService(t *testing.T) { data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), data.NewField("value", nil, []*float64{fp(2)})) - dataSvc := tsdb.NewService() - dataSvc.PluginManager = &manager.PluginManager{ - BackendPluginManager: fakeBackendPM{}, - } - s := Service{DataService: &dataSvc} me := &mockEndpoint{ Frames: []*data.Frame{dsDF}, } - s.DataService.RegisterQueryHandler("test", func(*models.DataSource) (plugins.DataPlugin, error) { - return me, nil - }) + s := Service{DataService: me} bus.AddHandler("test", func(query *models.GetDataSourceQuery) error { query.Result = &models.DataSource{Id: 1, OrgId: 1, Type: "test"} return nil @@ -98,9 +88,8 @@ type mockEndpoint struct { Frames data.Frames } -// nolint:staticcheck // plugins.DataQueryResult deprecated -func (me *mockEndpoint) DataQuery(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) ( - plugins.DataResponse, error) { +// nolint:staticcheck // plugins.DataQueryResponse deprecated +func (me *mockEndpoint) DataQuery(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { return plugins.DataResponse{ Results: map[string]plugins.DataQueryResult{ "A": { @@ -110,6 +99,7 @@ func (me *mockEndpoint) DataQuery(ctx context.Context, ds *models.DataSource, qu }, nil } -type fakeBackendPM struct { - backendplugin.Manager +// nolint:staticcheck // plugins.DataQueryResponse deprecated +func (me *mockEndpoint) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { + return me.DataQuery(ctx, ds, query) } diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 42a338bc3da..8b6410ededb 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -8,11 +8,6 @@ import ( _ "github.com/cortexproject/cortex/pkg/util" _ "github.com/crewjam/saml" _ "github.com/gobwas/glob" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" - "github.com/grafana/grafana/pkg/services/licensing" - "github.com/grafana/grafana/pkg/services/validations" - "github.com/grafana/grafana/pkg/setting" _ "github.com/grafana/loki/clients/pkg/promtail/client" _ "github.com/grafana/loki/pkg/logproto" _ "github.com/grpc-ecosystem/go-grpc-middleware" @@ -29,11 +24,4 @@ import ( _ "gopkg.in/square/go-jose.v2" ) -func init() { - registry.RegisterService(&licensing.OSSLicensingService{}) - registry.RegisterService(&validations.OSSPluginRequestValidator{}) - registry.RegisterService(&ossaccesscontrol.OSSAccessControlService{}) - registry.RegisterService(&setting.OSSImpl{}) -} - var IsEnterprise bool = false diff --git a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go index 83bd78d8ac0..1fe5179cf0a 100644 --- a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go +++ b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go @@ -6,7 +6,6 @@ import ( "time" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" - "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics/metricutil" "github.com/grafana/grafana/pkg/setting" @@ -16,7 +15,7 @@ import ( var newProviderFunc = sdkhttpclient.NewProvider // New creates a new HTTP client provider with pre-configured middlewares. -func New(cfg *setting.Cfg) httpclient.Provider { +func New(cfg *setting.Cfg) *sdkhttpclient.Provider { logger := log.New("httpclient") userAgent := fmt.Sprintf("Grafana/%s", cfg.BuildVersion) diff --git a/pkg/infra/localcache/cache.go b/pkg/infra/localcache/cache.go index f1d465405e8..c3396ee5799 100644 --- a/pkg/infra/localcache/cache.go +++ b/pkg/infra/localcache/cache.go @@ -11,6 +11,10 @@ type CacheService struct { *gocache.Cache } +func ProvideService() *CacheService { + return New(5*time.Minute, 10*time.Minute) +} + // New returns a new CacheService func New(defaultExpiration, cleanupInterval time.Duration) *CacheService { return &CacheService{ diff --git a/pkg/infra/metrics/service.go b/pkg/infra/metrics/service.go index eff4949261c..843be0551d3 100644 --- a/pkg/infra/metrics/service.go +++ b/pkg/infra/metrics/service.go @@ -5,7 +5,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics/graphitebridge" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) @@ -20,22 +19,24 @@ func (lw *logWrapper) Println(v ...interface{}) { } func init() { - registry.RegisterService(&InternalMetricsService{}) initMetricVars() initFrontendMetrics() } +func ProvideService(cfg *setting.Cfg) (*InternalMetricsService, error) { + s := &InternalMetricsService{ + Cfg: cfg, + } + return s, s.readSettings() +} + type InternalMetricsService struct { - Cfg *setting.Cfg `inject:""` + Cfg *setting.Cfg intervalSeconds int64 graphiteCfg *graphitebridge.Config } -func (im *InternalMetricsService) Init() error { - return im.readSettings() -} - func (im *InternalMetricsService) Run(ctx context.Context) error { // Start Graphite Bridge if im.graphiteCfg != nil { diff --git a/pkg/infra/remotecache/remotecache.go b/pkg/infra/remotecache/remotecache.go index 931df03acfe..8527c735529 100644 --- a/pkg/infra/remotecache/remotecache.go +++ b/pkg/infra/remotecache/remotecache.go @@ -27,13 +27,18 @@ const ( ServiceName = "RemoteCache" ) -func init() { - rc := &RemoteCache{} - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: rc, - InitPriority: registry.Medium, - }) +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore) (*RemoteCache, error) { + client, err := createClient(cfg.RemoteCacheOptions, sqlStore) + if err != nil { + return nil, err + } + s := &RemoteCache{ + SQLStore: sqlStore, + Cfg: cfg, + log: log.New("cache.remote"), + client: client, + } + return s, nil } // CacheStorage allows the caller to set, get and delete items in the cache. @@ -55,8 +60,8 @@ type CacheStorage interface { type RemoteCache struct { log log.Logger client CacheStorage - SQLStore *sqlstore.SQLStore `inject:""` - Cfg *setting.Cfg `inject:""` + SQLStore *sqlstore.SQLStore + Cfg *setting.Cfg } // Get reads object from Cache @@ -78,15 +83,7 @@ func (ds *RemoteCache) Delete(key string) error { return ds.client.Delete(key) } -// Init initializes the service -func (ds *RemoteCache) Init() error { - ds.log = log.New("cache.remote") - var err error - ds.client, err = createClient(ds.Cfg.RemoteCacheOptions, ds.SQLStore) - return err -} - -// Run start the backend processes for cache clients +// Run starts the backend processes for cache clients. func (ds *RemoteCache) Run(ctx context.Context) error { // create new interface if more clients need GC jobs backgroundjob, ok := ds.client.(registry.BackgroundService) diff --git a/pkg/infra/remotecache/remotecache_test.go b/pkg/infra/remotecache/remotecache_test.go index 305aa8da662..225171a9bab 100644 --- a/pkg/infra/remotecache/remotecache_test.go +++ b/pkg/infra/remotecache/remotecache_test.go @@ -22,14 +22,10 @@ func init() { func createTestClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore *sqlstore.SQLStore) CacheStorage { t.Helper() - dc := &RemoteCache{ - SQLStore: sqlstore, - Cfg: &setting.Cfg{ - RemoteCacheOptions: opts, - }, + cfg := &setting.Cfg{ + RemoteCacheOptions: opts, } - - err := dc.Init() + dc, err := ProvideService(cfg, sqlstore) require.Nil(t, err, "Failed to init client for test") return dc @@ -37,7 +33,7 @@ func createTestClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore * func TestCachedBasedOnConfig(t *testing.T) { cfg := setting.NewCfg() - err := cfg.Load(&setting.CommandLineArgs{ + err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.Nil(t, err, "Failed to load config") diff --git a/pkg/infra/remotecache/testing.go b/pkg/infra/remotecache/testing.go index 1333ef1ea04..72319d2510e 100644 --- a/pkg/infra/remotecache/testing.go +++ b/pkg/infra/remotecache/testing.go @@ -17,16 +17,11 @@ func NewFakeStore(t *testing.T) *RemoteCache { ConnStr: "", } - SQLStore := sqlstore.InitTestDB(t) + sqlStore := sqlstore.InitTestDB(t) - dc := &RemoteCache{ - SQLStore: SQLStore, - Cfg: &setting.Cfg{ - RemoteCacheOptions: opts, - }, - } - - err := dc.Init() + dc, err := ProvideService(&setting.Cfg{ + RemoteCacheOptions: opts, + }, sqlStore) require.NoError(t, err, "Failed to init remote cache for test") return dc diff --git a/pkg/infra/serverlock/serverlock.go b/pkg/infra/serverlock/serverlock.go index 95b2027a81f..2eea513c851 100644 --- a/pkg/infra/serverlock/serverlock.go +++ b/pkg/infra/serverlock/serverlock.go @@ -5,27 +5,23 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" ) -func init() { - registry.RegisterService(&ServerLockService{}) +func ProvideService(sqlStore *sqlstore.SQLStore) *ServerLockService { + return &ServerLockService{ + SQLStore: sqlStore, + log: log.New("infra.lockservice"), + } } // ServerLockService allows servers in HA mode to claim a lock // and execute an function if the server was granted the lock type ServerLockService struct { - SQLStore *sqlstore.SQLStore `inject:""` + SQLStore *sqlstore.SQLStore log log.Logger } -// Init this service -func (sl *ServerLockService) Init() error { - sl.log = log.New("infra.lockservice") - return nil -} - // LockAndExecute try to create a lock for this server and only executes the // `fn` function when successful. This should not be used at low internal. But services // that needs to be run once every ex 10m. diff --git a/pkg/infra/tracing/tracing.go b/pkg/infra/tracing/tracing.go index c23f3c88ee8..7fdb3efd477 100644 --- a/pkg/infra/tracing/tracing.go +++ b/pkg/infra/tracing/tracing.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" opentracing "github.com/opentracing/opentracing-go" @@ -21,8 +20,20 @@ const ( envJaegerAgentPort = "JAEGER_AGENT_PORT" ) -func init() { - registry.RegisterService(&TracingService{}) +func ProvideService(cfg *setting.Cfg) (*TracingService, error) { + ts := &TracingService{ + Cfg: cfg, + log: log.New("tracing"), + } + if err := ts.parseSettings(); err != nil { + return nil, err + } + + if ts.enabled { + return ts, ts.initGlobalTracer() + } + + return ts, nil } type TracingService struct { @@ -37,20 +48,7 @@ type TracingService struct { zipkinPropagation bool disableSharedZipkinSpans bool - Cfg *setting.Cfg `inject:""` -} - -func (ts *TracingService) Init() error { - ts.log = log.New("tracing") - if err := ts.parseSettings(); err != nil { - return err - } - - if ts.enabled { - return ts.initGlobalTracer() - } - - return nil + Cfg *setting.Cfg } func (ts *TracingService) parseSettings() error { diff --git a/pkg/infra/tracing/tracing_test.go b/pkg/infra/tracing/tracing_test.go index 6b82004db71..fa97b5675bd 100644 --- a/pkg/infra/tracing/tracing_test.go +++ b/pkg/infra/tracing/tracing_test.go @@ -107,15 +107,16 @@ func TestInitJaegerCfg_EnabledViaHost(t *testing.T) { require.NoError(t, os.Unsetenv("JAEGER_AGENT_HOST")) }() - ts := &TracingService{Cfg: setting.NewCfg()} + cfg := setting.NewCfg() + ts := &TracingService{Cfg: cfg} _, err := ts.Cfg.Raw.NewSection("tracing.jaeger") require.NoError(t, err) require.NoError(t, ts.parseSettings()) - cfg, err := ts.initJaegerCfg() + jaegerCfg, err := ts.initJaegerCfg() require.NoError(t, err) - assert.False(t, cfg.Disabled) - assert.Equal(t, "example.com:6831", cfg.Reporter.LocalAgentHostPort) + assert.False(t, jaegerCfg.Disabled) + assert.Equal(t, "example.com:6831", jaegerCfg.Reporter.LocalAgentHostPort) } func TestInitJaegerCfg_EnabledViaHostPort(t *testing.T) { @@ -126,13 +127,14 @@ func TestInitJaegerCfg_EnabledViaHostPort(t *testing.T) { require.NoError(t, os.Unsetenv("JAEGER_AGENT_PORT")) }() - ts := &TracingService{Cfg: setting.NewCfg()} + cfg := setting.NewCfg() + ts := &TracingService{Cfg: cfg} _, err := ts.Cfg.Raw.NewSection("tracing.jaeger") require.NoError(t, err) require.NoError(t, ts.parseSettings()) - cfg, err := ts.initJaegerCfg() + jaegerCfg, err := ts.initJaegerCfg() require.NoError(t, err) - assert.False(t, cfg.Disabled) - assert.Equal(t, "example.com:12345", cfg.Reporter.LocalAgentHostPort) + assert.False(t, jaegerCfg.Disabled) + assert.Equal(t, "example.com:12345", jaegerCfg.Reporter.LocalAgentHostPort) } diff --git a/pkg/infra/usagestats/service.go b/pkg/infra/usagestats/service.go index 59a53ae5fea..e2171cc5733 100644 --- a/pkg/infra/usagestats/service.go +++ b/pkg/infra/usagestats/service.go @@ -9,20 +9,12 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) -var metricsLogger = log.New("metrics") - -func init() { - registry.RegisterService(&UsageStatsService{ - log: log.New("infra.usagestats"), - externalMetrics: make([]MetricsFunc, 0), - }) -} +var metricsLogger log.Logger = log.New("metrics") type UsageStats interface { GetUsageReport(context.Context) (UsageReport, error) @@ -33,12 +25,12 @@ type UsageStats interface { type MetricsFunc func() (map[string]interface{}, error) type UsageStatsService struct { - Cfg *setting.Cfg `inject:""` - Bus bus.Bus `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - AlertingUsageStats alerting.UsageStatsQuerier `inject:""` - PluginManager plugins.Manager `inject:""` - SocialService social.Service `inject:""` + Cfg *setting.Cfg + Bus bus.Bus + SQLStore *sqlstore.SQLStore + AlertingUsageStats alerting.UsageStatsQuerier + PluginManager plugins.Manager + SocialService social.Service log log.Logger @@ -47,9 +39,19 @@ type UsageStatsService struct { concurrentUserStatsCache memoConcurrentUserStats } -func (uss *UsageStatsService) Init() error { - uss.oauthProviders = uss.SocialService.GetOAuthProviders() - return nil +func ProvideService(cfg *setting.Cfg, bus bus.Bus, sqlStore *sqlstore.SQLStore, + alertingStats alerting.UsageStatsQuerier, pluginManager plugins.Manager, + socialService social.Service) *UsageStatsService { + s := &UsageStatsService{ + Cfg: cfg, + Bus: bus, + SQLStore: sqlStore, + AlertingUsageStats: alertingStats, + oauthProviders: socialService.GetOAuthProviders(), + PluginManager: pluginManager, + log: log.New("infra.usagestats"), + } + return s } func (uss *UsageStatsService) Run(ctx context.Context) error { diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 26f7050472a..2c07aa2384b 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -13,7 +13,6 @@ import ( "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -22,12 +21,8 @@ var ( logger = log.New("social") ) -func init() { - registry.RegisterService(&SocialService{}) -} - type SocialService struct { - Cfg *setting.Cfg `inject:""` + cfg *setting.Cfg socialMap map[string]SocialConnector oAuthProvider map[string]*OAuthInfo @@ -54,12 +49,15 @@ type OAuthInfo struct { TlsSkipVerify bool } -func (ss *SocialService) Init() error { - ss.oAuthProvider = make(map[string]*OAuthInfo) - ss.socialMap = make(map[string]SocialConnector) +func ProvideService(cfg *setting.Cfg) *SocialService { + ss := SocialService{ + cfg: cfg, + oAuthProvider: make(map[string]*OAuthInfo), + socialMap: make(map[string]SocialConnector), + } for _, name := range allOauthes { - sec := ss.Cfg.Raw.Section("auth." + name) + sec := cfg.Raw.Section("auth." + name) info := &OAuthInfo{ ClientId: sec.Key("client_id").String(), @@ -107,7 +105,7 @@ func (ss *SocialService) Init() error { TokenURL: info.TokenUrl, AuthStyle: oauth2.AuthStyleAutoDetect, }, - RedirectURL: strings.TrimSuffix(ss.Cfg.AppURL, "/") + SocialBaseUrl + name, + RedirectURL: strings.TrimSuffix(cfg.AppURL, "/") + SocialBaseUrl + name, Scopes: info.Scopes, } @@ -144,7 +142,7 @@ func (ss *SocialService) Init() error { ss.socialMap["azuread"] = &SocialAzureAD{ SocialBase: newSocialBase(name, &config, info), allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), - autoAssignOrgRole: ss.Cfg.AutoAssignOrgRole, + autoAssignOrgRole: cfg.AutoAssignOrgRole, } } @@ -182,22 +180,22 @@ func (ss *SocialService) Init() error { ClientID: info.ClientId, ClientSecret: info.ClientSecret, Endpoint: oauth2.Endpoint{ - AuthURL: ss.Cfg.GrafanaComURL + "/oauth2/authorize", - TokenURL: ss.Cfg.GrafanaComURL + "/api/oauth2/token", + AuthURL: cfg.GrafanaComURL + "/oauth2/authorize", + TokenURL: cfg.GrafanaComURL + "/api/oauth2/token", AuthStyle: oauth2.AuthStyleInHeader, }, - RedirectURL: strings.TrimSuffix(ss.Cfg.AppURL, "/") + SocialBaseUrl + name, + RedirectURL: strings.TrimSuffix(cfg.AppURL, "/") + SocialBaseUrl + name, Scopes: info.Scopes, } ss.socialMap[grafanaCom] = &SocialGrafanaCom{ SocialBase: newSocialBase(name, &config, info), - url: ss.Cfg.GrafanaComURL, + url: cfg.GrafanaComURL, allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()), } } } - return nil + return &ss } type BasicUserInfo struct { @@ -270,7 +268,7 @@ func newSocialBase(name string, config *oauth2.Config, info *OAuthInfo) *SocialB func (ss *SocialService) GetOAuthProviders() map[string]bool { result := map[string]bool{} - if ss.Cfg == nil || ss.Cfg.Raw == nil { + if ss.cfg == nil || ss.cfg.Raw == nil { return result } @@ -279,7 +277,7 @@ func (ss *SocialService) GetOAuthProviders() map[string]bool { name = grafanaCom } - sec := ss.Cfg.Raw.Section("auth." + name) + sec := ss.cfg.Raw.Section("auth." + name) if sec == nil { continue } diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index f7cbdf5102e..c477ff3be91 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -23,9 +23,7 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/auth" - "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler/authproxy" "github.com/grafana/grafana/pkg/services/rendering" @@ -669,47 +667,18 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa t.Helper() sqlStore := sqlstore.InitTestDB(t) - remoteCacheSvc := &remotecache.RemoteCache{} if cfg == nil { cfg = setting.NewCfg() } cfg.RemoteCacheOptions = &setting.RemoteCacheOptions{ Name: "database", } + remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore) + require.NoError(t, err) userAuthTokenSvc := auth.NewFakeUserAuthTokenService() renderSvc := &fakeRenderService{} authJWTSvc := models.NewFakeJWTService() - ctxHdlr := &contexthandler.ContextHandler{} - - err := registry.BuildServiceGraph([]interface{}{cfg}, []*registry.Descriptor{ - { - Name: sqlstore.ServiceName, - Instance: sqlStore, - }, - { - Name: remotecache.ServiceName, - Instance: remoteCacheSvc, - }, - { - Name: auth.ServiceName, - Instance: userAuthTokenSvc, - }, - { - Name: rendering.ServiceName, - Instance: renderSvc, - }, - { - Name: jwt.ServiceName, - Instance: authJWTSvc, - }, - { - Name: contexthandler.ServiceName, - Instance: ctxHdlr, - }, - }) - require.NoError(t, err) - - return ctxHdlr + return contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore) } type fakeRenderService struct { diff --git a/pkg/middleware/quota.go b/pkg/middleware/quota.go index 72bcc2e4e53..678fb5eeb17 100644 --- a/pkg/middleware/quota.go +++ b/pkg/middleware/quota.go @@ -11,6 +11,9 @@ import ( // Quota returns a function that returns a function used to call quotaservice based on target name func Quota(quotaService *quota.QuotaService) func(string) macaron.Handler { + if quotaService == nil { + panic("quotaService is nil") + } //https://open.spotify.com/track/7bZSoBEAEEUsGEuLOf94Jm?si=T1Tdju5qRSmmR0zph_6RBw fuuuuunky return func(target string) macaron.Handler { return func(c *models.ReqContext) { diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index b3f24f8a09b..50be4a3eda3 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -44,7 +44,7 @@ func init() { prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram) } -// RequestMetrics is a middleware handler that instruments the request +// RequestMetrics is a middleware handler that instruments the request. func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler { return func(handler string) macaron.Handler { return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { diff --git a/pkg/models/jwt.go b/pkg/models/jwt.go index b8c884d9bf4..66cad7ae709 100644 --- a/pkg/models/jwt.go +++ b/pkg/models/jwt.go @@ -18,10 +18,6 @@ func (s *FakeJWTService) Verify(ctx context.Context, token string) (JWTClaims, e return s.VerifyProvider(ctx, token) } -func (s *FakeJWTService) Init() error { - return nil -} - func NewFakeJWTService() *FakeJWTService { return &FakeJWTService{ VerifyProvider: func(ctx context.Context, token string) (JWTClaims, error) { diff --git a/pkg/models/user_token.go b/pkg/models/user_token.go index f31c6da67e3..c27b669575c 100644 --- a/pkg/models/user_token.go +++ b/pkg/models/user_token.go @@ -4,6 +4,8 @@ import ( "context" "errors" "net" + + "github.com/grafana/grafana/pkg/registry" ) // Typed errors @@ -73,3 +75,7 @@ type UserTokenService interface { GetUserTokens(ctx context.Context, userId int64) ([]*UserToken, error) GetUserRevokedTokens(ctx context.Context, userId int64) ([]*UserToken, error) } + +type UserTokenBackgroundService interface { + registry.BackgroundService +} diff --git a/pkg/plugins/backendplugin/ifaces.go b/pkg/plugins/backendplugin/ifaces.go index be0e3b819b5..cf190153ec8 100644 --- a/pkg/plugins/backendplugin/ifaces.go +++ b/pkg/plugins/backendplugin/ifaces.go @@ -27,7 +27,7 @@ type Manager interface { // QueryData query data from a registered backend plugin. QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) // CallResource calls a plugin resource. - CallResource(pluginConfig backend.PluginContext, ctx *models.ReqContext, path string) + CallResource(pCtx backend.PluginContext, reqCtx *models.ReqContext, path string) // Get plugin by its ID. Get(pluginID string) (Plugin, bool) } diff --git a/pkg/plugins/backendplugin/manager/manager.go b/pkg/plugins/backendplugin/manager/manager.go index 4c18dba379a..9bbcb94ea39 100644 --- a/pkg/plugins/backendplugin/manager/manager.go +++ b/pkg/plugins/backendplugin/manager/manager.go @@ -19,40 +19,40 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/proxyutil" ) -func init() { - registry.RegisterServiceWithPriority(&manager{ - logger: log.New("plugins.backend"), - plugins: map[string]backendplugin.Plugin{}, - }, registry.MediumHigh) +func ProvideService(cfg *setting.Cfg, licensing models.Licensing, + pluginRequestValidator models.PluginRequestValidator) *Manager { + s := &Manager{ + Cfg: cfg, + License: licensing, + PluginRequestValidator: pluginRequestValidator, + logger: log.New("plugins.backend"), + plugins: map[string]backendplugin.Plugin{}, + } + return s } -type manager struct { - Cfg *setting.Cfg `inject:""` - License models.Licensing `inject:""` - PluginRequestValidator models.PluginRequestValidator `inject:""` +type Manager struct { + Cfg *setting.Cfg + License models.Licensing + PluginRequestValidator models.PluginRequestValidator pluginsMu sync.RWMutex plugins map[string]backendplugin.Plugin logger log.Logger } -func (m *manager) Init() error { - return nil -} - -func (m *manager) Run(ctx context.Context) error { +func (m *Manager) Run(ctx context.Context) error { <-ctx.Done() m.stop(ctx) return ctx.Err() } // Register registers a backend plugin -func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryFunc) error { +func (m *Manager) Register(pluginID string, factory backendplugin.PluginFactoryFunc) error { m.logger.Debug("Registering backend plugin", "pluginId", pluginID) m.pluginsMu.Lock() defer m.pluginsMu.Unlock() @@ -97,7 +97,7 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF } // RegisterAndStart registers and starts a backend plugin -func (m *manager) RegisterAndStart(ctx context.Context, pluginID string, factory backendplugin.PluginFactoryFunc) error { +func (m *Manager) RegisterAndStart(ctx context.Context, pluginID string, factory backendplugin.PluginFactoryFunc) error { err := m.Register(pluginID, factory) if err != nil { return err @@ -114,7 +114,7 @@ func (m *manager) RegisterAndStart(ctx context.Context, pluginID string, factory } // UnregisterAndStop unregisters and stops a backend plugin -func (m *manager) UnregisterAndStop(ctx context.Context, pluginID string) error { +func (m *Manager) UnregisterAndStop(ctx context.Context, pluginID string) error { m.logger.Debug("Unregistering backend plugin", "pluginId", pluginID) m.pluginsMu.Lock() defer m.pluginsMu.Unlock() @@ -139,13 +139,13 @@ func (m *manager) UnregisterAndStop(ctx context.Context, pluginID string) error return nil } -func (m *manager) IsRegistered(pluginID string) bool { +func (m *Manager) IsRegistered(pluginID string) bool { p, _ := m.Get(pluginID) return p != nil && !p.IsDecommissioned() } -func (m *manager) Get(pluginID string) (backendplugin.Plugin, bool) { +func (m *Manager) Get(pluginID string) (backendplugin.Plugin, bool) { m.pluginsMu.RLock() p, ok := m.plugins[pluginID] m.pluginsMu.RUnlock() @@ -157,7 +157,7 @@ func (m *manager) Get(pluginID string) (backendplugin.Plugin, bool) { return p, ok } -func (m *manager) getAWSEnvironmentVariables() []string { +func (m *Manager) getAWSEnvironmentVariables() []string { variables := []string{} if m.Cfg.AWSAssumeRoleEnabled { variables = append(variables, awsds.AssumeRoleEnabledEnvVarKeyName+"=true") @@ -169,7 +169,7 @@ func (m *manager) getAWSEnvironmentVariables() []string { return variables } -func (m *manager) getAzureEnvironmentVariables() []string { +func (m *Manager) getAzureEnvironmentVariables() []string { variables := []string{} if m.Cfg.Azure.Cloud != "" { variables = append(variables, "AZURE_CLOUD="+m.Cfg.Azure.Cloud) @@ -185,7 +185,7 @@ func (m *manager) getAzureEnvironmentVariables() []string { } // start starts a managed backend plugin -func (m *manager) start(ctx context.Context, p backendplugin.Plugin) { +func (m *Manager) start(ctx context.Context, p backendplugin.Plugin) { if !p.IsManaged() { return } @@ -196,7 +196,7 @@ func (m *manager) start(ctx context.Context, p backendplugin.Plugin) { } // StartPlugin starts a non-managed backend plugin -func (m *manager) StartPlugin(ctx context.Context, pluginID string) error { +func (m *Manager) StartPlugin(ctx context.Context, pluginID string) error { m.pluginsMu.RLock() p, registered := m.plugins[pluginID] m.pluginsMu.RUnlock() @@ -212,7 +212,7 @@ func (m *manager) StartPlugin(ctx context.Context, pluginID string) error { } // stop stops all managed backend plugins -func (m *manager) stop(ctx context.Context) { +func (m *Manager) stop(ctx context.Context) { m.pluginsMu.RLock() defer m.pluginsMu.RUnlock() var wg sync.WaitGroup @@ -231,7 +231,7 @@ func (m *manager) stop(ctx context.Context) { } // CollectMetrics collects metrics from a registered backend plugin. -func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error) { +func (m *Manager) CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error) { p, registered := m.Get(pluginID) if !registered { return nil, backendplugin.ErrPluginNotRegistered @@ -250,7 +250,7 @@ func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend } // CheckHealth checks the health of a registered backend plugin. -func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginContext) (*backend.CheckHealthResult, error) { +func (m *Manager) CheckHealth(ctx context.Context, pluginContext backend.PluginContext) (*backend.CheckHealthResult, error) { var dsURL string if pluginContext.DataSourceInstanceSettings != nil { dsURL = pluginContext.DataSourceInstanceSettings.URL @@ -290,7 +290,7 @@ func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginC return resp, nil } -func (m *manager) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (m *Manager) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { p, registered := m.Get(req.PluginContext.PluginID) if !registered { return nil, backendplugin.ErrPluginNotRegistered @@ -321,7 +321,7 @@ type keepCookiesJSONModel struct { KeepCookies []string `json:"keepCookies"` } -func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request, pCtx backend.PluginContext) error { +func (m *Manager) callResourceInternal(w http.ResponseWriter, req *http.Request, pCtx backend.PluginContext) error { p, registered := m.Get(pCtx.PluginID) if !registered { return backendplugin.ErrPluginNotRegistered @@ -382,7 +382,7 @@ func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request, } // CallResource calls a plugin resource. -func (m *manager) CallResource(pCtx backend.PluginContext, reqCtx *models.ReqContext, path string) { +func (m *Manager) CallResource(pCtx backend.PluginContext, reqCtx *models.ReqContext, path string) { var dsURL string if pCtx.DataSourceInstanceSettings != nil { dsURL = pCtx.DataSourceInstanceSettings.URL diff --git a/pkg/plugins/backendplugin/manager/manager_test.go b/pkg/plugins/backendplugin/manager/manager_test.go index 702f1e036b3..196164900e4 100644 --- a/pkg/plugins/backendplugin/manager/manager_test.go +++ b/pkg/plugins/backendplugin/manager/manager_test.go @@ -310,7 +310,7 @@ func TestManager(t *testing.T) { type managerScenarioCtx struct { cfg *setting.Cfg license *testLicensingService - manager *manager + manager *Manager factory backendplugin.PluginFactoryFunc plugin *testPlugin env []string @@ -331,7 +331,7 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m ctx := &managerScenarioCtx{ cfg: cfg, license: license, - manager: &manager{ + manager: &Manager{ Cfg: cfg, License: license, PluginRequestValidator: validator, @@ -340,9 +340,6 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m }, } - err := ctx.manager.Init() - require.NoError(t, err) - ctx.factory = func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) { ctx.plugin = &testPlugin{ pluginID: pluginID, diff --git a/pkg/plugins/frontend_plugin_test.go b/pkg/plugins/frontend_plugin_test.go index ee315af27b3..030c7c26ec0 100644 --- a/pkg/plugins/frontend_plugin_test.go +++ b/pkg/plugins/frontend_plugin_test.go @@ -4,12 +4,13 @@ import ( "testing" "github.com/grafana/grafana/pkg/setting" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" ) func TestFrontendPlugin(t *testing.T) { - Convey("When setting paths based on App on Windows", t, func() { - setting.StaticRootPath = "c:\\grafana\\public" + t.Run("When setting paths based on App on Windows", func(t *testing.T) { + cfg := setting.NewCfg() + cfg.StaticRootPath = "c:\\grafana\\public" fp := &FrontendPluginBase{ PluginBase: PluginBase{ @@ -26,9 +27,8 @@ func TestFrontendPlugin(t *testing.T) { }, }, } - cfg := setting.NewCfg() - fp.setPathsBasedOnApp(app, cfg) - So(fp.Module, ShouldEqual, "app/plugins/app/testdata/datasources/datasource/module") + fp.setPathsBasedOnApp(app, cfg) + require.Equal(t, "app/plugins/app/testdata/datasources/datasource/module", fp.Module) }) } diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index 783bbcfc289..f9759aebdda 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -28,7 +28,6 @@ type Manager interface { // AppCount gets the number of apps. AppCount() int // GetEnabledPlugins gets enabled plugins. - // GetEnabledPlugins gets enabled plugins. GetEnabledPlugins(orgID int64) (*EnabledPlugins, error) // GrafanaLatestVersion gets the latest Grafana version. GrafanaLatestVersion() string diff --git a/pkg/plugins/manager/dashboard_import_test.go b/pkg/plugins/manager/dashboard_import_test.go index 52f2d120f0d..e06e00983ba 100644 --- a/pkg/plugins/manager/dashboard_import_test.go +++ b/pkg/plugins/manager/dashboard_import_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" ) @@ -79,15 +80,16 @@ func pluginScenario(t *testing.T, desc string, fn func(*testing.T, *PluginManage t.Helper() t.Run("Given a plugin", func(t *testing.T) { - pm := newManager(&setting.Cfg{ + cfg := &setting.Cfg{ FeatureToggles: map[string]bool{}, PluginSettings: setting.PluginSettings{ "test-app": map[string]string{ "path": "testdata/test-app", }, }, - }) - err := pm.Init() + } + pm := newManager(cfg, &sqlstore.SQLStore{}, &fakeBackendPluginManager{}) + err := pm.init() require.NoError(t, err) t.Run(desc, func(t *testing.T) { diff --git a/pkg/plugins/manager/dashboards_test.go b/pkg/plugins/manager/dashboards_test.go index ccd780641bb..fb66c4d572d 100644 --- a/pkg/plugins/manager/dashboards_test.go +++ b/pkg/plugins/manager/dashboards_test.go @@ -3,24 +3,26 @@ package manager import ( "testing" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetPluginDashboards(t *testing.T) { - pm := newManager(&setting.Cfg{ + cfg := &setting.Cfg{ FeatureToggles: map[string]bool{}, PluginSettings: setting.PluginSettings{ "test-app": map[string]string{ "path": "testdata/test-app", }, }, - }) - err := pm.Init() + } + pm := newManager(cfg, &sqlstore.SQLStore{}, &fakeBackendPluginManager{}) + err := pm.init() require.NoError(t, err) bus.AddHandler("test", func(query *models.GetDashboardQuery) error { @@ -48,12 +50,12 @@ func TestGetPluginDashboards(t *testing.T) { dashboards, err := pm.GetPluginDashboards(1, "test-app") require.NoError(t, err) - assert.Len(t, dashboards, 2) - assert.Equal(t, "Nginx Connections", dashboards[0].Title) - assert.Equal(t, int64(25), dashboards[0].Revision) - assert.Equal(t, int64(22), dashboards[0].ImportedRevision) - assert.Equal(t, "db/nginx-connections", dashboards[0].ImportedUri) + require.Len(t, dashboards, 2) + require.Equal(t, "Nginx Connections", dashboards[0].Title) + require.Equal(t, int64(25), dashboards[0].Revision) + require.Equal(t, int64(22), dashboards[0].ImportedRevision) + require.Equal(t, "db/nginx-connections", dashboards[0].ImportedUri) - assert.Equal(t, int64(2), dashboards[1].Revision) - assert.Equal(t, int64(0), dashboards[1].ImportedRevision) + require.Equal(t, int64(2), dashboards[1].Revision) + require.Equal(t, int64(0), dashboards[1].ImportedRevision) } diff --git a/pkg/plugins/manager/manager.go b/pkg/plugins/manager/manager.go index f2cf70f8421..7a4699dc9f5 100644 --- a/pkg/plugins/manager/manager.go +++ b/pkg/plugins/manager/manager.go @@ -22,7 +22,6 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/manager/installer" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -52,9 +51,9 @@ type PluginScanner struct { } type PluginManager struct { - BackendPluginManager backendplugin.Manager `inject:""` - Cfg *setting.Cfg `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` + BackendPluginManager backendplugin.Manager + Cfg *setting.Cfg + SQLStore *sqlstore.SQLStore pluginInstaller plugins.PluginInstaller log log.Logger scanningErrors []error @@ -75,28 +74,30 @@ type PluginManager struct { pluginsMu sync.RWMutex } -func init() { - registry.Register(®istry.Descriptor{ - Name: "PluginManager", - Instance: newManager(nil), - InitPriority: registry.MediumHigh, - }) +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, backendPM backendplugin.Manager) (*PluginManager, error) { + pm := newManager(cfg, sqlStore, backendPM) + if err := pm.init(); err != nil { + return nil, err + } + return pm, nil } -func newManager(cfg *setting.Cfg) *PluginManager { +func newManager(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, backendPM backendplugin.Manager) *PluginManager { return &PluginManager{ - Cfg: cfg, - dataSources: map[string]*plugins.DataSourcePlugin{}, - plugins: map[string]*plugins.PluginBase{}, - panels: map[string]*plugins.PanelPlugin{}, - apps: map[string]*plugins.AppPlugin{}, + Cfg: cfg, + SQLStore: sqlStore, + BackendPluginManager: backendPM, + dataSources: map[string]*plugins.DataSourcePlugin{}, + plugins: map[string]*plugins.PluginBase{}, + panels: map[string]*plugins.PanelPlugin{}, + apps: map[string]*plugins.AppPlugin{}, + pluginScanningErrors: map[string]plugins.PluginError{}, + log: log.New("plugins"), } } -func (pm *PluginManager) Init() error { - pm.log = log.New("plugins") +func (pm *PluginManager) init() error { plog = log.New("plugins") - pm.pluginScanningErrors = map[string]plugins.PluginError{} pm.pluginInstaller = installer.New(false, pm.Cfg.BuildVersion, installerLog) pm.log.Info("Starting plugin search") @@ -119,12 +120,7 @@ func (pm *PluginManager) Init() error { } } - err = pm.initExternalPlugins() - if err != nil { - return err - } - - return nil + return pm.initExternalPlugins() } func (pm *PluginManager) initExternalPlugins() error { diff --git a/pkg/plugins/manager/manager_test.go b/pkg/plugins/manager/manager_test.go index d1a18af6dd5..64bef480aa8 100644 --- a/pkg/plugins/manager/manager_test.go +++ b/pkg/plugins/manager/manager_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,7 +36,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.BundledPluginsPath = bundledPluginsPath pm.Cfg.StaticRootPath = staticRootPath }) - err = pm.Init() + err = pm.init() require.NoError(t, err) assert.Empty(t, pm.scanningErrors) @@ -51,7 +52,7 @@ func TestPluginManager_Init(t *testing.T) { }, } }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Empty(t, pm.scanningErrors) @@ -68,7 +69,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/unsigned-datasource" pm.Cfg.Env = setting.Prod }) - err := pm.Init() + err := pm.init() require.NoError(t, err) const pluginID = "test" @@ -82,7 +83,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/unsigned-datasource" pm.Cfg.Env = setting.Dev }) - err := pm.Init() + err := pm.init() require.NoError(t, err) const pluginID = "test" @@ -99,7 +100,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/unsigned-panel" pm.Cfg.Env = setting.Prod }) - err := pm.Init() + err := pm.init() require.NoError(t, err) const pluginID = "test-panel" @@ -113,7 +114,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/unsigned-panel" pm.Cfg.Env = setting.Dev }) - err := pm.Init() + err := pm.init() require.NoError(t, err) pluginID := "test-panel" @@ -130,7 +131,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/unsigned-datasource" pm.Cfg.PluginsAllowUnsigned = []string{"test"} }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Empty(t, pm.scanningErrors) @@ -140,7 +141,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/invalid-v1-signature" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) const pluginID = "test" @@ -155,7 +156,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/lacking-files" pm.BackendPluginManager = fm }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors) @@ -167,7 +168,7 @@ func TestPluginManager_Init(t *testing.T) { pm.Cfg.PluginsPath = "testdata/behind-feature-flag" pm.BackendPluginManager = &fm }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Empty(t, pm.scanningErrors) @@ -178,7 +179,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/duplicate-plugins" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Len(t, pm.scanningErrors, 1) @@ -191,7 +192,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(manager *PluginManager) { manager.Cfg.PluginsPath = pluginsDir }) - err := pm.Init() + err := pm.init() require.NoError(t, err) require.Empty(t, pm.scanningErrors) @@ -277,7 +278,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/valid-v2-pvt-signature" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has an invalid signature`)}, pm.scanningErrors) @@ -297,7 +298,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/valid-v2-pvt-signature-root-url-uri" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) require.Empty(t, pm.scanningErrors) @@ -323,7 +324,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/valid-v2-pvt-signature" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) require.Empty(t, pm.scanningErrors) @@ -349,7 +350,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/invalid-v2-signature" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors) assert.Nil(t, pm.plugins[("test")]) @@ -365,7 +366,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/invalid-v2-signature-2" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors) assert.Nil(t, pm.plugins[("test")]) @@ -381,7 +382,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/includes-symlinks" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) require.Empty(t, pm.scanningErrors) @@ -410,7 +411,7 @@ func TestPluginManager_Init(t *testing.T) { pm := createManager(t, func(pm *PluginManager) { pm.Cfg.PluginsPath = "testdata/symbolic-plugin-dirs" }) - err := pm.Init() + err := pm.init() require.NoError(t, err) // This plugin should be properly registered, even though it is symlinked to plugins dir require.Empty(t, pm.scanningErrors) @@ -446,7 +447,7 @@ func TestPluginManager_Installer(t *testing.T) { pm.BackendPluginManager = fm }) - err := pm.Init() + err := pm.init() require.NoError(t, err) // mock installer @@ -712,12 +713,13 @@ func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager { staticRootPath, err := filepath.Abs("../../../public/") require.NoError(t, err) - pm := newManager(&setting.Cfg{ + cfg := &setting.Cfg{ Raw: ini.Empty(), Env: setting.Prod, StaticRootPath: staticRootPath, - }) - pm.BackendPluginManager = &fakeBackendPluginManager{} + } + pm := newManager(cfg, &sqlstore.SQLStore{}, &fakeBackendPluginManager{}) + for _, cb := range cbs { cb(pm) } diff --git a/pkg/plugins/plugincontext/plugincontext.go b/pkg/plugins/plugincontext/plugincontext.go index 0a284af147e..e5b438c5576 100644 --- a/pkg/plugins/plugincontext/plugincontext.go +++ b/pkg/plugins/plugincontext/plugincontext.go @@ -12,34 +12,28 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/adapters" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/util/errutil" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "PluginContextProvider", - Instance: newProvider(), - }) -} - -func newProvider() *Provider { - return &Provider{} +func ProvideService(bus bus.Bus, cacheService *localcache.CacheService, pluginManager plugins.Manager, + dataSourceCache datasources.CacheService) *Provider { + return &Provider{ + Bus: bus, + CacheService: cacheService, + PluginManager: pluginManager, + DataSourceCache: dataSourceCache, + } } type Provider struct { - Bus bus.Bus `inject:""` - CacheService *localcache.CacheService `inject:""` - PluginManager plugins.Manager `inject:""` - DatasourceCache datasources.CacheService `inject:""` + Bus bus.Bus + CacheService *localcache.CacheService + PluginManager plugins.Manager + DataSourceCache datasources.CacheService } -func (p *Provider) Init() error { - return nil -} - -// Get allows getting plugin context by its id. If datasourceUID is not empty string +// Get allows getting plugin context by its ID. If datasourceUID is not empty string // then PluginContext.DataSourceInstanceSettings will be resolved and appended to // returned context. func (p *Provider) Get(pluginID string, datasourceUID string, user *models.SignedInUser, skipCache bool) (backend.PluginContext, bool, error) { @@ -81,7 +75,7 @@ func (p *Provider) Get(pluginID string, datasourceUID string, user *models.Signe } if datasourceUID != "" { - ds, err := p.DatasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache) + ds, err := p.DataSourceCache.GetDatasourceByUID(datasourceUID, user, skipCache) if err != nil { return pc, false, errutil.Wrap("Failed to get datasource", err) } diff --git a/pkg/plugins/plugindashboards/service.go b/pkg/plugins/plugindashboards/service.go index ccbf5a6f704..8ad8fe2070f 100644 --- a/pkg/plugins/plugindashboards/service.go +++ b/pkg/plugins/plugindashboards/service.go @@ -1,43 +1,34 @@ package plugindashboards import ( - "context" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/tsdb" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "PluginDashboardService", - Instance: &Service{}, - }) +func ProvideService(dataService *tsdb.Service, pluginManager plugins.Manager, sqlStore *sqlstore.SQLStore) *Service { + s := &Service{ + DataService: dataService, + PluginManager: pluginManager, + SQLStore: sqlStore, + logger: log.New("plugindashboards"), + } + bus.AddEventListener(s.handlePluginStateChanged) + s.updateAppDashboards() + return s } type Service struct { - DataService *tsdb.Service `inject:""` - PluginManager plugins.Manager `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` + DataService *tsdb.Service + PluginManager plugins.Manager + SQLStore *sqlstore.SQLStore logger log.Logger } -func (s *Service) Init() error { - bus.AddEventListener(s.handlePluginStateChanged) - s.logger = log.New("plugindashboards") - return nil -} - -func (s *Service) Run(ctx context.Context) error { - s.updateAppDashboards() - return nil -} - func (s *Service) updateAppDashboards() { s.logger.Debug("Looking for app dashboard updates") diff --git a/pkg/registry/di.go b/pkg/registry/di.go deleted file mode 100644 index 4d58fe2a6ec..00000000000 --- a/pkg/registry/di.go +++ /dev/null @@ -1,45 +0,0 @@ -package registry - -import ( - "fmt" - - "github.com/facebookgo/inject" -) - -// BuildServiceGraph builds a graph of services and their dependencies. -// The services are initialized after the graph is built. -func BuildServiceGraph(objs []interface{}, services []*Descriptor) error { - if services == nil { - services = GetServices() - } - for _, service := range services { - objs = append(objs, service.Instance) - } - - serviceGraph := inject.Graph{} - - // Provide services and their dependencies to the graph. - for _, obj := range objs { - if err := serviceGraph.Provide(&inject.Object{Value: obj}); err != nil { - return fmt.Errorf("failed to provide object to the graph: %w", err) - } - } - - // Resolve services and their dependencies. - if err := serviceGraph.Populate(); err != nil { - return fmt.Errorf("failed to populate service dependencies: %w", err) - } - - // Initialize services. - for _, service := range services { - if IsDisabled(service.Instance) { - continue - } - - if err := service.Instance.Init(); err != nil { - return fmt.Errorf("service init failed: %w", err) - } - } - - return nil -} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 4453e89cfeb..8f5e35a5c23 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -2,113 +2,13 @@ package registry import ( "context" - "reflect" - "sort" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ) -type Descriptor struct { - Name string - Instance Service - InitPriority Priority -} - -var services []*Descriptor - -func RegisterServiceWithPriority(instance Service, priority Priority) { - Register(&Descriptor{ - Name: reflect.TypeOf(instance).Elem().Name(), - Instance: instance, - InitPriority: priority, - }) -} - -func RegisterService(instance Service) { - Register(&Descriptor{ - Name: reflect.TypeOf(instance).Elem().Name(), - Instance: instance, - InitPriority: Medium, - }) -} - -func Register(descriptor *Descriptor) { - if descriptor == nil { - return - } - // Overwrite any existing equivalent service - for i, svc := range services { - if svc.Name == descriptor.Name { - services[i] = descriptor - return - } - } - - services = append(services, descriptor) -} - -// GetService gets the registered service descriptor with a certain name. -// If none is found, nil is returned. -func GetService(name string) *Descriptor { - for _, svc := range services { - if svc.Name == name { - return svc - } - } - - return nil -} - -func GetServices() []*Descriptor { - slice := getServicesWithOverrides() - - sort.Slice(slice, func(i, j int) bool { - return slice[i].InitPriority > slice[j].InitPriority - }) - - return slice -} - -type OverrideServiceFunc func(descriptor Descriptor) (*Descriptor, bool) - -var overrides []OverrideServiceFunc - -func RegisterOverride(fn OverrideServiceFunc) { - overrides = append(overrides, fn) -} - -func ClearOverrides() { - overrides = nil -} - -func getServicesWithOverrides() []*Descriptor { - slice := []*Descriptor{} - for _, s := range services { - var descriptor *Descriptor - for _, fn := range overrides { - if newDescriptor, override := fn(*s); override { - descriptor = newDescriptor - break - } - } - - if descriptor != nil { - slice = append(slice, descriptor) - } else { - slice = append(slice, s) - } - } - - return slice -} - -// Service interface is the lowest common shape that services -// are expected to fulfill to be started within Grafana. -type Service interface { - // Init is called by Grafana main process which gives the service - // the possibility do some initial work before its started. Things - // like adding routes, bus handlers should be done in the Init function - Init() error +// BackgroundServiceRegistry provides background services. +type BackgroundServiceRegistry interface { + GetServices() []BackgroundService } // CanBeDisabled allows the services to decide if it should @@ -137,17 +37,8 @@ type DatabaseMigrator interface { AddMigration(mg *migrator.Migrator) } -// IsDisabled returns whether a service is disabled. -func IsDisabled(srv Service) bool { +// IsDisabled returns whether a background service is disabled. +func IsDisabled(srv BackgroundService) bool { canBeDisabled, ok := srv.(CanBeDisabled) return ok && canBeDisabled.IsDisabled() } - -type Priority int - -const ( - High Priority = 100 - MediumHigh Priority = 75 - Medium Priority = 50 - Low Priority = 0 -) diff --git a/pkg/server/backgroundsvcs/background_services.go b/pkg/server/backgroundsvcs/background_services.go new file mode 100644 index 00000000000..4239f47bf2f --- /dev/null +++ b/pkg/server/backgroundsvcs/background_services.go @@ -0,0 +1,76 @@ +package backgroundsvcs + +import ( + "github.com/grafana/grafana/pkg/api" + "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/models" + backendmanager "github.com/grafana/grafana/pkg/plugins/backendplugin/manager" + "github.com/grafana/grafana/pkg/plugins/manager" + "github.com/grafana/grafana/pkg/plugins/plugindashboards" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/cleanup" + "github.com/grafana/grafana/pkg/services/live" + "github.com/grafana/grafana/pkg/services/live/pushhttp" + "github.com/grafana/grafana/pkg/services/ngalert" + "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/rendering" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch" + "github.com/grafana/grafana/pkg/tsdb/elasticsearch" + "github.com/grafana/grafana/pkg/tsdb/graphite" + "github.com/grafana/grafana/pkg/tsdb/influxdb" + "github.com/grafana/grafana/pkg/tsdb/loki" + "github.com/grafana/grafana/pkg/tsdb/opentsdb" + "github.com/grafana/grafana/pkg/tsdb/prometheus" + "github.com/grafana/grafana/pkg/tsdb/tempo" + "github.com/grafana/grafana/pkg/tsdb/testdatasource" +) + +func ProvideBackgroundServiceRegistry( + httpServer *api.HTTPServer, ng *ngalert.AlertNG, cleanup *cleanup.CleanUpService, + live *live.GrafanaLive, pushGateway *pushhttp.Gateway, notifications *notifications.NotificationService, + rendering *rendering.RenderingService, tokenService models.UserTokenBackgroundService, + provisioning *provisioning.ProvisioningServiceImpl, alerting *alerting.AlertEngine, pm *manager.PluginManager, + backendPM *backendmanager.Manager, metrics *metrics.InternalMetricsService, + usageStats *usagestats.UsageStatsService, tracing *tracing.TracingService, remoteCache *remotecache.RemoteCache, + // Need to make sure these are initialized, is there a better place to put them? + _ *azuremonitor.Service, _ *cloudwatch.CloudWatchService, _ *elasticsearch.Service, _ *graphite.Service, _ *influxdb.Service, + _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service, _ *testdatasource.TestDataPlugin, _ *plugindashboards.Service, + +) *BackgroundServiceRegistry { + return NewBackgroundServiceRegistry( + httpServer, + ng, + cleanup, + live, + pushGateway, + notifications, + rendering, + tokenService, + provisioning, + alerting, + pm, + backendPM, + metrics, + usageStats, + tracing, + remoteCache) +} + +// BackgroundServiceRegistry provides background services. +type BackgroundServiceRegistry struct { + Services []registry.BackgroundService +} + +func NewBackgroundServiceRegistry(services ...registry.BackgroundService) *BackgroundServiceRegistry { + return &BackgroundServiceRegistry{services} +} + +func (r *BackgroundServiceRegistry) GetServices() []registry.BackgroundService { + return r.Services +} diff --git a/pkg/server/server.go b/pkg/server/server.go index efe5d629623..35111c77ed4 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -3,53 +3,32 @@ package server import ( "context" "errors" - "flag" "fmt" "io/ioutil" "net" "os" "path/filepath" + "reflect" "strconv" "sync" - "time" + + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/api" - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/bus" _ "github.com/grafana/grafana/pkg/extensions" - "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" - "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" - _ "github.com/grafana/grafana/pkg/infra/remotecache" - _ "github.com/grafana/grafana/pkg/infra/serverlock" - _ "github.com/grafana/grafana/pkg/infra/tracing" - _ "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/login" - _ "github.com/grafana/grafana/pkg/login/social" - "github.com/grafana/grafana/pkg/middleware" - _ "github.com/grafana/grafana/pkg/plugins/manager" + "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/registry" - _ "github.com/grafana/grafana/pkg/services/alerting" - _ "github.com/grafana/grafana/pkg/services/auth" - _ "github.com/grafana/grafana/pkg/services/auth/jwt" - _ "github.com/grafana/grafana/pkg/services/cleanup" - _ "github.com/grafana/grafana/pkg/services/librarypanels" - _ "github.com/grafana/grafana/pkg/services/login/authinfoservice" - _ "github.com/grafana/grafana/pkg/services/login/loginservice" - _ "github.com/grafana/grafana/pkg/services/ngalert" - _ "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/provisioning" - _ "github.com/grafana/grafana/pkg/services/rendering" - _ "github.com/grafana/grafana/pkg/services/search" - _ "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" "golang.org/x/sync/errgroup" ) -// Config contains parameters for the New function. -type Config struct { - ConfigFile string +// Options contains parameters for the New function. +type Options struct { HomePath string PidFile string Version string @@ -58,59 +37,46 @@ type Config struct { Listener net.Listener } -type serviceRegistry interface { - IsDisabled(srv registry.Service) bool - GetServices() []*registry.Descriptor -} - -type globalServiceRegistry struct{} - -func (r *globalServiceRegistry) IsDisabled(srv registry.Service) bool { - return registry.IsDisabled(srv) -} - -func (r *globalServiceRegistry) GetServices() []*registry.Descriptor { - return registry.GetServices() -} - -type roleRegistry interface { - // RegisterFixedRoles registers all roles declared to AccessControl - RegisterFixedRoles() error -} - // New returns a new instance of Server. -func New(cfg Config) (*Server, error) { - s := newServer(cfg) +func New(opts Options, cfg *setting.Cfg, httpServer *api.HTTPServer, roleRegistry accesscontrol.RoleRegistry, + provisioningService provisioning.ProvisioningService, backgroundServiceProvider registry.BackgroundServiceRegistry, +) (*Server, error) { + s, err := newServer(opts, cfg, httpServer, roleRegistry, provisioningService, backgroundServiceProvider) + if err != nil { + return nil, err + } + if err := s.init(); err != nil { return nil, err } + return s, nil } -func newServer(cfg Config) *Server { +func newServer(opts Options, cfg *setting.Cfg, httpServer *api.HTTPServer, roleRegistry accesscontrol.RoleRegistry, + provisioningService provisioning.ProvisioningService, backgroundServiceProvider registry.BackgroundServiceRegistry, +) (*Server, error) { rootCtx, shutdownFn := context.WithCancel(context.Background()) childRoutines, childCtx := errgroup.WithContext(rootCtx) - return &Server{ - context: childCtx, - shutdownFn: shutdownFn, - shutdownFinished: make(chan struct{}), - childRoutines: childRoutines, - log: log.New("server"), - // Need to use the singleton setting.Cfg instance, to make sure we use the same as is injected in the DI - // graph - cfg: setting.GetCfg(), - - configFile: cfg.ConfigFile, - homePath: cfg.HomePath, - pidFile: cfg.PidFile, - version: cfg.Version, - commit: cfg.Commit, - buildBranch: cfg.BuildBranch, - - serviceRegistry: &globalServiceRegistry{}, - listener: cfg.Listener, + s := &Server{ + context: childCtx, + childRoutines: childRoutines, + HTTPServer: httpServer, + provisioningService: provisioningService, + roleRegistry: roleRegistry, + shutdownFn: shutdownFn, + shutdownFinished: make(chan struct{}), + log: log.New("server"), + cfg: cfg, + pidFile: opts.PidFile, + version: opts.Version, + commit: opts.Commit, + buildBranch: opts.BuildBranch, + backgroundServices: backgroundServiceProvider.GetServices(), } + + return s, nil } // Server is responsible for managing the lifecycle of services. @@ -124,20 +90,16 @@ type Server struct { shutdownFinished chan struct{} isInitialized bool mtx sync.Mutex - listener net.Listener - configFile string - homePath string - pidFile string - version string - commit string - buildBranch string + pidFile string + version string + commit string + buildBranch string + backgroundServices []registry.BackgroundService - serviceRegistry serviceRegistry - - HTTPServer *api.HTTPServer `inject:""` - AccessControl roleRegistry `inject:""` - ProvisioningService provisioning.ProvisioningService `inject:""` + HTTPServer *api.HTTPServer + roleRegistry accesscontrol.RoleRegistry + provisioningService provisioning.ProvisioningService } // init initializes the server and its services. @@ -150,36 +112,19 @@ func (s *Server) init() error { } s.isInitialized = true - s.loadConfiguration() s.writePIDFile() if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil { return err } login.Init() + social.ProvideService(s.cfg) - services := s.serviceRegistry.GetServices() - if err := s.buildServiceGraph(services); err != nil { + if err := s.roleRegistry.RegisterFixedRoles(); err != nil { return err } - if s.listener != nil { - for _, service := range services { - if httpS, ok := service.Instance.(*api.HTTPServer); ok { - // Configure the api.HTTPServer if necessary - // Hopefully we can find a better solution, maybe with a more advanced DI framework, f.ex. Dig? - s.log.Debug("Using provided listener for HTTP server") - httpS.Listener = s.listener - } - } - } - - // Register all fixed roles - if err := s.AccessControl.RegisterFixedRoles(); err != nil { - return err - } - - return s.ProvisioningService.RunInitProvisioners() + return s.provisioningService.RunInitProvisioners() } // Run initializes and starts services. This will block until all services have @@ -191,36 +136,32 @@ func (s *Server) Run() error { return err } - services := s.serviceRegistry.GetServices() + services := s.backgroundServices // Start background services. for _, svc := range services { - service, ok := svc.Instance.(registry.BackgroundService) - if !ok { + if registry.IsDisabled(svc) { continue } - if s.serviceRegistry.IsDisabled(svc.Instance) { - continue - } - - // Variable is needed for accessing loop variable in callback - descriptor := svc + service := svc + serviceName := reflect.TypeOf(service).String() s.childRoutines.Go(func() error { select { case <-s.context.Done(): return s.context.Err() default: } + s.log.Debug("Starting background service " + serviceName) err := service.Run(s.context) // Do not return context.Canceled error since errgroup.Group only // returns the first error to the caller - thus we can miss a more // interesting error. if err != nil && !errors.Is(err, context.Canceled) { - s.log.Error("Stopped "+descriptor.Name, "reason", err) - return fmt.Errorf("%s run error: %w", descriptor.Name, err) + s.log.Error("Stopped background service "+serviceName, "reason", err) + return fmt.Errorf("%s run error: %w", serviceName, err) } - s.log.Debug("Stopped "+descriptor.Name, "reason", err) + s.log.Debug("Stopped background service "+serviceName, "reason", err) return nil }) } @@ -285,43 +226,6 @@ func (s *Server) writePIDFile() { s.log.Info("Writing PID file", "path", s.pidFile, "pid", pid) } -// buildServiceGraph builds a graph of services and their dependencies. -func (s *Server) buildServiceGraph(services []*registry.Descriptor) error { - // Specify service dependencies. - objs := []interface{}{ - bus.GetBus(), - s.cfg, - routing.NewRouteRegister(middleware.ProvideRouteOperationName, middleware.RequestMetrics(s.cfg)), - localcache.New(5*time.Minute, 10*time.Minute), - httpclientprovider.New(s.cfg), - s, - } - return registry.BuildServiceGraph(objs, services) -} - -// loadConfiguration loads settings and configuration from config files. -func (s *Server) loadConfiguration() { - args := &setting.CommandLineArgs{ - Config: s.configFile, - HomePath: s.homePath, - Args: flag.Args(), - } - - if err := s.cfg.Load(args); err != nil { - _, _ = fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error()) - os.Exit(1) - } - - s.log.Info("Starting "+setting.ApplicationName, - "version", s.version, - "commit", s.commit, - "branch", s.buildBranch, - "compiled", time.Unix(setting.BuildStamp, 0), - ) - - s.cfg.LogConfigSources() -} - // notifySystemd sends state notifications to systemd. func (s *Server) notifySystemd(state string) { notifySocket := os.Getenv("NOTIFY_SOCKET") diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 09b4c0c30f0..f1980a62f92 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -3,45 +3,36 @@ package server import ( "context" "errors" + "fmt" "testing" "time" "github.com/grafana/grafana/pkg/registry" - + "github.com/grafana/grafana/pkg/server/backgroundsvcs" + "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" ) -type testServiceRegistry struct { - services []*registry.Descriptor -} - -func (r *testServiceRegistry) GetServices() []*registry.Descriptor { - return r.services -} - -func (r *testServiceRegistry) IsDisabled(_ registry.Service) bool { - return false -} - type testService struct { - started chan struct{} - initErr error - runErr error + started chan struct{} + runErr error + isDisabled bool } -func newTestService(initErr, runErr error) *testService { +func newTestService(runErr error, disabled bool) *testService { return &testService{ - started: make(chan struct{}), - initErr: initErr, - runErr: runErr, + started: make(chan struct{}), + runErr: runErr, + isDisabled: disabled, } } -func (s *testService) Init() error { - return s.initErr -} - func (s *testService) Run(ctx context.Context) error { + if s.isDisabled { + return fmt.Errorf("Shouldn't run disabled service") + } + if s.runErr != nil { return s.runErr } @@ -50,8 +41,14 @@ func (s *testService) Run(ctx context.Context) error { return ctx.Err() } -func testServer() *Server { - s := newServer(Config{}) +func (s *testService) IsDisabled() bool { + return s.isDisabled +} + +func testServer(t *testing.T, services ...registry.BackgroundService) *Server { + t.Helper() + s, err := newServer(Options{}, setting.NewCfg(), nil, &ossaccesscontrol.OSSAccessControlService{}, nil, backgroundsvcs.NewBackgroundServiceRegistry(services...)) + require.NoError(t, err) // Required to skip configuration initialization that causes // DI errors in this test. s.isInitialized = true @@ -59,25 +56,8 @@ func testServer() *Server { } func TestServer_Run_Error(t *testing.T) { - s := testServer() - - var testErr = errors.New("boom") - - s.serviceRegistry = &testServiceRegistry{ - services: []*registry.Descriptor{ - { - Name: "TestService1", - Instance: newTestService(nil, nil), - InitPriority: registry.High, - }, - { - Name: "TestService2", - Instance: newTestService(nil, testErr), - InitPriority: registry.High, - }, - }, - } - + testErr := errors.New("boom") + s := testServer(t, newTestService(nil, false), newTestService(testErr, false)) err := s.Run() require.ErrorIs(t, err, testErr) require.NotZero(t, s.ExitCode(err)) @@ -86,22 +66,7 @@ func TestServer_Run_Error(t *testing.T) { func TestServer_Shutdown(t *testing.T) { ctx := context.Background() - s := testServer() - services := []*registry.Descriptor{ - { - Name: "TestService1", - Instance: newTestService(nil, nil), - InitPriority: registry.High, - }, - { - Name: "TestService2", - Instance: newTestService(nil, nil), - InitPriority: registry.High, - }, - } - s.serviceRegistry = &testServiceRegistry{ - services: services, - } + s := testServer(t, newTestService(nil, false), newTestService(nil, true)) ch := make(chan error) @@ -109,8 +74,10 @@ func TestServer_Shutdown(t *testing.T) { defer close(ch) // Wait until all services launched. - for _, svc := range services { - <-svc.Instance.(*testService).started + for _, svc := range s.backgroundServices { + if !svc.(*testService).isDisabled { + <-svc.(*testService).started + } } ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() diff --git a/pkg/server/test_env.go b/pkg/server/test_env.go new file mode 100644 index 00000000000..009770ef581 --- /dev/null +++ b/pkg/server/test_env.go @@ -0,0 +1,12 @@ +package server + +import "github.com/grafana/grafana/pkg/services/sqlstore" + +func ProvideTestEnv(server *Server, store *sqlstore.SQLStore) (*TestEnv, error) { + return &TestEnv{server, store}, nil +} + +type TestEnv struct { + Server *Server + SQLStore *sqlstore.SQLStore +} diff --git a/pkg/server/wire.go b/pkg/server/wire.go new file mode 100644 index 00000000000..195ded23a43 --- /dev/null +++ b/pkg/server/wire.go @@ -0,0 +1,159 @@ +// +build wireinject + +package server + +import ( + "github.com/google/wire" + + sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/api" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/infra/httpclient" + "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" + "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/serverlock" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/infra/usagestats" + "github.com/grafana/grafana/pkg/login/social" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/plugins/backendplugin" + backendmanager "github.com/grafana/grafana/pkg/plugins/backendplugin/manager" + "github.com/grafana/grafana/pkg/plugins/manager" + "github.com/grafana/grafana/pkg/plugins/plugincontext" + "github.com/grafana/grafana/pkg/plugins/plugindashboards" + "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/auth/jwt" + "github.com/grafana/grafana/pkg/services/cleanup" + "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/datasourceproxy" + "github.com/grafana/grafana/pkg/services/hooks" + "github.com/grafana/grafana/pkg/services/libraryelements" + "github.com/grafana/grafana/pkg/services/librarypanels" + "github.com/grafana/grafana/pkg/services/live" + "github.com/grafana/grafana/pkg/services/live/pushhttp" + "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/login/authinfoservice" + "github.com/grafana/grafana/pkg/services/login/loginservice" + "github.com/grafana/grafana/pkg/services/ngalert" + ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics" + "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/oauthtoken" + "github.com/grafana/grafana/pkg/services/quota" + "github.com/grafana/grafana/pkg/services/rendering" + "github.com/grafana/grafana/pkg/services/schemaloader" + "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/shorturls" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor" + "github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch" + "github.com/grafana/grafana/pkg/tsdb/elasticsearch" + "github.com/grafana/grafana/pkg/tsdb/graphite" + "github.com/grafana/grafana/pkg/tsdb/influxdb" + "github.com/grafana/grafana/pkg/tsdb/loki" + "github.com/grafana/grafana/pkg/tsdb/opentsdb" + "github.com/grafana/grafana/pkg/tsdb/postgres" + "github.com/grafana/grafana/pkg/tsdb/prometheus" + "github.com/grafana/grafana/pkg/tsdb/tempo" + "github.com/grafana/grafana/pkg/tsdb/testdatasource" +) + +var wireBasicSet = wire.NewSet( + tsdb.NewService, + wire.Bind(new(plugins.DataRequestHandler), new(*tsdb.Service)), + alerting.ProvideAlertEngine, + wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)), + setting.NewCfgFromArgs, + New, + api.ProvideHTTPServer, + bus.ProvideBus, + wire.Bind(new(bus.Bus), new(*bus.InProcBus)), + rendering.ProvideService, + wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), + routing.ProvideRegister, + wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), + hooks.ProvideService, + localcache.ProvideService, + usagestats.ProvideService, + wire.Bind(new(usagestats.UsageStats), new(*usagestats.UsageStatsService)), + manager.ProvideService, + wire.Bind(new(plugins.Manager), new(*manager.PluginManager)), + backendmanager.ProvideService, + wire.Bind(new(backendplugin.Manager), new(*backendmanager.Manager)), + cloudwatch.ProvideService, + cloudwatch.ProvideLogsService, + cloudmonitoring.ProvideService, + azuremonitor.ProvideService, + postgres.ProvideService, + httpclientprovider.New, + wire.Bind(new(httpclient.Provider), new(*sdkhttpclient.Provider)), + serverlock.ProvideService, + cleanup.ProvideService, + shorturls.ProvideService, + wire.Bind(new(shorturls.Service), new(*shorturls.ShortURLService)), + quota.ProvideService, + remotecache.ProvideService, + loginservice.ProvideService, + wire.Bind(new(login.Service), new(*loginservice.Implementation)), + authinfoservice.ProvideAuthInfoService, + wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)), + datasourceproxy.ProvideService, + search.ProvideService, + live.ProvideService, + pushhttp.ProvideService, + plugincontext.ProvideService, + contexthandler.ProvideService, + jwt.ProvideService, + wire.Bind(new(models.JWTService), new(*jwt.AuthService)), + plugindashboards.ProvideService, + schemaloader.ProvideService, + ngalert.ProvideService, + librarypanels.ProvideService, + wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), + libraryelements.ProvideService, + wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), + notifications.ProvideService, + tracing.ProvideService, + metrics.ProvideService, + testdatasource.ProvideService, + opentsdb.ProvideService, + social.ProvideService, + influxdb.ProvideService, + wire.Bind(new(social.Service), new(*social.SocialService)), + oauthtoken.ProvideService, + wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), + tempo.ProvideService, + loki.ProvideService, + graphite.ProvideService, + prometheus.ProvideService, + elasticsearch.ProvideService, +) + +var wireSet = wire.NewSet( + wireBasicSet, + sqlstore.ProvideService, + ngmetrics.ProvideService, +) + +var wireTestSet = wire.NewSet( + wireBasicSet, + ProvideTestEnv, + sqlstore.ProvideServiceForTests, + ngmetrics.ProvideServiceForTest, +) + +func Initialize(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions) (*Server, error) { + wire.Build(wireExtsSet) + return &Server{}, nil +} + +func InitializeForTest(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions) (*TestEnv, error) { + wire.Build(wireExtsTestSet) + return &TestEnv{Server: &Server{}, SQLStore: &sqlstore.SQLStore{}}, nil +} diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go new file mode 100644 index 00000000000..f340c772105 --- /dev/null +++ b/pkg/server/wireexts_oss.go @@ -0,0 +1,56 @@ +// +build wireinject,oss + +package server + +import ( + "github.com/google/wire" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/server/backgroundsvcs" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/login/authinfoservice" + "github.com/grafana/grafana/pkg/services/provisioning" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/setting" +) + +var wireExtsBasicSet = wire.NewSet( + auth.ProvideUserAuthTokenService, + wire.Bind(new(models.UserTokenService), new(*auth.UserAuthTokenService)), + wire.Bind(new(models.UserTokenBackgroundService), new(*auth.UserAuthTokenService)), + licensing.ProvideService, + wire.Bind(new(models.Licensing), new(*licensing.OSSLicensingService)), + setting.ProvideProvider, + wire.Bind(new(setting.Provider), new(*setting.OSSImpl)), + ossaccesscontrol.ProvideService, + wire.Bind(new(accesscontrol.RoleRegistry), new(*ossaccesscontrol.OSSAccessControlService)), + wire.Bind(new(accesscontrol.AccessControl), new(*ossaccesscontrol.OSSAccessControlService)), + validations.ProvideValidator, + wire.Bind(new(models.PluginRequestValidator), new(*validations.OSSPluginRequestValidator)), + provisioning.ProvideService, + wire.Bind(new(provisioning.ProvisioningService), new(*provisioning.ProvisioningServiceImpl)), + backgroundsvcs.ProvideBackgroundServiceRegistry, + wire.Bind(new(registry.BackgroundServiceRegistry), new(*backgroundsvcs.BackgroundServiceRegistry)), + datasources.ProvideCacheService, + wire.Bind(new(datasources.CacheService), new(*datasources.CacheServiceImpl)), + migrations.ProvideOSSMigrations, + wire.Bind(new(registry.DatabaseMigrator), new(*migrations.OSSMigrations)), + authinfoservice.ProvideOSSUserProtectionService, + wire.Bind(new(login.UserProtectionService), new(*authinfoservice.OSSUserProtectionImpl)), +) + +var wireExtsSet = wire.NewSet( + wireSet, + wireExtsBasicSet, +) + +var wireExtsTestSet = wire.NewSet( + wireTestSet, + wireExtsBasicSet, +) diff --git a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go index 91a0a584682..f37815f7739 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go @@ -12,21 +12,22 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -// OSSAccessControlService is the service implementing role based access control. -type OSSAccessControlService struct { - Cfg *setting.Cfg `inject:""` - UsageStats usagestats.UsageStats `inject:""` - Log log.Logger - registrations accesscontrol.RegistrationList +func ProvideService(cfg *setting.Cfg, usageStats usagestats.UsageStats) *OSSAccessControlService { + s := &OSSAccessControlService{ + Cfg: cfg, + UsageStats: usageStats, + Log: log.New("accesscontrol"), + } + s.registerUsageMetrics() + return s } -// Init initializes the OSSAccessControlService. -func (ac *OSSAccessControlService) Init() error { - ac.Log = log.New("accesscontrol") - - ac.registerUsageMetrics() - - return nil +// OSSAccessControlService is the service implementing role based access control. +type OSSAccessControlService struct { + Cfg *setting.Cfg + UsageStats usagestats.UsageStats + Log log.Logger + registrations accesscontrol.RegistrationList } func (ac *OSSAccessControlService) IsDisabled() bool { diff --git a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go index 84bc997214a..345e61e0e41 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" @@ -20,16 +19,8 @@ func setupTestEnv(t testing.TB) *OSSAccessControlService { cfg := setting.NewCfg() cfg.FeatureToggles = map[string]bool{"accesscontrol": true} - - ac := OSSAccessControlService{ - Cfg: cfg, - UsageStats: &usageStatsMock{metricsFuncs: make([]usagestats.MetricsFunc, 0)}, - Log: log.New("accesscontrol-test"), - } - - err := ac.Init() - require.NoError(t, err) - return &ac + ac := ProvideService(cfg, &usageStatsMock{metricsFuncs: make([]usagestats.MetricsFunc, 0)}) + return ac } func removeRoleHelper(role string) { @@ -128,7 +119,6 @@ func TestEvaluatingPermissions(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { ac := setupTestEnv(t) - t.Cleanup(registry.ClearOverrides) user := &models.SignedInUser{ UserId: 1, @@ -172,15 +162,7 @@ func TestUsageMetrics(t *testing.T) { cfg.FeatureToggles = map[string]bool{"accesscontrol": true} } - s := &OSSAccessControlService{ - Cfg: cfg, - UsageStats: &usageStatsMock{t: t, metricsFuncs: make([]usagestats.MetricsFunc, 0)}, - Log: log.New("accesscontrol-test"), - } - - err := s.Init() - assert.Nil(t, err) - + s := ProvideService(cfg, &usageStatsMock{t: t, metricsFuncs: make([]usagestats.MetricsFunc, 0)}) report, err := s.UsageStats.GetUsageReport(context.Background()) assert.Nil(t, err) diff --git a/pkg/services/accesscontrol/roles.go b/pkg/services/accesscontrol/roles.go index c503bae728f..4a69a32da17 100644 --- a/pkg/services/accesscontrol/roles.go +++ b/pkg/services/accesscontrol/roles.go @@ -8,6 +8,11 @@ import ( "github.com/grafana/grafana/pkg/models" ) +type RoleRegistry interface { + // RegisterFixedRoles registers all roles declared to AccessControl + RegisterFixedRoles() error +} + // Roles definition var ( datasourcesEditorReadRole = RoleDTO{ diff --git a/pkg/services/alerting/alerting_usage_test.go b/pkg/services/alerting/alerting_usage_test.go index 8f9602f638d..e31307ce6a5 100644 --- a/pkg/services/alerting/alerting_usage_test.go +++ b/pkg/services/alerting/alerting_usage_test.go @@ -55,9 +55,6 @@ func TestAlertingUsageStats(t *testing.T) { return nil }) - err := ae.Init() - require.NoError(t, err, "Init should not return error") - result, err := ae.QueryUsageStats() require.NoError(t, err, "getAlertingUsage should not return error") @@ -106,8 +103,6 @@ func TestParsingAlertRuleSettings(t *testing.T) { } ae := &AlertEngine{} - err := ae.Init() - require.NoError(t, err, "Init should not return an error") for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 520186fe435..2a4ac15543d 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" "github.com/opentracing/opentracing-go" @@ -24,11 +23,11 @@ import ( // schedules alert evaluations and makes sure notifications // are sent. type AlertEngine struct { - RenderService rendering.Service `inject:""` - Bus bus.Bus `inject:""` - RequestValidator models.PluginRequestValidator `inject:""` - DataService plugins.DataRequestHandler `inject:""` - Cfg *setting.Cfg `inject:""` + RenderService rendering.Service + Bus bus.Bus + RequestValidator models.PluginRequestValidator + DataService plugins.DataRequestHandler + Cfg *setting.Cfg execQueue chan *Job ticker *Ticker @@ -39,17 +38,21 @@ type AlertEngine struct { resultHandler resultHandler } -func init() { - registry.RegisterService(&AlertEngine{}) -} - // IsDisabled returns true if the alerting service is disable for this instance. func (e *AlertEngine) IsDisabled() bool { return !setting.AlertingEnabled || !setting.ExecuteAlerts || e.Cfg.IsNgAlertEnabled() } -// Init initializes the AlertingService. -func (e *AlertEngine) Init() error { +// ProvideAlertEngine returns a new AlertEngine. +func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator, + dataService plugins.DataRequestHandler, cfg *setting.Cfg) *AlertEngine { + e := &AlertEngine{ + Cfg: cfg, + RenderService: renderer, + Bus: bus, + RequestValidator: requestValidator, + DataService: dataService, + } e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1) e.execQueue = make(chan *Job, 1000) e.scheduler = newScheduler() @@ -57,7 +60,8 @@ func (e *AlertEngine) Init() error { e.ruleReader = newRuleReader() e.log = log.New("alerting.engine") e.resultHandler = newResultHandler(e.RenderService) - return nil + + return e } // Run starts the alerting service background process. diff --git a/pkg/services/alerting/engine_integration_test.go b/pkg/services/alerting/engine_integration_test.go index cb4051780d4..d623c631df2 100644 --- a/pkg/services/alerting/engine_integration_test.go +++ b/pkg/services/alerting/engine_integration_test.go @@ -17,9 +17,7 @@ import ( func TestEngineTimeouts(t *testing.T) { Convey("Alerting engine timeout tests", t, func() { - engine := &AlertEngine{} - err := engine.Init() - So(err, ShouldBeNil) + engine := ProvideAlertEngine(nil, nil, nil, nil, setting.NewCfg()) setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 engine.resultHandler = &FakeResultHandler{} diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index d2367690c38..3326f690b37 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -39,9 +39,7 @@ func (handler *FakeResultHandler) handle(evalContext *EvalContext) error { func TestEngineProcessJob(t *testing.T) { Convey("Alerting engine job processing", t, func() { - engine := &AlertEngine{} - err := engine.Init() - So(err, ShouldBeNil) + engine := ProvideAlertEngine(nil, nil, nil, nil, setting.NewCfg()) setting.AlertingEvaluationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go index 2e3d8f5ae30..d1de62d43fd 100644 --- a/pkg/services/auth/auth_token.go +++ b/pkg/services/auth/auth_token.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -20,28 +19,26 @@ import ( const ServiceName = "UserAuthTokenService" -func init() { - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: &UserAuthTokenService{}, - InitPriority: registry.Medium, - }) -} - var getTime = time.Now const urgentRotateTime = 1 * time.Minute -type UserAuthTokenService struct { - SQLStore *sqlstore.SQLStore `inject:""` - ServerLockService *serverlock.ServerLockService `inject:""` - Cfg *setting.Cfg `inject:""` - log log.Logger +func ProvideUserAuthTokenService(sqlStore *sqlstore.SQLStore, serverLockService *serverlock.ServerLockService, + cfg *setting.Cfg) *UserAuthTokenService { + s := &UserAuthTokenService{ + SQLStore: sqlStore, + ServerLockService: serverLockService, + Cfg: cfg, + log: log.New("auth"), + } + return s } -func (s *UserAuthTokenService) Init() error { - s.log = log.New("auth") - return nil +type UserAuthTokenService struct { + SQLStore *sqlstore.SQLStore + ServerLockService *serverlock.ServerLockService + Cfg *setting.Cfg + log log.Logger } func (s *UserAuthTokenService) ActiveTokenCount(ctx context.Context) (int64, error) { diff --git a/pkg/services/auth/jwt/auth.go b/pkg/services/auth/jwt/auth.go index 463d4dcb20c..753a188fd02 100644 --- a/pkg/services/auth/jwt/auth.go +++ b/pkg/services/auth/jwt/auth.go @@ -7,38 +7,34 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "gopkg.in/square/go-jose.v2/jwt" ) const ServiceName = "AuthService" -func init() { - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: &AuthService{}, - InitPriority: registry.Medium, - }) +func ProvideService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache) (*AuthService, error) { + s := newService(cfg, remoteCache) + if err := s.init(); err != nil { + return nil, err + } + + return s, nil } -type AuthService struct { - Cfg *setting.Cfg `inject:""` - RemoteCache *remotecache.RemoteCache `inject:""` - - keySet keySet - log log.Logger - expect map[string]interface{} - expectRegistered jwt.Expected +func newService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache) *AuthService { + return &AuthService{ + Cfg: cfg, + RemoteCache: remoteCache, + log: log.New("auth.jwt"), + } } -func (s *AuthService) Init() error { +func (s *AuthService) init() error { if !s.Cfg.JWTAuthEnabled { return nil } - s.log = log.New("auth.jwt") - if err := s.initClaimExpectations(); err != nil { return err } @@ -49,6 +45,16 @@ func (s *AuthService) Init() error { return nil } +type AuthService struct { + Cfg *setting.Cfg + RemoteCache *remotecache.RemoteCache + + keySet keySet + log log.Logger + expect map[string]interface{} + expectRegistered jwt.Expected +} + func (s *AuthService) Verify(ctx context.Context, strToken string) (models.JWTClaims, error) { s.log.Debug("Parsing JSON Web Token") diff --git a/pkg/services/auth/jwt/auth_test.go b/pkg/services/auth/jwt/auth_test.go index a33803768e6..dcca7373553 100644 --- a/pkg/services/auth/jwt/auth_test.go +++ b/pkg/services/auth/jwt/auth_test.go @@ -13,8 +13,6 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/remotecache" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -368,33 +366,18 @@ func scenario(t *testing.T, desc string, fn scenarioFunc, cbs ...configureFunc) } func initAuthService(t *testing.T, cbs ...configureFunc) (*AuthService, error) { - sqlStore := sqlstore.InitTestDB(t) - remoteCacheSvc := &remotecache.RemoteCache{} + t.Helper() + cfg := setting.NewCfg() cfg.JWTAuthEnabled = true cfg.JWTAuthExpectClaims = "{}" - cfg.RemoteCacheOptions = &setting.RemoteCacheOptions{Name: "database"} + for _, cb := range cbs { cb(t, cfg) } - service := &AuthService{} - - err := registry.BuildServiceGraph([]interface{}{cfg}, []*registry.Descriptor{ - { - Name: sqlstore.ServiceName, - Instance: sqlStore, - }, - { - Name: remotecache.ServiceName, - Instance: remoteCacheSvc, - }, - { - Name: ServiceName, - Instance: service, - }, - }) - + service := newService(cfg, remotecache.NewFakeStore(t)) + err := service.init() return service, err } diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 5397b86f083..efc4957377f 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -13,25 +13,26 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/setting" ) +func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockService, + shortURLService shorturls.Service) *CleanUpService { + s := &CleanUpService{ + Cfg: cfg, + ServerLockService: serverLockService, + ShortURLService: shortURLService, + log: log.New("cleanup"), + } + return s +} + type CleanUpService struct { log log.Logger - Cfg *setting.Cfg `inject:""` - ServerLockService *serverlock.ServerLockService `inject:""` - ShortURLService shorturls.Service `inject:""` -} - -func init() { - registry.RegisterService(&CleanUpService{}) -} - -func (srv *CleanUpService) Init() error { - srv.log = log.New("cleanup") - return nil + Cfg *setting.Cfg + ServerLockService *serverlock.ServerLockService + ShortURLService shorturls.Service } func (srv *CleanUpService) Run(ctx context.Context) error { diff --git a/pkg/services/contexthandler/auth_proxy_test.go b/pkg/services/contexthandler/auth_proxy_test.go index 6474f9e91ae..719f780dd3c 100644 --- a/pkg/services/contexthandler/auth_proxy_test.go +++ b/pkg/services/contexthandler/auth_proxy_test.go @@ -10,9 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/auth" - "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/contexthandler/authproxy" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -30,6 +28,10 @@ func TestInitContextWithAuthProxy_CachedInvalidUserID(t *testing.T) { const userID = int64(1) const orgID = int64(4) + svc := getContextHandler(t) + + // XXX: These handlers have to be injected AFTER calling getContextHandler, since the latter + // creates a SQLStore which installs its own handlers. upsertHandler := func(cmd *models.UpsertUserCommand) error { require.Equal(t, name, cmd.ExternalUser.Login) cmd.Result = &models.User{Id: userID} @@ -53,8 +55,6 @@ func TestInitContextWithAuthProxy_CachedInvalidUserID(t *testing.T) { bus.ClearBusHandlers() }) - svc := getContextHandler(t) - req, err := http.NewRequest("POST", "http://example.com", nil) require.NoError(t, err) ctx := &models.ReqContext{ @@ -90,15 +90,10 @@ type fakeRenderService struct { rendering.Service } -func (s *fakeRenderService) Init() error { - return nil -} - func getContextHandler(t *testing.T) *ContextHandler { t.Helper() sqlStore := sqlstore.InitTestDB(t) - remoteCacheSvc := &remotecache.RemoteCache{} cfg := setting.NewCfg() cfg.RemoteCacheOptions = &setting.RemoteCacheOptions{ @@ -107,38 +102,11 @@ func getContextHandler(t *testing.T) *ContextHandler { cfg.AuthProxyHeaderName = "X-Killa" cfg.AuthProxyEnabled = true cfg.AuthProxyHeaderProperty = "username" + remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore) + require.NoError(t, err) userAuthTokenSvc := auth.NewFakeUserAuthTokenService() renderSvc := &fakeRenderService{} authJWTSvc := models.NewFakeJWTService() - svc := &ContextHandler{} - err := registry.BuildServiceGraph([]interface{}{cfg}, []*registry.Descriptor{ - { - Name: sqlstore.ServiceName, - Instance: sqlStore, - }, - { - Name: remotecache.ServiceName, - Instance: remoteCacheSvc, - }, - { - Name: auth.ServiceName, - Instance: userAuthTokenSvc, - }, - { - Name: rendering.ServiceName, - Instance: renderSvc, - }, - { - Name: jwt.ServiceName, - Instance: authJWTSvc, - }, - { - Name: ServiceName, - Instance: svc, - }, - }) - require.NoError(t, err) - - return svc + return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore) } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 236f122226f..21b00cdfc94 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/middleware/cookies" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/contexthandler/authproxy" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/rendering" @@ -35,33 +34,32 @@ const ( const ServiceName = "ContextHandler" -func init() { - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: &ContextHandler{}, - InitPriority: registry.High, - }) +func ProvideService(cfg *setting.Cfg, tokenService models.UserTokenService, jwtService models.JWTService, + remoteCache *remotecache.RemoteCache, renderService rendering.Service, sqlStore *sqlstore.SQLStore) *ContextHandler { + return &ContextHandler{ + Cfg: cfg, + AuthTokenService: tokenService, + JWTAuthService: jwtService, + RemoteCache: remoteCache, + RenderService: renderService, + SQLStore: sqlStore, + } } // ContextHandler is a middleware. type ContextHandler struct { - Cfg *setting.Cfg `inject:""` - AuthTokenService models.UserTokenService `inject:""` - JWTAuthService models.JWTService `inject:""` - RemoteCache *remotecache.RemoteCache `inject:""` - RenderService rendering.Service `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` + Cfg *setting.Cfg + AuthTokenService models.UserTokenService + JWTAuthService models.JWTService + RemoteCache *remotecache.RemoteCache + RenderService rendering.Service + SQLStore *sqlstore.SQLStore // GetTime returns the current time. // Stubbable by tests. GetTime func() time.Time } -// Init initializes the service. -func (h *ContextHandler) Init() error { - return nil -} - type reqContextKey struct{} // FromContext returns the ReqContext value stored in a context.Context, if any. diff --git a/pkg/services/datasourceproxy/datasourceproxy.go b/pkg/services/datasourceproxy/datasourceproxy.go index ca88494eb86..234cc02321b 100644 --- a/pkg/services/datasourceproxy/datasourceproxy.go +++ b/pkg/services/datasourceproxy/datasourceproxy.go @@ -12,37 +12,41 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/setting" ) -func init() { - registry.RegisterService(&DatasourceProxyService{}) +func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator models.PluginRequestValidator, + pm plugins.Manager, cfg *setting.Cfg, httpClientProvider httpclient.Provider, + oauthTokenService *oauthtoken.Service) *DataSourceProxyService { + return &DataSourceProxyService{ + DataSourceCache: dataSourceCache, + PluginRequestValidator: plugReqValidator, + PluginManager: pm, + Cfg: cfg, + HTTPClientProvider: httpClientProvider, + OAuthTokenService: oauthTokenService, + } } -type DatasourceProxyService struct { - DatasourceCache datasources.CacheService `inject:""` - PluginRequestValidator models.PluginRequestValidator `inject:""` - PluginManager plugins.Manager `inject:""` - Cfg *setting.Cfg `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` - OAuthTokenService *oauthtoken.Service `inject:""` +type DataSourceProxyService struct { + DataSourceCache datasources.CacheService + PluginRequestValidator models.PluginRequestValidator + PluginManager plugins.Manager + Cfg *setting.Cfg + HTTPClientProvider httpclient.Provider + OAuthTokenService *oauthtoken.Service } -func (p *DatasourceProxyService) Init() error { - return nil -} - -func (p *DatasourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { +func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { p.ProxyDatasourceRequestWithID(c, c.ParamsInt64(":id")) } -func (p *DatasourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqContext, dsID int64) { +func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqContext, dsID int64) { c.TimeRequest(metrics.MDataSourceProxyReqTimer) - ds, err := p.DatasourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) + ds, err := p.DataSourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) if err != nil { if errors.Is(err, models.ErrDataSourceAccessDenied) { c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) diff --git a/pkg/services/datasources/cache.go b/pkg/services/datasources/cache.go index 4d882e3a207..9268cd44d8d 100644 --- a/pkg/services/datasources/cache.go +++ b/pkg/services/datasources/cache.go @@ -6,30 +6,24 @@ import ( "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" ) +func ProvideCacheService(cacheService *localcache.CacheService, sqlStore *sqlstore.SQLStore) *CacheServiceImpl { + return &CacheServiceImpl{ + CacheService: cacheService, + SQLStore: sqlStore, + } +} + type CacheService interface { GetDatasource(datasourceID int64, user *models.SignedInUser, skipCache bool) (*models.DataSource, error) GetDatasourceByUID(datasourceUID string, user *models.SignedInUser, skipCache bool) (*models.DataSource, error) } type CacheServiceImpl struct { - CacheService *localcache.CacheService `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` -} - -func init() { - registry.Register(®istry.Descriptor{ - Name: "DatasourceCacheService", - Instance: &CacheServiceImpl{}, - InitPriority: registry.Low, - }) -} - -func (dc *CacheServiceImpl) Init() error { - return nil + CacheService *localcache.CacheService + SQLStore *sqlstore.SQLStore } func (dc *CacheServiceImpl) GetDatasource( diff --git a/pkg/services/hooks/hooks.go b/pkg/services/hooks/hooks.go index 92d08db01aa..87e93f7e721 100644 --- a/pkg/services/hooks/hooks.go +++ b/pkg/services/hooks/hooks.go @@ -3,7 +3,6 @@ package hooks import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" ) type IndexDataHook func(indexData *dtos.IndexViewData, req *models.ReqContext) @@ -15,12 +14,8 @@ type HooksService struct { loginHooks []LoginHook } -func init() { - registry.RegisterService(&HooksService{}) -} - -func (srv *HooksService) Init() error { - return nil +func ProvideService() *HooksService { + return &HooksService{} } func (srv *HooksService) AddIndexDataHook(hook IndexDataHook) { diff --git a/pkg/services/libraryelements/libraryelements.go b/pkg/services/libraryelements/libraryelements.go index 3fff1d6364c..cfd32a418a0 100644 --- a/pkg/services/libraryelements/libraryelements.go +++ b/pkg/services/libraryelements/libraryelements.go @@ -4,11 +4,21 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister) *LibraryElementService { + l := &LibraryElementService{ + Cfg: cfg, + SQLStore: sqlStore, + RouteRegister: routeRegister, + log: log.New("library-elements"), + } + l.registerAPIEndpoints() + return l +} + // Service is a service for operating on library elements. type Service interface { CreateElement(c *models.ReqContext, cmd CreateLibraryElementCommand) (LibraryElementDTO, error) @@ -20,25 +30,12 @@ type Service interface { // LibraryElementService is the service for the Library Element feature. type LibraryElementService struct { - Cfg *setting.Cfg `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - RouteRegister routing.RouteRegister `inject:""` + Cfg *setting.Cfg + SQLStore *sqlstore.SQLStore + RouteRegister routing.RouteRegister log log.Logger } -func init() { - registry.RegisterService(&LibraryElementService{}) -} - -// Init initializes the LibraryElement service -func (l *LibraryElementService) Init() error { - l.log = log.New("library-elements") - - l.registerAPIEndpoints() - - return nil -} - // CreateElement creates a Library Element. func (l *LibraryElementService) CreateElement(c *models.ReqContext, cmd CreateLibraryElementCommand) (LibraryElementDTO, error) { return l.createLibraryElement(c, cmd) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 232dfaffe47..024bb8af1ce 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -281,8 +280,6 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo t.Helper() t.Run(desc, func(t *testing.T) { - t.Cleanup(registry.ClearOverrides) - ctx := macaron.Context{ Req: macaron.Request{Request: &http.Request{}}, } diff --git a/pkg/services/librarypanels/librarypanels.go b/pkg/services/librarypanels/librarypanels.go index 1cbaba1d970..480f880b984 100644 --- a/pkg/services/librarypanels/librarypanels.go +++ b/pkg/services/librarypanels/librarypanels.go @@ -7,12 +7,22 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister, + libraryElementService libraryelements.Service) *LibraryPanelService { + return &LibraryPanelService{ + Cfg: cfg, + SQLStore: sqlStore, + RouteRegister: routeRegister, + LibraryElementService: libraryElementService, + log: log.New("library-panels"), + } +} + // Service is a service for operating on library panels. type Service interface { LoadLibraryPanelsForDashboard(c *models.ReqContext, dash *models.Dashboard) error @@ -22,23 +32,13 @@ type Service interface { // LibraryPanelService is the service for the Panel Library feature. type LibraryPanelService struct { - Cfg *setting.Cfg `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - RouteRegister routing.RouteRegister `inject:""` - LibraryElementService libraryelements.Service `inject:""` + Cfg *setting.Cfg + SQLStore *sqlstore.SQLStore + RouteRegister routing.RouteRegister + LibraryElementService libraryelements.Service log log.Logger } -func init() { - registry.RegisterService(&LibraryPanelService{}) -} - -// Init initializes the LibraryPanel service -func (lps *LibraryPanelService) Init() error { - lps.log = log.New("library-panels") - return nil -} - // LoadLibraryPanelsForDashboard loops through all panels in dashboard JSON and replaces any library panel JSON // with JSON stored for library panel in db. func (lps *LibraryPanelService) LoadLibraryPanelsForDashboard(c *models.ReqContext, dash *models.Dashboard) error { diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 1ec6da66584..1e81460199d 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" dboards "github.com/grafana/grafana/pkg/dashboards" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -716,8 +715,6 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo t.Helper() t.Run(desc, func(t *testing.T) { - t.Cleanup(registry.ClearOverrides) - ctx := macaron.Context{ Req: macaron.Request{Request: &http.Request{}}, } diff --git a/pkg/services/licensing/oss.go b/pkg/services/licensing/oss.go index 5cb5a681b95..4c68cd68a3e 100644 --- a/pkg/services/licensing/oss.go +++ b/pkg/services/licensing/oss.go @@ -12,8 +12,8 @@ const ( ) type OSSLicensingService struct { - Cfg *setting.Cfg `inject:""` - HooksService *hooks.HooksService `inject:""` + Cfg *setting.Cfg + HooksService *hooks.HooksService } func (*OSSLicensingService) HasLicense() bool { @@ -44,7 +44,15 @@ func (l *OSSLicensingService) LicenseURL(user *models.SignedInUser) string { return "https://grafana.com/oss/grafana?utm_source=grafana_footer" } -func (l *OSSLicensingService) Init() error { +func (*OSSLicensingService) HasValidLicense() bool { + return false +} + +func ProvideService(cfg *setting.Cfg, hooksService *hooks.HooksService) *OSSLicensingService { + l := &OSSLicensingService{ + Cfg: cfg, + HooksService: hooksService, + } l.HooksService.AddIndexDataHook(func(indexData *dtos.IndexViewData, req *models.ReqContext) { for _, node := range indexData.NavTree { if node.Id == "admin" { @@ -57,10 +65,5 @@ func (l *OSSLicensingService) Init() error { } } }) - - return nil -} - -func (*OSSLicensingService) HasValidLicense() bool { - return false + return l } diff --git a/pkg/services/live/database/tests/setup.go b/pkg/services/live/database/tests/setup.go index 1d2f504dc96..b037cc31096 100644 --- a/pkg/services/live/database/tests/setup.go +++ b/pkg/services/live/database/tests/setup.go @@ -5,39 +5,13 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/registry" - "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/live/database" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" ) // SetupTestStorage initializes a storage to used by the integration tests. // This is required to properly register and execute migrations. func SetupTestStorage(t *testing.T) *database.Storage { - cfg := setting.NewCfg() - // Live is disabled by default and only if it's enabled its database migrations run - // and the related database tables are created. - cfg.FeatureToggles = map[string]bool{"live": true} - - gLive := live.NewGrafanaLive() - gLive.Cfg = cfg - - // Hook for initialising the service after the Cfg is populated - // so that database migrations will run. - overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) { - if _, ok := descriptor.Instance.(*live.GrafanaLive); ok { - return ®istry.Descriptor{ - Name: descriptor.Name, - Instance: gLive, - InitPriority: descriptor.InitPriority, - }, true - } - return nil, false - } - registry.RegisterOverride(overrideServiceFunc) - - // Now we can use sql.Store. sqlStore := sqlstore.InitTestDB(t) localCache := localcache.New(time.Hour, time.Hour) return database.NewStorage(sqlStore, localCache) diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 71a1791814f..7e28ec0d99d 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -21,7 +21,6 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/manager" "github.com/grafana/grafana/pkg/plugins/plugincontext" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/live/database" "github.com/grafana/grafana/pkg/services/live/features" @@ -33,7 +32,6 @@ import ( "github.com/grafana/grafana/pkg/services/live/runstream" "github.com/grafana/grafana/pkg/services/live/survey" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/cloudwatch" "github.com/grafana/grafana/pkg/util" @@ -49,14 +47,9 @@ var ( loggerCF = log.New("live.centrifuge") ) -func init() { - registry.RegisterServiceWithPriority(NewGrafanaLive(), registry.Low) -} - func NewGrafanaLive() *GrafanaLive { return &GrafanaLive{ - channels: make(map[string]models.ChannelHandler), - channelsMu: sync.RWMutex{}, + channels: make(map[string]models.ChannelHandler), GrafanaScope: CoreGrafanaScope{ Features: make(map[string]models.ChannelHandlerFactory), }, @@ -71,98 +64,41 @@ type CoreGrafanaScope struct { Dashboards models.DashboardActivityChannel } -// GrafanaLive manages live real-time connections to Grafana (over WebSocket at this moment). -// The main concept here is Channel. Connections can subscribe to many channels. Each channel -// can have different permissions and properties but once a connection subscribed to a channel -// it starts receiving all messages published into this channel. Thus GrafanaLive is a PUB/SUB -// server. -type GrafanaLive struct { - PluginContextProvider *plugincontext.Provider `inject:""` - Cfg *setting.Cfg `inject:""` - RouteRegister routing.RouteRegister `inject:""` - LogsService *cloudwatch.LogsService `inject:""` - PluginManager *manager.PluginManager `inject:""` - CacheService *localcache.CacheService `inject:""` - DatasourceCache datasources.CacheService `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - - node *centrifuge.Node - surveyCaller *survey.Caller - - // Websocket handlers - websocketHandler interface{} - pushWebsocketHandler interface{} - - // Full channel handler - channels map[string]models.ChannelHandler - channelsMu sync.RWMutex - - // The core internal features - GrafanaScope CoreGrafanaScope - - ManagedStreamRunner *managedstream.Runner - - contextGetter *liveplugin.ContextGetter - runStreamManager *runstream.Manager - storage *database.Storage -} - -func (g *GrafanaLive) getStreamPlugin(pluginID string) (backend.StreamHandler, error) { - plugin, ok := g.PluginManager.BackendPluginManager.Get(pluginID) - if !ok { - return nil, fmt.Errorf("plugin not found: %s", pluginID) +func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, routeRegister routing.RouteRegister, + logsService *cloudwatch.LogsService, pluginManager *manager.PluginManager, cacheService *localcache.CacheService, + dataSourceCache datasources.CacheService, sqlStore *sqlstore.SQLStore) (*GrafanaLive, error) { + g := &GrafanaLive{ + Cfg: cfg, + PluginContextProvider: plugCtxProvider, + RouteRegister: routeRegister, + LogsService: logsService, + PluginManager: pluginManager, + CacheService: cacheService, + DataSourceCache: dataSourceCache, + SQLStore: sqlStore, + channels: make(map[string]models.ChannelHandler), + GrafanaScope: CoreGrafanaScope{ + Features: make(map[string]models.ChannelHandlerFactory), + }, } - streamHandler, ok := plugin.(backend.StreamHandler) - if !ok { - return nil, fmt.Errorf("%s plugin does not implement StreamHandler: %#v", pluginID, plugin) - } - return streamHandler, nil -} -// AddMigration defines database migrations. -// This is an implementation of registry.DatabaseMigrator. -func (g *GrafanaLive) AddMigration(mg *migrator.Migrator) { - if g == nil || g.Cfg == nil || !g.Cfg.IsLiveConfigEnabled() { - return - } - database.AddLiveChannelMigrations(mg) -} - -func (g *GrafanaLive) Run(ctx context.Context) error { - if g.runStreamManager != nil { - // Only run stream manager if GrafanaLive properly initialized. - _ = g.runStreamManager.Run(ctx) - return g.node.Shutdown(context.Background()) - } - return nil -} - -var clientConcurrency = 8 - -func (g *GrafanaLive) IsHA() bool { - return g.Cfg != nil && g.Cfg.LiveHAEngine != "" -} - -// Init initializes Live service. -// Required to implement the registry.Service interface. -func (g *GrafanaLive) Init() error { logger.Debug("GrafanaLive initialization", "ha", g.IsHA()) // We use default config here as starting point. Default config contains // reasonable values for available options. - cfg := centrifuge.DefaultConfig + scfg := centrifuge.DefaultConfig - // cfg.LogLevel = centrifuge.LogLevelDebug - cfg.LogHandler = handleLog - cfg.LogLevel = centrifuge.LogLevelError - cfg.MetricsNamespace = "grafana_live" + // scfg.LogLevel = centrifuge.LogLevelDebug + scfg.LogHandler = handleLog + scfg.LogLevel = centrifuge.LogLevelError + scfg.MetricsNamespace = "grafana_live" // Node is the core object in Centrifuge library responsible for many useful // things. For example Node allows to publish messages to channels from server // side with its Publish method. - node, err := centrifuge.New(cfg) + node, err := centrifuge.New(scfg) if err != nil { - return err + return nil, err } g.node = node @@ -178,7 +114,7 @@ func (g *GrafanaLive) Init() error { for _, redisConf := range redisShardConfigs { redisShard, err := centrifuge.NewRedisShard(node, redisConf) if err != nil { - return fmt.Errorf("error connecting to Live Redis: %v", err) + return nil, fmt.Errorf("error connecting to Live Redis: %v", err) } redisShards = append(redisShards, redisShard) } @@ -199,7 +135,7 @@ func (g *GrafanaLive) Init() error { Shards: redisShards, }) if err != nil { - return fmt.Errorf("error creating Live Redis broker: %v", err) + return nil, fmt.Errorf("error creating Live Redis broker: %v", err) } node.SetBroker(broker) @@ -208,7 +144,7 @@ func (g *GrafanaLive) Init() error { Shards: redisShards, }) if err != nil { - return fmt.Errorf("error creating Live Redis presence manager: %v", err) + return nil, fmt.Errorf("error creating Live Redis presence manager: %v", err) } node.SetPresenceManager(presenceManager) } @@ -235,7 +171,7 @@ func (g *GrafanaLive) Init() error { }) cmd := redisClient.Ping() if _, err := cmd.Result(); err != nil { - return fmt.Errorf("error pinging Redis: %v", err) + return nil, fmt.Errorf("error pinging Redis: %v", err) } managedStreamRunner = managedstream.NewRunner( g.Publish, @@ -252,7 +188,7 @@ func (g *GrafanaLive) Init() error { g.surveyCaller = survey.NewCaller(managedStreamRunner, node) err = g.surveyCaller.SetupHandlers() if err != nil { - return err + return nil, err } // Set ConnectHandler called when client successfully connected to Node. Your code @@ -312,12 +248,12 @@ func (g *GrafanaLive) Init() error { // Run node. This method does not block. if err := node.Run(); err != nil { - return err + return nil, err } appURL, err := url.Parse(g.Cfg.AppURL) if err != nil { - return fmt.Errorf("error parsing AppURL %s: %w", g.Cfg.AppURL, err) + return nil, fmt.Errorf("error parsing AppURL %s: %w", g.Cfg.AppURL, err) } originPatterns := g.Cfg.LiveAllowedOrigins @@ -368,6 +304,62 @@ func (g *GrafanaLive) Init() error { group.Get("/push/:streamId", g.pushWebsocketHandler) }, middleware.ReqOrgAdmin) + return g, nil +} + +// GrafanaLive manages live real-time connections to Grafana (over WebSocket at this moment). +// The main concept here is Channel. Connections can subscribe to many channels. Each channel +// can have different permissions and properties but once a connection subscribed to a channel +// it starts receiving all messages published into this channel. Thus GrafanaLive is a PUB/SUB +// server. +type GrafanaLive struct { + PluginContextProvider *plugincontext.Provider + Cfg *setting.Cfg + RouteRegister routing.RouteRegister + LogsService *cloudwatch.LogsService + PluginManager *manager.PluginManager + CacheService *localcache.CacheService + DataSourceCache datasources.CacheService + SQLStore *sqlstore.SQLStore + + node *centrifuge.Node + surveyCaller *survey.Caller + + // Websocket handlers + websocketHandler interface{} + pushWebsocketHandler interface{} + + // Full channel handler + channels map[string]models.ChannelHandler + channelsMu sync.RWMutex + + // The core internal features + GrafanaScope CoreGrafanaScope + + ManagedStreamRunner *managedstream.Runner + + contextGetter *liveplugin.ContextGetter + runStreamManager *runstream.Manager + storage *database.Storage +} + +func (g *GrafanaLive) getStreamPlugin(pluginID string) (backend.StreamHandler, error) { + plugin, ok := g.PluginManager.BackendPluginManager.Get(pluginID) + if !ok { + return nil, fmt.Errorf("plugin not found: %s", pluginID) + } + streamHandler, ok := plugin.(backend.StreamHandler) + if !ok { + return nil, fmt.Errorf("%s plugin does not implement StreamHandler: %#v", pluginID, plugin) + } + return streamHandler, nil +} + +func (g *GrafanaLive) Run(ctx context.Context) error { + if g.runStreamManager != nil { + // Only run stream manager if GrafanaLive properly initialized. + return g.runStreamManager.Run(ctx) + } return nil } @@ -419,6 +411,12 @@ func checkAllowedOrigin(origin string, appURL *url.URL, originGlobs []glob.Glob) return false, nil } +var clientConcurrency = 8 + +func (g *GrafanaLive) IsHA() bool { + return g.Cfg != nil && g.Cfg.LiveHAEngine != "" +} + func runConcurrentlyIfNeeded(ctx context.Context, semaphore chan struct{}, fn func()) error { if cap(semaphore) > 1 { select { @@ -691,7 +689,7 @@ func (g *GrafanaLive) handleStreamScope(u *models.SignedInUser, namespace string } func (g *GrafanaLive) handleDatasourceScope(user *models.SignedInUser, namespace string) (models.ChannelHandlerFactory, error) { - ds, err := g.DatasourceCache.GetDatasourceByUID(namespace, user, false) + ds, err := g.DataSourceCache.GetDatasourceByUID(namespace, user, false) if err != nil { return nil, fmt.Errorf("error getting datasource: %w", err) } diff --git a/pkg/services/live/pushhttp/push.go b/pkg/services/live/pushhttp/push.go index 60b50141df5..cae30da5297 100644 --- a/pkg/services/live/pushhttp/push.go +++ b/pkg/services/live/pushhttp/push.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/live/convert" "github.com/grafana/grafana/pkg/services/live/pushurl" @@ -19,26 +18,24 @@ var ( logger = log.New("live.push_http") ) -func init() { - registry.RegisterServiceWithPriority(&Gateway{}, registry.Low) +func ProvideService(cfg *setting.Cfg, live *live.GrafanaLive) *Gateway { + logger.Info("Live Push Gateway initialization") + g := &Gateway{ + Cfg: cfg, + GrafanaLive: live, + converter: convert.NewConverter(), + } + return g } // Gateway receives data and translates it to Grafana Live publications. type Gateway struct { - Cfg *setting.Cfg `inject:""` - GrafanaLive *live.GrafanaLive `inject:""` + Cfg *setting.Cfg + GrafanaLive *live.GrafanaLive converter *convert.Converter } -// Init Gateway. -func (g *Gateway) Init() error { - logger.Info("Live Push Gateway initialization") - - g.converter = convert.NewConverter() - return nil -} - // Run Gateway. func (g *Gateway) Run(ctx context.Context) error { <-ctx.Done() diff --git a/pkg/services/login/authinfoservice/service.go b/pkg/services/login/authinfoservice/service.go index 2ce5630a270..95d72f3d8fd 100644 --- a/pkg/services/login/authinfoservice/service.go +++ b/pkg/services/login/authinfoservice/service.go @@ -7,33 +7,27 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/sqlstore" ) const genericOAuthModule = "oauth_generic_oauth" -func init() { - srv := &Implementation{} - - registry.Register(®istry.Descriptor{ - Name: "UserAuthInfo", - Instance: srv, - InitPriority: registry.MediumHigh, - }) -} - type Implementation struct { - Bus bus.Bus `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - UserProtectionService login.UserProtectionService `inject:""` + Bus bus.Bus + SQLStore *sqlstore.SQLStore + UserProtectionService login.UserProtectionService logger log.Logger } -func (s *Implementation) Init() error { - s.logger = log.New("login.authinfo") +func ProvideAuthInfoService(bus bus.Bus, store *sqlstore.SQLStore, service login.UserProtectionService) *Implementation { + s := &Implementation{ + Bus: bus, + SQLStore: store, + UserProtectionService: service, + logger: log.New("login.authinfo"), + } s.Bus.AddHandler(s.GetExternalUserInfoByLogin) s.Bus.AddHandler(s.GetAuthInfo) @@ -41,7 +35,7 @@ func (s *Implementation) Init() error { s.Bus.AddHandler(s.UpdateAuthInfo) s.Bus.AddHandler(s.DeleteAuthInfo) - return nil + return s } func (s *Implementation) getUserById(id int64) (bool, *models.User, error) { diff --git a/pkg/services/login/authinfoservice/user_auth_test.go b/pkg/services/login/authinfoservice/user_auth_test.go index 22351da7131..64b6f1d96a3 100644 --- a/pkg/services/login/authinfoservice/user_auth_test.go +++ b/pkg/services/login/authinfoservice/user_auth_test.go @@ -20,12 +20,7 @@ import ( //nolint:goconst func TestUserAuth(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) - srv := &Implementation{ - Bus: bus.New(), - SQLStore: sqlStore, - UserProtectionService: OSSUserProtectionImpl{}, - } - srv.Init() + srv := ProvideAuthInfoService(bus.New(), sqlStore, &OSSUserProtectionImpl{}) t.Run("Given 5 users", func(t *testing.T) { for i := 0; i < 5; i++ { diff --git a/pkg/services/login/authinfoservice/userprotection.go b/pkg/services/login/authinfoservice/userprotection.go index 7bc5f3bd02d..242ac85cb35 100644 --- a/pkg/services/login/authinfoservice/userprotection.go +++ b/pkg/services/login/authinfoservice/userprotection.go @@ -2,20 +2,15 @@ package authinfoservice import ( "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" ) -func init() { - registry.RegisterService(&OSSUserProtectionImpl{}) -} - type OSSUserProtectionImpl struct { } -func (OSSUserProtectionImpl) Init() error { - return nil +func ProvideOSSUserProtectionService() *OSSUserProtectionImpl { + return &OSSUserProtectionImpl{} } -func (OSSUserProtectionImpl) AllowUserMapping(_ *models.User, _ string) error { +func (*OSSUserProtectionImpl) AllowUserMapping(_ *models.User, _ string) error { return nil } diff --git a/pkg/services/login/loginservice/loginservice.go b/pkg/services/login/loginservice/loginservice.go index 1bc5ae667ef..a453848b677 100644 --- a/pkg/services/login/loginservice/loginservice.go +++ b/pkg/services/login/loginservice/loginservice.go @@ -7,32 +7,32 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/sqlstore" ) -func init() { - registry.RegisterService(&Implementation{}) -} - var ( logger = log.New("login.ext_user") ) -type Implementation struct { - SQLStore *sqlstore.SQLStore `inject:""` - Bus bus.Bus `inject:""` - AuthInfoService login.AuthInfoService `inject:""` - QuotaService *quota.QuotaService `inject:""` - TeamSync login.TeamSyncFunc +func ProvideService(sqlStore *sqlstore.SQLStore, bus bus.Bus, quotaService *quota.QuotaService, authInfoService login.AuthInfoService) *Implementation { + s := &Implementation{ + SQLStore: sqlStore, + Bus: bus, + QuotaService: quotaService, + AuthInfoService: authInfoService, + } + bus.AddHandler(s.UpsertUser) + return s } -func (ls *Implementation) Init() error { - ls.Bus.AddHandler(ls.UpsertUser) - - return nil +type Implementation struct { + SQLStore *sqlstore.SQLStore + Bus bus.Bus + AuthInfoService login.AuthInfoService + QuotaService *quota.QuotaService + TeamSync login.TeamSyncFunc } // CreateUser creates inserts a new one. diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 48115089b7d..2534f2e0367 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -60,7 +60,7 @@ type API struct { InstanceStore store.InstanceStore AlertingStore store.AlertingStore AdminConfigStore store.AdminConfigurationStore - DataProxy *datasourceproxy.DatasourceProxyService + DataProxy *datasourceproxy.DataSourceProxyService MultiOrgAlertmanager *notifier.MultiOrgAlertmanager StateManager *state.Manager } diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 6798a833f45..b01de85b7d8 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -10,7 +10,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/quota" - coreapi "github.com/grafana/grafana/pkg/api" + "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" @@ -304,5 +304,5 @@ func toNamespaceErrorResponse(err error) response.Response { if errors.Is(err, models.ErrDashboardIdentifierNotSet) { return ErrResp(http.StatusBadRequest, err, err.Error()) } - return coreapi.ToFolderErrorResponse(err) + return apierrors.ToFolderErrorResponse(err) } diff --git a/pkg/services/ngalert/api/lotex_prom.go b/pkg/services/ngalert/api/lotex_prom.go index 96442fd7915..933c86d5c68 100644 --- a/pkg/services/ngalert/api/lotex_prom.go +++ b/pkg/services/ngalert/api/lotex_prom.go @@ -76,7 +76,7 @@ func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Respon } func (p *LotexProm) getEndpoints(ctx *models.ReqContext) (*promEndpoints, error) { - ds, err := p.DataProxy.DatasourceCache.GetDatasource(ctx.ParamsInt64("Recipient"), ctx.SignedInUser, ctx.SkipCache) + ds, err := p.DataProxy.DataSourceCache.GetDatasource(ctx.ParamsInt64("Recipient"), ctx.SignedInUser, ctx.SkipCache) if err != nil { return nil, err } diff --git a/pkg/services/ngalert/api/lotex_ruler.go b/pkg/services/ngalert/api/lotex_ruler.go index 3f1476be812..af0fa3500a9 100644 --- a/pkg/services/ngalert/api/lotex_ruler.go +++ b/pkg/services/ngalert/api/lotex_ruler.go @@ -150,7 +150,7 @@ func (r *LotexRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimo } func (r *LotexRuler) getPrefix(ctx *models.ReqContext) (string, error) { - ds, err := r.DataProxy.DatasourceCache.GetDatasource(ctx.ParamsInt64("Recipient"), ctx.SignedInUser, ctx.SkipCache) + ds, err := r.DataProxy.DataSourceCache.GetDatasource(ctx.ParamsInt64("Recipient"), ctx.SignedInUser, ctx.SkipCache) if err != nil { return "", err } diff --git a/pkg/services/ngalert/api/util.go b/pkg/services/ngalert/api/util.go index 444c2cdd53d..e863c02b977 100644 --- a/pkg/services/ngalert/api/util.go +++ b/pkg/services/ngalert/api/util.go @@ -83,7 +83,7 @@ func replacedResponseWriter(ctx *models.ReqContext) (*models.ReqContext, *respon } type AlertingProxy struct { - DataProxy *datasourceproxy.DatasourceProxyService + DataProxy *datasourceproxy.DataSourceProxyService } // withReq proxies a different request diff --git a/pkg/services/ngalert/metrics/metrics.go b/pkg/services/ngalert/metrics/metrics.go index e0f37a99e46..8e898d9a434 100644 --- a/pkg/services/ngalert/metrics/metrics.go +++ b/pkg/services/ngalert/metrics/metrics.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -24,7 +23,15 @@ const ( ProxyBackend = "proxy" ) -var GlobalMetrics = NewMetrics(prometheus.DefaultRegisterer) +// ProvideService is a Metrics factory. +func ProvideService() *Metrics { + return NewMetrics(prometheus.DefaultRegisterer) +} + +// ProvideServiceForTest is a Metrics factory used for test. +func ProvideServiceForTest() *Metrics { + return NewMetrics(prometheus.NewRegistry()) +} type Metrics struct { *metrics.Alerts @@ -39,21 +46,6 @@ type Metrics struct { GroupRules *prometheus.GaugeVec } -func init() { - registry.RegisterService(GlobalMetrics) -} - -func (m *Metrics) Init() error { - return nil -} - -// SwapRegisterer overwrites the prometheus register used by a *Metrics in place. -// It's used by tests to prevent duplicate registration errors -func (m *Metrics) SwapRegisterer(r prometheus.Registerer) { - next := NewMetrics(r) - *m = *next -} - func NewMetrics(r prometheus.Registerer) *Metrics { return &Metrics{ Registerer: r, diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index a0f8121fc15..dc68e16e76a 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -4,24 +4,22 @@ import ( "context" "time" + "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/services/quota" "golang.org/x/sync/errgroup" + "github.com/grafana/grafana/pkg/services/ngalert/api" + "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/state" - - "github.com/benbjohnson/clock" + "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/ngalert/api" - "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/schedule" - "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb" @@ -34,23 +32,48 @@ const ( // because this could cause existing alert definition // with intervals that are not exactly divided by this number // not to be evaluated - baseIntervalSeconds = 10 + defaultBaseIntervalSeconds = 10 // default alert definition interval - defaultIntervalSeconds int64 = 6 * baseIntervalSeconds + defaultIntervalSeconds int64 = 6 * defaultBaseIntervalSeconds ) +func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister, + sqlStore *sqlstore.SQLStore, dataService *tsdb.Service, dataProxy *datasourceproxy.DataSourceProxyService, + quotaService *quota.QuotaService, m *metrics.Metrics) (*AlertNG, error) { + ng := &AlertNG{ + Cfg: cfg, + DataSourceCache: dataSourceCache, + RouteRegister: routeRegister, + SQLStore: sqlStore, + DataService: dataService, + DataProxy: dataProxy, + QuotaService: quotaService, + Metrics: m, + Log: log.New("ngalert"), + } + + if ng.IsDisabled() { + return ng, nil + } + + if err := ng.init(); err != nil { + return nil, err + } + + return ng, nil +} + // AlertNG is the service for evaluating the condition of an alert definition. type AlertNG struct { - Cfg *setting.Cfg `inject:""` - Log log.Logger - Metrics *metrics.Metrics `inject:""` - - DatasourceCache datasources.CacheService `inject:""` - RouteRegister routing.RouteRegister `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - DataService *tsdb.Service `inject:""` - DataProxy *datasourceproxy.DatasourceProxyService `inject:""` - QuotaService *quota.QuotaService `inject:""` + Cfg *setting.Cfg + DataSourceCache datasources.CacheService + RouteRegister routing.RouteRegister + SQLStore *sqlstore.SQLStore + DataService *tsdb.Service + DataProxy *datasourceproxy.DataSourceProxyService + QuotaService *quota.QuotaService + Metrics *metrics.Metrics + Log log.Logger schedule schedule.ScheduleService stateManager *state.Manager @@ -58,14 +81,12 @@ type AlertNG struct { MultiOrgAlertmanager *notifier.MultiOrgAlertmanager } -func init() { - registry.RegisterService(&AlertNG{}) -} - -// Init initializes the AlertingService. -func (ng *AlertNG) Init() error { - ng.Log = log.New("ngalert") - baseInterval := baseIntervalSeconds * time.Second +func (ng *AlertNG) init() error { + baseInterval := ng.Cfg.AlertingBaseInterval + if baseInterval <= 0 { + baseInterval = defaultBaseIntervalSeconds + } + baseInterval *= time.Second store := &store.DBstore{ BaseInterval: baseInterval, @@ -95,13 +116,15 @@ func (ng *AlertNG) Init() error { Metrics: ng.Metrics, AdminConfigPollInterval: ng.Cfg.AdminConfigPollInterval, } + stateManager := state.NewManager(ng.Log, ng.Metrics, store, store) + schedule := schedule.NewScheduler(schedCfg, ng.DataService, ng.Cfg.AppURL, stateManager) - ng.stateManager = state.NewManager(ng.Log, ng.Metrics, store, store) - ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService, ng.Cfg.AppURL, ng.stateManager) + ng.stateManager = stateManager + ng.schedule = schedule api := api.API{ Cfg: ng.Cfg, - DatasourceCache: ng.DatasourceCache, + DatasourceCache: ng.DataSourceCache, RouteRegister: ng.RouteRegister, DataService: ng.DataService, Schedule: ng.schedule, diff --git a/pkg/services/ngalert/schedule/schedule_test.go b/pkg/services/ngalert/schedule/schedule_test.go index 87ecb8401af..fbb0b6ef717 100644 --- a/pkg/services/ngalert/schedule/schedule_test.go +++ b/pkg/services/ngalert/schedule/schedule_test.go @@ -8,19 +8,17 @@ import ( "testing" "time" + "github.com/benbjohnson/clock" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/tests" - - "github.com/benbjohnson/clock" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,8 +32,9 @@ type evalAppliedInfo struct { } func TestWarmStateCache(t *testing.T) { - evaluationTime, _ := time.Parse("2006-01-02", "2021-03-25") - dbstore := tests.SetupTestEnv(t, 1) + evaluationTime, err := time.Parse("2006-01-02", "2021-03-25") + require.NoError(t, err) + _, dbstore := tests.SetupTestEnv(t, 1) rule := tests.CreateTestAlertRule(t, dbstore, 600) @@ -92,8 +91,6 @@ func TestWarmStateCache(t *testing.T) { } _ = dbstore.SaveAlertInstance(saveCmd2) - t.Cleanup(registry.ClearOverrides) - schedCfg := schedule.SchedulerCfg{ C: clock.NewMock(), BaseInterval: time.Second, @@ -121,8 +118,7 @@ func TestWarmStateCache(t *testing.T) { } func TestAlertingTicker(t *testing.T) { - dbstore := tests.SetupTestEnv(t, 1) - t.Cleanup(registry.ClearOverrides) + _, dbstore := tests.SetupTestEnv(t, 1) alerts := make([]*models.AlertRule, 0) diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 90ac76cd778..619ed97d5ef 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" @@ -18,16 +18,12 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/setting" - - "github.com/benbjohnson/clock" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" ) func TestSendingToExternalAlertmanager(t *testing.T) { - t.Cleanup(registry.ClearOverrides) - fakeAM := NewFakeExternalAlertmanager(t) defer fakeAM.Close() fakeRuleStore := newFakeRuleStore(t) @@ -93,8 +89,6 @@ func TestSendingToExternalAlertmanager(t *testing.T) { } func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) { - t.Cleanup(registry.ClearOverrides) - fakeAM := NewFakeExternalAlertmanager(t) defer fakeAM.Close() fakeRuleStore := newFakeRuleStore(t) diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 39cfe682607..465e7ab539d 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/ngalert/tests" "github.com/stretchr/testify/require" @@ -874,7 +873,7 @@ func TestStaleResultsHandler(t *testing.T) { t.Fatalf("error parsing date format: %s", err.Error()) } - dbstore := tests.SetupTestEnv(t, 1) + _, dbstore := tests.SetupTestEnv(t, 1) rule := tests.CreateTestAlertRule(t, dbstore, 600) @@ -901,8 +900,6 @@ func TestStaleResultsHandler(t *testing.T) { } _ = dbstore.SaveAlertInstance(saveCmd2) - t.Cleanup(registry.ClearOverrides) - testCases := []struct { desc string evalResults []eval.Results diff --git a/pkg/services/ngalert/store/instance_database_test.go b/pkg/services/ngalert/store/instance_database_test.go index ee056d2d300..2b34ae23b75 100644 --- a/pkg/services/ngalert/store/instance_database_test.go +++ b/pkg/services/ngalert/store/instance_database_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/tests" @@ -26,8 +25,7 @@ func mockTimeNow() { } func TestAlertInstanceOperations(t *testing.T) { - dbstore := tests.SetupTestEnv(t, baseIntervalSeconds) - t.Cleanup(registry.ClearOverrides) + _, dbstore := tests.SetupTestEnv(t, baseIntervalSeconds) alertRule1 := tests.CreateTestAlertRule(t, dbstore, 60) orgID := alertRule1.OrgID diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index e3044416c44..6198ea4399f 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/infra/log" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/prometheus/client_golang/prometheus" @@ -17,61 +19,33 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/api/routing" - - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" ) // SetupTestEnv initializes a store to used by the tests. -func SetupTestEnv(t *testing.T, baseIntervalSeconds int64) *store.DBstore { +func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *store.DBstore) { + t.Helper() + cfg := setting.NewCfg() + cfg.AlertingBaseInterval = baseInterval // AlertNG is disabled by default and only if it's enabled // its database migrations run and the relative database tables are created cfg.FeatureToggles = map[string]bool{"ngalert": true} - ng := overrideAlertNGInRegistry(t, cfg) - ng.SQLStore = sqlstore.InitTestDB(t) - - err := ng.Init() + m := metrics.NewMetrics(prometheus.NewRegistry()) + ng, err := ngalert.ProvideService(cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t), nil, nil, nil, + m) require.NoError(t, err) - return &store.DBstore{ + return ng, &store.DBstore{ SQLStore: ng.SQLStore, - BaseInterval: time.Duration(baseIntervalSeconds) * time.Second, + BaseInterval: baseInterval * time.Second, Logger: log.New("ngalert-test"), } } -func overrideAlertNGInRegistry(t *testing.T, cfg *setting.Cfg) ngalert.AlertNG { - ng := ngalert.AlertNG{ - Cfg: cfg, - RouteRegister: routing.NewRouteRegister(), - Log: log.New("ngalert-test"), - Metrics: metrics.NewMetrics(prometheus.NewRegistry()), - } - - // hook for initialising the service after the Cfg is populated - // so that database migrations will run - overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) { - if _, ok := descriptor.Instance.(*ngalert.AlertNG); ok { - return ®istry.Descriptor{ - Name: descriptor.Name, - Instance: &ng, - InitPriority: descriptor.InitPriority, - }, true - } - return nil, false - } - - registry.RegisterOverride(overrideServiceFunc) - - return ng -} - -// createTestAlertRule creates a dummy alert definition to be used by the tests. +// CreateTestAlertRule creates a dummy alert definition to be used by the tests. func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds int64) *models.AlertRule { d := rand.Intn(1000) ruleGroup := fmt.Sprintf("ruleGroup-%d", d) diff --git a/pkg/services/notifications/codes.go b/pkg/services/notifications/codes.go index ea9beb30cc3..4f95316d322 100644 --- a/pkg/services/notifications/codes.go +++ b/pkg/services/notifications/codes.go @@ -49,12 +49,12 @@ func createTimeLimitCode(data string, minutes int, startInf interface{}) (string } // verify time limit code -func validateUserEmailCode(user *models.User, code string) (bool, error) { +func validateUserEmailCode(cfg *setting.Cfg, user *models.User, code string) (bool, error) { if len(code) <= 18 { return false, nil } - minutes := setting.EmailCodeValidMinutes + minutes := cfg.EmailCodeValidMinutes code = code[:timeLimitCodeLength] // split code @@ -94,8 +94,8 @@ func getLoginForEmailCode(code string) string { return string(b) } -func createUserEmailCode(u *models.User, startInf interface{}) (string, error) { - minutes := setting.EmailCodeValidMinutes +func createUserEmailCode(cfg *setting.Cfg, u *models.User, startInf interface{}) (string, error) { + minutes := cfg.EmailCodeValidMinutes data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands code, err := createTimeLimitCode(data, minutes, startInf) if err != nil { diff --git a/pkg/services/notifications/codes_test.go b/pkg/services/notifications/codes_test.go index d2b1f3a6179..f470c2f8b5e 100644 --- a/pkg/services/notifications/codes_test.go +++ b/pkg/services/notifications/codes_test.go @@ -10,10 +10,11 @@ import ( func TestEmailCodes(t *testing.T) { Convey("When generating code", t, func() { - setting.EmailCodeValidMinutes = 120 + cfg := setting.NewCfg() + cfg.EmailCodeValidMinutes = 120 user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} - code, err := createUserEmailCode(user, nil) + code, err := createUserEmailCode(cfg, user, nil) So(err, ShouldBeNil) Convey("getLoginForCode should return login", func() { @@ -22,14 +23,14 @@ func TestEmailCodes(t *testing.T) { }) Convey("Can verify valid code", func() { - isValid, err := validateUserEmailCode(user, code) + isValid, err := validateUserEmailCode(cfg, user, code) So(err, ShouldBeNil) So(isValid, ShouldBeTrue) }) Convey("Cannot verify in-valid code", func() { code = "ASD" - isValid, err := validateUserEmailCode(user, code) + isValid, err := validateUserEmailCode(cfg, user, code) So(err, ShouldBeNil) So(isValid, ShouldBeFalse) }) diff --git a/pkg/services/notifications/email.go b/pkg/services/notifications/email.go index 08cdd6b445a..4241425ad02 100644 --- a/pkg/services/notifications/email.go +++ b/pkg/services/notifications/email.go @@ -24,11 +24,11 @@ type Message struct { AttachedFiles []*AttachedFile } -func setDefaultTemplateData(data map[string]interface{}, u *models.User) { +func setDefaultTemplateData(cfg *setting.Cfg, data map[string]interface{}, u *models.User) { data["AppUrl"] = setting.AppUrl data["BuildVersion"] = setting.BuildVersion data["BuildStamp"] = setting.BuildStamp - data["EmailCodeValidHours"] = setting.EmailCodeValidMinutes / 60 + data["EmailCodeValidHours"] = cfg.EmailCodeValidMinutes / 60 data["Subject"] = map[string]interface{}{} if u != nil { data["Name"] = u.NameOrFallback() diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 070230f0718..f278c865300 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -15,13 +15,12 @@ import ( "strconv" "strings" - gomail "gopkg.in/mail.v2" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + gomail "gopkg.in/mail.v2" ) var ( @@ -185,7 +184,7 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) ( data = make(map[string]interface{}, 10) } - setDefaultTemplateData(data, nil) + setDefaultTemplateData(ns.Cfg, data, nil) body := make(map[string]string) for _, contentType := range ns.Cfg.Smtp.ContentTypes { diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index a62e381e307..4cc2be70859 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -23,23 +22,14 @@ var tmplResetPassword = "reset_password" var tmplSignUpStarted = "signup_started" var tmplWelcomeOnSignUp = "welcome_on_signup" -func init() { - registry.RegisterService(&NotificationService{}) -} - -type NotificationService struct { - Bus bus.Bus `inject:""` - Cfg *setting.Cfg `inject:""` - - mailQueue chan *Message - webhookQueue chan *Webhook - log log.Logger -} - -func (ns *NotificationService) Init() error { - ns.log = log.New("notifications") - ns.mailQueue = make(chan *Message, 10) - ns.webhookQueue = make(chan *Webhook, 10) +func ProvideService(bus bus.Bus, cfg *setting.Cfg) (*NotificationService, error) { + ns := &NotificationService{ + Bus: bus, + Cfg: cfg, + log: log.New("notifications"), + mailQueue: make(chan *Message, 10), + webhookQueue: make(chan *Webhook, 10), + } ns.Bus.AddHandler(ns.sendResetPasswordEmail) ns.Bus.AddHandler(ns.validateResetPasswordCode) @@ -60,19 +50,27 @@ func (ns *NotificationService) Init() error { templatePattern := filepath.Join(ns.Cfg.StaticRootPath, pattern) _, err := mailTemplates.ParseGlob(templatePattern) if err != nil { - return err + return nil, err } } if !util.IsEmail(ns.Cfg.Smtp.FromAddress) { - return errors.New("invalid email address for SMTP from_address config") + return nil, errors.New("invalid email address for SMTP from_address config") } - if setting.EmailCodeValidMinutes == 0 { - setting.EmailCodeValidMinutes = 120 + if cfg.EmailCodeValidMinutes == 0 { + cfg.EmailCodeValidMinutes = 120 } + return ns, nil +} - return nil +type NotificationService struct { + Bus bus.Bus + Cfg *setting.Cfg + + mailQueue chan *Message + webhookQueue chan *Webhook + log log.Logger } func (ns *NotificationService) Run(ctx context.Context) error { @@ -151,7 +149,7 @@ func (ns *NotificationService) sendEmailCommandHandler(cmd *models.SendEmailComm } func (ns *NotificationService) sendResetPasswordEmail(cmd *models.SendResetPasswordEmailCommand) error { - code, err := createUserEmailCode(cmd.User, nil) + code, err := createUserEmailCode(ns.Cfg, cmd.User, nil) if err != nil { return err } @@ -176,7 +174,7 @@ func (ns *NotificationService) validateResetPasswordCode(query *models.ValidateR return err } - validEmailCode, err := validateUserEmailCode(userQuery.Result, query.Code) + validEmailCode, err := validateUserEmailCode(ns.Cfg, userQuery.Result, query.Code) if err != nil { return err } diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go index 566dbee2017..6ab461cc5ee 100644 --- a/pkg/services/notifications/notifications_test.go +++ b/pkg/services/notifications/notifications_test.go @@ -22,7 +22,7 @@ func TestNotificationService(t *testing.T) { ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"} ns.Bus = bus.New() - err := ns.Init() + ns, err := ProvideService(bus.New(), ns.Cfg) require.NoError(t, err) t.Run("When sending reset email password", func(t *testing.T) { diff --git a/pkg/services/notifications/send_email_integration_test.go b/pkg/services/notifications/send_email_integration_test.go index cf819c7427f..ef1b5496b90 100644 --- a/pkg/services/notifications/send_email_integration_test.go +++ b/pkg/services/notifications/send_email_integration_test.go @@ -24,9 +24,6 @@ func TestEmailIntegrationTest(t *testing.T) { ns.Cfg.Smtp.FromName = "Grafana Admin" ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"} - err := ns.Init() - So(err, ShouldBeNil) - Convey("When sending reset email password", func() { cmd := &models.SendEmailCommand{ diff --git a/pkg/services/oauthtoken/oauth_token.go b/pkg/services/oauthtoken/oauth_token.go index 0e85e2844dc..b55f24e98f6 100644 --- a/pkg/services/oauthtoken/oauth_token.go +++ b/pkg/services/oauthtoken/oauth_token.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "golang.org/x/oauth2" ) @@ -16,12 +15,8 @@ var ( logger = log.New("oauthtoken") ) -func init() { - registry.RegisterService(&Service{}) -} - type Service struct { - SocialService social.Service `inject:""` + SocialService social.Service } type OAuthTokenService interface { @@ -29,8 +24,10 @@ type OAuthTokenService interface { IsOAuthPassThruEnabled(*models.DataSource) bool } -func (o *Service) Init() error { - return nil +func ProvideService(socialService social.Service) *Service { + return &Service{ + SocialService: socialService, + } } // GetCurrentOAuthToken returns the OAuth token, if any, for the authenticated user. Will try to refresh the token if it has expired. diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 6ec45b34389..df0fa51b8aa 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -17,6 +17,21 @@ import ( "github.com/grafana/grafana/pkg/util/errutil" ) +func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginManager plugifaces.Manager) ( + *ProvisioningServiceImpl, error) { + s := &ProvisioningServiceImpl{ + Cfg: cfg, + SQLStore: sqlStore, + PluginManager: pluginManager, + log: log.New("provisioning"), + newDashboardProvisioner: dashboards.New, + provisionNotifiers: notifiers.Provision, + provisionDatasources: datasources.Provision, + provisionPlugins: plugins.Provision, + } + return s, nil +} + type ProvisioningService interface { registry.BackgroundService RunInitProvisioners() error @@ -28,17 +43,9 @@ type ProvisioningService interface { GetAllowUIUpdatesFromConfig(name string) bool } -func init() { - registry.Register(®istry.Descriptor{ - Name: "ProvisioningService", - Instance: NewProvisioningServiceImpl(), - InitPriority: registry.Low, - }) -} - // Add a public constructor for overriding service to be able to instantiate OSS as fallback -func NewProvisioningServiceImpl() *provisioningServiceImpl { - return &provisioningServiceImpl{ +func NewProvisioningServiceImpl() *ProvisioningServiceImpl { + return &ProvisioningServiceImpl{ log: log.New("provisioning"), newDashboardProvisioner: dashboards.New, provisionNotifiers: notifiers.Provision, @@ -53,8 +60,8 @@ func newProvisioningServiceImpl( provisionNotifiers func(string) error, provisionDatasources func(string) error, provisionPlugins func(string, plugifaces.Manager) error, -) *provisioningServiceImpl { - return &provisioningServiceImpl{ +) *ProvisioningServiceImpl { + return &ProvisioningServiceImpl{ log: log.New("provisioning"), newDashboardProvisioner: newDashboardProvisioner, provisionNotifiers: provisionNotifiers, @@ -63,10 +70,10 @@ func newProvisioningServiceImpl( } } -type provisioningServiceImpl struct { - Cfg *setting.Cfg `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - PluginManager plugifaces.Manager `inject:""` +type ProvisioningServiceImpl struct { + Cfg *setting.Cfg + SQLStore *sqlstore.SQLStore + PluginManager plugifaces.Manager log log.Logger pollingCtxCancel context.CancelFunc newDashboardProvisioner dashboards.DashboardProvisionerFactory @@ -77,11 +84,7 @@ type provisioningServiceImpl struct { mutex sync.Mutex } -func (ps *provisioningServiceImpl) Init() error { - return nil -} - -func (ps *provisioningServiceImpl) RunInitProvisioners() error { +func (ps *ProvisioningServiceImpl) RunInitProvisioners() error { err := ps.ProvisionDatasources() if err != nil { return err @@ -100,7 +103,7 @@ func (ps *provisioningServiceImpl) RunInitProvisioners() error { return nil } -func (ps *provisioningServiceImpl) Run(ctx context.Context) error { +func (ps *ProvisioningServiceImpl) Run(ctx context.Context) error { err := ps.ProvisionDashboards() if err != nil { ps.log.Error("Failed to provision dashboard", "error", err) @@ -129,25 +132,25 @@ func (ps *provisioningServiceImpl) Run(ctx context.Context) error { } } -func (ps *provisioningServiceImpl) ProvisionDatasources() error { +func (ps *ProvisioningServiceImpl) ProvisionDatasources() error { datasourcePath := filepath.Join(ps.Cfg.ProvisioningPath, "datasources") err := ps.provisionDatasources(datasourcePath) return errutil.Wrap("Datasource provisioning error", err) } -func (ps *provisioningServiceImpl) ProvisionPlugins() error { +func (ps *ProvisioningServiceImpl) ProvisionPlugins() error { appPath := filepath.Join(ps.Cfg.ProvisioningPath, "plugins") err := ps.provisionPlugins(appPath, ps.PluginManager) return errutil.Wrap("app provisioning error", err) } -func (ps *provisioningServiceImpl) ProvisionNotifications() error { +func (ps *ProvisioningServiceImpl) ProvisionNotifications() error { alertNotificationsPath := filepath.Join(ps.Cfg.ProvisioningPath, "notifiers") err := ps.provisionNotifiers(alertNotificationsPath) return errutil.Wrap("Alert notification provisioning error", err) } -func (ps *provisioningServiceImpl) ProvisionDashboards() error { +func (ps *ProvisioningServiceImpl) ProvisionDashboards() error { dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards") dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath, ps.SQLStore) if err != nil { @@ -170,15 +173,15 @@ func (ps *provisioningServiceImpl) ProvisionDashboards() error { return nil } -func (ps *provisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string { +func (ps *ProvisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string { return ps.dashboardProvisioner.GetProvisionerResolvedPath(name) } -func (ps *provisioningServiceImpl) GetAllowUIUpdatesFromConfig(name string) bool { +func (ps *ProvisioningServiceImpl) GetAllowUIUpdatesFromConfig(name string) bool { return ps.dashboardProvisioner.GetAllowUIUpdatesFromConfig(name) } -func (ps *provisioningServiceImpl) cancelPolling() { +func (ps *ProvisioningServiceImpl) cancelPolling() { if ps.pollingCtxCancel != nil { ps.log.Debug("Stop polling for dashboard changes") ps.pollingCtxCancel() diff --git a/pkg/services/provisioning/provisioning_test.go b/pkg/services/provisioning/provisioning_test.go index ebbe162d21d..9979b47d6e8 100644 --- a/pkg/services/provisioning/provisioning_test.go +++ b/pkg/services/provisioning/provisioning_test.go @@ -76,7 +76,7 @@ type serviceTestStruct struct { cancel func() mock *dashboards.ProvisionerMock - service *provisioningServiceImpl + service *ProvisioningServiceImpl } func setup() *serviceTestStruct { diff --git a/pkg/services/quota/quota.go b/pkg/services/quota/quota.go index 945078e4178..7cb1cb115bd 100644 --- a/pkg/services/quota/quota.go +++ b/pkg/services/quota/quota.go @@ -5,23 +5,21 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) var ErrInvalidQuotaTarget = errors.New("invalid quota target") -func init() { - registry.RegisterService(&QuotaService{}) +func ProvideService(cfg *setting.Cfg, tokenService models.UserTokenService) *QuotaService { + return &QuotaService{ + Cfg: cfg, + AuthTokenService: tokenService, + } } type QuotaService struct { - AuthTokenService models.UserTokenService `inject:""` - Cfg *setting.Cfg `inject:""` -} - -func (qs *QuotaService) Init() error { - return nil + AuthTokenService models.UserTokenService + Cfg *setting.Cfg } func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) { diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index 254a4726735..383c97a79d5 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -18,18 +18,12 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) func init() { remotecache.Register(&RenderUser{}) - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: &RenderingService{}, - InitPriority: registry.High, - }) } const ServiceName = "RenderingService" @@ -50,39 +44,49 @@ type RenderingService struct { inProgressCount int version string - Cfg *setting.Cfg `inject:""` - RemoteCacheService *remotecache.RemoteCache `inject:""` - PluginManager plugins.Manager `inject:""` + Cfg *setting.Cfg + RemoteCacheService *remotecache.RemoteCache + PluginManager plugins.Manager } -func (rs *RenderingService) Init() error { - rs.log = log.New("rendering") - +func ProvideService(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, pm plugins.Manager) (*RenderingService, error) { // ensure ImagesDir exists - err := os.MkdirAll(rs.Cfg.ImagesDir, 0700) + err := os.MkdirAll(cfg.ImagesDir, 0700) if err != nil { - return fmt.Errorf("failed to create images directory %q: %w", rs.Cfg.ImagesDir, err) + return nil, fmt.Errorf("failed to create images directory %q: %w", cfg.ImagesDir, err) } // ensure CSVsDir exists - err = os.MkdirAll(rs.Cfg.CSVsDir, 0700) + err = os.MkdirAll(cfg.CSVsDir, 0700) if err != nil { - return fmt.Errorf("failed to create CSVs directory %q: %w", rs.Cfg.CSVsDir, err) + return nil, fmt.Errorf("failed to create CSVs directory %q: %w", cfg.CSVsDir, err) } + var domain string // set value used for domain attribute of renderKey cookie switch { - case rs.Cfg.RendererUrl != "": + case cfg.RendererUrl != "": // RendererCallbackUrl has already been passed, it won't generate an error. - u, _ := url.Parse(rs.Cfg.RendererCallbackUrl) - rs.domain = u.Hostname() - case rs.Cfg.HTTPAddr != setting.DefaultHTTPAddr: - rs.domain = rs.Cfg.HTTPAddr + u, err := url.Parse(cfg.RendererCallbackUrl) + if err != nil { + return nil, err + } + + domain = u.Hostname() + case cfg.HTTPAddr != setting.DefaultHTTPAddr: + domain = cfg.HTTPAddr default: - rs.domain = "localhost" + domain = "localhost" } - return nil + s := &RenderingService{ + Cfg: cfg, + RemoteCacheService: remoteCache, + PluginManager: pm, + log: log.New("rendering"), + domain: domain, + } + return s, nil } func (rs *RenderingService) Run(ctx context.Context) error { diff --git a/pkg/services/rendering/rendering_test.go b/pkg/services/rendering/rendering_test.go index 3390b0e9225..198d9a42074 100644 --- a/pkg/services/rendering/rendering_test.go +++ b/pkg/services/rendering/rendering_test.go @@ -9,8 +9,9 @@ import ( func TestGetUrl(t *testing.T) { path := "render/d-solo/5SdHCadmz/panel-tests-graph?orgId=1&from=1587390211965&to=1587393811965&panelId=5&width=1000&height=500&tz=Europe%2FStockholm" + cfg := setting.NewCfg() rs := &RenderingService{ - Cfg: setting.NewCfg(), + Cfg: cfg, } t.Run("When renderer and callback url configured should return callback url plus path", func(t *testing.T) { diff --git a/pkg/services/schemaloader/schemaloader.go b/pkg/services/schemaloader/schemaloader.go index 647c6b206eb..08b7ca154de 100644 --- a/pkg/services/schemaloader/schemaloader.go +++ b/pkg/services/schemaloader/schemaloader.go @@ -10,17 +10,9 @@ import ( "github.com/grafana/grafana/pkg/schema/load" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: &SchemaLoaderService{}, - }) -} - const ServiceName = "SchemaLoader" var baseLoadPath load.BaseLoadPaths = load.BaseLoadPaths{ @@ -34,22 +26,25 @@ type RenderUser struct { OrgRole string } +func ProvideService(cfg *setting.Cfg) (*SchemaLoaderService, error) { + dashFam, err := load.BaseDashboardFamily(baseLoadPath) + if err != nil { + return nil, fmt.Errorf("failed to load dashboard cue schema from path %q: %w", baseLoadPath, err) + } + s := &SchemaLoaderService{ + Cfg: cfg, + DashFamily: dashFam, + log: log.New("schemaloader"), + } + return s, nil +} + type SchemaLoaderService struct { log log.Logger DashFamily schema.VersionedCueSchema - Cfg *setting.Cfg `inject:""` + Cfg *setting.Cfg } -func (rs *SchemaLoaderService) Init() error { - rs.log = log.New("schemaloader") - var err error - rs.DashFamily, err = load.BaseDashboardFamily(baseLoadPath) - - if err != nil { - return fmt.Errorf("failed to load dashboard cue schema from path %q: %w", baseLoadPath, err) - } - return nil -} func (rs *SchemaLoaderService) IsDisabled() bool { if rs.Cfg == nil { return true diff --git a/pkg/services/search/service.go b/pkg/services/search/service.go index 2d6ff30094c..a42294b792e 100644 --- a/pkg/services/search/service.go +++ b/pkg/services/search/service.go @@ -7,11 +7,19 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" ) -func init() { - registry.RegisterService(&SearchService{}) +func ProvideService(cfg *setting.Cfg, bus bus.Bus) *SearchService { + s := &SearchService{ + Cfg: cfg, + Bus: bus, + sortOptions: map[string]SortOption{ + SortAlphaAsc.Name: SortAlphaAsc, + SortAlphaDesc.Name: SortAlphaDesc, + }, + } + s.Bus.AddHandler(s.searchHandler) + return s } type Query struct { @@ -51,22 +59,12 @@ type FindPersistedDashboardsQuery struct { } type SearchService struct { - Bus bus.Bus `inject:""` - Cfg *setting.Cfg `inject:""` + Bus bus.Bus + Cfg *setting.Cfg sortOptions map[string]SortOption } -func (s *SearchService) Init() error { - s.Bus.AddHandler(s.searchHandler) - s.sortOptions = map[string]SortOption{ - SortAlphaAsc.Name: SortAlphaAsc, - SortAlphaDesc.Name: SortAlphaDesc, - } - - return nil -} - func (s *SearchService) searchHandler(query *Query) error { dashboardQuery := FindPersistedDashboardsQuery{ Title: query.Title, diff --git a/pkg/services/shorturls/short_url_service.go b/pkg/services/shorturls/short_url_service.go index bc379518598..09470283835 100644 --- a/pkg/services/shorturls/short_url_service.go +++ b/pkg/services/shorturls/short_url_service.go @@ -5,15 +5,16 @@ import ( "time" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" ) var getTime = time.Now -func init() { - registry.RegisterService(&ShortURLService{}) +func ProvideService(sqlStore *sqlstore.SQLStore) *ShortURLService { + return &ShortURLService{ + SQLStore: sqlStore, + } } type Service interface { @@ -24,11 +25,7 @@ type Service interface { } type ShortURLService struct { - SQLStore *sqlstore.SQLStore `inject:""` -} - -func (s *ShortURLService) Init() error { - return nil + SQLStore *sqlstore.SQLStore } func (s ShortURLService) GetShortURLByUID(ctx context.Context, user *models.SignedInUser, uid string) (*models.ShortUrl, error) { diff --git a/pkg/services/live/database/migrations.go b/pkg/services/sqlstore/migrations/live_mig.go similarity index 93% rename from pkg/services/live/database/migrations.go rename to pkg/services/sqlstore/migrations/live_mig.go index 12d7a6f9bd1..e8bcffd941d 100644 --- a/pkg/services/live/database/migrations.go +++ b/pkg/services/sqlstore/migrations/live_mig.go @@ -1,10 +1,10 @@ -package database +package migrations import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" // For now disable migration. For now we are using local cache as storage to evaluate ideas. // This will be turned on soon though. -func AddLiveChannelMigrations(mg *migrator.Migrator) { +func addLiveChannelMigrations(mg *migrator.Migrator) { //liveMessage := migrator.Table{ // Name: "live_message", // Columns: []*migrator.Column{ diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 01569e1b386..9173a14a68a 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -10,7 +10,14 @@ import ( // 2. Always add new migrations (to change or undo previous migrations) // 3. Some migrations are not yet written (rename column, table, drop table, index etc) -func AddMigrations(mg *Migrator) { +type OSSMigrations struct { +} + +func ProvideOSSMigrations() *OSSMigrations { + return &OSSMigrations{} +} + +func (*OSSMigrations) AddMigration(mg *Migrator) { addMigrationLogMigrations(mg) addUserMigrations(mg) addTempUserMigrations(mg) @@ -41,6 +48,9 @@ func AddMigrations(mg *Migrator) { ualert.AddTablesMigrations(mg) ualert.AddDashAlertMigration(mg) addLibraryElementsMigrations(mg) + if mg.Cfg != nil || mg.Cfg.IsLiveConfigEnabled() { + addLiveChannelMigrations(mg) + } ualert.RerunDashAlertMigration(mg) } diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go index 23765632f0d..70fe6190be4 100644 --- a/pkg/services/sqlstore/migrations/migrations_test.go +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -25,7 +25,8 @@ func TestMigrations(t *testing.T) { require.Error(t, err) mg := NewMigrator(x, &setting.Cfg{}) - AddMigrations(mg) + migrations := &OSSMigrations{} + migrations.AddMigration(mg) expectedMigrations := mg.MigrationsCount() err = mg.Start() @@ -38,7 +39,7 @@ func TestMigrations(t *testing.T) { require.Equal(t, expectedMigrations, result.Count) mg = NewMigrator(x, &setting.Cfg{}) - AddMigrations(mg) + migrations.AddMigration(mg) err = mg.Start() require.NoError(t, err) diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index a68346ac48e..b2da6b0b862 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -154,6 +154,8 @@ func (db *MySQLDialect) TruncateDBTables() error { for _, table := range tables { switch table.Name { + case "migration_log": + continue case "dashboard_acl": // keep default dashboard permissions if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;", db.Quote(table.Name))); err != nil { diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index b698d5007f5..ccbb9301d30 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -144,14 +144,19 @@ func (db *PostgresDialect) CleanDB() error { // TruncateDBTables truncates all the tables. // A special case is the dashboard_acl table where we keep the default permissions. func (db *PostgresDialect) TruncateDBTables() error { + tables, err := db.engine.DBMetas() + if err != nil { + return err + } sess := db.engine.NewSession() defer sess.Close() - for _, table := range db.engine.Tables { + for _, table := range tables { switch table.Name { case "": continue case "migration_log": + continue case "dashboard_acl": // keep default dashboard permissions if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;", db.Quote(table.Name))); err != nil { diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index cb5ca9301d0..6e17eba6797 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -102,6 +102,7 @@ func (db *SQLite3) TruncateDBTables() error { for _, table := range tables { switch table.Name { case "migration_log": + continue case "dashboard_acl": // keep default dashboard permissions if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %q WHERE dashboard_id != -1 AND org_id != -1;", table.Name)); err != nil { diff --git a/pkg/services/sqlstore/searchstore/search_test.go b/pkg/services/sqlstore/searchstore/search_test.go index f32a66e0b9b..8cc58fc21fa 100644 --- a/pkg/services/sqlstore/searchstore/search_test.go +++ b/pkg/services/sqlstore/searchstore/search_test.go @@ -11,15 +11,12 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/permissions" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var dialect migrator.Dialect - const ( limit int64 = 15 page int64 = 1 @@ -45,7 +42,7 @@ func TestBuilder_EqualResults_Basic(t *testing.T) { searchstore.OrgFilter{OrgId: user.OrgId}, searchstore.TitleSorter{}, }, - Dialect: dialect, + Dialect: db.Dialect, } res := []sqlstore.DashboardSearchProjection{} @@ -82,7 +79,7 @@ func TestBuilder_Pagination(t *testing.T) { searchstore.OrgFilter{OrgId: user.OrgId}, searchstore.TitleSorter{}, }, - Dialect: dialect, + Dialect: db.Dialect, } resPg1 := []sqlstore.DashboardSearchProjection{} @@ -130,14 +127,14 @@ func TestBuilder_Permissions(t *testing.T) { searchstore.OrgFilter{OrgId: user.OrgId}, searchstore.TitleSorter{}, permissions.DashboardPermissionFilter{ - Dialect: dialect, + Dialect: db.Dialect, OrgRole: user.OrgRole, OrgId: user.OrgId, UserId: user.UserId, PermissionLevel: level, }, }, - Dialect: dialect, + Dialect: db.Dialect, } res := []sqlstore.DashboardSearchProjection{} @@ -153,7 +150,6 @@ func TestBuilder_Permissions(t *testing.T) { func setupTestEnvironment(t *testing.T) *sqlstore.SQLStore { t.Helper() store := sqlstore.InitTestDB(t) - dialect = store.Dialect return store } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 4267e960ab6..122d2470a7f 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "strings" + "sync" "time" "github.com/go-sql-driver/mysql" @@ -38,44 +39,62 @@ var ( // ContextSessionKey is used as key to save values in `context.Context` type ContextSessionKey struct{} -const ServiceName = "SqlStore" -const InitPriority = registry.High - -func init() { - ss := &SQLStore{} - ss.Register() -} - type SQLStore struct { - Cfg *setting.Cfg `inject:""` - Bus bus.Bus `inject:""` - CacheService *localcache.CacheService `inject:""` + Cfg *setting.Cfg + Bus bus.Bus + CacheService *localcache.CacheService dbCfg DatabaseConfig engine *xorm.Engine log log.Logger Dialect migrator.Dialect skipEnsureDefaultOrgAndUser bool + migrations registry.DatabaseMigrator } -// Register registers the SQLStore service with the DI system. -func (ss *SQLStore) Register() { +func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, bus bus.Bus, migrations registry.DatabaseMigrator) (*SQLStore, error) { // This change will make xorm use an empty default schema for postgres and // by that mimic the functionality of how it was functioning before // xorm's changes above. xorm.DefaultPostgresSchema = "" + s, err := newSQLStore(cfg, cacheService, bus, nil, migrations) + if err != nil { + return nil, err + } - registry.Register(®istry.Descriptor{ - Name: ServiceName, - Instance: ss, - InitPriority: InitPriority, - }) + if err := s.Migrate(); err != nil { + return nil, err + } + + if err := s.Reset(); err != nil { + return nil, err + } + + return s, nil } -func (ss *SQLStore) Init() error { - ss.log = log.New("sqlstore") - if err := ss.initEngine(); err != nil { - return errutil.Wrap("failed to connect to database", err) +func ProvideServiceForTests(migrations registry.DatabaseMigrator) (*SQLStore, error) { + return initTestDB(migrations, InitTestDBOpt{EnsureDefaultOrgAndUser: true}) +} + +func newSQLStore(cfg *setting.Cfg, cacheService *localcache.CacheService, bus bus.Bus, engine *xorm.Engine, + migrations registry.DatabaseMigrator, opts ...InitTestDBOpt) (*SQLStore, error) { + ss := &SQLStore{ + Cfg: cfg, + Bus: bus, + CacheService: cacheService, + log: log.New("sqlstore"), + skipEnsureDefaultOrgAndUser: false, + migrations: migrations, + } + for _, opt := range opts { + if !opt.EnsureDefaultOrgAndUser { + ss.skipEnsureDefaultOrgAndUser = true + } + } + + if err := ss.initEngine(engine); err != nil { + return nil, errutil.Wrap("failed to connect to database", err) } ss.Dialect = migrator.NewDialect(ss.engine) @@ -84,22 +103,6 @@ func (ss *SQLStore) Init() error { x = ss.engine dialect = ss.Dialect - if !ss.dbCfg.SkipMigrations { - migrator := migrator.NewMigrator(ss.engine, ss.Cfg) - migrations.AddMigrations(migrator) - - for _, descriptor := range registry.GetServices() { - sc, ok := descriptor.Instance.(registry.DatabaseMigrator) - if ok { - sc.AddMigration(migrator) - } - } - - if err := migrator.Start(); err != nil { - return err - } - } - // Init repo instances annotations.SetRepository(&SQLAnnotationRepo{}) annotations.SetAnnotationCleaner(&AnnotationCleanupService{batchSize: ss.Cfg.AnnotationCleanupJobBatchSize, log: log.New("annotationcleaner")}) @@ -111,17 +114,32 @@ func (ss *SQLStore) Init() error { ss.addPreferencesQueryAndCommandHandlers() ss.addDashboardQueryAndCommandHandlers() - if err := ss.Reset(); err != nil { - return err - } - // Make sure the changes are synced, so they get shared with eventual other DB connections - if !ss.dbCfg.SkipMigrations { - if err := ss.Sync(); err != nil { - return err - } + // if err := ss.Reset(); err != nil { + // return nil, err + // } + // // Make sure the changes are synced, so they get shared with eventual other DB connections + // // XXX: Why is this only relevant when not skipping migrations? + // if !ss.dbCfg.SkipMigrations { + // if err := ss.Sync(); err != nil { + // return nil, err + // } + // } + + return ss, nil +} + +// Migrate performs database migrations. +// Has to be done in a second phase (after initialization), since other services can register migrations during +// the initialization phase. +func (ss *SQLStore) Migrate() error { + if ss.dbCfg.SkipMigrations { + return nil } - return nil + migrator := migrator.NewMigrator(ss.engine, ss.Cfg) + ss.migrations.AddMigration(migrator) + + return migrator.Start() } // Sync syncs changes to the database. @@ -278,7 +296,7 @@ func (ss *SQLStore) buildConnectionString() (string, error) { } // initEngine initializes ss.engine. -func (ss *SQLStore) initEngine() error { +func (ss *SQLStore) initEngine(engine *xorm.Engine) error { if ss.engine != nil { sqlog.Debug("Already connected to database") return nil @@ -323,9 +341,12 @@ func (ss *SQLStore) initEngine() error { } } } - engine, err := xorm.NewEngine(ss.dbCfg.Type, connectionString) - if err != nil { - return err + if engine == nil { + var err error + engine, err = xorm.NewEngine(ss.dbCfg.Type, connectionString) + if err != nil { + return err + } } engine.SetMaxOpenConns(ss.dbCfg.MaxOpenConn) @@ -406,6 +427,7 @@ type ITestDB interface { } var testSQLStore *SQLStore +var testSQLStoreMutex sync.Mutex // InitTestDBOpt contains options for InitTestDB. type InitTestDBOpt struct { @@ -413,98 +435,121 @@ type InitTestDBOpt struct { EnsureDefaultOrgAndUser bool } +// InitTestDBWithMigration initializes the test DB given custom migrations. +func InitTestDBWithMigration(t ITestDB, migration registry.DatabaseMigrator, opts ...InitTestDBOpt) *SQLStore { + t.Helper() + store, err := initTestDB(migration, opts...) + if err != nil { + t.Fatalf("failed to initialize sql store: %s", err) + } + return store +} + // InitTestDB initializes the test DB. func InitTestDB(t ITestDB, opts ...InitTestDBOpt) *SQLStore { t.Helper() + store, err := initTestDB(&migrations.OSSMigrations{}, opts...) + if err != nil { + t.Fatalf("failed to initialize sql store: %s", err) + } + return store +} + +func initTestDB(migration registry.DatabaseMigrator, opts ...InitTestDBOpt) (*SQLStore, error) { + testSQLStoreMutex.Lock() + defer testSQLStoreMutex.Unlock() if testSQLStore == nil { - testSQLStore = &SQLStore{} - testSQLStore.Bus = bus.New() - testSQLStore.CacheService = localcache.New(5*time.Minute, 10*time.Minute) - testSQLStore.skipEnsureDefaultOrgAndUser = true - - for _, opt := range opts { - testSQLStore.skipEnsureDefaultOrgAndUser = !opt.EnsureDefaultOrgAndUser - } - dbType := migrator.SQLite + if len(opts) == 0 { + opts = []InitTestDBOpt{{EnsureDefaultOrgAndUser: false}} + } + // environment variable present for test db? if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present { - t.Logf("Using database type %q", db) dbType = db } // set test db config - testSQLStore.Cfg = setting.NewCfg() - sec, err := testSQLStore.Cfg.Raw.NewSection("database") + cfg := setting.NewCfg() + sec, err := cfg.Raw.NewSection("database") if err != nil { - t.Fatalf("Failed to create section: %s", err) + return nil, err } if _, err := sec.NewKey("type", dbType); err != nil { - t.Fatalf("Failed to create key: %s", err) + return nil, err } switch dbType { case "mysql": if _, err := sec.NewKey("connection_string", sqlutil.MySQLTestDB().ConnStr); err != nil { - t.Fatalf("Failed to create key: %s", err) + return nil, err } case "postgres": if _, err := sec.NewKey("connection_string", sqlutil.PostgresTestDB().ConnStr); err != nil { - t.Fatalf("Failed to create key: %s", err) + return nil, err } default: if _, err := sec.NewKey("connection_string", sqlutil.SQLite3TestDB().ConnStr); err != nil { - t.Fatalf("Failed to create key: %s", err) + return nil, err } } // useful if you already have a database that you want to use for tests. // cannot just set it on testSQLStore as it overrides the config in Init if _, present := os.LookupEnv("SKIP_MIGRATIONS"); present { - t.Log("Skipping database migrations") if _, err := sec.NewKey("skip_migrations", "true"); err != nil { - t.Fatalf("Failed to create key: %s", err) + return nil, err } } // need to get engine to clean db before we init - t.Logf("Creating database connection: %q", sec.Key("connection_string")) engine, err := xorm.NewEngine(dbType, sec.Key("connection_string").String()) if err != nil { - t.Fatalf("Failed to init test database: %v", err) + return nil, err } - testSQLStore.Dialect = migrator.NewDialect(engine) + engine.DatabaseTZ = time.UTC + engine.TZLocation = time.UTC + + testSQLStore, err = newSQLStore(cfg, localcache.New(5*time.Minute, 10*time.Minute), bus.GetBus(), engine, migration, opts...) + if err != nil { + return nil, err + } + + if err := testSQLStore.Migrate(); err != nil { + return nil, err + } + + if err := dialect.TruncateDBTables(); err != nil { + return nil, err + } + + if err := testSQLStore.Reset(); err != nil { + return nil, err + } + + // Make sure the changes are synced, so they get shared with eventual other DB connections + // XXX: Why is this only relevant when not skipping migrations? + if !testSQLStore.dbCfg.SkipMigrations { + if err := testSQLStore.Sync(); err != nil { + return nil, err + } + } // temp global var until we get rid of global vars dialect = testSQLStore.Dialect - - t.Logf("Cleaning DB") - if err := dialect.CleanDB(); err != nil { - t.Fatalf("Failed to clean test db: %s", err) - } - - if err := testSQLStore.Init(); err != nil { - t.Fatalf("Failed to init test database: %s", err) - } - t.Log("Successfully initialized test database") - - testSQLStore.engine.DatabaseTZ = time.UTC - testSQLStore.engine.TZLocation = time.UTC - - return testSQLStore + return testSQLStore, nil } - t.Log("Truncating DB tables") if err := dialect.TruncateDBTables(); err != nil { - t.Fatalf("Failed to truncate test db: %s", err) + return nil, err } if err := testSQLStore.Reset(); err != nil { - t.Fatalf("Failed to reset SQLStore: %s", err) + return nil, err } - return testSQLStore + return testSQLStore, nil } func IsTestDbMySQL() bool { diff --git a/pkg/services/validations/oss.go b/pkg/services/validations/oss.go index dc6ab81b399..bb7551e802a 100644 --- a/pkg/services/validations/oss.go +++ b/pkg/services/validations/oss.go @@ -6,10 +6,10 @@ import ( type OSSPluginRequestValidator struct{} -func (*OSSPluginRequestValidator) Init() error { - return nil -} - func (*OSSPluginRequestValidator) Validate(string, *http.Request) error { return nil } + +func ProvideValidator() *OSSPluginRequestValidator { + return &OSSPluginRequestValidator{} +} diff --git a/pkg/setting/dynamic_settings_test.go b/pkg/setting/dynamic_settings_test.go index 978ace89694..8b18415d592 100644 --- a/pkg/setting/dynamic_settings_test.go +++ b/pkg/setting/dynamic_settings_test.go @@ -9,7 +9,6 @@ import ( func TestDynamicSettingsSupport_Override(t *testing.T) { cfg := NewCfg() - envKey := "GF_FOO_BAR" sectionName := "foo" keyName := "bar" @@ -28,6 +27,7 @@ func TestDynamicSettingsSupport_Override(t *testing.T) { func TestDynamicSettingsSupport_NoOverride(t *testing.T) { cfg := NewCfg() + sectionName := "foo" keyName := "bar" expected := "default value" diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index 5e387669833..a00a4f0be9f 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -92,12 +92,14 @@ type ReloadHandler interface { type SettingsBag map[string]map[string]string type SettingsRemovals map[string][]string -type OSSImpl struct { - Cfg *Cfg `inject:""` +func ProvideProvider(cfg *Cfg) *OSSImpl { + return &OSSImpl{ + Cfg: cfg, + } } -func (o OSSImpl) Init() error { - return nil +type OSSImpl struct { + Cfg *Cfg } func (o OSSImpl) Current() SettingsBag { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index c827f1c4555..0f1355a4a61 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -21,7 +21,7 @@ import ( "github.com/gobwas/glob" "github.com/prometheus/common/model" - ini "gopkg.in/ini.v1" + "gopkg.in/ini.v1" "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana/pkg/components/gtime" @@ -44,6 +44,7 @@ const ( Dev = "development" Prod = "production" Test = "test" + ApplicationName = "Grafana" ) // This constant corresponds to the default value for ldap_sync_ttl in .ini files @@ -64,12 +65,11 @@ var ( InstanceName string // build - BuildVersion string - BuildCommit string - BuildBranch string - BuildStamp int64 - IsEnterprise bool - ApplicationName string + BuildVersion string + BuildCommit string + BuildBranch string + BuildStamp int64 + IsEnterprise bool // packaging Packaging = "unknown" @@ -84,7 +84,6 @@ var ( // Security settings. SecretKey string DisableGravatar bool - EmailCodeValidMinutes int DataProxyWhiteList map[string]bool CookieSecure bool CookieSameSiteDisabled bool @@ -204,6 +203,9 @@ type Cfg struct { EnableGzip bool EnforceDomain bool + // Security settings + EmailCodeValidMinutes int + // build BuildVersion string BuildCommit string @@ -215,6 +217,7 @@ type Cfg struct { Packaging string // Paths + HomePath string ProvisioningPath string DataPath string LogsPath string @@ -402,6 +405,12 @@ type Cfg struct { // Grafana.com URL GrafanaComURL string + // Alerting + + // AlertingBaseInterval controls the alerting base interval in seconds. + // Only for internal use and not user configuration. + AlertingBaseInterval time.Duration + // Geomap base layer config GeomapDefaultBaseLayerConfig map[string]interface{} GeomapEnableCustomBaseLayers bool @@ -636,9 +645,9 @@ func makeAbsolute(path string, root string) string { return filepath.Join(root, path) } -func loadSpecifiedConfigFile(configFile string, masterFile *ini.File) error { +func (cfg *Cfg) loadSpecifiedConfigFile(configFile string, masterFile *ini.File) error { if configFile == "" { - configFile = filepath.Join(HomePath, CustomInitPath) + configFile = filepath.Join(cfg.HomePath, CustomInitPath) // return without error if custom file does not exist if !pathExists(configFile) { return nil @@ -674,7 +683,7 @@ func loadSpecifiedConfigFile(configFile string, masterFile *ini.File) error { return nil } -func (cfg *Cfg) loadConfiguration(args *CommandLineArgs) (*ini.File, error) { +func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) { // load config defaults defaultConfigFile := path.Join(HomePath, "conf/defaults.ini") configFiles = append(configFiles, defaultConfigFile) @@ -701,7 +710,7 @@ func (cfg *Cfg) loadConfiguration(args *CommandLineArgs) (*ini.File, error) { applyCommandLineDefaultProperties(commandLineProps, parsedFile) // load specified config file - err = loadSpecifiedConfigFile(args.Config, parsedFile) + err = cfg.loadSpecifiedConfigFile(args.Config, parsedFile) if err != nil { err2 := cfg.initLogging(parsedFile) if err2 != nil { @@ -748,25 +757,29 @@ func pathExists(path string) bool { return false } -func setHomePath(args *CommandLineArgs) { +func (cfg *Cfg) setHomePath(args CommandLineArgs) { if args.HomePath != "" { - HomePath = args.HomePath + cfg.HomePath = args.HomePath + HomePath = cfg.HomePath return } var err error - HomePath, err = filepath.Abs(".") + cfg.HomePath, err = filepath.Abs(".") if err != nil { panic(err) } + + HomePath = cfg.HomePath // check if homepath is correct - if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) { + if pathExists(filepath.Join(cfg.HomePath, "conf/defaults.ini")) { return } // try down one path - if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) { - HomePath = filepath.Join(HomePath, "../") + if pathExists(filepath.Join(cfg.HomePath, "../conf/defaults.ini")) { + cfg.HomePath = filepath.Join(cfg.HomePath, "../") + HomePath = cfg.HomePath } } @@ -779,6 +792,15 @@ func NewCfg() *Cfg { } } +func NewCfgFromArgs(args CommandLineArgs) (*Cfg, error) { + cfg := NewCfg() + if err := cfg.Load(args); err != nil { + return nil, err + } + + return cfg, nil +} + var theCfg *Cfg // GetCfg gets the Cfg singleton. @@ -790,7 +812,10 @@ func GetCfg() *Cfg { return theCfg } - theCfg = NewCfg() + theCfg, err := NewCfgFromArgs(CommandLineArgs{}) + if err != nil { + panic(err) + } return theCfg } @@ -806,8 +831,8 @@ func (cfg *Cfg) validateStaticRootPath() error { return nil } -func (cfg *Cfg) Load(args *CommandLineArgs) error { - setHomePath(args) +func (cfg *Cfg) Load(args CommandLineArgs) error { + cfg.setHomePath(args) // Fix for missing IANA db on Windows _, zoneInfoSet := os.LookupEnv(zoneInfo) @@ -836,8 +861,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { cfg.ErrTemplateName = "error" - ApplicationName = "Grafana" - Env = valueAsString(iniFile.Section(""), "app_mode", "development") cfg.Env = Env InstanceName = valueAsString(iniFile.Section(""), "instance_name", "unknown_instance_name") @@ -876,7 +899,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { if err := readAuthSettings(iniFile, cfg); err != nil { return err } - if err := readRenderingSettings(iniFile, cfg); err != nil { + if err := cfg.readRenderingSettings(iniFile); err != nil { return err } @@ -999,6 +1022,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { return err } + cfg.LogConfigSources() + return nil } @@ -1332,7 +1357,7 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error { return nil } -func readRenderingSettings(iniFile *ini.File, cfg *Cfg) error { +func (cfg *Cfg) readRenderingSettings(iniFile *ini.File) error { renderSec := iniFile.Section("rendering") cfg.RendererUrl = valueAsString(renderSec, "server_url", "") cfg.RendererCallbackUrl = valueAsString(renderSec, "callback_url", "") diff --git a/pkg/setting/setting_session_test.go b/pkg/setting/setting_session_test.go index 0f1840f0c04..a0f97540ccd 100644 --- a/pkg/setting/setting_session_test.go +++ b/pkg/setting/setting_session_test.go @@ -18,20 +18,23 @@ func (stub *testLogger) Warn(testMessage string, ctx ...interface{}) { stub.warnCalled = true stub.warnMessage = testMessage } + +func (stub *testLogger) Info(testMessage string, ctx ...interface{}) { + +} + func TestSessionSettings(t *testing.T) { Convey("session config", t, func() { skipStaticRootValidation = true Convey("Reading session should log error ", func() { - var ( - cfg = NewCfg() - homePath = "../../" - ) + cfg := NewCfg() + homePath := "../../" stub := &testLogger{} cfg.Logger = stub - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: homePath, Config: filepath.Join(homePath, "pkg/setting/testdata/session.ini"), }) diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 411d10b69c7..486dd6ef47d 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -28,7 +28,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Given the default ini files", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{HomePath: "../../", Config: "../../conf/defaults.ini"}) + err := cfg.Load(CommandLineArgs{HomePath: "../../", Config: "../../conf/defaults.ini"}) So(err, ShouldBeNil) So(cfg.AdminUser, ShouldEqual, "admin") @@ -58,7 +58,7 @@ func TestLoadingSettings(t *testing.T) { customInitPath := CustomInitPath CustomInitPath = "conf/sample.ini" cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{HomePath: "../../"}) + err := cfg.Load(CommandLineArgs{HomePath: "../../"}) So(err, ShouldBeNil) // Restore CustomInitPath to avoid side effects. CustomInitPath = customInitPath @@ -69,7 +69,7 @@ func TestLoadingSettings(t *testing.T) { require.NoError(t, err) cfg := NewCfg() - err = cfg.Load(&CommandLineArgs{HomePath: "../../"}) + err = cfg.Load(CommandLineArgs{HomePath: "../../"}) So(err, ShouldBeNil) So(cfg.AdminUser, ShouldEqual, "superduper") @@ -82,7 +82,7 @@ func TestLoadingSettings(t *testing.T) { require.NoError(t, err) cfg := NewCfg() - err = cfg.Load(&CommandLineArgs{HomePath: "../../"}) + err = cfg.Load(CommandLineArgs{HomePath: "../../"}) So(err, ShouldBeNil) So(appliedEnvOverrides, ShouldContain, "GF_SECURITY_ADMIN_PASSWORD=*********") @@ -93,7 +93,7 @@ func TestLoadingSettings(t *testing.T) { require.NoError(t, err) cfg := NewCfg() - err = cfg.Load(&CommandLineArgs{HomePath: "../../"}) + err = cfg.Load(CommandLineArgs{HomePath: "../../"}) So(err, ShouldBeNil) So(appliedEnvOverrides, ShouldContain, "GF_DATABASE_URL=mysql://user:xxxxx@localhost:3306/database") @@ -110,7 +110,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Should be able to override via command line", func() { if runtime.GOOS == windows { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{`cfg:paths.data=c:\tmp\data`, `cfg:paths.logs=c:\tmp\logs`}, }) @@ -119,7 +119,7 @@ func TestLoadingSettings(t *testing.T) { So(cfg.LogsPath, ShouldEqual, `c:\tmp\logs`) } else { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=/tmp/data", "cfg:paths.logs=/tmp/logs"}, }) @@ -132,7 +132,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Should be able to override defaults via command line", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{ "cfg:default.server.domain=test2", @@ -147,7 +147,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Defaults can be overridden in specified config file", func() { if runtime.GOOS == windows { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "pkg/setting/testdata/override_windows.ini"), Args: []string{`cfg:default.paths.data=c:\tmp\data`}, @@ -157,7 +157,7 @@ func TestLoadingSettings(t *testing.T) { So(cfg.DataPath, ShouldEqual, `c:\tmp\override`) } else { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "pkg/setting/testdata/override.ini"), Args: []string{"cfg:default.paths.data=/tmp/data"}, @@ -171,7 +171,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Command line overrides specified config file", func() { if runtime.GOOS == windows { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "pkg/setting/testdata/override_windows.ini"), Args: []string{`cfg:paths.data=c:\tmp\data`}, @@ -181,7 +181,7 @@ func TestLoadingSettings(t *testing.T) { So(cfg.DataPath, ShouldEqual, `c:\tmp\data`) } else { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Config: filepath.Join(HomePath, "pkg/setting/testdata/override.ini"), Args: []string{"cfg:paths.data=/tmp/data"}, @@ -197,7 +197,7 @@ func TestLoadingSettings(t *testing.T) { err := os.Setenv("GF_DATA_PATH", `c:\tmp\env_override`) require.NoError(t, err) cfg := NewCfg() - err = cfg.Load(&CommandLineArgs{ + err = cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=${GF_DATA_PATH}"}, }) @@ -208,7 +208,7 @@ func TestLoadingSettings(t *testing.T) { err := os.Setenv("GF_DATA_PATH", "/tmp/env_override") require.NoError(t, err) cfg := NewCfg() - err = cfg.Load(&CommandLineArgs{ + err = cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:paths.data=${GF_DATA_PATH}"}, }) @@ -220,7 +220,7 @@ func TestLoadingSettings(t *testing.T) { Convey("instance_name default to hostname even if hostname env is empty", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", }) So(err, ShouldBeNil) @@ -232,7 +232,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Reading callback_url should add trailing slash", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:rendering.callback_url=http://myserver/renderer"}, }) @@ -243,7 +243,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Only sync_ttl should return the value sync_ttl", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:auth.proxy.sync_ttl=2"}, }) @@ -254,7 +254,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Only ldap_sync_ttl should return the value ldap_sync_ttl", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:auth.proxy.ldap_sync_ttl=5"}, }) @@ -265,7 +265,7 @@ func TestLoadingSettings(t *testing.T) { Convey("ldap_sync should override ldap_sync_ttl that is default value", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:auth.proxy.sync_ttl=5"}, }) @@ -276,7 +276,7 @@ func TestLoadingSettings(t *testing.T) { Convey("ldap_sync should not override ldap_sync_ttl that is different from default value", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{ + err := cfg.Load(CommandLineArgs{ HomePath: "../../", Args: []string{"cfg:auth.proxy.ldap_sync_ttl=12", "cfg:auth.proxy.sync_ttl=5"}, }) diff --git a/pkg/tests/api/alerting/api_admin_configuration_test.go b/pkg/tests/api/alerting/api_admin_configuration_test.go index 6c6d40b9460..f6cabe2f55d 100644 --- a/pkg/tests/api/alerting/api_admin_configuration_test.go +++ b/pkg/tests/api/alerting/api_admin_configuration_test.go @@ -9,18 +9,14 @@ import ( "testing" "time" - ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/prometheus/common/model" - - "github.com/grafana/grafana/pkg/services/ngalert/schedule" - - apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - - "github.com/grafana/grafana/pkg/models" - "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/models" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" ) func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) { @@ -30,10 +26,9 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) { NGAlertAdminConfigIntervalSeconds: 2, }) - s := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, s := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler s.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, s) // Create a user to make authenticated requests createUser(t, s, models.CreateUserCommand{ diff --git a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go index 1336fb59d13..770a5809add 100644 --- a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go @@ -31,10 +31,13 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + + // editor from main organisation requests configuration + alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr) + // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // create user under main organisation userID := createUser(t, store, models.CreateUserCommand{ @@ -54,9 +57,6 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) { OrgId: orgID, }) - // editor from main organisation requests configuration - alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr) - // On a blank start with no configuration, it saves and delivers the default configuration. { resp := getRequest(t, alertConfigURL, http.StatusOK) // nolint @@ -139,16 +139,17 @@ func TestAlertmanagerConfigurationPersistSecrets(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr) + // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Password: "editor", Login: "editor", }) - alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr) generatedUID := "" // create a new configuration that has a secret diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index 548eaa0b95f..ad5ffd92188 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -33,10 +33,9 @@ func TestAMConfigAccess(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create a users to make authenticated requests createUser(t, store, models.CreateUserCommand{ @@ -392,10 +391,9 @@ func TestAlertAndGroupsQuery(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // unauthenticated request to get the alerts should fail { @@ -560,10 +558,9 @@ func TestRulerAccess(t *testing.T) { ViewersCanEdit: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create the namespace we'll save our alerts to. _, err := createFolder(t, store, 0, "default") @@ -687,10 +684,9 @@ func TestDeleteFolderWithRules(t *testing.T) { ViewersCanEdit: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create the namespace we'll save our alerts to. namespaceUID, err := createFolder(t, store, 0, "default") @@ -846,10 +842,9 @@ func TestAlertRuleCRUD(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), @@ -1792,8 +1787,8 @@ func TestAlertmanagerStatus(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + + grafanaListedAddr, _ := testinfra.StartGrafana(t, dir, path) // Get the Alertmanager current status. { @@ -1855,10 +1850,9 @@ func TestQuota(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create the namespace we'll save our alerts to. _, err := createFolder(t, store, 0, "default") @@ -1958,10 +1952,9 @@ func TestEval(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), diff --git a/pkg/tests/api/alerting/api_available_channel_test.go b/pkg/tests/api/alerting/api_available_channel_test.go index bab414d84ed..24861e6259c 100644 --- a/pkg/tests/api/alerting/api_available_channel_test.go +++ b/pkg/tests/api/alerting/api_available_channel_test.go @@ -19,9 +19,8 @@ func TestAvailableChannels(t *testing.T) { DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create a user to make authenticated requests createUser(t, store, models.CreateUserCommand{ diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 1b2750e8303..894a770f873 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -36,9 +36,10 @@ func TestTestReceivers(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Login: "grafana", @@ -65,9 +66,10 @@ func TestTestReceivers(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Login: "grafana", @@ -131,9 +133,10 @@ func TestTestReceivers(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Login: "grafana", @@ -193,9 +196,10 @@ func TestTestReceivers(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Login: "grafana", @@ -264,9 +268,10 @@ func TestTestReceivers(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableFeatureToggles: []string{"ngalert"}, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) + createUser(t, store, models.CreateUserCommand{ DefaultOrgRole: string(models.ROLE_EDITOR), Login: "grafana", @@ -358,9 +363,8 @@ func TestNotificationChannels(t *testing.T) { DisableAnonymous: true, }) - s := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, s := testinfra.StartGrafana(t, dir, path) s.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, s) mockChannel := newMockNotificationChannel(t, grafanaListedAddr) amConfig := getAlertmanagerConfig(mockChannel.server.Addr) diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go index 3447ea20927..213e65b54b8 100644 --- a/pkg/tests/api/alerting/api_prometheus_test.go +++ b/pkg/tests/api/alerting/api_prometheus_test.go @@ -24,10 +24,10 @@ func TestPrometheusRules(t *testing.T) { EnableFeatureToggles: []string{"ngalert"}, DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create the namespace under default organisation (orgID = 1) where we'll save our alerts to. _, err := createFolder(t, store, 0, "default") @@ -268,10 +268,10 @@ func TestPrometheusRulesPermissions(t *testing.T) { EnableFeatureToggles: []string{"ngalert"}, DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create a user to make authenticated requests createUser(t, store, models.CreateUserCommand{ diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 7117f719d9d..6407cd66aa2 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -25,10 +25,10 @@ func TestAlertRulePermissions(t *testing.T) { EnableFeatureToggles: []string{"ngalert"}, DisableAnonymous: true, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create a user to make authenticated requests createUser(t, store, models.CreateUserCommand{ @@ -312,10 +312,9 @@ func TestAlertRuleConflictingTitle(t *testing.T) { ViewersCanEdit: true, }) - store := testinfra.SetUpDatabase(t, dir) + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) // override bus to get the GetSignedInUserQuery handler store.Bus = bus.GetBus() - grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store) // Create the namespace we'll save our alerts to. _, err := createFolder(t, store, 0, "folder1") diff --git a/pkg/tests/api/metrics/api_metrics_test.go b/pkg/tests/api/metrics/api_metrics_test.go index c69673e9b4b..7344b293a0d 100644 --- a/pkg/tests/api/metrics/api_metrics_test.go +++ b/pkg/tests/api/metrics/api_metrics_test.go @@ -30,8 +30,8 @@ import ( func TestQueryCloudWatchMetrics(t *testing.T) { grafDir, cfgPath := testinfra.CreateGrafDir(t) - sqlStore := setUpDatabase(t, grafDir) - addr := testinfra.StartGrafana(t, grafDir, cfgPath, sqlStore) + addr, sqlStore := testinfra.StartGrafana(t, grafDir, cfgPath) + setUpDatabase(t, sqlStore) origNewCWClient := cloudwatch.NewCWClient t.Cleanup(func() { @@ -94,8 +94,8 @@ func TestQueryCloudWatchMetrics(t *testing.T) { func TestQueryCloudWatchLogs(t *testing.T) { grafDir, cfgPath := testinfra.CreateGrafDir(t) - sqlStore := setUpDatabase(t, grafDir) - addr := testinfra.StartGrafana(t, grafDir, cfgPath, sqlStore) + addr, store := testinfra.StartGrafana(t, grafDir, cfgPath) + setUpDatabase(t, store) origNewCWLogsClient := cloudwatch.NewCWLogsClient t.Cleanup(func() { @@ -173,11 +173,10 @@ func makeCWRequest(t *testing.T, req dtos.MetricRequest, addr string) backend.Qu return tr } -func setUpDatabase(t *testing.T, grafDir string) *sqlstore.SQLStore { +func setUpDatabase(t *testing.T, store *sqlstore.SQLStore) { t.Helper() - sqlStore := testinfra.SetUpDatabase(t, grafDir) - err := sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { + err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { _, err := sess.Insert(&models.DataSource{ Id: 1, // This will be the ID of the main org @@ -192,8 +191,6 @@ func setUpDatabase(t *testing.T, grafDir string) *sqlstore.SQLStore { require.NoError(t, err) // Make sure changes are synced with other goroutines - err = sqlStore.Sync() + err = store.Sync() require.NoError(t, err) - - return sqlStore } diff --git a/pkg/tests/api/plugins/api_install_test.go b/pkg/tests/api/plugins/api_install_test.go index 6b155e09df8..03b7db4e21c 100644 --- a/pkg/tests/api/plugins/api_install_test.go +++ b/pkg/tests/api/plugins/api_install_test.go @@ -29,9 +29,9 @@ func TestPluginInstallAccess(t *testing.T) { dir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ PluginAdminEnabled: true, }) - store := testinfra.SetUpDatabase(t, dir) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, cfgPath) store.Bus = bus.GetBus() // in order to allow successful user auth - grafanaListedAddr := testinfra.StartGrafana(t, dir, cfgPath, store) createUser(t, store, usernameNonAdmin, defaultPassword, false) createUser(t, store, usernameAdmin, defaultPassword, true) diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index a32c1f3ea84..3668a22a7c7 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -11,13 +11,12 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/server" - "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/ini.v1" @@ -25,47 +24,28 @@ import ( // StartGrafana starts a Grafana server. // The server address is returned. -func StartGrafana(t *testing.T, grafDir, cfgPath string, sqlStore *sqlstore.SQLStore) string { +func StartGrafana(t *testing.T, grafDir, cfgPath string) (string, *sqlstore.SQLStore) { t.Helper() ctx := context.Background() - // Prevent duplicate registration errors between tests by replacing - // the registry used. - metrics.GlobalMetrics.SwapRegisterer(prometheus.NewRegistry()) - - origSQLStore := registry.GetService(sqlstore.ServiceName) - t.Cleanup(func() { - registry.Register(origSQLStore) - }) - registry.Register(®istry.Descriptor{ - Name: sqlstore.ServiceName, - Instance: sqlStore, - InitPriority: sqlstore.InitPriority, - }) - - t.Logf("Registered SQL store %p", sqlStore) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) - server, err := server.New(server.Config{ - ConfigFile: cfgPath, - HomePath: grafDir, - Listener: listener, - }) - require.NoError(t, err) + cmdLineArgs := setting.CommandLineArgs{Config: cfgPath, HomePath: grafDir} + serverOpts := server.Options{Listener: listener, HomePath: grafDir} + apiServerOpts := api.ServerOptions{Listener: listener} - t.Cleanup(func() { - // Have to reset the route register between tests, since it doesn't get re-created - server.HTTPServer.RouteRegister.Reset() - }) + env, err := server.InitializeForTest(cmdLineArgs, serverOpts, apiServerOpts) + require.NoError(t, err) + require.NoError(t, env.SQLStore.Sync()) go func() { // When the server runs, it will also build and initialize the service graph - if err := server.Run(); err != nil { + if err := env.Server.Run(); err != nil { t.Log("Server exited uncleanly", "error", err) } }() t.Cleanup(func() { - if err := server.Shutdown(ctx, "test cleanup"); err != nil { + if err := env.Server.Shutdown(ctx, "test cleanup"); err != nil { t.Error("Timed out waiting on server to shut down") } }) @@ -83,7 +63,7 @@ func StartGrafana(t *testing.T, grafDir, cfgPath string, sqlStore *sqlstore.SQLS t.Logf("Grafana is listening on %s", addr) - return addr + return addr, env.SQLStore } // SetUpDatabase sets up the Grafana database. diff --git a/pkg/tests/web/index_view_test.go b/pkg/tests/web/index_view_test.go index 8c4af6aa641..724f50a26ca 100644 --- a/pkg/tests/web/index_view_test.go +++ b/pkg/tests/web/index_view_test.go @@ -18,8 +18,7 @@ func TestIndexView(t *testing.T) { grafDir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ EnableCSP: true, }) - sqlStore := testinfra.SetUpDatabase(t, grafDir) - addr := testinfra.StartGrafana(t, grafDir, cfgPath, sqlStore) + addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath) // nolint:bodyclose resp, html := makeRequest(t, addr) @@ -29,8 +28,7 @@ func TestIndexView(t *testing.T) { t.Run("CSP disabled", func(t *testing.T) { grafDir, cfgPath := testinfra.CreateGrafDir(t) - sqlStore := testinfra.SetUpDatabase(t, grafDir) - addr := testinfra.StartGrafana(t, grafDir, cfgPath, sqlStore) + addr, _ := testinfra.StartGrafana(t, grafDir, cfgPath) // nolint:bodyclose resp, html := makeRequest(t, addr) diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 7065bed1d66..5102b9e1308 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" ) @@ -32,12 +31,36 @@ var ( legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "AzureMonitorService", - InitPriority: registry.Low, - Instance: &Service{}, +func ProvideService(cfg *setting.Cfg, httpClientProvider *httpclient.Provider, pluginManager plugins.Manager, backendPluginManager backendplugin.Manager) *Service { + executors := map[string]azDatasourceExecutor{ + azureMonitor: &AzureMonitorDatasource{}, + appInsights: &ApplicationInsightsDatasource{}, + azureLogAnalytics: &AzureLogAnalyticsDatasource{}, + insightsAnalytics: &InsightsAnalyticsDatasource{}, + azureResourceGraph: &AzureResourceGraphDatasource{}, + } + + im := datasource.NewInstanceManager(NewInstanceSettings(cfg, *httpClientProvider, executors)) + + s := &Service{ + Cfg: cfg, + PluginManager: pluginManager, + im: im, + executors: executors, + } + + mux := s.newMux() + resourceMux := http.NewServeMux() + s.registerRoutes(resourceMux) + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: mux, + CallResourceHandler: httpadapter.New(resourceMux), }) + + if err := backendPluginManager.RegisterAndStart(context.Background(), dsName, factory); err != nil { + azlog.Error("Failed to register plugin", "error", err) + } + return s } type serviceProxy interface { @@ -45,12 +68,10 @@ type serviceProxy interface { } type Service struct { - PluginManager plugins.Manager `inject:""` - Cfg *setting.Cfg `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - HTTPClientProvider *httpclient.Provider `inject:""` - im instancemgmt.InstanceManager - executors map[string]azDatasourceExecutor + PluginManager plugins.Manager + Cfg *setting.Cfg + im instancemgmt.InstanceManager + executors map[string]azDatasourceExecutor } type azureMonitorSettings struct { @@ -179,27 +200,3 @@ func (s *Service) newMux() *datasource.QueryTypeMux { } return mux } - -func (s *Service) Init() error { - proxy := &httpServiceProxy{} - s.executors = map[string]azDatasourceExecutor{ - azureMonitor: &AzureMonitorDatasource{proxy: proxy}, - appInsights: &ApplicationInsightsDatasource{proxy: proxy}, - azureLogAnalytics: &AzureLogAnalyticsDatasource{proxy: proxy}, - insightsAnalytics: &InsightsAnalyticsDatasource{proxy: proxy}, - azureResourceGraph: &AzureResourceGraphDatasource{proxy: proxy}, - } - s.im = datasource.NewInstanceManager(NewInstanceSettings(s.Cfg, *s.HTTPClientProvider, s.executors)) - mux := s.newMux() - resourceMux := http.NewServeMux() - s.registerRoutes(resourceMux) - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: mux, - CallResourceHandler: httpadapter.New(resourceMux), - }) - - if err := s.BackendPluginManager.Register(dsName, factory); err != nil { - azlog.Error("Failed to register plugin", "error", err) - } - return nil -} diff --git a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go index 16f16d78330..1da80c6b783 100644 --- a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go +++ b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go @@ -17,7 +17,6 @@ import ( "time" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -30,7 +29,7 @@ import ( ) var ( - slog log.Logger + slog = log.New("tsdb.cloudMonitoring") ) var ( @@ -67,22 +66,16 @@ const ( perSeriesAlignerDefault string = "ALIGN_MEAN" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "CloudMonitoringService", - InitPriority: registry.Low, - Instance: &Service{}, - }) +func ProvideService(pluginManager plugins.Manager) *Service { + return &Service{ + PluginManager: pluginManager, + } } type Service struct { - PluginManager plugins.Manager `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` - Cfg *setting.Cfg `inject:""` -} - -func (s *Service) Init() error { - return nil + PluginManager plugins.Manager + HTTPClientProvider httpclient.Provider + Cfg *setting.Cfg } // Executor executes queries for the CloudMonitoring datasource. @@ -109,10 +102,6 @@ func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, er }, nil } -func init() { - slog = log.New("tsdb.cloudMonitoring") -} - // Query takes in the frontend queries, parses them into the CloudMonitoring query format // executes the queries against the CloudMonitoring API and parses the response into // the time series or table format diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index ae9f5cd7cf7..d81962f3045 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -27,7 +27,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) @@ -56,33 +55,31 @@ const logStreamIdentifierInternal = "__logstream__grafana_internal__" var plog = log.New("tsdb.cloudwatch") var aliasFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) -func init() { - registry.Register(®istry.Descriptor{ - Name: "CloudWatchService", - InitPriority: registry.Low, - Instance: &CloudWatchService{}, - }) -} - -type CloudWatchService struct { - LogsService *LogsService `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - Cfg *setting.Cfg `inject:""` -} - -func (s *CloudWatchService) Init() error { +func ProvideService(cfg *setting.Cfg, logsService *LogsService, backendPM backendplugin.Manager) (*CloudWatchService, error) { plog.Debug("initing") im := datasource.NewInstanceManager(NewInstanceSettings()) factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: newExecutor(s.LogsService, im, s.Cfg, awsds.NewSessionCache()), + QueryDataHandler: newExecutor(logsService, im, cfg, awsds.NewSessionCache()), }) - if err := s.BackendPluginManager.Register("cloudwatch", factory); err != nil { + if err := backendPM.Register("cloudwatch", factory); err != nil { plog.Error("Failed to register plugin", "error", err) + return nil, err } - return nil + + return &CloudWatchService{ + LogsService: logsService, + Cfg: cfg, + BackendPluginManager: backendPM, + }, nil +} + +type CloudWatchService struct { + LogsService *LogsService + BackendPluginManager backendplugin.Manager + Cfg *setting.Cfg } type SessionCache interface { diff --git a/pkg/tsdb/cloudwatch/logs.go b/pkg/tsdb/cloudwatch/logs.go index 19e080b33ad..6ef9fa8b132 100644 --- a/pkg/tsdb/cloudwatch/logs.go +++ b/pkg/tsdb/cloudwatch/logs.go @@ -5,11 +5,14 @@ import ( "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/registry" ) -func init() { - registry.RegisterService(&LogsService{}) +func ProvideLogsService() *LogsService { + return &LogsService{ + // nolint:staticcheck // plugins.DataQueryResponse deprecated + responseChannels: make(map[string]chan *backend.QueryDataResponse), + queues: make(map[string](chan bool)), + } } // LogsService provides methods for querying CloudWatch Logs. @@ -21,14 +24,6 @@ type LogsService struct { queueLock sync.Mutex } -// Init is called by the DI framework to initialize the instance. -func (s *LogsService) Init() error { - // nolint:staticcheck // plugins.DataQueryResult deprecated - s.responseChannels = make(map[string]chan *backend.QueryDataResponse) - s.queues = make(map[string](chan bool)) - return nil -} - // nolint:staticcheck // plugins.DataQueryResult deprecated func (s *LogsService) AddResponseChannel(name string, channel chan *backend.QueryDataResponse) error { s.channelMu.Lock() diff --git a/pkg/tsdb/data_plugin_adapter.go b/pkg/tsdb/data_plugin_adapter.go index 9ae7517b053..0dd7913735f 100644 --- a/pkg/tsdb/data_plugin_adapter.go +++ b/pkg/tsdb/data_plugin_adapter.go @@ -13,7 +13,7 @@ import ( ) // nolint:staticcheck // plugins.DataQuery deprecated -func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler, oAuthService *oauthtoken.Service) plugins.DataPluginFunc { +func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler, oAuthService oauthtoken.OAuthTokenService) plugins.DataPluginFunc { return plugins.DataPluginFunc(func(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { instanceSettings, err := modelToInstanceSettings(ds) if err != nil { diff --git a/pkg/tsdb/elasticsearch/elasticsearch.go b/pkg/tsdb/elasticsearch/elasticsearch.go index 8f74c47a4a5..4a31ff2115f 100644 --- a/pkg/tsdb/elasticsearch/elasticsearch.go +++ b/pkg/tsdb/elasticsearch/elasticsearch.go @@ -14,38 +14,34 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/tsdb" es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client" ) var eslog = log.New("tsdb.elasticsearch") -func init() { - registry.Register(®istry.Descriptor{ - Name: "ElasticSearchService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - type Service struct { - BackendPluginManager backendplugin.Manager `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` - intervalCalculator tsdb.Calculator - im instancemgmt.InstanceManager + HTTPClientProvider httpclient.Provider + intervalCalculator tsdb.Calculator + im instancemgmt.InstanceManager } -func (s *Service) Init() error { +func ProvideService(httpClientProvider httpclient.Provider, backendPluginManager backendplugin.Manager) (*Service, error) { eslog.Debug("initializing") + im := datasource.NewInstanceManager(newInstanceSettings()) + s := newService(im, httpClientProvider) + factory := coreplugin.New(backend.ServeOpts{ QueryDataHandler: newService(im, s.HTTPClientProvider), }) - if err := s.BackendPluginManager.Register("elasticsearch", factory); err != nil { + + if err := backendPluginManager.Register("elasticsearch", factory); err != nil { eslog.Error("Failed to register plugin", "error", err) + return nil, err } - return nil + + return s, nil } // newService creates a new executor func. diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index cf137be0c79..f0db0e9688f 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -26,25 +26,31 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/opentracing/opentracing-go" ) type Service struct { - logger log.Logger - im instancemgmt.InstanceManager - BackendPluginManager backendplugin.Manager `inject:""` - Cfg *setting.Cfg `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` + logger log.Logger + im instancemgmt.InstanceManager } -func init() { - registry.Register(®istry.Descriptor{ - Name: "GraphiteService", - InitPriority: registry.Low, - Instance: &Service{}, +func ProvideService(httpClientProvider httpclient.Provider, manager backendplugin.Manager) (*Service, error) { + s := &Service{ + logger: log.New("tsdb.graphite"), + im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)), + } + + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: s, }) + + if err := manager.Register("graphite", factory); err != nil { + s.logger.Error("Failed to register plugin", "error", err) + return nil, err + } + + return s, nil } type datasourceInfo struct { @@ -75,20 +81,6 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst } } -func (s *Service) Init() error { - s.logger = log.New("tsdb.graphite") - s.im = datasource.NewInstanceManager(newInstanceSettings(s.HTTPClientProvider)) - - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: s, - }) - - if err := s.BackendPluginManager.Register("graphite", factory); err != nil { - s.logger.Error("Failed to register plugin", "error", err) - } - return nil -} - func (s *Service) getDSInfo(pluginCtx backend.PluginContext) (*datasourceInfo, error) { i, err := s.im.Get(pluginCtx) if err != nil { diff --git a/pkg/tsdb/influxdb/flux/flux.go b/pkg/tsdb/influxdb/flux/flux.go index 4083fbeaa79..50cc340dd18 100644 --- a/pkg/tsdb/influxdb/flux/flux.go +++ b/pkg/tsdb/influxdb/flux/flux.go @@ -12,12 +12,8 @@ import ( ) var ( - glog log.Logger -) - -func init() { glog = log.New("tsdb.influx_flux") -} +) // Query builds flux queries, executes them, and returns the results. func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend.QueryDataRequest) ( diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index 0c2c0b5ac20..bef00d1d120 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -11,58 +11,47 @@ import ( "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/influxdb/flux" "github.com/grafana/grafana/pkg/tsdb/influxdb/models" - - "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" ) type Service struct { - HTTPClientProvider httpclient.Provider `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - QueryParser *InfluxdbQueryParser - ResponseParser *ResponseParser + QueryParser *InfluxdbQueryParser + ResponseParser *ResponseParser + glog log.Logger im instancemgmt.InstanceManager } -var ( - glog log.Logger -) +var ErrInvalidHttpMode = errors.New("'httpMode' should be either 'GET' or 'POST'") -var ErrInvalidHttpMode error = errors.New("'httpMode' should be either 'GET' or 'POST'") - -func init() { - registry.Register(®istry.Descriptor{ - Name: "InfluxDBService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - -func (s *Service) Init() error { - glog = log.New("tsdb.influxdb") - s.QueryParser = &InfluxdbQueryParser{} - s.ResponseParser = &ResponseParser{} - s.im = datasource.NewInstanceManager(newInstanceSettings(s.HTTPClientProvider)) +func ProvideService(httpClient httpclient.Provider, backendPluginManager backendplugin.Manager) (*Service, error) { + im := datasource.NewInstanceManager(newInstanceSettings(httpClient)) + s := &Service{ + QueryParser: &InfluxdbQueryParser{}, + ResponseParser: &ResponseParser{}, + glog: log.New("tsdb.influxdb"), + im: im, + } factory := coreplugin.New(backend.ServeOpts{ QueryDataHandler: s, }) - if err := s.BackendPluginManager.Register("influxdb", factory); err != nil { - glog.Error("Failed to register plugin", "error", err) + if err := backendPluginManager.Register("influxdb", factory); err != nil { + s.glog.Error("Failed to register plugin", "error", err) + return nil, err } - return nil + return s, nil } func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { @@ -107,7 +96,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst } func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - glog.Debug("Received a query request", "numQueries", len(req.Queries)) + s.glog.Debug("Received a query request", "numQueries", len(req.Queries)) dsInfo, err := s.getDSInfo(req.PluginContext) if err != nil { @@ -118,7 +107,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) return flux.Query(ctx, dsInfo, *req) } - glog.Debug("Making a non-Flux type query") + s.glog.Debug("Making a non-Flux type query") // NOTE: the following path is currently only called from alerting queries // In dashboards, the request runs through proxy and are managed in the frontend @@ -134,7 +123,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) } if setting.Env == setting.Dev { - glog.Debug("Influxdb query", "raw query", rawQuery) + s.glog.Debug("Influxdb query", "raw query", rawQuery) } request, err := s.createRequest(ctx, dsInfo, rawQuery) @@ -148,7 +137,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) } defer func() { if err := res.Body.Close(); err != nil { - glog.Warn("Failed to close response body", "err", err) + s.glog.Warn("Failed to close response body", "err", err) } }() if res.StatusCode/100 != 2 { @@ -216,7 +205,7 @@ func (s *Service) createRequest(ctx context.Context, dsInfo *models.DatasourceIn req.URL.RawQuery = params.Encode() - glog.Debug("Influxdb request", "url", req.URL.String()) + s.glog.Debug("Influxdb request", "url", req.URL.String()) return req, nil } diff --git a/pkg/tsdb/influxdb/influxdb_test.go b/pkg/tsdb/influxdb/influxdb_test.go index a6b774a380f..da7424ee00f 100644 --- a/pkg/tsdb/influxdb/influxdb_test.go +++ b/pkg/tsdb/influxdb/influxdb_test.go @@ -22,10 +22,10 @@ func TestExecutor_createRequest(t *testing.T) { s := &Service{ QueryParser: &InfluxdbQueryParser{}, ResponseParser: &ResponseParser{}, + glog: log.New("test"), } t.Run("createRequest with GET httpMode", func(t *testing.T) { - glog = log.New("test") req, err := s.createRequest(context.Background(), datasource, query) require.NoError(t, err) diff --git a/pkg/tsdb/influxdb/response_parser.go b/pkg/tsdb/influxdb/response_parser.go index 7ae78623e3e..2a98bd9a52c 100644 --- a/pkg/tsdb/influxdb/response_parser.go +++ b/pkg/tsdb/influxdb/response_parser.go @@ -16,12 +16,8 @@ import ( type ResponseParser struct{} var ( - legendFormat *regexp.Regexp -) - -func init() { legendFormat = regexp.MustCompile(`\[\[([\@\/\w-]+)(\.[\@\/\w-]+)*\]\]*|\$\s*([\@\/\w-]+?)*`) -} +) func (rp *ResponseParser) Parse(buf io.ReadCloser, query *Query) *backend.QueryDataResponse { resp := backend.NewQueryDataResponse() diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index d37b921464f..6acb97d07c7 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/loki/pkg/logcli/client" "github.com/grafana/loki/pkg/loghttp" @@ -32,13 +31,30 @@ import ( type Service struct { intervalCalculator tsdb.Calculator im instancemgmt.InstanceManager + plog log.Logger +} - HTTPClientProvider httpclient.Provider `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` +func ProvideService(httpClientProvider httpclient.Provider, manager backendplugin.Manager) (*Service, error) { + im := datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)) + s := &Service{ + im: im, + intervalCalculator: tsdb.NewCalculator(), + plog: log.New("tsdb.loki"), + } + + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: s, + }) + + if err := manager.Register("loki", factory); err != nil { + s.plog.Error("Failed to register plugin", "error", err) + return nil, err + } + + return s, nil } var ( - plog = log.New("tsdb.loki") legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) ) @@ -59,28 +75,6 @@ type ResponseModel struct { Resolution int64 `json:"resolution"` } -func init() { - registry.Register(®istry.Descriptor{ - Name: "LokiService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - -func (s *Service) Init() error { - s.im = datasource.NewInstanceManager(newInstanceSettings(s.HTTPClientProvider)) - s.intervalCalculator = tsdb.NewCalculator() - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: s, - }) - - if err := s.BackendPluginManager.Register("loki", factory); err != nil { - plog.Error("Failed to register plugin", "error", err) - } - - return nil -} - func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { opts, err := settings.HTTPClientOptions() @@ -143,7 +137,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) } for _, query := range queries { - plog.Debug("Sending query", "start", query.Start, "end", query.End, "step", query.Step, "query", query.Expr) + s.plog.Debug("Sending query", "start", query.Start, "end", query.End, "step", query.Step, "query", query.Expr) span, _ := opentracing.StartSpanFromContext(ctx, "alerting.loki") span.SetTag("expr", query.Expr) span.SetTag("start_unixnano", query.Start.UnixNano()) diff --git a/pkg/tsdb/opentsdb/opentsdb.go b/pkg/tsdb/opentsdb/opentsdb.go index 54aae6058ca..ed0f2ad8d2f 100644 --- a/pkg/tsdb/opentsdb/opentsdb.go +++ b/pkg/tsdb/opentsdb/opentsdb.go @@ -22,17 +22,31 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "golang.org/x/net/context/ctxhttp" ) type Service struct { - HTTPClientProvider httpclient.Provider `inject:""` - Cfg *setting.Cfg `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` + logger log.Logger + im instancemgmt.InstanceManager +} - im instancemgmt.InstanceManager +func ProvideService(httpClientProvider httpclient.Provider, manager backendplugin.Manager) (*Service, error) { + im := datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)) + s := &Service{ + logger: log.New("tsdb.opentsdb"), + im: im, + } + + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: s, + }) + err := manager.RegisterAndStart(context.Background(), "opentsdb", factory) + if err != nil { + return nil, err + } + + return s, nil } type datasourceInfo struct { @@ -42,28 +56,6 @@ type datasourceInfo struct { type DsAccess string -func init() { - registry.Register(®istry.Descriptor{ - Name: "OpenTSDBService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - -func (s *Service) Init() error { - s.im = datasource.NewInstanceManager(newInstanceSettings(s.HTTPClientProvider)) - - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: s, - }) - - if err := s.BackendPluginManager.Register("opentsdb", factory); err != nil { - plog.Error("Failed to register plugin", "error", err) - } - - return nil -} - func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { opts, err := settings.HTTPClientOptions() @@ -85,10 +77,6 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst } } -var ( - plog = log.New("tsdb.opentsdb") -) - func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { var tsdbQuery OpenTsdbQuery @@ -104,7 +92,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) // TODO: Don't use global variable if setting.Env == setting.Dev { - plog.Debug("OpenTsdb request", "params", tsdbQuery) + s.logger.Debug("OpenTsdb request", "params", tsdbQuery) } dsInfo, err := s.getDSInfo(req.PluginContext) @@ -139,13 +127,13 @@ func (s *Service) createRequest(dsInfo *datasourceInfo, data OpenTsdbQuery) (*ht postData, err := json.Marshal(data) if err != nil { - plog.Info("Failed marshaling data", "error", err) + s.logger.Info("Failed marshaling data", "error", err) return nil, fmt.Errorf("failed to create request: %w", err) } req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData))) if err != nil { - plog.Info("Failed to create request", "error", err) + s.logger.Info("Failed to create request", "error", err) return nil, fmt.Errorf("failed to create request: %w", err) } @@ -162,19 +150,19 @@ func (s *Service) parseResponse(res *http.Response) (*backend.QueryDataResponse, } defer func() { if err := res.Body.Close(); err != nil { - plog.Warn("Failed to close response body", "err", err) + s.logger.Warn("Failed to close response body", "err", err) } }() if res.StatusCode/100 != 2 { - plog.Info("Request failed", "status", res.Status, "body", string(body)) + s.logger.Info("Request failed", "status", res.Status, "body", string(body)) return nil, fmt.Errorf("request failed, status: %s", res.Status) } var responseData []OpenTsdbResponse err = json.Unmarshal(body, &responseData) if err != nil { - plog.Info("Failed to unmarshal opentsdb response", "error", err, "status", res.Status, "body", string(body)) + s.logger.Info("Failed to unmarshal opentsdb response", "error", err, "status", res.Status, "body", string(body)) return nil, err } @@ -187,7 +175,7 @@ func (s *Service) parseResponse(res *http.Response) (*backend.QueryDataResponse, for timeString, value := range val.DataPoints { timestamp, err := strconv.ParseInt(timeString, 10, 64) if err != nil { - plog.Info("Failed to unmarshal opentsdb timestamp", "timestamp", timeString) + s.logger.Info("Failed to unmarshal opentsdb timestamp", "timestamp", timeString) return nil, err } timeVector = append(timeVector, time.Unix(timestamp, 0).UTC()) diff --git a/pkg/tsdb/opentsdb/opentsdb_test.go b/pkg/tsdb/opentsdb/opentsdb_test.go index 95bd9a06cb0..7ef63ee7e54 100644 --- a/pkg/tsdb/opentsdb/opentsdb_test.go +++ b/pkg/tsdb/opentsdb/opentsdb_test.go @@ -10,12 +10,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/infra/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOpenTsdbExecutor(t *testing.T) { - service := &Service{} + service := &Service{ + logger: log.New("test"), + } t.Run("create request", func(t *testing.T) { req, err := service.createRequest(&datasourceInfo{}, OpenTsdbQuery{}) diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index 6d0e229ec84..9fdd5a02b75 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" @@ -18,26 +17,21 @@ import ( "github.com/grafana/grafana/pkg/tsdb/sqleng" ) -func init() { - registry.Register(®istry.Descriptor{ - Name: "PostgresService", - InitPriority: registry.Low, - Instance: &PostgresService{}, - }) +func ProvideService(cfg *setting.Cfg) *PostgresService { + logger := log.New("tsdb.postgres") + return &PostgresService{ + Cfg: cfg, + logger: logger, + tlsManager: newTLSManager(logger, cfg.DataPath), + } } type PostgresService struct { - Cfg *setting.Cfg `inject:""` + Cfg *setting.Cfg logger log.Logger tlsManager tlsSettingsProvider } -func (s *PostgresService) Init() error { - s.logger = log.New("tsdb.postgres") - s.tlsManager = newTLSManager(s.logger, s.Cfg.DataPath) - return nil -} - //nolint: staticcheck // plugins.DataPlugin deprecated func (s *PostgresService) NewExecutor(datasource *models.DataSource) (plugins.DataPlugin, error) { s.logger.Debug("Creating Postgres query endpoint") diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 35121ab0bc1..ad9163a88fc 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -20,7 +20,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/tsdb" "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/api" @@ -42,31 +41,31 @@ type DatasourceInfo struct { TimeInterval string } -func init() { - registry.Register(®istry.Descriptor{ - Name: "PrometheusService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - type Service struct { - BackendPluginManager backendplugin.Manager `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` - intervalCalculator tsdb.Calculator - im instancemgmt.InstanceManager + httpClientProvider httpclient.Provider + intervalCalculator tsdb.Calculator + im instancemgmt.InstanceManager } -func (s *Service) Init() error { +func ProvideService(httpClientProvider httpclient.Provider, backendPluginManager backendplugin.Manager) (*Service, error) { plog.Debug("initializing") im := datasource.NewInstanceManager(newInstanceSettings()) - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: newService(im, s.HTTPClientProvider), - }) - if err := s.BackendPluginManager.Register("prometheus", factory); err != nil { - plog.Error("Failed to register plugin", "error", err) + + s := &Service{ + httpClientProvider: httpClientProvider, + intervalCalculator: tsdb.NewCalculator(), + im: im, } - return nil + + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: s, + }) + if err := backendPluginManager.Register("prometheus", factory); err != nil { + plog.Error("Failed to register plugin", "error", err) + return nil, err + } + + return s, nil } func newInstanceSettings() datasource.InstanceFactoryFunc { @@ -112,15 +111,6 @@ func newInstanceSettings() datasource.InstanceFactoryFunc { } } -// newService creates a new executor func. -func newService(im instancemgmt.InstanceManager, httpClientProvider httpclient.Provider) *Service { - return &Service{ - im: im, - HTTPClientProvider: httpClientProvider, - intervalCalculator: tsdb.NewCalculator(), - } -} - //nolint: staticcheck // plugins.DataResponse deprecated func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { if len(req.Queries) == 0 { @@ -188,7 +178,7 @@ func getClient(dsInfo *DatasourceInfo, s *Service) (apiv1.API, error) { customMiddlewares := customQueryParametersMiddleware(plog) opts.Middlewares = []sdkhttpclient.Middleware{customMiddlewares} - roundTripper, err := s.HTTPClientProvider.GetTransport(*opts) + roundTripper, err := s.httpClientProvider.GetTransport(*opts) if err != nil { return nil, err } diff --git a/pkg/tsdb/service.go b/pkg/tsdb/service.go index 8b98cf27eb0..6f88b382c87 100644 --- a/pkg/tsdb/service.go +++ b/pkg/tsdb/service.go @@ -8,10 +8,8 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/azuremonitor" "github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" "github.com/grafana/grafana/pkg/tsdb/mssql" "github.com/grafana/grafana/pkg/tsdb/mysql" @@ -19,51 +17,53 @@ import ( ) // NewService returns a new Service. -func NewService() Service { - return Service{ - //nolint: staticcheck // plugins.DataPlugin deprecated - registry: map[string]func(*models.DataSource) (plugins.DataPlugin, error){}, - } +func NewService( + cfg *setting.Cfg, pluginManager plugins.Manager, backendPluginManager backendplugin.Manager, + oauthTokenService *oauthtoken.Service, httpClientProvider httpclient.Provider, cloudMonitoringService *cloudmonitoring.Service, + postgresService *postgres.PostgresService, +) *Service { + s := newService(cfg, pluginManager, backendPluginManager, oauthTokenService) + + // register backend data sources using legacy plugin + // contracts/non-SDK contracts + s.registry["mssql"] = mssql.NewExecutor + s.registry["postgres"] = postgresService.NewExecutor + s.registry["mysql"] = mysql.New(httpClientProvider) + s.registry["stackdriver"] = cloudMonitoringService.NewExecutor + + return s } -func init() { - svc := NewService() - registry.Register(®istry.Descriptor{ - Name: "DataService", - Instance: &svc, - }) +func newService(cfg *setting.Cfg, manager plugins.Manager, backendPluginManager backendplugin.Manager, + oauthTokenService oauthtoken.OAuthTokenService) *Service { + return &Service{ + Cfg: cfg, + PluginManager: manager, + BackendPluginManager: backendPluginManager, + // nolint:staticcheck // plugins.DataPlugin deprecated + registry: map[string]func(*models.DataSource) (plugins.DataPlugin, error){}, + OAuthTokenService: oauthTokenService, + } } // Service handles data requests to data sources. type Service struct { - Cfg *setting.Cfg `inject:""` - PostgresService *postgres.PostgresService `inject:""` - CloudMonitoringService *cloudmonitoring.Service `inject:""` - AzureMonitorService *azuremonitor.Service `inject:""` - PluginManager plugins.Manager `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - HTTPClientProvider httpclient.Provider `inject:""` - OAuthTokenService *oauthtoken.Service `inject:""` + Cfg *setting.Cfg + PluginManager plugins.Manager + BackendPluginManager backendplugin.Manager + OAuthTokenService oauthtoken.OAuthTokenService //nolint: staticcheck // plugins.DataPlugin deprecated registry map[string]func(*models.DataSource) (plugins.DataPlugin, error) } -// Init initialises the service. -func (s *Service) Init() error { - s.registry["mssql"] = mssql.NewExecutor - s.registry["postgres"] = s.PostgresService.NewExecutor - s.registry["mysql"] = mysql.New(s.HTTPClientProvider) - s.registry["stackdriver"] = s.CloudMonitoringService.NewExecutor - return nil -} - //nolint: staticcheck // plugins.DataPlugin deprecated func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { if factory, exists := s.registry[ds.Type]; exists { var err error plugin, err := factory(ds) if err != nil { + //nolint: staticcheck // plugins.DataPlugin deprecated return plugins.DataResponse{}, fmt.Errorf("could not instantiate endpoint for data plugin %q: %w", ds.Type, err) } diff --git a/pkg/tsdb/service_test.go b/pkg/tsdb/service_test.go index 37c42220b2c..86c66659bae 100644 --- a/pkg/tsdb/service_test.go +++ b/pkg/tsdb/service_test.go @@ -10,7 +10,9 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/manager" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) func TestHandleRequest(t *testing.T) { @@ -124,11 +126,23 @@ func (m *fakeBackendPM) QueryData(ctx context.Context, req *backend.QueryDataReq return nil, nil } -func createService() (Service, *fakeExecutor, *fakeBackendPM) { - s := NewService() - fakeBackendPluginManager := &fakeBackendPM{} - s.PluginManager = &manager.PluginManager{} - s.BackendPluginManager = fakeBackendPluginManager +type fakeOAuthTokenService struct { +} + +func (s *fakeOAuthTokenService) GetCurrentOAuthToken(context.Context, *models.SignedInUser) *oauth2.Token { + return nil +} + +func (s *fakeOAuthTokenService) IsOAuthPassThruEnabled(*models.DataSource) bool { + return false +} + +func createService() (*Service, *fakeExecutor, *fakeBackendPM) { + fakeBackendPM := &fakeBackendPM{} + manager := &manager.PluginManager{ + BackendPluginManager: fakeBackendPM, + } + s := newService(setting.NewCfg(), manager, fakeBackendPM, &fakeOAuthTokenService{}) e := &fakeExecutor{ //nolint: staticcheck // plugins.DataPlugin deprecated results: make(map[string]plugins.DataQueryResult), @@ -139,5 +153,5 @@ func createService() (Service, *fakeExecutor, *fakeBackendPM) { return e, nil } - return s, e, fakeBackendPluginManager + return s, e, fakeBackendPM } diff --git a/pkg/tsdb/tempo/tempo.go b/pkg/tsdb/tempo/tempo.go index 12e31a6ef38..aca87ef2102 100644 --- a/pkg/tsdb/tempo/tempo.go +++ b/pkg/tsdb/tempo/tempo.go @@ -15,16 +15,33 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" - otlp "go.opentelemetry.io/collector/model/otlp" + "go.opentelemetry.io/collector/model/otlp" ) type Service struct { - HTTPClientProvider httpclient.Provider `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` + im instancemgmt.InstanceManager + tlog log.Logger +} - im instancemgmt.InstanceManager +func ProvideService(httpClientProvider httpclient.Provider, manager backendplugin.Manager) (*Service, error) { + im := datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)) + + s := &Service{ + tlog: log.New("tsdb.tempo"), + im: im, + } + + factory := coreplugin.New(backend.ServeOpts{ + QueryDataHandler: s, + }) + + if err := manager.Register("tempo", factory); err != nil { + s.tlog.Error("Failed to register plugin", "error", err) + return nil, err + } + + return s, nil } type datasourceInfo struct { @@ -36,32 +53,6 @@ type QueryModel struct { TraceID string `json:"query"` } -var ( - tlog = log.New("tsdb.tempo") -) - -func init() { - registry.Register(®istry.Descriptor{ - Name: "TempoService", - InitPriority: registry.Low, - Instance: &Service{}, - }) -} - -func (s *Service) Init() error { - s.im = datasource.NewInstanceManager(newInstanceSettings(s.HTTPClientProvider)) - - factory := coreplugin.New(backend.ServeOpts{ - QueryDataHandler: s, - }) - - if err := s.BackendPluginManager.Register("tempo", factory); err != nil { - tlog.Error("Failed to register plugin", "error", err) - } - - return nil -} - func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { opts, err := settings.HTTPClientOptions() @@ -110,7 +101,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) defer func() { if err := resp.Body.Close(); err != nil { - tlog.Warn("failed to close response body", "err", err) + s.tlog.Warn("failed to close response body", "err", err) } }() @@ -150,7 +141,7 @@ func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, tra req.Header.Set("Accept", "application/protobuf") - tlog.Debug("Tempo request", "url", req.URL.String(), "headers", req.Header) + s.tlog.Debug("Tempo request", "url", req.URL.String(), "headers", req.Header) return req, nil } diff --git a/pkg/tsdb/tempo/tempo_test.go b/pkg/tsdb/tempo/tempo_test.go index ac85d8818e8..741a7bd3e40 100644 --- a/pkg/tsdb/tempo/tempo_test.go +++ b/pkg/tsdb/tempo/tempo_test.go @@ -4,13 +4,14 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/infra/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTempo(t *testing.T) { t.Run("createRequest - success", func(t *testing.T) { - service := &Service{} + service := &Service{tlog: log.New("tempo-test")} req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID") require.NoError(t, err) assert.Equal(t, 1, len(req.Header)) diff --git a/pkg/tsdb/testdatasource/csv_data.go b/pkg/tsdb/testdatasource/csv_data.go index 86756594d0f..ee8bd133dea 100644 --- a/pkg/tsdb/testdatasource/csv_data.go +++ b/pkg/tsdb/testdatasource/csv_data.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" ) -func (p *testDataPlugin) handleCsvContentScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleCsvContentScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -43,7 +43,7 @@ func (p *testDataPlugin) handleCsvContentScenario(ctx context.Context, req *back return resp, nil } -func (p *testDataPlugin) handleCsvFileScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleCsvFileScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -72,14 +72,14 @@ func (p *testDataPlugin) handleCsvFileScenario(ctx context.Context, req *backend return resp, nil } -func (p *testDataPlugin) loadCsvFile(fileName string) (*data.Frame, error) { +func (p *TestDataPlugin) loadCsvFile(fileName string) (*data.Frame, error) { validFileName := regexp.MustCompile(`([\w_]+)\.csv`) if !validFileName.MatchString(fileName) { return nil, fmt.Errorf("invalid csv file name: %q", fileName) } - filePath := filepath.Join(p.Cfg.StaticRootPath, "testdata", fileName) + filePath := filepath.Join(p.cfg.StaticRootPath, "testdata", fileName) // Can ignore gosec G304 here, because we check the file pattern above // nolint:gosec @@ -97,7 +97,7 @@ func (p *testDataPlugin) loadCsvFile(fileName string) (*data.Frame, error) { return p.loadCsvContent(fileReader, fileName) } -func (p *testDataPlugin) loadCsvContent(ioReader io.Reader, name string) (*data.Frame, error) { +func (p *TestDataPlugin) loadCsvContent(ioReader io.Reader, name string) (*data.Frame, error) { reader := csv.NewReader(ioReader) // Read the header records diff --git a/pkg/tsdb/testdatasource/csv_data_test.go b/pkg/tsdb/testdatasource/csv_data_test.go index 6e0a389e155..316a8a96546 100644 --- a/pkg/tsdb/testdatasource/csv_data_test.go +++ b/pkg/tsdb/testdatasource/csv_data_test.go @@ -17,8 +17,8 @@ func TestCSVFileScenario(t *testing.T) { cfg.DataPath = t.TempDir() cfg.StaticRootPath = "../../../public" - p := &testDataPlugin{ - Cfg: cfg, + p := &TestDataPlugin{ + cfg: cfg, } t.Run("loadCsvFile", func(t *testing.T) { diff --git a/pkg/tsdb/testdatasource/flight_path.go b/pkg/tsdb/testdatasource/flight_path.go index 855aa8d3f15..79b0ad87a34 100644 --- a/pkg/tsdb/testdatasource/flight_path.go +++ b/pkg/tsdb/testdatasource/flight_path.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" ) -func (p *testDataPlugin) handleFlightPathScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleFlightPathScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { diff --git a/pkg/tsdb/testdatasource/flight_path_test.go b/pkg/tsdb/testdatasource/flight_path_test.go index cec54926c74..456e292e75e 100644 --- a/pkg/tsdb/testdatasource/flight_path_test.go +++ b/pkg/tsdb/testdatasource/flight_path_test.go @@ -16,8 +16,8 @@ import ( func TestFlightPathScenario(t *testing.T) { cfg := setting.NewCfg() - p := &testDataPlugin{ - Cfg: cfg, + p := &TestDataPlugin{ + cfg: cfg, } t.Run("simple flight", func(t *testing.T) { diff --git a/pkg/tsdb/testdatasource/resource_handler.go b/pkg/tsdb/testdatasource/resource_handler.go index 7929b6fc285..1a241aed663 100644 --- a/pkg/tsdb/testdatasource/resource_handler.go +++ b/pkg/tsdb/testdatasource/resource_handler.go @@ -15,7 +15,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" ) -func (p *testDataPlugin) registerRoutes(mux *http.ServeMux) { +func (p *TestDataPlugin) registerRoutes(mux *http.ServeMux) { mux.HandleFunc("/", p.testGetHandler) mux.HandleFunc("/scenarios", p.getScenariosHandler) mux.HandleFunc("/stream", p.testStreamHandler) @@ -24,7 +24,7 @@ func (p *testDataPlugin) registerRoutes(mux *http.ServeMux) { mux.HandleFunc("/boom", p.testPanicHandler) } -func (p *testDataPlugin) testGetHandler(rw http.ResponseWriter, req *http.Request) { +func (p *TestDataPlugin) testGetHandler(rw http.ResponseWriter, req *http.Request) { p.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) if req.Method != http.MethodGet { @@ -38,7 +38,7 @@ func (p *testDataPlugin) testGetHandler(rw http.ResponseWriter, req *http.Reques rw.WriteHeader(http.StatusOK) } -func (p *testDataPlugin) getScenariosHandler(rw http.ResponseWriter, req *http.Request) { +func (p *TestDataPlugin) getScenariosHandler(rw http.ResponseWriter, req *http.Request) { result := make([]interface{}, 0) scenarioIds := make([]string, 0) @@ -69,7 +69,7 @@ func (p *testDataPlugin) getScenariosHandler(rw http.ResponseWriter, req *http.R } } -func (p *testDataPlugin) testStreamHandler(rw http.ResponseWriter, req *http.Request) { +func (p *TestDataPlugin) testStreamHandler(rw http.ResponseWriter, req *http.Request) { p.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) if req.Method != http.MethodGet { @@ -152,6 +152,6 @@ func createJSONHandler(logger log.Logger) http.Handler { }) } -func (p *testDataPlugin) testPanicHandler(rw http.ResponseWriter, req *http.Request) { +func (p *TestDataPlugin) testPanicHandler(rw http.ResponseWriter, req *http.Request) { panic("BOOM") } diff --git a/pkg/tsdb/testdatasource/scenarios.go b/pkg/tsdb/testdatasource/scenarios.go index fcf2c743edc..fde7981773e 100644 --- a/pkg/tsdb/testdatasource/scenarios.go +++ b/pkg/tsdb/testdatasource/scenarios.go @@ -54,12 +54,12 @@ type Scenario struct { handler backend.QueryDataHandlerFunc } -func (p *testDataPlugin) registerScenario(scenario *Scenario) { +func (p *TestDataPlugin) registerScenario(scenario *Scenario) { p.scenarios[scenario.ID] = scenario p.queryMux.HandleFunc(scenario.ID, scenario.handler) } -func (p *testDataPlugin) registerScenarios() { +func (p *TestDataPlugin) registerScenarios() { p.registerScenario(&Scenario{ ID: string(exponentialHeatmapBucketDataQuery), Name: "Exponential heatmap bucket data", @@ -213,7 +213,7 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means } // handleFallbackScenario handles the scenario where queryType is not set and fallbacks to scenarioId. -func (p *testDataPlugin) handleFallbackScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleFallbackScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { scenarioQueries := map[string][]backend.DataQuery{} for _, q := range req.Queries { @@ -256,7 +256,7 @@ func (p *testDataPlugin) handleFallbackScenario(ctx context.Context, req *backen return resp, nil } -func (p *testDataPlugin) handleRandomWalkScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleRandomWalkScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -276,7 +276,7 @@ func (p *testDataPlugin) handleRandomWalkScenario(ctx context.Context, req *back return resp, nil } -func (p *testDataPlugin) handleDatapointsOutsideRangeScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleDatapointsOutsideRangeScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -300,7 +300,7 @@ func (p *testDataPlugin) handleDatapointsOutsideRangeScenario(ctx context.Contex return resp, nil } -func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -344,7 +344,7 @@ func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req return resp, nil } -func (p *testDataPlugin) handleRandomWalkWithErrorScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleRandomWalkWithErrorScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -362,7 +362,7 @@ func (p *testDataPlugin) handleRandomWalkWithErrorScenario(ctx context.Context, return resp, nil } -func (p *testDataPlugin) handleRandomWalkSlowScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleRandomWalkSlowScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -383,7 +383,7 @@ func (p *testDataPlugin) handleRandomWalkSlowScenario(ctx context.Context, req * return resp, nil } -func (p *testDataPlugin) handleRandomWalkTableScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleRandomWalkTableScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -400,7 +400,7 @@ func (p *testDataPlugin) handleRandomWalkTableScenario(ctx context.Context, req return resp, nil } -func (p *testDataPlugin) handlePredictableCSVWaveScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handlePredictableCSVWaveScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -421,7 +421,7 @@ func (p *testDataPlugin) handlePredictableCSVWaveScenario(ctx context.Context, r return resp, nil } -func (p *testDataPlugin) handlePredictablePulseScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handlePredictablePulseScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -442,15 +442,15 @@ func (p *testDataPlugin) handlePredictablePulseScenario(ctx context.Context, req return resp, nil } -func (p *testDataPlugin) handleServerError500Scenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleServerError500Scenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { panic("Test Data Panic!") } -func (p *testDataPlugin) handleClientSideScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleClientSideScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { return backend.NewQueryDataResponse(), nil } -func (p *testDataPlugin) handleArrowScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleArrowScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -474,7 +474,7 @@ func (p *testDataPlugin) handleArrowScenario(ctx context.Context, req *backend.Q return resp, nil } -func (p *testDataPlugin) handleExponentialHeatmapBucketDataScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleExponentialHeatmapBucketDataScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -489,7 +489,7 @@ func (p *testDataPlugin) handleExponentialHeatmapBucketDataScenario(ctx context. return resp, nil } -func (p *testDataPlugin) handleLinearHeatmapBucketDataScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleLinearHeatmapBucketDataScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -504,7 +504,7 @@ func (p *testDataPlugin) handleLinearHeatmapBucketDataScenario(ctx context.Conte return resp, nil } -func (p *testDataPlugin) handleTableStaticScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleTableStaticScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { @@ -533,7 +533,7 @@ func (p *testDataPlugin) handleTableStaticScenario(ctx context.Context, req *bac return resp, nil } -func (p *testDataPlugin) handleLogsScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleLogsScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { diff --git a/pkg/tsdb/testdatasource/scenarios_test.go b/pkg/tsdb/testdatasource/scenarios_test.go index 816f695dc7d..8cbcd71bc24 100644 --- a/pkg/tsdb/testdatasource/scenarios_test.go +++ b/pkg/tsdb/testdatasource/scenarios_test.go @@ -15,7 +15,7 @@ import ( ) func TestTestdataScenarios(t *testing.T) { - p := &testDataPlugin{} + p := &TestDataPlugin{} t.Run("random walk ", func(t *testing.T) { t.Run("Should start at the requested value", func(t *testing.T) { diff --git a/pkg/tsdb/testdatasource/testdata.go b/pkg/tsdb/testdatasource/testdata.go index 6acf54efce8..c0dc491cadc 100644 --- a/pkg/tsdb/testdatasource/testdata.go +++ b/pkg/tsdb/testdatasource/testdata.go @@ -9,37 +9,41 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" - "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) -func init() { - registry.RegisterService(&testDataPlugin{}) -} - -type testDataPlugin struct { - BackendPluginManager backendplugin.Manager `inject:""` - Cfg *setting.Cfg `inject:""` - logger log.Logger - scenarios map[string]*Scenario - queryMux *datasource.QueryTypeMux -} - -func (p *testDataPlugin) Init() error { - p.logger = log.New("tsdb.testdata") - p.scenarios = map[string]*Scenario{} - p.queryMux = datasource.NewQueryTypeMux() - p.registerScenarios() +func ProvideService(cfg *setting.Cfg, manager backendplugin.Manager) (*TestDataPlugin, error) { resourceMux := http.NewServeMux() - p.registerRoutes(resourceMux) + p := new(resourceMux) factory := coreplugin.New(backend.ServeOpts{ QueryDataHandler: p.queryMux, CallResourceHandler: httpadapter.New(resourceMux), StreamHandler: newTestStreamHandler(p.logger), }) - err := p.BackendPluginManager.Register("testdata", factory) + err := manager.Register("testdata", factory) if err != nil { - p.logger.Error("Failed to register plugin", "error", err) + return nil, err } - return nil + + return p, nil +} + +func new(resourceMux *http.ServeMux) *TestDataPlugin { + p := &TestDataPlugin{ + logger: log.New("tsdb.testdata"), + scenarios: map[string]*Scenario{}, + queryMux: datasource.NewQueryTypeMux(), + } + + p.registerScenarios() + p.registerRoutes(resourceMux) + + return p +} + +type TestDataPlugin struct { + cfg *setting.Cfg + logger log.Logger + scenarios map[string]*Scenario + queryMux *datasource.QueryTypeMux } diff --git a/pkg/tsdb/testdatasource/usa_stats.go b/pkg/tsdb/testdatasource/usa_stats.go index 57dee9aed72..1c0d17d805d 100644 --- a/pkg/tsdb/testdatasource/usa_stats.go +++ b/pkg/tsdb/testdatasource/usa_stats.go @@ -36,7 +36,7 @@ type usaQuery struct { period time.Duration } -func (p *testDataPlugin) handleUSAScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { +func (p *TestDataPlugin) handleUSAScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { resp := backend.NewQueryDataResponse() for _, q := range req.Queries { diff --git a/pkg/tsdb/testdatasource/usa_stats_test.go b/pkg/tsdb/testdatasource/usa_stats_test.go index 5612c192190..034d9b8ee38 100644 --- a/pkg/tsdb/testdatasource/usa_stats_test.go +++ b/pkg/tsdb/testdatasource/usa_stats_test.go @@ -17,8 +17,8 @@ import ( func TestUSAScenario(t *testing.T) { cfg := setting.NewCfg() - p := &testDataPlugin{ - Cfg: cfg, + p := &TestDataPlugin{ + cfg: cfg, } t.Run("usa query modes", func(t *testing.T) { diff --git a/scripts/lib.star b/scripts/lib.star index 799593842cd..56fd30cbe8e 100644 --- a/scripts/lib.star +++ b/scripts/lib.star @@ -1,6 +1,6 @@ load('scripts/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token') -grabpl_version = '2.3.3' +grabpl_version = '2.4.0' build_image = 'grafana/build-container:1.4.1' publish_image = 'grafana/grafana-ci-deploy:1.3.1' grafana_docker_image = 'grafana/drone-grafana-docker:0.3.2' @@ -225,6 +225,9 @@ def lint_backend_step(edition): 'initialize', ], 'commands': [ + # Generate Go code, will install Wire + # TODO: Install Wire in Docker image instead + 'make gen-go', # Don't use Make since it will re-download the linters './bin/grabpl lint-backend --edition {}'.format(edition), ],