diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index ae8b600826f..141ad441208 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -6,6 +6,7 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const lodashPlugin = require('eslint-plugin-lodash'); const barrelPlugin = require('eslint-plugin-no-barrel-files'); const reactPlugin = require('eslint-plugin-react'); +const hooksPlugin = require('eslint-plugin-react-hooks'); const testingLibraryPlugin = require('eslint-plugin-testing-library'); const grafanaConfig = require('@grafana/eslint-config/flat'); @@ -16,7 +17,7 @@ const grafanaI18nPlugin = require('@grafana/i18n/eslint-plugin'); // as we just want to pull in all of the necessary configuration but not run the rules // (this should only be concerned with checking rules that we want to improve, // so there's no need to try and run the rules that will be linted properly anyway) -const { rules, ...baseConfig } = grafanaConfig; +const { rules, ...baseConfig } = grafanaConfig.find((config) => config?.name === '@grafana/eslint-config/flat'); /** * @type {Array} @@ -53,6 +54,13 @@ module.exports = [ // @ts-ignore - not sure why but flat config is typed as a maybe? ...reactPlugin.configs.flat['jsx-runtime'], }, + // FIXME: Remove once eslint-config-grafana is updated to include correct plugin + { + name: 'react-hooks-plugin', + plugins: { + 'react-hooks': hooksPlugin, + }, + }, { files: ['**/*.{ts,tsx,js}'], ...baseConfig, diff --git a/.drone.star b/.drone.star deleted file mode 100644 index a563ee0682a..00000000000 --- a/.drone.star +++ /dev/null @@ -1,38 +0,0 @@ -# To generate the .drone.yml file: -# 1. Modify the *.star definitions -# 2. Login to drone and export the env variables (token and server) shown here: https://drone.grafana.net/account -# 3. Run `make drone` -# More information about this process here: https://github.com/grafana/deployment_tools/blob/master/docs/infrastructure/drone/signing.md -""" -This module returns a Drone configuration including pipelines and secrets. -""" - -load("scripts/drone/events/main.star", "main_pipelines") -load( - "scripts/drone/events/release.star", - "publish_artifacts_pipelines", - "publish_npm_pipelines", - "publish_packages_pipeline", -) -load("scripts/drone/events/rrc-patch.star", "rrc_patch_pipelines") -load( - "scripts/drone/pipelines/publish_images.star", - "publish_image_pipelines_public", -) -load( - "scripts/drone/rgm.star", - "rgm", -) -load("scripts/drone/vault.star", "secrets") - -def main(_ctx): - return ( - main_pipelines() + - rrc_patch_pipelines() + - publish_image_pipelines_public() + - publish_artifacts_pipelines("public") + - publish_npm_pipelines() + - publish_packages_pipeline() + - rgm() + - secrets() - ) diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index bbd3e820619..00000000000 --- a/.drone.yml +++ /dev/null @@ -1,1880 +0,0 @@ ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: main-docs -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.21.3 - name: identify-runner -- commands: - - yarn install --immutable || yarn install --immutable - depends_on: [] - image: node:22.16.0-alpine - name: yarn-install -- commands: - - yarn run prettier:checkDocs - depends_on: - - yarn-install - environment: - NODE_OPTIONS: --max_old_space_size=8192 - image: node:22.16.0-alpine - name: lint-docs -- commands: - - mkdir -p /hugo/content/docs/grafana/latest - - 'echo -e ''---\nredirectURL: /docs/grafana/latest/\ntype: redirect\nversioned: - true\n---\n'' > /hugo/content/docs/grafana/_index.md' - - cp -r docs/sources/* /hugo/content/docs/grafana/latest/ - - cd /hugo && make prod - image: grafana/docs-base:latest - name: build-docs-website - pull: always -- commands: - - '# It is required that code generated from Thema/CUE be committed and in sync - with its inputs.' - - '# The following command will fail if running code generators produces any diff - in output.' - - apk add --update make - - CODEGEN_VERIFY=1 make gen-cue - depends_on: [] - image: golang:1.24.6-alpine - name: verify-gen-cue -trigger: - branch: main - event: - - push - paths: - include: - - '*.md' - - docs/** - - packages/**/*.md - - latest.json - repo: - - grafana/grafana -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: main-build-e2e-publish -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - echo $(/usr/bin/github-app-external-token) > /github-app/token - environment: - GITHUB_APP_ID: - from_secret: github-app-app-id - GITHUB_APP_INSTALLATION_ID: - from_secret: github-app-installation-id - GITHUB_APP_PRIVATE_KEY: - from_secret: github-app-private-key - failure: ignore - image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 - name: github-app-generate-token - volumes: - - name: github-app - path: /github-app -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.21.3 - name: identify-runner -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - '# It is required that code generated from Thema/CUE be committed and in sync - with its inputs.' - - '# The following command will fail if running code generators produces any diff - in output.' - - apk add --update make - - CODEGEN_VERIFY=1 make gen-cue - depends_on: [] - image: golang:1.24.6-alpine - name: verify-gen-cue -- commands: - - '# It is required that generated jsonnet is committed and in sync with its inputs.' - - '# The following command will fail if running code generators produces any diff - in output.' - - apk add --update make - - CODEGEN_VERIFY=1 make gen-jsonnet - depends_on: [] - image: golang:1.24.6-alpine - name: verify-gen-jsonnet -- commands: - - yarn install --immutable || yarn install --immutable - depends_on: [] - image: node:22.16.0-alpine - name: yarn-install -- commands: - - apk add --update jq - - new_version=$(cat package.json | jq -r .version | sed s/pre/${DRONE_BUILD_NUMBER}/g) - - 'echo "New version: $new_version"' - - yarn run lerna version $new_version --exact --no-git-tag-version --no-push --force-publish - -y - - yarn install --mode=update-lockfile - depends_on: - - yarn-install - image: node:22.16.0-alpine - name: update-package-json-version -- commands: - - apk add --update jq bash - - yarn packages:build - - yarn packages:pack - - ./scripts/validate-npm-packages.sh - depends_on: - - yarn-install - - update-package-json-version - environment: - NODE_OPTIONS: --max_old_space_size=8192 - image: node:22.16.0-alpine - name: build-frontend-packages -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --version - - docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' - - docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - - go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 - -a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu - -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 - -a docker:grafana:linux/arm/v7:ubuntu --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER - --ubuntu-base=ubuntu-base --alpine-base=alpine-base --tag-format='{{ .version_base - }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID - }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD > packages.txt - - find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i - depends_on: - - update-package-json-version - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - image: golang:1.24.6-alpine - name: rgm-package - pull: always - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/grabpl artifacts docker publish --dockerhub-repo grafana/grafana - depends_on: - - rgm-package - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_grafanauploads - GITHUB_APP_ID: "329617" - GITHUB_APP_INSTALLATION_ID: "37346161" - GITHUB_APP_PRIVATE_KEY: - from_secret: delivery-bot-app-private-key - image: google/cloud-sdk:431.0.0 - name: publish-images-grafana - volumes: - - name: docker - path: /var/run/docker.sock - when: - repo: - - grafana/grafana -- commands: - - yarn e2e:plugin:build - depends_on: - - yarn-install - environment: - NODE_OPTIONS: --max_old_space_size=8192 - image: node:22.16.0-alpine - name: build-test-plugins -- commands: - - apk add --update tar bash - - mkdir grafana - - tar --strip-components=1 -xvf ./dist/*amd64.tar.gz -C grafana - - cp -r devenv scripts tools grafana && cd grafana && ./scripts/grafana-server/start-server - depends_on: - - rgm-package - detach: true - environment: - GF_APP_MODE: development - GF_SERVER_HTTP_PORT: "3001" - GF_SERVER_ROUTER_LOGGING: "1" - image: alpine:3.21.3 - name: grafana-server -- commands: - - GITHUB_TOKEN=$(cat /github-app/token) - - cd / - - ./cpp-e2e/scripts/ci-run.sh azure ${DRONE_SOURCE_BRANCH} - depends_on: - - grafana-server - - github-app-generate-token - environment: - AZURE_SP_APP_ID: - from_secret: azure_sp_app_id - AZURE_SP_PASSWORD: - from_secret: azure_sp_app_pw - AZURE_TENANT: - from_secret: azure_tenant - CYPRESS_CI: "true" - HOST: grafana-server - image: us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-14.3.2:1.0.0 - name: end-to-end-tests-cloud-plugins-suite-azure - volumes: - - name: github-app - path: /github-app - when: - paths: - include: - - pkg/tsdb/azuremonitor/** - - public/app/plugins/datasource/azuremonitor/** - - e2e/cloud-plugins-suite/azure-monitor.spec.ts - repo: - - grafana/grafana -- commands: - - npx wait-on@7.0.1 http://$HOST:$PORT - - yarn playwright install --with-deps chromium - - GRAFANA_URL=http://$HOST:$PORT yarn e2e:playwright --grep @plugins - depends_on: - - grafana-server - - build-test-plugins - environment: - HOST: grafana-server - PORT: "3001" - PROV_DIR: /grafana/scripts/grafana-server/tmp/conf/provisioning - image: node:22-bookworm - name: playwright-plugin-e2e -- commands: - - apt-get update - - apt-get install -yq zip - - printenv GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY > /tmp/gcpkey_upload_artifacts.json - - gcloud auth activate-service-account --key-file=/tmp/gcpkey_upload_artifacts.json - - gsutil cp -r ./playwright-report/. gs://releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report - - export E2E_PLAYWRIGHT_REPORT_URL=https://storage.googleapis.com/releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report/index.html - - "echo \"E2E Playwright report uploaded to: \n $${E2E_PLAYWRIGHT_REPORT_URL}\"" - depends_on: - - playwright-plugin-e2e - environment: - GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY: - from_secret: gcp_upload_artifacts_key - failure: ignore - image: google/cloud-sdk:431.0.0 - name: playwright-e2e-report-upload - when: - status: - - success - - failure -- commands: - - GITHUB_TOKEN=$(cat /github-app/token) - - if [ ! -d ./playwright-report/trace ]; then echo 'all tests passed'; exit 0; fi - - export E2E_PLAYWRIGHT_REPORT_URL=https://storage.googleapis.com/releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report/index.html - - 'curl -L -X POST https://api.github.com/repos/grafana/grafana/issues/${DRONE_PULL_REQUEST}/comments - -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $${GITHUB_TOKEN}" - -H "X-GitHub-Api-Version: 2022-11-28" -d "{\"body\":\"❌ Failed to run Playwright - plugin e2e tests.

Click [here]($${E2E_PLAYWRIGHT_REPORT_URL}) to - browse the Playwright report and trace viewer.
For information on how to - run Playwright tests locally, refer to the [Developer guide](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#to-run-the-playwright-tests). - \"}"' - depends_on: - - playwright-e2e-report-upload - - github-app-generate-token - failure: ignore - image: byrnedo/alpine-curl:0.1.8 - name: playwright-e2e-report-post-link - volumes: - - name: github-app - path: /github-app - when: - status: - - success - - failure -- commands: - - export GITHUB_TOKEN=$(cat /github-app/token) - - if [ -z `find ./e2e -type f -name *spec.ts.mp4` ]; then echo 'no e2e videos found - from remaining tests'; exit 0; fi - - apt-get update - - apt-get install -yq zip - - printenv GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY > /tmp/gcpkey_upload_artifacts.json - - gcloud auth activate-service-account --key-file=/tmp/gcpkey_upload_artifacts.json - - find ./e2e -type f -name "*spec.ts.mp4" | zip e2e/videos.zip -@ - - gsutil cp e2e/videos.zip gs://$${E2E_TEST_ARTIFACTS_BUCKET}/${DRONE_BUILD_NUMBER}/artifacts/videos/videos.zip - - export E2E_ARTIFACTS_VIDEO_ZIP=https://storage.googleapis.com/$${E2E_TEST_ARTIFACTS_BUCKET}/${DRONE_BUILD_NUMBER}/artifacts/videos/videos.zip - - 'echo "E2E Test artifacts uploaded to: $${E2E_ARTIFACTS_VIDEO_ZIP}"' - - 'curl -X POST https://api.github.com/repos/${DRONE_REPO}/statuses/${DRONE_COMMIT_SHA} - -H "Authorization: token $${GITHUB_TOKEN}" -d "{\"state\":\"success\",\"target_url\":\"$${E2E_ARTIFACTS_VIDEO_ZIP}\", - \"description\": \"Click on the details to download e2e recording videos\", \"context\": - \"e2e_artifacts\"}"' - depends_on: - - end-to-end-tests-cloud-plugins-suite-azure - - playwright-plugin-e2e - - github-app-generate-token - environment: - E2E_TEST_ARTIFACTS_BUCKET: releng-pipeline-artifacts-dev - GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY: - from_secret: gcp_upload_artifacts_key - failure: ignore - image: google/cloud-sdk:431.0.0 - name: e2e-tests-artifacts-upload - volumes: - - name: github-app - path: /github-app - when: - status: - - success - - failure -- commands: - - yarn storybook:build - - ./bin/build verify-storybook - depends_on: - - rgm-package - - build-frontend-packages - environment: - NODE_OPTIONS: --max_old_space_size=4096 - image: node:22.16.0-alpine - name: build-storybook - when: - paths: - include: - - packages/grafana-ui/** -- commands: - - npx wait-on@7.0.1 http://$HOST:$PORT - - pa11y-ci --config e2e/pa11yci.conf.js - depends_on: - - grafana-server - environment: - GRAFANA_MISC_STATS_API_KEY: - from_secret: grafana_misc_stats_api_key - HOST: grafana-server - NO_THRESHOLDS: "true" - PORT: 3001 - failure: ignore - image: grafana/docker-puppeteer:1.1.0 - name: test-a11y-frontend -- commands: - - ./bin/build store-storybook --deployment canary - depends_on: - - build-storybook - - end-to-end-tests-cloud-plugins-suite-azure - - playwright-plugin-e2e - environment: - GCP_KEY: - from_secret: gcp_grafanauploads - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: store-storybook - when: - paths: - include: - - packages/grafana-ui/** - repo: - - grafana/grafana -- commands: - - apk add --update bash grep git - - ./scripts/ci-frontend-metrics.sh ./grafana/public/build | ./bin/build publish-metrics - $$GRAFANA_MISC_STATS_API_KEY - depends_on: - - test-a11y-frontend - environment: - GRAFANA_MISC_STATS_API_KEY: - from_secret: grafana_misc_stats_api_key - failure: ignore - image: node:22.16.0-alpine - name: publish-frontend-metrics - when: - repo: - - grafana/grafana -- commands: - - ./bin/grabpl artifacts docker publish --dockerhub-repo grafana/grafana-oss - depends_on: - - rgm-package - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_grafanauploads - GITHUB_APP_ID: "329617" - GITHUB_APP_INSTALLATION_ID: "37346161" - GITHUB_APP_PRIVATE_KEY: - from_secret: delivery-bot-app-private-key - image: google/cloud-sdk:431.0.0 - name: publish-images-grafana-oss - volumes: - - name: docker - path: /var/run/docker.sock - when: - repo: - - grafana/grafana -- commands: - - apk add --update bash git - - ./scripts/publish-npm-packages.sh --dist-tag 'canary' --registry 'https://registry.npmjs.org' - depends_on: - - end-to-end-tests-cloud-plugins-suite-azure - - playwright-plugin-e2e - - build-frontend-packages - environment: - NPM_TOKEN: - from_secret: npm_token - image: node:22.16.0-alpine - name: release-canary-npm-packages - when: - paths: - include: - - packages/** - repo: - - grafana/grafana -- commands: - - ./bin/build upload-packages --edition oss - depends_on: - - rgm-package - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: upload-packages - when: - repo: - - grafana/grafana -- commands: - - ./bin/build upload-cdn --edition oss - depends_on: - - rgm-package - environment: - GCP_KEY: - from_secret: gcp_grafanauploads - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: upload-cdn-assets - when: - repo: - - grafana/grafana -trigger: - branch: main - event: - - push - paths: - exclude: - - '*.md' - - docs/** - - latest.json - repo: - - grafana/grafana -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: github-app - temp: {} ---- -clone: - retries: 3 -depends_on: -- main-build-e2e-publish -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: main-trigger-downstream -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- image: grafana/drone-downstream - name: trigger-enterprise-downstream - settings: - params: - - SOURCE_BUILD_NUMBER=${DRONE_COMMIT} - - SOURCE_COMMIT=${DRONE_COMMIT} - repositories: - - grafana/grafana-enterprise@main - server: https://drone.grafana.net - token: - from_secret: drone_token -trigger: - branch: main - event: - - push - paths: - exclude: - - '*.md' - - docs/** - - latest.json - repo: - - grafana/grafana -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: -- main-build-e2e-publish -kind: pipeline -name: main-notify -platform: - arch: amd64 - os: linux -steps: -- image: plugins/slack - name: slack - settings: - channel: grafana-ci-notifications - template: |- - Build {{build.number}} failed for commit: : {{build.link}} - Branch: - Author: {{build.author}} - webhook: - from_secret: slack_webhook -trigger: - branch: main - event: - - push - paths: - exclude: - - '*.md' - - docs/** - - latest.json - repo: - - grafana/grafana - status: - - failure -type: docker ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rrc-trigger-downstream -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- image: grafana/drone-downstream - name: trigger-enterprise-downstream - settings: - params: - - SOURCE_BUILD_NUMBER=${DRONE_COMMIT} - - SOURCE_COMMIT=${DRONE_COMMIT} - - SOURCE_TAG=${DRONE_TAG} - repositories: - - grafana/grafana-enterprise@${DRONE_SOURCE_BRANCH} - server: https://drone.grafana.net - token: - from_secret: drone_token -trigger: - branch: - - instant - - fast - - steady - - slow - ref: - include: - - refs/tags/rrc* -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: publish-docker-public -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.21.3 - name: identify-runner -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - ./bin/build artifacts docker fetch --edition oss - depends_on: - - compile-build-cmd - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_grafanauploads - image: google/cloud-sdk:431.0.0 - name: fetch-images - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - apk add bash - - |2- - - bash -c ' - IMAGE_TAG=$(echo "$${TAG}" | sed -e "s/+/-/g") - debug= - if [[ -n $${DRY_RUN} ]]; then debug=echo; fi - docker login -u $${DOCKER_USER} -p $${DOCKER_PASSWORD} - - # Push the grafana-image-tags images - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Create the grafana manifests - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG} grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG}-ubuntu grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Push the grafana manifests - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG} - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG}-ubuntu - - # if LATEST is set, then also create & push latest - if [[ -n $${LATEST} ]]; then - $$debug docker manifest create grafana/grafana:latest grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker manifest create grafana/grafana:latest-ubuntu grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - $$debug docker manifest push grafana/grafana:latest - $$debug docker manifest push grafana/grafana:latest-ubuntu - - fi' - depends_on: - - fetch-images - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - image: docker:27-cli - name: publish-images-grafana - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/grabpl artifacts docker publish --dockerhub-repo grafana/grafana-oss --version-tag - ${DRONE_TAG} - depends_on: - - fetch-images - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_grafanauploads - GITHUB_APP_ID: "329617" - GITHUB_APP_INSTALLATION_ID: "37346161" - GITHUB_APP_PRIVATE_KEY: - from_secret: delivery-bot-app-private-key - image: google/cloud-sdk:431.0.0 - name: publish-images-grafana-oss - volumes: - - name: docker - path: /var/run/docker.sock -trigger: - event: - - promote - target: - - public -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: manually-publish-docker-public -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.21.3 - name: identify-runner -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - ./bin/build artifacts docker fetch --edition oss - depends_on: - - compile-build-cmd - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_grafanauploads - image: google/cloud-sdk:431.0.0 - name: fetch-images - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - apk add bash - - |2- - - bash -c ' - IMAGE_TAG=$(echo "$${TAG}" | sed -e "s/+/-/g") - debug= - if [[ -n $${DRY_RUN} ]]; then debug=echo; fi - docker login -u $${DOCKER_USER} -p $${DOCKER_PASSWORD} - - # Push the grafana-image-tags images - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Create the grafana manifests - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG} grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG}-ubuntu grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Push the grafana manifests - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG} - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG}-ubuntu - - # if LATEST is set, then also create & push latest - if [[ -n $${LATEST} ]]; then - $$debug docker manifest create grafana/grafana:latest grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker manifest create grafana/grafana:latest-ubuntu grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - $$debug docker manifest push grafana/grafana:latest - $$debug docker manifest push grafana/grafana:latest-ubuntu - - fi' - depends_on: - - fetch-images - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - image: docker:27-cli - name: publish-images-grafana - volumes: - - name: docker - path: /var/run/docker.sock -trigger: - event: - - promote - target: - - publish-docker-public -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: create-release-pr -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - export GITHUB_TOKEN=$(cat /github-app/token) - - apk add perl - - v_target=`echo $${TAG} | perl -pe 's/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/v\1.\2.x/'` - - curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr - - gh workflow run -f dry_run=$${DRY_RUN} -f version=$${TAG} -f target=$${v_target} - -f latest=$${LATEST} --repo=grafana/grafana release-pr.yml - depends_on: [] - environment: - GH_CLI_URL: https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz - image: byrnedo/alpine-curl:0.1.8 - name: create-release-pr - volumes: - - name: github-app - path: /github-app -trigger: - event: - - promote - target: release-pr -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: github-app - temp: {} ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: publish-artifacts-public -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket - $${PRERELEASE_BUCKET} - depends_on: - - compile-build-cmd - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: publish-artifacts -- commands: - - ./bin/build artifacts storybook --tag ${DRONE_TAG} - depends_on: - - compile-build-cmd - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - image: grafana/grafana-ci-deploy:1.3.3 - name: publish-storybook -- commands: - - echo $(/usr/bin/github-app-external-token) > /github-app/token - environment: - GITHUB_APP_ID: - from_secret: github-app-app-id - GITHUB_APP_INSTALLATION_ID: - from_secret: github-app-installation-id - GITHUB_APP_PRIVATE_KEY: - from_secret: github-app-private-key - failure: ignore - image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 - name: github-app-generate-token - volumes: - - name: github-app - path: /github-app -- commands: - - export GITHUB_TOKEN=$(cat /github-app/token) - - apk add perl - - v_target=`echo $${TAG} | perl -pe 's/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/v\1.\2.x/'` - - curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr - - gh workflow run -f dry_run=$${DRY_RUN} -f version=$${TAG} -f target=$${v_target} - -f latest=$${LATEST} --repo=grafana/grafana release-pr.yml - depends_on: - - publish-artifacts - - github-app-generate-token - environment: - GH_CLI_URL: https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz - image: byrnedo/alpine-curl:0.1.8 - name: create-release-pr - volumes: - - name: github-app - path: /github-app -trigger: - event: - - promote - target: - - public -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: github-app - temp: {} ---- -clone: - retries: 3 -depends_on: [] -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: publish-npm-packages-public -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - yarn install --immutable || yarn install --immutable - depends_on: [] - image: node:22.16.0-alpine - name: yarn-install -- commands: - - ./bin/build artifacts npm retrieve --tag ${DRONE_TAG} - depends_on: - - compile-build-cmd - - yarn-install - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - PRERELEASE_BUCKET: - from_secret: prerelease_bucket - failure: ignore - image: grafana/grafana-ci-deploy:1.3.3 - name: retrieve-npm-packages -- commands: - - ./bin/build artifacts npm release --tag ${DRONE_TAG} - depends_on: - - compile-build-cmd - - retrieve-npm-packages - environment: - NPM_TOKEN: - from_secret: npm_token - failure: ignore - image: node:22.16.0-alpine - name: release-npm-packages -trigger: - event: - - promote - target: - - public -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: verify-grafanacom-artifacts -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - apk add curl bash - - "\n for i in {1..5}; do\n if ./scripts/drone/verify-grafanacom.sh; - then\n exit 0\n elif [ $i -eq 5 ]; then\n exit - 1\n else\n sleep 60\n fi\n done\n - \ " - depends_on: [] - image: node:22.16.0-alpine - name: verify-grafanacom -trigger: - event: - - promote - target: verify-grafanacom-artifacts -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: -- publish-artifacts-public -- publish-docker-public -environment: - EDITION: oss -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: publish-packages -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- depends_on: - - compile-build-cmd - image: us.gcr.io/kubernetes-dev/package-publish:latest - name: publish-linux-packages-deb - privileged: true - settings: - access_key_id: - from_secret: packages_access_key_id - deb_distribution: auto - gpg_passphrase: - from_secret: packages_gpg_passphrase - gpg_private_key: - from_secret: packages_gpg_private_key - gpg_public_key: - from_secret: packages_gpg_public_key - package_path: gs://grafana-prerelease/artifacts/downloads/*${DRONE_TAG}/oss/**.deb - secret_access_key: - from_secret: packages_secret_access_key - service_account_json: - from_secret: packages_service_account - target_bucket: grafana-packages -- depends_on: - - compile-build-cmd - image: us.gcr.io/kubernetes-dev/package-publish:latest - name: publish-linux-packages-rpm - privileged: true - settings: - access_key_id: - from_secret: packages_access_key_id - deb_distribution: auto - gpg_passphrase: - from_secret: packages_gpg_passphrase - gpg_private_key: - from_secret: packages_gpg_private_key - gpg_public_key: - from_secret: packages_gpg_public_key - package_path: gs://grafana-prerelease/artifacts/downloads/*${DRONE_TAG}/oss/**.rpm - secret_access_key: - from_secret: packages_secret_access_key - service_account_json: - from_secret: packages_service_account - target_bucket: grafana-packages -- commands: - - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} - depends_on: - - publish-linux-packages-deb - - publish-linux-packages-rpm - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - GRAFANA_COM_API_KEY: - from_secret: grafana_api_key - image: grafana/grafana-ci-deploy:1.3.3 - name: publish-grafanacom -- commands: - - apk add curl bash - - "\n for i in {1..5}; do\n if ./scripts/drone/verify-grafanacom.sh; - then\n exit 0\n elif [ $i -eq 5 ]; then\n exit - 1\n else\n sleep 60\n fi\n done\n - \ " - depends_on: - - publish-grafanacom - image: node:22.16.0-alpine - name: verify-grafanacom -trigger: - event: - - promote - target: - - public -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: publish-grafanacom -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.24.6-alpine - name: compile-build-cmd -- commands: - - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} - depends_on: - - compile-build-cmd - environment: - GCP_KEY: - from_secret: gcp_grafanauploads_base64 - GRAFANA_COM_API_KEY: - from_secret: grafana_api_key - image: grafana/grafana-ci-deploy:1.3.3 - name: publish-grafanacom -trigger: - event: - - promote - target: publish-grafanacom -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-main-prerelease -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GRAFANA_DIR=$$(pwd) - - export GITHUB_TOKEN=$(cat /github-app/token) - - ./pkg/build/daggerbuild/scripts/drone_build_main.sh - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.6-alpine - name: rgm-build - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -trigger: - branch: main - event: - - push - paths: - exclude: - - '*.md' - - docs/** - - packages/**/*.md - - latest.json - repo: - - grafana/grafana -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-tag-prerelease -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GRAFANA_DIR=$$(pwd) - - export GITHUB_TOKEN=$(cat /github-app/token) - - ./pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.6-alpine - name: rgm-build - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -trigger: - event: - exclude: - - promote - ref: - exclude: - - refs/tags/*-cloud* - include: - - refs/tags/v* -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: -- rgm-tag-prerelease -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-tag-verify-prerelease-assets -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - apt-get update && apt-get install -yq gettext - - printenv GCP_KEY | base64 -d > /tmp/key.json - - gcloud auth activate-service-account --key-file=/tmp/key.json - - ./scripts/list-release-artifacts.sh ${DRONE_TAG} | xargs -n1 gsutil stat >> /tmp/stat.log - - '! cat /tmp/stat.log | grep "No URLs matched"' - depends_on: - - clone - environment: - BUCKET: grafana-prerelease - GCP_KEY: - from_secret: gcp_key_base64 - image: google/cloud-sdk:431.0.0 - name: gsutil-stat -trigger: - event: - exclude: - - promote - ref: - exclude: - - refs/tags/*-cloud* - include: - - refs/tags/v* -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-version-branch-prerelease -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GRAFANA_DIR=$$(pwd) - - export GITHUB_TOKEN=$(cat /github-app/token) - - ./pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.6-alpine - name: rgm-build - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -trigger: - ref: - - refs/heads/v[0-9]* -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: -- rgm-version-branch-prerelease -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-prerelease-verify-prerelease-assets -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - apt-get update && apt-get install -yq gettext - - printenv GCP_KEY | base64 -d > /tmp/key.json - - gcloud auth activate-service-account --key-file=/tmp/key.json - - ./scripts/list-release-artifacts.sh ${DRONE_TAG} | xargs -n1 gsutil stat >> /tmp/stat.log - - '! cat /tmp/stat.log | grep "No URLs matched"' - depends_on: - - clone - environment: - BUCKET: grafana-prerelease - GCP_KEY: - from_secret: gcp_key_base64 - image: google/cloud-sdk:431.0.0 - name: gsutil-stat -trigger: - ref: - - refs/heads/v[0-9]* -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-promotion -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - echo $(/usr/bin/github-app-external-token) > /github-app/token - environment: - GITHUB_APP_ID: - from_secret: github-app-app-id - GITHUB_APP_INSTALLATION_ID: - from_secret: github-app-installation-id - GITHUB_APP_PRIVATE_KEY: - from_secret: github-app-private-key - failure: ignore - image: us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59 - name: github-app-generate-token - volumes: - - name: github-app - path: /github-app -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GITHUB_TOKEN=$(cat /github-app/token) - - dagger run --silent go run ./pkg/build/cmd artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} - --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --build-id=$${DRONE_BUILD_NUMBER} - --version=$${VERSION} - depends_on: - - github-app-generate-token - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.6-alpine - name: rgm-build - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -- commands: - - printenv GCP_KEY_BASE64 | base64 -d > /tmp/key.json - - gcloud auth activate-service-account --key-file=/tmp/key.json - - gcloud storage cp -r dist/* $${UPLOAD_TO} - depends_on: - - rgm-build - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - image: google/cloud-sdk:alpine - name: rgm-copy -trigger: - event: - - promote - target: upload-packages -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker -- name: github-app - path: /github-app -- name: github-app - temp: {} ---- -get: - name: app-id - path: ci/data/repo/grafana/grafana/github-app -kind: secret -name: github-app-app-id ---- -get: - name: app-installation-id - path: ci/data/repo/grafana/grafana/github-app -kind: secret -name: github-app-installation-id ---- -get: - name: private-key - path: ci/data/repo/grafana/grafana/github-app -kind: secret -name: github-app-private-key ---- -get: - name: credentials.json - path: infra/data/ci/grafana-release-eng/grafanauploads -kind: secret -name: gcp_grafanauploads ---- -get: - name: credentials_base64 - path: infra/data/ci/grafana-release-eng/grafanauploads -kind: secret -name: gcp_grafanauploads_base64 ---- -get: - name: api_key - path: infra/data/ci/grafana-release-eng/grafanacom -kind: secret -name: grafana_api_key ---- -get: - name: .dockerconfigjson - path: secret/data/common/gcr -kind: secret -name: gcr ---- -get: - name: .dockerconfigjson - path: secret/data/common/gar -kind: secret -name: gar ---- -get: - name: machine-user-token - path: infra/data/ci/drone -kind: secret -name: drone_token ---- -get: - name: bucket - path: infra/data/ci/grafana/prerelease -kind: secret -name: prerelease_bucket ---- -get: - name: username - path: ci/data/common/dockerhub -kind: secret -name: docker_username ---- -get: - name: password - path: ci/data/common/dockerhub -kind: secret -name: docker_password ---- -get: - name: credentials.json - path: infra/data/ci/grafana/releng/artifacts-uploader-service-account -kind: secret -name: gcp_upload_artifacts_key ---- -get: - name: credentials.json - path: infra/data/ci/grafana/assets-downloader-build-container-service-account -kind: secret -name: gcp_download_build_container_assets_key ---- -get: - name: application_id - path: infra/data/ci/datasources/cpp-azure-resourcemanager-credentials -kind: secret -name: azure_sp_app_id ---- -get: - name: application_secret - path: infra/data/ci/datasources/cpp-azure-resourcemanager-credentials -kind: secret -name: azure_sp_app_pw ---- -get: - name: tenant_id - path: infra/data/ci/datasources/cpp-azure-resourcemanager-credentials -kind: secret -name: azure_tenant ---- -get: - name: token - path: infra/data/ci/grafana-release-eng/npm -kind: secret -name: npm_token ---- -get: - name: public-key-b64 - path: infra/data/ci/packages-publish/gpg -kind: secret -name: packages_gpg_public_key ---- -get: - name: private-key-b64 - path: infra/data/ci/packages-publish/gpg -kind: secret -name: packages_gpg_private_key ---- -get: - name: passphrase - path: infra/data/ci/packages-publish/gpg -kind: secret -name: packages_gpg_passphrase ---- -get: - name: credentials.json - path: infra/data/ci/packages-publish/service-account -kind: secret -name: packages_service_account ---- -get: - name: AccessID - path: infra/data/ci/packages-publish/bucket-credentials -kind: secret -name: packages_access_key_id ---- -get: - name: Secret - path: infra/data/ci/packages-publish/bucket-credentials -kind: secret -name: packages_secret_access_key ---- -get: - name: static_asset_editions - path: infra/data/ci/grafana-release-eng/artifact-publishing -kind: secret -name: static_asset_editions ---- -get: - name: gcp_service_account_prod_base64 - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: gcp_key_base64 ---- -get: - name: destination_prod - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: destination ---- -get: - name: storybook_destination - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: rgm_storybook_destination ---- -get: - name: cdn_destination - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: rgm_cdn_destination ---- -get: - name: downloads_destination - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: rgm_downloads_destination ---- -get: - name: dagger_token - path: infra/data/ci/grafana-release-eng/rgm -kind: secret -name: dagger_token ---- -get: - name: PRIVATE_KEY - path: ci/data/repo/grafana/grafana/delivery-bot-app -kind: secret -name: delivery-bot-app-private-key ---- -get: - name: service-account - path: secret/data/common/gcr -kind: secret -name: gcr_credentials ---- -kind: signature -hmac: 70171a24c7e9ab7af105bd39d02f9aa6d28a4505963dfc9140f53cdfe4965ef8 - -... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b4cae4112b3..2dac93d87e7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -328,9 +328,6 @@ # Continuous Integration -.drone.yml @grafana/grafana-developer-enablement-squad -.drone.star @grafana/grafana-developer-enablement-squad -/scripts/drone/ @grafana/grafana-developer-enablement-squad /pkg/build/ @grafana/grafana-developer-enablement-squad /.dockerignore @grafana/grafana-developer-enablement-squad /Dockerfile @grafana/grafana-developer-enablement-squad @@ -411,9 +408,14 @@ /public/locales/i18next-parser-enterprise.config.cjs @grafana/grafana-frontend-platform /public/app/core/internationalization/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform -/e2e-playwright/ @grafana/grafana-frontend-platform /e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources -/e2e-playwright/dashboard-new-layouts @grafana/dashboards-squad +/e2e-playwright/dashboard-new-layouts/ @grafana/dashboards-squad +/e2e-playwright/dashboards-search-suite/ @grafana/dashboards-squad +/e2e-playwright/dashboards/DashboardLiveTest.json @grafana/dashboards-squad +/e2e-playwright/dashboards/DataLinkWithoutSlugTest.json @grafana/dashboards-squad +/e2e-playwright/dashboards/PanelSandboxDashboard.json @grafana/plugins-platform-frontend +/e2e-playwright/dashboards/TestDashboard.json @grafana/dashboards-squad @grafana/grafana-search-navigate-organise +/e2e-playwright/dashboards/TestV2Dashboard.json @grafana/dashboards-squad /e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-browse.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts @grafana/sharing-squad @@ -447,6 +449,9 @@ /e2e-playwright/dashboards-suite/templating-dashboard-links-and-variables.spec.ts @grafana/dashboards-squad /e2e-playwright/dashboards-suite/textbox-variables.spec.ts @grafana/dashboards-squad /e2e-playwright/dashboards-suite/utils/makeDashboard.ts @grafana/grafana-search-navigate-organise +/e2e-playwright/fixtures/exemplars-query-response.json @grafana/observability-traces-and-profiling +/e2e-playwright/fixtures/long-trace-response.json @grafana/observability-traces-and-profiling +/e2e-playwright/fixtures/tempo-response.json @grafana/oss-big-tent /e2e-playwright/panels-suite/dashlist.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/panels-suite/datagrid-data-change.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/datagrid-editing-features.spec.ts @grafana/dataviz-squad @@ -458,10 +463,43 @@ /e2e-playwright/panels-suite/panelEdit_queries.spec.ts @grafana/dashboards-squad /e2e-playwright/panels-suite/panelEdit_transforms.spec.ts @grafana/datapro /e2e-playwright/panels-suite/table-kitchenSink.spec.ts @grafana/dataviz-squad +/e2e-playwright/panels-suite/table-markdown.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/table-sparkline.spec.ts @grafana/dataviz-squad /e2e-playwright/plugin-e2e/ @grafana/oss-big-tent @grafana/partner-datasources /e2e-playwright/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend -/e2e-playwright/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend +/e2e-playwright/smoke-tests-suite/ @grafana/grafana-frontend-platform +/e2e-playwright/start-server @grafana/grafana-frontend-platform +/e2e-playwright/storybook/ @grafana/grafana-frontend-platform +/e2e-playwright/test-plugins/ @grafana/plugins-platform-frontend +/e2e-playwright/unauthenticated/login.spec.ts @grafana/grafana-frontend-platform +/e2e-playwright/utils/ @grafana/grafana-frontend-platform +/e2e-playwright/various-suite/bar-gauge.spec.ts @grafana/dataviz-squad +/e2e-playwright/various-suite/bookmarks.spec.ts @grafana/grafana-search-navigate-organise +/e2e-playwright/various-suite/exemplars.spec.ts @grafana/observability-traces-and-profiling +/e2e-playwright/various-suite/explore.spec.ts @grafana/observability-traces-and-profiling +/e2e-playwright/various-suite/filter-annotations.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts @grafana/plugins-platform-frontend +/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts @grafana/plugins-platform-frontend +/e2e-playwright/various-suite/gauge.spec.ts @grafana/dataviz-squad +/e2e-playwright/various-suite/graph-auto-migrate.spec.ts @grafana/dataviz-squad +/e2e-playwright/various-suite/inspect-drawer.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/keybinds.spec.ts @grafana/grafana-frontend-platform +/e2e-playwright/various-suite/loki-query-builder.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/loki-table-explore-to-dash.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/migrate-to-cloud.spec.ts @grafana/grafana-operator-experience-squad +/e2e-playwright/various-suite/navigation.spec.ts @grafana/grafana-search-navigate-organise +/e2e-playwright/various-suite/pie-chart.spec.ts @grafana/dataviz-squad +/e2e-playwright/various-suite/prometheus-annotations.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/prometheus-config.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/prometheus-editor.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/prometheus-variable-editor.spec.ts @grafana/oss-big-tent +/e2e-playwright/various-suite/query-editor.spec.ts @grafana/observability-traces-and-profiling +/e2e-playwright/various-suite/return-to-previous.spec.ts @grafana/grafana-search-navigate-organise +/e2e-playwright/various-suite/solo-route.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/trace-view-scrolling.spec.ts @grafana/observability-traces-and-profiling +/e2e-playwright/various-suite/verify-i18n.spec.ts @grafana/grafana-frontend-platform +/e2e-playwright/various-suite/visualization-suggestions.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/perf-test.spec.ts @grafana/grafana-frontend-platform # Packages /packages/README.md @grafana/grafana-frontend-platform @@ -639,7 +677,6 @@ /packages/grafana-schema/src/**/nodegraph @grafana/observability-traces-and-profiling @grafana/app-o11y-visualizations /packages/grafana-schema/src/**/parca @grafana/oss-big-tent /packages/grafana-schema/src/**/piechart @grafana/dataviz-squad -/packages/grafana-schema/src/**/publicdashboard @grafana/grafana-operator-experience-squad /packages/grafana-schema/src/**/stat @grafana/dataviz-squad /packages/grafana-schema/src/**/statetimeline @grafana/dataviz-squad /packages/grafana-schema/src/**/statushistory @grafana/dataviz-squad @@ -1216,6 +1253,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/analytics-events-report.yml @grafana/grafana-frontend-platform /.github/workflows/pr-e2e-tests.yml @grafana/grafana-developer-enablement-squad /.github/workflows/skye-add-to-project.yml @grafana/grafana-frontend-platform +/.github/workflows/frontend-perf-tests.yaml @grafana/grafana-frontend-platform /.github/zizmor.yml @grafana/grafana-developer-enablement-squad /.github/license_finder.yaml @bergquist /.github/actionlint.yaml @grafana/grafana-developer-enablement-squad diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index fb0362bb580..96e38b5c5c6 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -139,3 +139,70 @@ jobs: cache-dependency-path: 'yarn.lock' - run: yarn install --immutable --check-cache - run: yarn run betterer:ci + lint-frontend-api-clients: + permissions: + contents: read + id-token: write + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-api-clients-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true + name: Verify API clients + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - run: yarn install --immutable --check-cache + - name: Generate API clients + run: | + extract_error_message='ERROR! API client generation failed!' + yarn generate-apis || (echo "${extract_error_message}" && false) + - name: Verify generated clients + run: | + uncommited_error_message="ERROR! API client generation has not been committed. Please run 'yarn generate-apis', commit the changes and push again." + file_diff="$(git diff ':!conf')" + if [ -n "$file_diff" ]; then + echo "$file_diff" + echo "${uncommited_error_message}" + exit 1 + fi + lint-frontend-api-clients-enterprise: + permissions: + contents: read + id-token: write + # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + name: Verify API clients (enterprise) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - name: Generate API clients + run: | + extract_error_message='ERROR! API client generation failed!' + yarn generate-apis || (echo "${extract_error_message}" && false) + - name: Verify generated clients + run: | + uncommited_error_message="ERROR! API client generation has not been committed. Please run 'yarn generate-apis', commit the changes and push again." + file_diff="$(git diff ':!conf')" + if [ -n "$file_diff" ]; then + echo "$file_diff" + echo "${uncommited_error_message}" + exit 1 + fi diff --git a/.github/workflows/frontend-perf-tests.yaml b/.github/workflows/frontend-perf-tests.yaml new file mode 100644 index 00000000000..5b054231ae5 --- /dev/null +++ b/.github/workflows/frontend-perf-tests.yaml @@ -0,0 +1,133 @@ +name: Frontend performance tests + +on: + schedule: + - cron: "0 * * * *" # Run hourly + workflow_dispatch: # Allow manual triggering + +jobs: + performance-tests: + permissions: + contents: read + id-token: write + runs-on: ubuntu-x64-large + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + persist-credentials: false + + - id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@62722333225a1fae03ae27a63d638f9bc2176edb #get-vault-secrets-v1.2.0 + with: + repo_secrets: | + PROMETHEUS_URL=frontend_perf_tests:prometheus_push_url + PROMETHEUS_USER=frontend_perf_tests:prometheus_user + PROMETHEUS_TOKEN=frontend_perf_tests:prometheus_token + FSPERFBASELINE_USERNAME=frontend_perf_tests:fsperfbaseline_username + FSPERFBASELINE_PASSWORD=frontend_perf_tests:fsperfbaseline_password + FSPERF_USERNAME=frontend_perf_tests:fsperf_username + FSPERF_PASSWORD=frontend_perf_tests:fsperf_password + + - name: Authenticate Docker + uses: grafana/shared-workflows/actions/login-to-gar@main + id: login-to-gar + with: + registry: 'us-docker.pkg.dev' + + - name: Setup Node.js + uses: ./.github/actions/setup-node + + - name: Yarn install + run: yarn install --immutable + env: + PUPPETEER_SKIP_DOWNLOAD: true + CYPRESS_INSTALL_BINARY: 0 + + - name: Install Playwright browsers + run: yarn playwright install chromium --with-deps + + - name: Run Playwright tests (fsperfbaseline) + id: pw-fsperfbaseline + continue-on-error: true + env: + PLAYWRIGHT_JSON_OUTPUT_NAME: ./fsperfbaseline-pw-results.json + METRICS_OUTPUT_PATH: ./fsperfbaseline-metrics.txt + GRAFANA_URL: https://fsperfbaseline.grafana-dev.net + GRAFANA_ADMIN_USER: ${{ env.FSPERFBASELINE_USERNAME }} + GRAFANA_ADMIN_PASSWORD: ${{ env.FSPERFBASELINE_PASSWORD }} + run: yarn e2e:playwright --grep @performance --reporter json + + - name: Run Playwright tests (fsperf) + id: pw-fsperf + continue-on-error: true + env: + PLAYWRIGHT_JSON_OUTPUT_NAME: ./fsperf-pw-results.json + METRICS_OUTPUT_PATH: ./fsperf-metrics.txt + GRAFANA_URL: https://fsperf.grafana-dev.net + GRAFANA_ADMIN_USER: ${{ env.FSPERF_USERNAME }} + GRAFANA_ADMIN_PASSWORD: ${{ env.FSPERF_PASSWORD }} + run: yarn e2e:playwright --grep @performance --reporter json + + - name: Bench report (fsperfbaseline) + id: bench-fsperfbaseline + if: always() + continue-on-error: true + run: | + docker run \ + --rm \ + --volume="./fsperfbaseline-pw-results.json:/pw-results.json" \ + --volume="./fsperfbaseline-metrics.txt:/metrics.txt" \ + -e PROMETHEUS_URL="$PROMETHEUS_URL" \ + -e PROMETHEUS_USER="$PROMETHEUS_USER" \ + -e PROMETHEUS_PASSWORD="$PROMETHEUS_TOKEN" \ + us-docker.pkg.dev/grafanalabs-global/docker-grafana-bench-prod/grafana-bench:v0.6.0 report \ + --grafana-url "http://fsperfbaseline.grafana-dev.net" \ + --test-suite-name "FrontendPerfTests" \ + --report-input playwright \ + --report-output log \ + --prometheus-metrics \ + --run-metrics-file "/metrics.txt" \ + --log-level DEBUG \ + /pw-results.json + + - name: Bench report (fsperf) + id: bench-fsperf + if: always() + continue-on-error: true + run: | + docker run \ + --rm \ + --volume="./fsperf-pw-results.json:/pw-results.json" \ + --volume="./fsperf-metrics.txt:/metrics.txt" \ + -e PROMETHEUS_URL="$PROMETHEUS_URL" \ + -e PROMETHEUS_USER="$PROMETHEUS_USER" \ + -e PROMETHEUS_PASSWORD="$PROMETHEUS_TOKEN" \ + us-docker.pkg.dev/grafanalabs-global/docker-grafana-bench-prod/grafana-bench:v0.6.0 report \ + --grafana-url "http://fsperf.grafana-dev.net" \ + --test-suite-name "FrontendPerfTests" \ + --report-input playwright \ + --report-output log \ + --prometheus-metrics \ + --run-metrics-file "/metrics.txt" \ + --log-level DEBUG \ + /pw-results.json + + - name: Check status + env: + FSPERF_PW_OUTCOME: ${{ steps.pw-fsperf.outcome }} + FSPERF_PW_BASELINE_OUTCOME: ${{ steps.pw-fsperfbaseline.outcome }} + FSPERF_BENCH_OUTCOME: ${{ steps.bench-fsperf.outcome }} + FSPERF_BENCH_BASELINE_OUTCOME: ${{ steps.bench-fsperfbaseline.outcome }} + + run: | + echo "FSPERF_PW_OUTCOME: $FSPERF_PW_OUTCOME" + echo "FSPERF_PW_BASELINE_OUTCOME: $FSPERF_PW_BASELINE_OUTCOME" + echo "FSPERF_BENCH_OUTCOME: $FSPERF_BENCH_OUTCOME" + echo "FSPERF_BENCH_BASELINE_OUTCOME: $FSPERF_BENCH_BASELINE_OUTCOME" + + if [[ "$FSPERF_PW_OUTCOME" != "success" || "$FSPERF_PW_BASELINE_OUTCOME" != "success" || "$FSPERF_BENCH_OUTCOME" != "success" || "$FSPERF_BENCH_BASELINE_OUTCOME" != "success" ]]; then + echo "One or more test steps failed." + exit 1 + fi diff --git a/Makefile b/Makefile index 2dc98f53460..ab99a49fc24 100644 --- a/Makefile +++ b/Makefile @@ -283,13 +283,9 @@ run-bra: ## [Deprecated] Build and run web server on filesystem changes. See /.b frontend-service-check: ./devenv/frontend-service/local-init.sh -.PHONY: frontend-service-up -frontend-service-up: frontend-service-check - tilt up -f devenv/frontend-service/Tiltfile - -.PHONY: frontend-service-down -frontend-service-down: frontend-service-check - tilt down -f devenv/frontend-service/Tiltfile +.PHONY: frontend-service +frontend-service: frontend-service-check + bash ./devenv/frontend-service/run.sh ##@ Testing @@ -528,25 +524,6 @@ gen-ts: tscriptify -interface -package=github.com/grafana/grafana/pkg/services/live/pipeline -import="import { FieldConfig } from '@grafana/data'" -target=public/app/features/live/pipeline/models.gen.ts pkg/services/live/pipeline/config.go go mod tidy -# This repository's configuration is protected (https://readme.drone.io/signature/). -# Use this make target to regenerate the configuration YAML files when -# you modify starlark files. -.PHONY: drone -drone: $(DRONE) - bash scripts/drone/env-var-check.sh - $(DRONE) starlark --format - $(DRONE) lint .drone.yml --trusted - $(DRONE) --server https://drone.grafana.net sign --save grafana/grafana - -# Generate an Emacs tags table (https://www.gnu.org/software/emacs/manual/html_node/emacs/Tags-Tables.html) for Starlark files. -.PHONY: scripts/drone/TAGS -scripts/drone/TAGS: $(shell find scripts/drone -name '*.star') - etags --lang none --regex="/def \(\w+\)[^:]+:/\1/" --regex="/\s*\(\w+\) =/\1/" $^ -o $@ - -.PHONY: format-drone -format-drone: - buildifier --lint=fix -r scripts/drone - .PHONY: go-race-is-enabled go-race-is-enabled: @if [ -n "$(GO_RACE)" ]; then \ diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 9a620ce12ad..9e1d9886718 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -82,13 +82,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 30aa1308939..7abf0ddda54 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -252,8 +252,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -270,8 +270,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= @@ -288,14 +288,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -308,8 +308,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index ed72fa1aa90..296f0d6981e 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -110,15 +110,15 @@ require ( go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 855fbd17a00..cb04c2614e6 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -314,15 +314,13 @@ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -341,14 +339,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -356,8 +351,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/dashboard/pkg/migration/schemaversion/migration_utils.go b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go new file mode 100644 index 00000000000..9b84ecaebe1 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go @@ -0,0 +1,79 @@ +package schemaversion + +// migration_utils.go contains shared utility functions used across multiple schema version migrations. + +// GetStringValue safely extracts a string value from a map, returning empty string if not found or not a string +func GetStringValue(m map[string]interface{}, key string) string { + if value, ok := m[key]; ok { + if s, ok := value.(string); ok { + return s + } + } + return "" +} + +// GetBoolValue safely extracts a boolean value from a map, returning false if not found or not a boolean +func GetBoolValue(m map[string]interface{}, key string) bool { + if value, ok := m[key]; ok { + if b, ok := value.(bool); ok { + return b + } + } + return false +} + +// GetIntValue safely extracts an integer value from a map, returning defaultValue if not found or not convertible +func GetIntValue(m map[string]interface{}, key string, defaultValue int) int { + if value, ok := m[key]; ok { + if i, ok := ConvertToInt(value); ok { + return i + } + } + return defaultValue +} + +// GetFloatValue safely extracts a float value from a map, returning defaultValue if not found or not convertible +func GetFloatValue(m map[string]interface{}, key string, defaultValue float64) float64 { + if value, ok := m[key]; ok { + if f, ok := ConvertToFloat(value); ok { + return f + } + } + return defaultValue +} + +// ConvertToFloat converts various numeric types to float64 +func ConvertToFloat(value interface{}) (float64, bool) { + switch v := value.(type) { + case float64: + return v, true + case int: + return float64(v), true + case int64: + return float64(v), true + case float32: + return float64(v), true + case int32: + return float64(v), true + default: + return 0, false + } +} + +// ConvertToInt converts various numeric types to int +func ConvertToInt(value interface{}) (int, bool) { + switch v := value.(type) { + case int: + return v, true + case float64: + return int(v), true + case int64: + return int(v), true + case float32: + return int(v), true + case int32: + return int(v), true + default: + return 0, false + } +} diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index e4c2ec13ddc..c6df95d196f 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -5,7 +5,7 @@ import ( ) const ( - MIN_VERSION = 16 + MIN_VERSION = 14 LATEST_VERSION = 41 ) @@ -38,6 +38,8 @@ type PanelPluginInfoProvider interface { func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 15: V15, + 16: V16, 17: V17, 18: V18, 19: V19, diff --git a/apps/dashboard/pkg/migration/schemaversion/v15.go b/apps/dashboard/pkg/migration/schemaversion/v15.go new file mode 100644 index 00000000000..832f9fb2c57 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v15.go @@ -0,0 +1,25 @@ +package schemaversion + +// V15 migration is a no-op migration +// It only updates the schema version to 15 +// It's created to keep the migration history consistent with frontend migrator +// No specific migration logic is needed between schema version 14 and 15 + +// Example before migration: +// { +// "schemaVersion": 14, +// "title": "My Dashboard", +// "panels": [...] +// } + +// Example after migration: +// { +// "schemaVersion": 15, +// "title": "My Dashboard", +// "panels": [...] +// } + +func V15(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 15 + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v15_test.go b/apps/dashboard/pkg/migration/schemaversion/v15_test.go new file mode 100644 index 00000000000..86c5a0b9c5a --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v15_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV15(t *testing.T) { + tests := []migrationTestCase{ + { + name: "v15 no-op migration, updates schema version only", + input: map[string]interface{}{ + "title": "V15 No-Op Migration Test Dashboard", + "schemaVersion": 14, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V15 No-Op Migration Test Dashboard", + "schemaVersion": 15, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V15) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v16.go b/apps/dashboard/pkg/migration/schemaversion/v16.go new file mode 100644 index 00000000000..cbe5854ab47 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v16.go @@ -0,0 +1,308 @@ +package schemaversion + +import ( + "math" +) + +const ( + gridColumnCount = 24.0 + defaultPanelSpan = 4.0 + defaultRowHeight = 250.0 + gridCellHeight = 30.0 + gridCellVMargin = 8.0 + minPanelHeight = gridCellHeight * 3.0 + panelHeightStep = gridCellHeight + gridCellVMargin +) + +// V16 migrates dashboard layout from the old row-based system to the modern grid-based layout. +// This migration follows the exact logic from DashboardMigrator.ts to ensure consistency between frontend and backend. +func V16(dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 16 + + upgradeToGridLayout(dashboard) + + return nil +} + +func upgradeToGridLayout(dashboard map[string]interface{}) { + rowsInterface, ok := dashboard["rows"] + if !ok { + return + } + + rows, ok := rowsInterface.([]interface{}) + if !ok { + return + } + + // Handle empty rows + if len(rows) == 0 { + dashboard["panels"] = []interface{}{} + delete(dashboard, "rows") + return + } + + yPos := 0 + widthFactor := gridColumnCount / 12.0 + + // Find max panel ID (lines 1014-1021 in TS) + maxPanelID := getMaxPanelID(rows) + nextRowID := maxPanelID + 1 + + // Get existing panels + var finalPanels []interface{} + if existingPanels, ok := dashboard["panels"].([]interface{}); ok { + finalPanels = existingPanels + } + + // Add special "row" panels if even one row is collapsed, repeated or has visible title (line 1028 in TS) + showRows := shouldShowRows(rows) + + // Process each row (line 1030 in TS) + for _, rowInterface := range rows { + row, ok := rowInterface.(map[string]interface{}) + if !ok { + continue + } + + // Skip repeated rows (line 1031-1033 in TS) + if _, hasRepeatIteration := row["repeatIteration"]; hasRepeatIteration { + continue + } + + height := getRowHeight(row) + rowGridHeight := getGridHeight(height) + isCollapsed := GetBoolValue(row, "collapse") + + var rowPanel map[string]interface{} + + // First pass: assign IDs to panels that don't have them + panelsInRow, ok := row["panels"].([]interface{}) + if !ok { + panelsInRow = []interface{}{} + } + + for _, panelInterface := range panelsInRow { + panel, ok := panelInterface.(map[string]interface{}) + if !ok { + continue + } + // Assign ID if missing + if _, hasID := panel["id"]; !hasID { + panel["id"] = nextRowID + nextRowID++ + } + } + + if showRows { + // add special row panel (lines 1041-1058 in TS) + rowPanel = map[string]interface{}{ + "id": nextRowID, + "type": "row", + "title": GetStringValue(row, "title"), + "collapsed": isCollapsed, + "repeat": GetStringValue(row, "repeat"), + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": yPos, + "w": int(gridColumnCount), + "h": rowGridHeight, + }, + } + nextRowID++ + yPos++ + } + + rowArea := newRowArea(rowGridHeight, gridColumnCount, yPos) + + // Process all panels in this row (lines 1062-1087 in TS) + for _, panelInterface := range panelsInRow { + panel, ok := panelInterface.(map[string]interface{}) + if !ok { + continue + } + + // Set default span (line 1063 in TS) + span := GetFloatValue(panel, "span", defaultPanelSpan) + + // Handle minSpan conversion (lines 1064-1066 in TS) + if minSpan, hasMinSpan := panel["minSpan"]; hasMinSpan { + if minSpanFloat, ok := ConvertToFloat(minSpan); ok && minSpanFloat > 0 { + panel["minSpan"] = int(math.Min(float64(gridColumnCount), (float64(gridColumnCount)/12.0)*minSpanFloat)) + } + } + + panelWidth := int(math.Floor(span * widthFactor)) + panelHeight := rowGridHeight + if panelHeightValue, hasHeight := panel["height"]; hasHeight { + if h, ok := ConvertToFloat(panelHeightValue); ok { + panelHeight = getGridHeight(h) + } + } + + panelPos := rowArea.getPanelPosition(panelHeight, panelWidth) + yPos = rowArea.yPos + + // Set gridPos (lines 1072-1077 in TS) + panel["gridPos"] = map[string]interface{}{ + "x": GetIntValue(panelPos, "x", 0), + "y": yPos + GetIntValue(panelPos, "y", 0), + "w": panelWidth, + "h": panelHeight, + } + rowArea.addPanel(panel["gridPos"].(map[string]interface{})) + + // Remove span (line 1080 in TS) + delete(panel, "span") + + // Exact logic from lines 1082-1086 in TS + if rowPanel != nil && isCollapsed { + // Add to collapsed row's nested panels + if rowPanelPanels, ok := rowPanel["panels"].([]interface{}); ok { + rowPanel["panels"] = append(rowPanelPanels, panel) + } + } else { + // Add directly to dashboard panels + finalPanels = append(finalPanels, panel) + } + } + + // Add row panel after processing all panels (lines 1089-1091 in TS) + if rowPanel != nil { + finalPanels = append(finalPanels, rowPanel) + } + + // Update yPos (lines 1093-1095 in TS) + if rowPanel == nil || !isCollapsed { + yPos += rowGridHeight + } + } + + // Update the dashboard + dashboard["panels"] = finalPanels + delete(dashboard, "rows") +} + +// rowArea represents dashboard row filled by panels +type rowArea struct { + area []int + yPos int + height int +} + +func newRowArea(height int, width int, rowYPos int) *rowArea { + area := make([]int, width) + return &rowArea{ + area: area, + yPos: rowYPos, + height: height, + } +} + +func (r *rowArea) reset() { + for i := range r.area { + r.area[i] = 0 + } +} + +func (r *rowArea) addPanel(gridPos map[string]interface{}) { + x := GetIntValue(gridPos, "x", 0) + y := GetIntValue(gridPos, "y", 0) + w := GetIntValue(gridPos, "w", 0) + h := GetIntValue(gridPos, "h", 0) + + for i := x; i < x+w && i < len(r.area); i++ { + newHeight := y + h - r.yPos + if newHeight > r.area[i] { + r.area[i] = newHeight + } + } +} + +func (r *rowArea) getPanelPosition(panelHeight int, panelWidth int) map[string]interface{} { + var startPlace, endPlace int + found := false + + // Find available space from right to left + for i := len(r.area) - 1; i >= 0; i-- { + if r.height-r.area[i] > 0 { + if !found { + endPlace = i + found = true + } else { + if i < len(r.area)-1 && r.area[i] <= r.area[i+1] { + startPlace = i + } else { + break + } + } + } else { + break + } + } + + if found && endPlace-startPlace >= panelWidth-1 { + // Find max height in the range + yPos := 0 + for i := startPlace; i <= endPlace && i < len(r.area); i++ { + if r.area[i] > yPos { + yPos = r.area[i] + } + } + return map[string]interface{}{ + "x": startPlace, + "y": yPos, + } + } + + // Wrap to next row + r.yPos += r.height + r.reset() + return r.getPanelPosition(panelHeight, panelWidth) +} + +func getMaxPanelID(rows []interface{}) int { + maxID := 0 + for _, rowInterface := range rows { + if row, ok := rowInterface.(map[string]interface{}); ok { + if panels, ok := row["panels"].([]interface{}); ok { + for _, panelInterface := range panels { + if panel, ok := panelInterface.(map[string]interface{}); ok { + if id := GetIntValue(panel, "id", 0); id > maxID { + maxID = id + } + } + } + } + } + } + return maxID +} + +func shouldShowRows(rows []interface{}) bool { + for _, rowInterface := range rows { + if row, ok := rowInterface.(map[string]interface{}); ok { + if GetBoolValue(row, "collapse") || GetBoolValue(row, "showTitle") || GetStringValue(row, "repeat") != "" { + return true + } + } + } + return false +} + +func getRowHeight(row map[string]interface{}) float64 { + if height, ok := row["height"]; ok { + if h, ok := ConvertToFloat(height); ok { + return h + } + } + return defaultRowHeight +} + +func getGridHeight(height float64) int { + if height < minPanelHeight { + height = minPanelHeight + } + return int(math.Ceil(height / panelHeightStep)) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v16_test.go b/apps/dashboard/pkg/migration/schemaversion/v16_test.go new file mode 100644 index 00000000000..201a27b88f2 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v16_test.go @@ -0,0 +1,1341 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV16(t *testing.T) { + tests := []migrationTestCase{ + { + name: "should create proper grid", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ collapse: false, height: 8 }, [[6], [6]]) + map[string]interface{}{ + "collapse": false, + "height": 304, // 8 * 38 (PANEL_HEIGHT_STEP) + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "id": 2, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 12, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should add special row panel if row is collapsed", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ collapse: true, height: 8 }, [[6], [6]]) + map[string]interface{}{ + "collapse": true, + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "id": 2, + }, + }, + }, + // createRow({ height: 8 }, [[12]]) + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 3, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 4, // Next ID after max panel ID (3) + "type": "row", + "title": "", + "collapsed": true, + "repeat": "", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 1, + "w": 12, + "h": 8, + }, + }, + }, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 3, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 2, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 5, // Next ID after row panel (4) + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should add special row panel if row has visible title", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ showTitle: true, title: 'Row', height: 8 }, [[6], [6]]) + map[string]interface{}{ + "showTitle": true, + "title": "Row", + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "id": 2, + }, + }, + }, + // createRow({ height: 8 }, [[12]]) + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 3, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 4, // Next ID after max panel ID (3) + "type": "row", + "title": "Row", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 3, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 10, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 5, // Next ID after row panel (4) + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 9, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should not add row panel if row has not visible title or not collapsed", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + map[string]interface{}{ + "collapse": true, + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 1, + }, + }, + }, + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 2, + }, + }, + }, + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 3, + }, + map[string]interface{}{ + "span": 6, + "id": 4, + }, + map[string]interface{}{ + "span": 6, + "id": 5, + }, + }, + }, + map[string]interface{}{ + "collapse": true, + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 6, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + // First row: collapsed row (panels go in collapsed row's panels array, row panel added after) + map[string]interface{}{ + "id": 7, + "type": "row", + "title": "", + "collapsed": true, + "repeat": "", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 24, + "h": 8, + }, + }, + }, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + // Second row: normal row (regular panel first, then row panel) + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 2, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 8, + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 24, + "h": 8, + }, + }, + // Third row: normal row (regular panels first, then row panel) + map[string]interface{}{ + "id": 3, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 11, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 4, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 19, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 5, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 19, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 9, + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 10, + "w": 24, + "h": 8, + }, + }, + // Fourth row: collapsed row (panels go in collapsed row's panels array, row panel added after) + map[string]interface{}{ + "id": 10, + "type": "row", + "title": "", + "collapsed": true, + "repeat": "", + "panels": []interface{}{ + map[string]interface{}{ + "id": 6, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 28, + "w": 24, + "h": 8, + }, + }, + }, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 27, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should add all rows if even one collapsed or titled row is present", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ collapse: true, height: 8 }, [[6], [6]]) + map[string]interface{}{ + "collapse": true, + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "id": 2, + }, + }, + }, + // createRow({ height: 8 }, [[12]]) + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 3, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 4, // Next ID after max panel ID (3) + "type": "row", + "title": "", + "collapsed": true, + "repeat": "", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 1, + "w": 12, + "h": 8, + }, + }, + }, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 3, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 2, + "w": 24, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 5, // Next ID after row panel (4) + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should properly place panels with fixed height", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 6 }, [[6], [6, 3], [6, 3]]) + map[string]interface{}{ + "height": 228, // 6 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "height": 114, // 3 * 38 + "id": 2, + }, + map[string]interface{}{ + "span": 6, + "height": 114, // 3 * 38 + "id": 3, + }, + }, + }, + // createRow({ height: 6 }, [[4], [4], [4, 3], [4, 3]]) + map[string]interface{}{ + "height": 228, // 6 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 4, + "id": 4, + }, + map[string]interface{}{ + "span": 4, + "id": 5, + }, + map[string]interface{}{ + "span": 4, + "height": 114, // 3 * 38 + "id": 6, + }, + map[string]interface{}{ + "span": 4, + "height": 114, // 3 * 38 + "id": 7, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 12, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 2, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 12, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 3, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 3, + "w": 12, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 4, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 6, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 5, + "gridPos": map[string]interface{}{ + "x": 8, + "y": 6, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 6, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 16, + "y": 6, + "w": 8, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 7, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 16, + "y": 9, + "w": 8, + "h": 3, + }, + }, + }, + }, + }, + { + name: "should place panel to the right side of panel having bigger height", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 6 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3]]) + map[string]interface{}{ + "height": 228, // 6 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 4, + "id": 1, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 2, + }, + map[string]interface{}{ + "span": 4, + "height": 228, // 6 * 38 + "id": 3, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 4, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 5, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 2, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 3, + "height": 228, // 6 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 4, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 5, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 3, + "w": 4, + "h": 3, + }, + }, + }, + }, + }, + { + name: "should fill current row if it possible", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 9 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3], [8, 3]]) + map[string]interface{}{ + "height": 342, // 9 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 4, + "id": 1, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 2, + }, + map[string]interface{}{ + "span": 4, + "height": 228, // 6 * 38 + "id": 3, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 4, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 5, + }, + map[string]interface{}{ + "span": 8, + "height": 114, // 3 * 38 + "id": 6, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 8, + "h": 9, + }, + }, + map[string]interface{}{ + "id": 2, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 3, + "height": 228, // 6 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 4, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 5, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 3, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 6, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 6, + "w": 16, + "h": 3, + }, + }, + }, + }, + }, + { + name: "should fill current row if it possible (2)", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 8 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3], [8, 3]]) + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 4, + "id": 1, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 2, + }, + map[string]interface{}{ + "span": 4, + "height": 228, // 6 * 38 + "id": 3, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 4, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 5, + }, + map[string]interface{}{ + "span": 8, + "height": 114, // 3 * 38 + "id": 6, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 8, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 3, + "height": 228, // 6 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 4, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 5, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 3, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 6, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 6, + "w": 16, + "h": 3, + }, + }, + }, + }, + }, + { + name: "should fill current row if panel height more than row height", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 6 }, [[4], [2, 3], [4, 8], [2, 3], [2, 3]]) + map[string]interface{}{ + "height": 228, // 6 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 4, + "id": 1, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 2, + }, + map[string]interface{}{ + "span": 4, + "height": 304, // 8 * 38 + "id": 3, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 4, + }, + map[string]interface{}{ + "span": 2, + "height": 114, // 3 * 38 + "id": 5, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 8, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 2, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 8, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 3, + "height": 304, // 8 * 38 + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 8, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 4, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 0, + "w": 4, + "h": 3, + }, + }, + map[string]interface{}{ + "id": 5, + "height": 114, // 3 * 38 + "gridPos": map[string]interface{}{ + "x": 20, + "y": 3, + "w": 4, + "h": 3, + }, + }, + }, + }, + }, + { + name: "should wrap panels to multiple rows", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + // createRow({ height: 6 }, [[6], [6], [12], [6], [3], [3]]) + map[string]interface{}{ + "height": 228, // 6 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + "id": 2, + }, + map[string]interface{}{ + "span": 12, + "id": 3, + }, + map[string]interface{}{ + "span": 6, + "id": 4, + }, + map[string]interface{}{ + "span": 3, + "id": 5, + }, + map[string]interface{}{ + "span": 3, + "id": 6, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 12, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 0, + "w": 12, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 3, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 6, + "w": 24, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 4, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 12, + "w": 12, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 5, + "gridPos": map[string]interface{}{ + "x": 12, + "y": 12, + "w": 6, + "h": 6, + }, + }, + map[string]interface{}{ + "id": 6, + "gridPos": map[string]interface{}{ + "x": 18, + "y": 12, + "w": 6, + "h": 6, + }, + }, + }, + }, + }, + { + name: "should add repeated row if repeat set", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + map[string]interface{}{ + "showTitle": true, + "title": "Row", + "height": 304, // 8 * 38 + "repeat": "server", + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + }, + }, + map[string]interface{}{ + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 12, + "id": 2, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + // Panel from first row + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + // Repeated row panel (comes after its panels) + map[string]interface{}{ + "id": 3, + "type": "row", + "title": "Row", + "collapsed": false, + "repeat": "server", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + // Panel from second row + map[string]interface{}{ + "id": 2, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 10, + "w": 24, + "h": 8, + }, + }, + // Second row panel (comes after its panels) + map[string]interface{}{ + "id": 4, + "type": "row", + "title": "", + "collapsed": false, + "repeat": "", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 9, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should ignore repeated row", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + map[string]interface{}{ + "showTitle": true, + "title": "Row1", + "height": 304, // 8 * 38 + "repeat": "server", + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + }, + }, + map[string]interface{}{ + "showTitle": true, + "title": "Row2", + "height": 304, // 8 * 38 + "repeatIteration": 12313, + "repeatRowId": 1, + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 2, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 3, // Next ID after max panel ID (2) + "type": "row", + "title": "Row1", + "collapsed": false, + "repeat": "server", + "panels": []interface{}{}, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + { + name: "should assign id", + input: map[string]interface{}{ + "schemaVersion": 15, + "rows": []interface{}{ + map[string]interface{}{ + "collapse": true, + "height": 304, // 8 * 38 + "panels": []interface{}{ + map[string]interface{}{ + "span": 6, + "id": 1, + }, + map[string]interface{}{ + "span": 6, + // no id - should be assigned + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 16, + "panels": []interface{}{ + map[string]interface{}{ + "id": 3, // Next ID after max panel ID (1) and assigned panel ID (2) + "type": "row", + "title": "", + "collapsed": true, + "repeat": "", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 1, + "w": 12, + "h": 8, + }, + }, + map[string]interface{}{ + "id": 2, // Should be assigned the next available ID + "gridPos": map[string]interface{}{ + "x": 12, + "y": 1, + "w": 12, + "h": 8, + }, + }, + }, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 0, + "w": 24, + "h": 8, + }, + }, + }, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V16) +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v17.go b/apps/dashboard/pkg/migration/schemaversion/v17.go index dc1142ef7e5..626a162ea0e 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v17.go +++ b/apps/dashboard/pkg/migration/schemaversion/v17.go @@ -63,16 +63,9 @@ func migrateMinSpanToMaxPerRow(panel map[string]interface{}) { return } - // Convert minSpan to a number (could be int, float64, etc.) - var minSpan float64 - switch v := minSpanValue.(type) { - case int: - minSpan = float64(v) - case float64: - minSpan = v - case int64: - minSpan = float64(v) - default: + // Convert minSpan to a number using shared utility + minSpan, ok := ConvertToFloat(minSpanValue) + if !ok { // If we can't convert minSpan to a number, just delete it and return delete(panel, "minSpan") return diff --git a/apps/dashboard/pkg/migration/schemaversion/v19.go b/apps/dashboard/pkg/migration/schemaversion/v19.go index b9ca23a131b..8856687b551 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v19.go +++ b/apps/dashboard/pkg/migration/schemaversion/v19.go @@ -84,8 +84,8 @@ func upgradePanelLink(link map[string]interface{}) map[string]interface{} { result := map[string]interface{}{ "url": url, - "title": getStringValue(link, "title"), - "targetBlank": getBoolValue(link, "targetBlank"), + "title": GetStringValue(link, "title"), + "targetBlank": GetBoolValue(link, "targetBlank"), } return result @@ -111,11 +111,11 @@ func buildPanelLinkURL(link map[string]interface{}) string { // Add query parameters params := []string{} - if getBoolValue(link, "keepTime") { + if GetBoolValue(link, "keepTime") { params = append(params, "$__url_time_range") } - if getBoolValue(link, "includeVars") { + if GetBoolValue(link, "includeVars") { params = append(params, "$__all_variables") } @@ -150,17 +150,3 @@ func slugifyForURL(name string) string { name = reSpaces.ReplaceAllString(name, "-") return name } - -func getStringValue(m map[string]interface{}, key string) string { - if v, ok := m[key].(string); ok { - return v - } - return "" -} - -func getBoolValue(m map[string]interface{}, key string) bool { - if v, ok := m[key].(bool); ok { - return v - } - return false -} diff --git a/apps/dashboard/pkg/migration/schemaversion/v27.go b/apps/dashboard/pkg/migration/schemaversion/v27.go index cdbe98a5907..e882b3969a1 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v27.go +++ b/apps/dashboard/pkg/migration/schemaversion/v27.go @@ -117,7 +117,7 @@ func removeRepeatedPanels(panels []interface{}) []interface{} { // Filter out repeats in collapsed rows if p["type"] == "row" { if nestedPanels, ok := p["panels"].([]interface{}); ok { - var filteredNestedPanels []interface{} + filteredNestedPanels := []interface{}{} for _, nestedPanel := range nestedPanels { if np, ok := nestedPanel.(map[string]interface{}); ok { if _, hasRepeatPanelId := np["repeatPanelId"]; !hasRepeatPanelId { diff --git a/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json b/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json new file mode 100644 index 00000000000..e16dca0e87a --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v15.no-op-migration.json @@ -0,0 +1,58 @@ +{ + "title": "V15 No-Op Migration Test Dashboard", + "schemaVersion": 14, + "panels": [ + { + "type": "graph", + "title": "CPU Usage", + "id": 1, + "yAxes": [ + { + "show": true, + "min": null, + "max": null + } + ] + }, + { + "type": "singlestat", + "title": "Memory Usage", + "id": 2, + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + ], + "templating": { + "list": [ + { + "name": "server", + "type": "query", + "datasource": "prometheus", + "query": "label_values(server)", + "refresh": 1, + "options": [] + } + ] + }, + "annotations": { + "list": [ + { + "name": "Annotations & Alerts", + "datasource": "grafana", + "enable": true + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } + } diff --git a/apps/dashboard/pkg/migration/testdata/input/v16.grid_layout_upgrade.json b/apps/dashboard/pkg/migration/testdata/input/v16.grid_layout_upgrade.json new file mode 100644 index 00000000000..088b607947c --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v16.grid_layout_upgrade.json @@ -0,0 +1,119 @@ +{ + "title": "V16 Grid Layout Migration Test Dashboard", + "schemaVersion": 15, + "rows": [ + { + "collapse": false, + "height": 250, + "panels": [ + { + "id": 1, + "type": "graph", + "title": "CPU Usage", + "span": 6 + }, + { + "id": 2, + "type": "singlestat", + "title": "Memory Usage", + "span": 6 + } + ] + }, + { + "collapse": true, + "height": 300, + "title": "Collapsed Row", + "panels": [ + { + "id": 3, + "type": "table", + "title": "Process List", + "span": 12 + }, + { + "id": 4, + "type": "graph", + "title": "Network I/O", + "span": 6, + "height": 200 + }, + { + "id": 5, + "type": "graph", + "title": "Disk I/O", + "span": 6, + "height": 200 + } + ] + }, + { + "showTitle": true, + "title": "Visible Row Title", + "height": 200, + "panels": [ + { + "id": 6, + "type": "gauge", + "title": "Temperature", + "span": 4, + "minSpan": 2 + }, + { + "id": 7, + "type": "stat", + "title": "Uptime", + "span": 4 + }, + { + "id": 8, + "type": "bargauge", + "title": "Load Average", + "span": 4 + } + ] + }, + { + "height": 150, + "panels": [ + { + "id": 9, + "type": "text", + "title": "Description Panel", + "span": 8 + }, + { + "id": 10, + "type": "logs", + "title": "System Logs", + "span": 4, + "height": 100 + } + ] + }, + { + "repeat": "server", + "height": 250, + "panels": [ + { + "id": 11, + "type": "graph", + "title": "Server Metrics", + "span": 12 + } + ] + }, + { + "repeatIteration": true, + "height": 250, + "panels": [ + { + "id": 12, + "type": "graph", + "title": "Should be skipped", + "span": 12 + } + ] + } + ] +} diff --git a/apps/dashboard/pkg/migration/testdata/output/v15.no-op-migration.json b/apps/dashboard/pkg/migration/testdata/output/v15.no-op-migration.json new file mode 100644 index 00000000000..e1e5f43be26 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v15.no-op-migration.json @@ -0,0 +1,145 @@ +{ + "annotations": { + "list": [ + { + "datasource": { + "uid": "grafana" + }, + "enable": true, + "name": "Annotations \u0026 Alerts" + } + ] + }, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "graph", + "yAxes": [ + { + "max": null, + "min": null, + "show": true + } + ] + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "color": null, + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "id": 2, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "refresh": "", + "schemaVersion": 41, + "templating": { + "list": [ + { + "datasource": { + "uid": "prometheus" + }, + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "title": "V15 No-Op Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/v16.grid_layout_upgrade.json b/apps/dashboard/pkg/migration/testdata/output/v16.grid_layout_upgrade.json new file mode 100644 index 00000000000..e19436dcd85 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/v16.grid_layout_upgrade.json @@ -0,0 +1,487 @@ +{ + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "graph" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 13, + "panels": [], + "repeat": "", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "row" + }, + { + "collapsed": true, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 14, + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Process List", + "type": "table" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 17 + }, + "height": 200, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Network I/O", + "type": "graph" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 17 + }, + "height": 200, + "id": 5, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Disk I/O", + "type": "graph" + } + ], + "repeat": "", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Collapsed Row", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 6, + "maxPerRow": 6, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Temperature", + "type": "gauge" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 18 + }, + "id": 7, + "options": { + "justifyMode": "auto", + "percentChangeColorMode": "standard", + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 18 + }, + "id": 8, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Load Average", + "type": "bargauge" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 15, + "panels": [], + "repeat": "", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Visible Row Title", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 25 + }, + "id": 9, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Description Panel", + "type": "text" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 25 + }, + "height": 100, + "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "System Logs", + "type": "logs" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 16, + "panels": [], + "repeat": "", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "row" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Metrics", + "type": "graph" + }, + { + "collapsed": false, + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 17, + "panels": [], + "repeat": "server", + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 41, + "title": "V16 Grid Layout Migration Test Dashboard" +} \ No newline at end of file diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 30bcc4d5a99..effa747e628 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -47,11 +47,11 @@ require ( go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/folder/go.sum b/apps/folder/go.sum index efbd47987b9..12336f31c0f 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -122,8 +122,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -132,14 +132,14 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/iam/Makefile b/apps/iam/Makefile index 6839689c867..04746e30a31 100644 --- a/apps/iam/Makefile +++ b/apps/iam/Makefile @@ -1,5 +1,7 @@ include ../sdk.mk +OPERATOR_DOCKERIMAGE := "github.com/grafana/grafana/apps/iam/operator" + .PHONY: generate generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation @$(APP_SDK_BIN) generate \ @@ -8,4 +10,45 @@ generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation --grouping=group \ --defencoding=none \ --noschemasinmanifest \ - --postprocess \ \ No newline at end of file + --postprocess + +.PHONY: deps +deps: + @go mod tidy + @GOWORK=off go mod vendor + +.PHONY: build +build: build/operator + +.PHONY: build/operator +build/operator: + docker build -t $(OPERATOR_DOCKERIMAGE) -f cmd/operator/Dockerfile . + +.PHONY: local/up +local/up: + @./local/scripts/cluster.sh create "local/k3d-config.json" + @cd local && tilt up + +.PHONY: local/generate +local/generate: + @grafana-app-sdk project local generate + +.PHONY: local/down +local/down: + @cd local && tilt down + +.PHONY: local/deploy_plugin +local/deploy_plugin: + -tilt disable grafana + cp -R plugin/dist local/mounted-files/plugin/dist + -tilt enable grafana + +.PHONY: local/push_operator +local/push_operator: build/operator + # Tag the docker image as part of localhost, which is what the generated k8s uses to avoid confusion with the real operator image + @docker tag "$(OPERATOR_DOCKERIMAGE):latest" "localhost/$(OPERATOR_DOCKERIMAGE):latest" + @./local/scripts/push_image.sh "localhost/$(OPERATOR_DOCKERIMAGE):latest" + +.PHONY: local/clean +local/clean: local/down + @./local/scripts/cluster.sh delete diff --git a/apps/iam/cmd/operator/Dockerfile b/apps/iam/cmd/operator/Dockerfile new file mode 100644 index 00000000000..513adce4f24 --- /dev/null +++ b/apps/iam/cmd/operator/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /build +COPY go.mod go.sum ./ +COPY vendor* ./vendor +RUN test -f vendor/modules.txt || go mod download + +COPY cmd cmd +COPY pkg pkg + +RUN go build -o "target/operator" cmd/operator/*.go + +FROM alpine AS runtime +COPY --from=builder /build/target/operator /usr/bin/operator + +ENTRYPOINT ["/usr/bin/operator"] \ No newline at end of file diff --git a/apps/iam/cmd/operator/authrt.go b/apps/iam/cmd/operator/authrt.go new file mode 100644 index 00000000000..55d1c4cbecc --- /dev/null +++ b/apps/iam/cmd/operator/authrt.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "net/http" + + utilnet "k8s.io/apimachinery/pkg/util/net" + + "github.com/grafana/authlib/authn" +) + +type authRoundTripper struct { + tokenExchangeClient *authn.TokenExchangeClient + transport http.RoundTripper +} + +func (t *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + tokenResponse, err := t.tokenExchangeClient.Exchange(req.Context(), authn.TokenExchangeRequest{ + Audiences: []string{"folder.grafana.app"}, + Namespace: "*", + }) + if err != nil { + return nil, fmt.Errorf("failed to exchange token: %w", err) + } + + // clone the request as RTs are not expected to mutate the passed request + req = utilnet.CloneRequest(req) + + req.Header.Set("X-Access-Token", "Bearer "+tokenResponse.Token) + return t.transport.RoundTrip(req) +} diff --git a/apps/iam/cmd/operator/config.go b/apps/iam/cmd/operator/config.go new file mode 100644 index 00000000000..7cb1133851c --- /dev/null +++ b/apps/iam/cmd/operator/config.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/grafana/grafana-app-sdk/plugin/kubeconfig" + "github.com/grafana/grafana-app-sdk/simple" +) + +const ( + ConnTypeGRPC = "grpc" + ConnTypeHTTP = "http" +) + +type Config struct { + OTelConfig simple.OpenTelemetryConfig + WebhookServer WebhookServerConfig + KubeConfig *kubeconfig.NamespacedConfig + ZanzanaClient ZanzanaClientConfig + FolderReconciler FolderReconcilerConfig +} + +type WebhookServerConfig struct { + Port int + TLSCertPath string + TLSKeyPath string +} + +type ZanzanaClientConfig struct { + Addr string +} + +type FolderReconcilerConfig struct { + Namespace string +} + +func LoadConfigFromEnv() (*Config, error) { + cfg := Config{} + cfg.OTelConfig.ServiceName = os.Getenv("OTEL_SERVICE_NAME") + switch strings.ToLower(os.Getenv("OTEL_CONN_TYPE")) { + case ConnTypeGRPC: + cfg.OTelConfig.ConnType = ConnTypeGRPC + case ConnTypeHTTP: + cfg.OTelConfig.ConnType = ConnTypeHTTP + case "": + // Default + cfg.OTelConfig.ConnType = ConnTypeHTTP + default: + return nil, fmt.Errorf("unknown OTEL_CONN_TYPE '%s'", os.Getenv("OTEL_CONN_TYPE")) + } + cfg.OTelConfig.Host = os.Getenv("OTEL_HOST") + portStr := os.Getenv("OTEL_PORT") + if portStr == "" { + if cfg.OTelConfig.ConnType == ConnTypeGRPC { + // Default OTel GRPC port + cfg.OTelConfig.Port = 4317 + } else { + // Default OTel HTTP port + cfg.OTelConfig.Port = 4318 + } + } else { + var err error + cfg.OTelConfig.Port, err = strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("invalid OTEL_PORT '%s': %w", portStr, err) + } + } + + whPortStr := os.Getenv("WEBHOOK_PORT") + if whPortStr == "" { + cfg.WebhookServer.Port = 8443 + } else { + var err error + cfg.WebhookServer.Port, err = strconv.Atoi(whPortStr) + if err != nil { + return nil, fmt.Errorf("invalid WEBHOOK_PORT '%s': %w", whPortStr, err) + } + } + + cfg.WebhookServer.TLSCertPath = os.Getenv("WEBHOOK_CERT_PATH") + cfg.WebhookServer.TLSKeyPath = os.Getenv("WEBHOOK_KEY_PATH") + + // Load the kube config + kubeConfigFile := os.Getenv("KUBE_CONFIG_FILE") + if kubeConfigFile != "" { + kubeConfig, err := LoadKubeConfigFromFile(kubeConfigFile) + if err != nil { + return nil, fmt.Errorf("unable to load kubernetes configuration from file '%s': %w", kubeConfigFile, err) + } + cfg.KubeConfig = kubeConfig + } else if folderAppURL := os.Getenv("FOLDER_APP_URL"); folderAppURL != "" { + exchangeUrl := os.Getenv("AUTH_TOKEN_EXCHANGE_URL") + authToken := os.Getenv("AUTH_TOKEN") + namespace := os.Getenv("FOLDER_APP_NAMESPACE") + if exchangeUrl == "" || authToken == "" { + return nil, fmt.Errorf("AUTH_TOKEN_EXCHANGE_URL and AUTH_TOKEN must be set when FOLDER_APP_URL is set") + } + + kubeConfig, err := LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace) + if err != nil { + return nil, fmt.Errorf("unable to load kubernetes configuration from folder app URL '%s': %w", folderAppURL, err) + } + cfg.KubeConfig = kubeConfig + } else { + kubeConfig, err := LoadInClusterConfig() + if err != nil { + return nil, fmt.Errorf("unable to load in-cluster kubernetes configuration: %w", err) + } + cfg.KubeConfig = kubeConfig + } + + cfg.ZanzanaClient.Addr = os.Getenv("ZANZANA_ADDR") + + cfg.FolderReconciler.Namespace = os.Getenv("FOLDER_RECONCILER_NAMESPACE") + + return &cfg, nil +} diff --git a/apps/iam/cmd/operator/kubeconfig.go b/apps/iam/cmd/operator/kubeconfig.go new file mode 100644 index 00000000000..b704979cd9a --- /dev/null +++ b/apps/iam/cmd/operator/kubeconfig.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "net/http" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/transport" + + "github.com/grafana/authlib/authn" + "github.com/grafana/grafana-app-sdk/plugin/kubeconfig" +) + +// LoadInClusterConfig loads a kubernetes in-cluster config. +// Since the in-cluster config doesn't have a namespace, it defaults to "default" +func LoadInClusterConfig() (*kubeconfig.NamespacedConfig, error) { + cfg, err := rest.InClusterConfig() + if err != nil { + return nil, err + } + cfg.APIPath = "/apis" + return &kubeconfig.NamespacedConfig{ + RestConfig: *cfg, + Namespace: "default", + }, nil +} + +// LoadKubeConfigFromEnv loads a NamespacedConfig from the value of an environment variable +func LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace string) (*kubeconfig.NamespacedConfig, error) { + tokenExchangeClient, err := authn.NewTokenExchangeClient(authn.TokenExchangeConfig{ + TokenExchangeURL: exchangeUrl, + Token: authToken, + }) + if err != nil { + return nil, fmt.Errorf("failed to create token exchange client: %w", err) + } + + return &kubeconfig.NamespacedConfig{ + RestConfig: rest.Config{ + APIPath: "/apis", + Host: folderAppURL, + WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper { + return &authRoundTripper{ + tokenExchangeClient: tokenExchangeClient, + transport: rt, + } + }), + TLSClientConfig: rest.TLSClientConfig{ + Insecure: true, + }, + }, + Namespace: namespace, + }, nil +} + +// LoadKubeConfigFromFile loads a NamespacedConfig from a file on-disk (such as a mounted secret) +func LoadKubeConfigFromFile(configPath string) (*kubeconfig.NamespacedConfig, error) { + // Load the kubeconfig file + config, err := clientcmd.LoadFromFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to load kubeconfig from %s: %w", configPath, err) + } + + // Build the REST config from the kubeconfig + restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to create REST config: %w", err) + } + + // Get the namespace from the current context, default to "default" if not set + namespace := "default" + if config.CurrentContext != "" { + if context, exists := config.Contexts[config.CurrentContext]; exists && context.Namespace != "" { + namespace = context.Namespace + } + } + + restConfig.APIPath = "/apis" + + return &kubeconfig.NamespacedConfig{ + RestConfig: *restConfig, + Namespace: namespace, + }, nil +} diff --git a/apps/iam/cmd/operator/main.go b/apps/iam/cmd/operator/main.go new file mode 100644 index 00000000000..dd4f770e1f8 --- /dev/null +++ b/apps/iam/cmd/operator/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "log/slog" + "os" + "os/signal" + + "github.com/grafana/grafana-app-sdk/k8s" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/operator" + "github.com/grafana/grafana-app-sdk/simple" + "github.com/grafana/grafana/apps/iam/pkg/app" +) + +func main() { + // Configure the default logger to use slog + logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + + //Load the config from the environment + cfg, err := LoadConfigFromEnv() + if err != nil { + logging.DefaultLogger.With("error", err).Error("Unable to load config from environment") + panic(err) + } + + // Set up tracing + if cfg.OTelConfig.Host != "" { + err = simple.SetTraceProvider(simple.OpenTelemetryConfig{ + Host: cfg.OTelConfig.Host, + Port: cfg.OTelConfig.Port, + ConnType: cfg.OTelConfig.ConnType, + ServiceName: cfg.OTelConfig.ServiceName, + }) + if err != nil { + logging.DefaultLogger.With("error", err).Error("Unable to set trace provider") + panic(err) + } + } + + // Create the operator config and the runner + operatorConfig := operator.RunnerConfig{ + KubeConfig: cfg.KubeConfig.RestConfig, + WebhookConfig: operator.RunnerWebhookConfig{ + Port: cfg.WebhookServer.Port, + TLSConfig: k8s.TLSConfig{ + CertPath: cfg.WebhookServer.TLSCertPath, + KeyPath: cfg.WebhookServer.TLSKeyPath, + }, + }, + MetricsConfig: operator.RunnerMetricsConfig{ + Enabled: true, + }, + } + + runner, err := operator.NewRunner(operatorConfig) + if err != nil { + logging.DefaultLogger.With("error", err).Error("Unable to create operator runner") + panic(err) + } + + // Context and cancel for the operator's Run method + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) + defer cancel() + + // Create app config from operator config + appCfg := app.AppConfig{ + ZanzanaAddr: cfg.ZanzanaClient.Addr, + FolderReconcilerNamespace: cfg.FolderReconciler.Namespace, + } + + // Run + logging.DefaultLogger.Info("Starting operator") + err = runner.Run(ctx, app.Provider(appCfg)) + if err != nil { + logging.DefaultLogger.With("error", err).Error("Operator exited with error") + panic(err) + } + logging.DefaultLogger.Info("Normal operator exit") +} diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 13c5115aff5..b792e14b6ad 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -2,65 +2,352 @@ module github.com/grafana/grafana/apps/iam go 1.24.6 +replace github.com/grafana/grafana => ../../ + +replace github.com/grafana/grafana/apps/folder => ../folder + +replace github.com/grafana/grafana/apps/dashboard => ../dashboard + +replace github.com/grafana/grafana/apps/secret => ../secret + +replace github.com/grafana/grafana/apps/provisioning => ../provisioning + +replace github.com/grafana/grafana/pkg/apimachinery => ../../pkg/apimachinery + +replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver + +replace github.com/grafana/grafana/pkg/aggregator => ../../pkg/aggregator + +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 + require ( + github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 + github.com/grafana/grafana v0.0.0-00010101000000-000000000000 github.com/grafana/grafana-app-sdk v0.40.3 - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e + github.com/grafana/grafana-app-sdk/logging v0.40.2 + github.com/grafana/grafana-app-sdk/plugin v0.40.3 + github.com/grafana/grafana/apps/folder v0.0.0 + github.com/grafana/grafana/pkg/apimachinery v0.0.0 + google.golang.org/grpc v1.74.2 k8s.io/apimachinery v0.33.3 + k8s.io/client-go v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff ) require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cuelang.org/go v0.11.1 // indirect + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect + github.com/Yiling-J/theine-go v0.6.1 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/apache/arrow-go/v18 v18.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/at-wat/mqtt-go v0.19.4 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect + github.com/aws/smithy-go v1.22.4 // indirect + github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bluele/gcache v0.0.2 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v1.0.0 // indirect + github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect + github.com/dlmiddlecote/sqlstats v1.0.2 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect + github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect + github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elazarl/goproxy v1.7.2 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fullstorydev/grpchan v1.1.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gchaincl/sqlhooks v1.3.0 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-test/deep v1.1.1 // indirect + github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/status v1.1.1 // indirect + github.com/golang-migrate/migrate/v4 v4.7.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.25.0 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/google/wire v0.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 // indirect + github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/dataplane/sdata v0.0.9 // indirect + github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect + github.com/grafana/grafana-aws-sdk v1.1.0 // indirect + github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // indirect + github.com/grafana/grafana-plugin-sdk-go v0.278.0 // indirect + github.com/grafana/grafana/apps/dashboard v0.0.0 // indirect + github.com/grafana/grafana/pkg/apiserver v0.0.0 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/sqlds/v4 v4.2.4 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jaegertracing/jaeger-idl v0.5.0 // indirect + github.com/jessevdk/go-flags v1.6.1 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect + github.com/jmespath-community/go-jmespath v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lestrrat-go/strftime v1.0.4 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/magefile/mage v1.15.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mithrandie/csvq v1.18.1 // indirect + github.com/mithrandie/csvq-driver v1.7.0 // indirect + github.com/mithrandie/go-file/v2 v2.1.0 // indirect + github.com/mithrandie/go-text v1.6.0 // indirect + github.com/mithrandie/ternary v1.1.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/natefinch/wrap v0.2.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nikunjy/rules v1.5.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/open-feature/go-sdk v1.14.1 // indirect + github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 // indirect + github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect + github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 // indirect + github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336 // indirect + github.com/openfga/openfga v1.8.13 // indirect + github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pressly/goose/v3 v3.24.3 // indirect + github.com/prometheus/alertmanager v0.28.0 // indirect github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tetratelabs/wazero v1.8.2 // indirect + github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect + github.com/tjhop/slog-gokit v0.1.3 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect + github.com/unknwon/com v1.0.1 // indirect + github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect + github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.mongodb.org/mongo-driver v1.17.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect + go.opentelemetry.io/otel/log v0.12.2 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/mock v0.5.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + gonum.org/v1/gonum v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect + gopkg.in/telebot.v3 v3.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.33.3 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/apiextensions-apiserver v0.33.3 // indirect + k8s.io/apiserver v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + modernc.org/libc v1.65.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.10.0 // indirect + modernc.org/sqlite v1.37.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect + xorm.io/builder v0.3.6 // indirect ) diff --git a/apps/iam/go.sum b/apps/iam/go.sum index efbd47987b9..ce4aafb9a4d 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -1,178 +1,2009 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= +cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= +cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= +cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= +github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= +github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= +github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= +github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= +github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= +github.com/Yiling-J/theine-go v0.6.1 h1:njE/rBBviU/Sq2G7PJKdLdwXg8j1azvZQulIjmshD+o= +github.com/Yiling-J/theine-go v0.6.1/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/apache/arrow-go/v18 v18.3.0 h1:Xq4A6dZj9Nu33sqZibzn012LNnewkTUlfKVUFD/RX/I= +github.com/apache/arrow-go/v18 v18.3.0/go.mod h1:eEM1DnUTHhgGAjf/ChvOAQbUQ+EPohtDrArffvUjPg8= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/at-wat/mqtt-go v0.19.4 h1:R2cbCU7O5PHQ38unbe1Y51ncG3KsFEJV6QeipDoqdLQ= +github.com/at-wat/mqtt-go v0.19.4/go.mod h1:AsiWc9kqVOhqq7LzUeWT/AkKUBfx3Sw5cEe8lc06fqA= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= +github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 h1:6VFPH/Zi9xYFMJKPQOX5URYkQoXRWeJ7V/7Y6ZDYoms= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= +github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0= +github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4= +github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk= +github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY= +github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= +github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= +github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= +github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= +github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= +github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= +github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= +github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= +github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= +github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= +github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o= +github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4= +github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= +github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= +github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= +github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= +github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= +github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= +github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= +github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= +github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs= +github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ= +github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II= +github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE= +github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8= +github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE= +github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms= +github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g= +github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y= +github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk= +github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= +github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM= +github.com/blugelabs/bluge v0.2.2/go.mod h1:am1LU9jS8dZgWkRzkGLQN3757EgMs3upWrU2fdN9foE= +github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo= +github.com/blugelabs/bluge_segment_api v0.2.0/go.mod h1:95XA+ZXfRj/IXADm7gZ+iTcWOJPg5jQTY1EReIzl3LA= +github.com/blugelabs/ice v1.0.0 h1:um7wf9e6jbkTVCrOyQq3tKK43fBMOvLUYxbj3Qtc4eo= +github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2Lo+IbQ= +github.com/blugelabs/ice/v2 v2.0.1 h1:mzHbntLjk2v7eDRgoXCgzOsPKN1Tenu9Svo6l9cTLS4= +github.com/blugelabs/ice/v2 v2.0.1/go.mod h1:QxAWSPNwZwsIqS25c3lbIPFQrVvT1sphf5x5DfMLH5M= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= +github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE= +github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= +github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= +github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8= +github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= +github.com/diegoholiveira/jsonlogic/v3 v3.7.4 h1:92HSmB9bwM/o0ZvrCpcvTP2EsPXSkKtAniIr2W/dcIM= +github.com/diegoholiveira/jsonlogic/v3 v3.7.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI= +github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxWhbByHgQSCUQvY= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= +github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAttAqWaudUAsM9iHASi/4eFBK+qn4qeaNto7g8bK4= +github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= +github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= +github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= +github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= +github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= +github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= +github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= +github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +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/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 h1:nn19bwMFBGWOvUTxZsdewOfRiXERhKHp0jEira8uQA8= +github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= +github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg= +github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= +github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU= github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk= github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k= github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= +github.com/grafana/grafana-app-sdk/plugin v0.40.3 h1:uH0oFZnYOUL+OXcyhd5NVYwoM+Wa0WUXvZ2Om1M91r0= +github.com/grafana/grafana-app-sdk/plugin v0.40.3/go.mod h1:+ylwE0P8WgPu5zURK5aDnVJpwRpuK3573rwrVV28qzQ= +github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtMDoe0KhmE= +github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8= +github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= +github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= +github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= +github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= +github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= +github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:2HRzUK/xQEYc+8d5If/XSusMcaYq9IptnBSHACiQcOQ= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6 h1:oJnbhG6ZNy10AjsgNeAtAKeGHogIGOMfAsBH6fYYa5M= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250620093340-be61a673dee6/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= +github.com/grafana/sqlds/v4 v4.2.4 h1:Xlxy1udWqDK0dlbuJ1qXL7K3EYaf+aKMl38zhd3VbQY= +github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/api v1.31.2 h1:NicObVJHcCmyOIl7Z9iHPvvFrocgTYo9cITSGg0/7pw= +github.com/hashicorp/consul/api v1.31.2/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= +github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc= +github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= +github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= +github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= +github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mithrandie/csvq v1.18.1 h1:f7NB2scbb7xx2ffPduJ2VtZ85RpWXfvanYskAkGlCBU= +github.com/mithrandie/csvq v1.18.1/go.mod h1:MRJj7AtcXfk7jhNGxLuJGP3LORmh4lpiPWxQ7VyCRn8= +github.com/mithrandie/csvq-driver v1.7.0 h1:ejiavXNWwTPMyr3fJFnhcqd1L1cYudA0foQy9cZrqhw= +github.com/mithrandie/csvq-driver v1.7.0/go.mod h1:HcN3xL9UCJnBYA/AIQOOB/KlyfXAiYr5yxDmiwrGk5o= +github.com/mithrandie/go-file/v2 v2.1.0 h1:XA5Tl+73GXMDvgwSE3Sg0uC5FkLr3hnXs8SpUas0hyg= +github.com/mithrandie/go-file/v2 v2.1.0/go.mod h1:9YtTF3Xo59GqC1Pxw6KyGVcM/qubAMlxVsqI/u9r++c= +github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyWO6s= +github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to= +github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4= +github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk= +github.com/natefinch/wrap v0.2.0/go.mod h1:6gMHlAl12DwYEfKP3TkuykYUfLSEAvHw67itm4/KAS8= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= +github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/open-feature/go-sdk v1.14.1 h1:jcxjCIG5Up3XkgYwWN5Y/WWfc6XobOhqrIwjyDBsoQo= +github.com/open-feature/go-sdk v1.14.1/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 h1:6jpO63NCEZv4xunJj+aNlDuFVuRkVBPMcIuxvFPYRWQ= +github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3/go.mod h1:dPUHjAIFzg+ci/wt6XxlNiiMkOh5Yw4SGyeRY0AFT0g= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 h1:ZdqlGnNwhWf3luhBQlIpbglvcCzjkcuEgOEhYhr5Emc= +github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5/go.mod h1:jrD4UG3ZCzuwImKHlyuIN2iWeYjlOX5+zJ/sX45efuE= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 h1:wEsCZ4oBuu8LfEJ3VXbveXO8uEhCthrxA40WSvxO044= +github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369/go.mod h1:m74TNgnAAIJ03gfHcx+xaRWnr+IbQy3y/AVNwwCFrC0= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336 h1:pYuYanFfgYrvDoSu/nnThT9P60mw5Yx7PMEI7FYychM= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336/go.mod h1:IWRgDIekw3UGSWINwmCALHpMmn6NEJzz6e7KZGm+xQ4= +github.com/openfga/openfga v1.8.13 h1:ROURkotKhbmtyBX3188+cNElN8AOZmTl0CMkxUqwawo= +github.com/openfga/openfga v1.8.13/go.mod h1:h1VGcVW81eY1YyDtFx5+gxxAIEhIiOGR9SRGgs/X/k8= +github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM= +github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= +github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= +github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= +github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= +github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= +github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= +github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/thejerf/slogassert v0.3.4 h1:VoTsXixRbXMrRSSxDjYTiEDCM4VWbsYPW5rB/hX24kM= +github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574GlUxHD4rs8= +github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE9IrvgBA2C1mRN1lc= +github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= +github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= +github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8= +go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc= +go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY= +go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= +go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0 h1:lREC4C0ilyP4WibDhQ7Gg2ygAQFP8oR07Fst/5cafwI= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.61.0/go.mod h1:HfvuU0kW9HewH14VCOLImqKvUgONodURG7Alj/IrnGI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0 h1:SoCgXYF4ISDtNyfLUzsGDaaudZVTx2yJhOyBO0+/GYk= +go.opentelemetry.io/contrib/propagators/jaeger v1.36.0/go.mod h1:VHu48l0YTRKSObdPQ+Sb8xMZvdnJlN7yhHuHoPgNqHM= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0 h1:bQ1Gvah4Sp8z7epSkgJaNTuZm7sutfA6Fji2/7cKFMc= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.30.0/go.mod h1:9b8Q9rH52NgYH3ShiTFB5wf18Vt3RTH/VMB7LDcC1ug= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw= +gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/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= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= +google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= +google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= +gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= +k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kms v0.33.3 h1:7cQWC+GSH211NgY8LRKjBXNtkzra5SkpYzeZrOt5D+8= +k8s.io/kms v0.33.3/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA= +modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc= +modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y= +modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= @@ -180,6 +2011,9 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= +xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= +xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= diff --git a/apps/iam/kinds/manifest.cue b/apps/iam/kinds/manifest.cue index 60a6d1f8faf..c9390acbf3e 100644 --- a/apps/iam/kinds/manifest.cue +++ b/apps/iam/kinds/manifest.cue @@ -10,7 +10,7 @@ manifest: { v0alpha1: { kinds: [ - globalrolev0alpha1, + globalrolev0alpha1, globalrolebindingv0alpha1, corerolev0alpha1, rolev0alpha1, @@ -21,4 +21,4 @@ v0alpha1: { teambindingv0alpha1, serviceaccountv0alpha1, ] -} \ No newline at end of file +} diff --git a/apps/iam/local/Tiltfile b/apps/iam/local/Tiltfile new file mode 100644 index 00000000000..6c53501d811 --- /dev/null +++ b/apps/iam/local/Tiltfile @@ -0,0 +1,57 @@ + +# version_settings() enforces a minimum Tilt version +# https://docs.tilt.dev/api.html#api.version_settings +version_settings(constraint='>=0.22.2') + +def name(c): + return c['metadata']['name'] + +def namespace(c): + if 'namespace' in c['metadata']: + return c['metadata']['namespace'] + return '' + +def decode(yaml): + resources = decode_yaml_stream(yaml) + + # workaround a bug in decode_yaml_stream where it returns duplicates + # This bug has been fixed in Tilt v0.17.3+ + filtered = [] + names = {} + for r in resources: + if r == None: + continue + + n = '%s:%s:%s' % (name(r), r['kind'], namespace(r)) + if n in names: + continue + + names[n] = True + filtered.append(r) + + return filtered + +def find_overlapping(o, yamls): + for elem in yamls: + if name(o) == name(elem) and o['kind'] == elem['kind'] and namespace(o) == namespace(elem): + return elem + return None + +yaml_objects = [] +# Parse all YAML files in our "yamls" directory +for filename in listdir('yamls'): + if filename.lower().endswith(('.yaml', '.yml')): + decoded = decode(read_file(filename)) + for o in decoded: + present = find_overlapping(o, yaml_objects) + if present != None: + print("Overlapping resource found: %s", filename) + exit(1) + yaml_objects += decoded + +bundle = encode_yaml_stream(yaml_objects) + +# k8s_yaml automatically creates resources in Tilt for the entities +# and will inject any images referenced in the Tiltfile when deploying +# https://docs.tilt.dev/api.html#api.k8s_yaml +k8s_yaml(bundle) diff --git a/apps/iam/local/k3d-config.json b/apps/iam/local/k3d-config.json new file mode 100644 index 00000000000..224d6026fc7 --- /dev/null +++ b/apps/iam/local/k3d-config.json @@ -0,0 +1,16 @@ +{ + "apiVersion": "k3d.io/v1alpha3", + "kind": "Simple", + "kubeAPI": { + "hostPort": "8556" + }, + "options": { + "k3d": { + "wait": true + }, + "kubeconfig": { + "switchCurrentContext": true, + "updateDefaultKubeconfig": true + } + } +} diff --git a/apps/iam/local/scripts/cluster.sh b/apps/iam/local/scripts/cluster.sh new file mode 100755 index 00000000000..1edf4502d9c --- /dev/null +++ b/apps/iam/local/scripts/cluster.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -eufo pipefail + +CLUSTER_NAME="grafana-iam-operator" + +create_cluster() { + K3D_CONFIG="${1:-k3d-config.json}" + + if ! k3d cluster list "${CLUSTER_NAME}" >/dev/null 2>&1; then + # Array of extra options to add to the k3d cluster create command + EXTRA_K3D_OPTS=() + + # Bug in k3d for btrfs filesystems workaround, see https://k3d.io/v5.2.2/faq/faq/#issues-with-btrfs + # Apple is APFS/HFS and stat has a different API, so we might as well skip that + if [[ "${OSTYPE}" != "darwin*" ]]; then + ROOTFS="$(stat -f --format="%T" "/")" + if [[ "${ROOTFS}" == "btrfs" ]]; then + EXTRA_K3D_OPTS+=("-v" "/dev/mapper:/dev/mapper") + fi + fi + + k3d cluster create "${CLUSTER_NAME}" --config "${K3D_CONFIG}" ${EXTRA_K3D_OPTS[@]+"${EXTRA_K3D_OPTS[@]}"} + else + echo "Cluster already exists" + fi +} + +delete_cluster() { + k3d cluster delete "${CLUSTER_NAME}" +} + +if [ $# -lt 1 ]; then + echo "Usage: ./cluster.sh [create|delete]" + exit 1 +fi + +if [ $1 == "create" ]; then + create_cluster $2 +elif [ $1 == "delete" ]; then + delete_cluster +else + echo "Unknown argument ${1}" +fi diff --git a/apps/iam/local/scripts/push_image.sh b/apps/iam/local/scripts/push_image.sh new file mode 100755 index 00000000000..33257688764 --- /dev/null +++ b/apps/iam/local/scripts/push_image.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eufo pipefail + +CLUSTER_NAME="grafana-iam-operator" +IMAGE="$1" + +if [[ "$IMAGE" == "" ]]; then + echo "usage: push_image.sh " + exit 1 +fi + +k3d image import "${IMAGE}" -c "${CLUSTER_NAME}" \ No newline at end of file diff --git a/apps/iam/local/yamls/operator.yaml b/apps/iam/local/yamls/operator.yaml new file mode 100644 index 00000000000..58fb79c68a5 --- /dev/null +++ b/apps/iam/local/yamls/operator.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: operator + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iam-app-operator + namespace: default +spec: + minReadySeconds: 10 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + name: iam-app-operator + template: + metadata: + labels: + name: iam-app-operator + spec: + serviceAccount: operator + containers: + - image: localhost/github.com/grafana/grafana/apps/iam/operator:latest + imagePullPolicy: IfNotPresent + name: iam-app-operator + command: ["/bin/sh"] + args: + - -c + - exec /usr/bin/operator + env: + - name: ZANZANA_ADDR + value: zanzana.default.svc.cluster.local:50051 + - name: FOLDER_APP_URL + value: https://host.docker.internal:6446 + - name: AUTH_TOKEN_EXCHANGE_URL + value: http://host.docker.internal:8080/v1/sign-access-token + - name: AUTH_TOKEN + value: "" + - name: FOLDER_APP_NAMESPACE + value: "grafana-folder" + - name: FOLDER_RECONCILER_NAMESPACE + value: "default" diff --git a/apps/iam/local/yamls/zanzana.yaml b/apps/iam/local/yamls/zanzana.yaml new file mode 100644 index 00000000000..095d7b58f27 --- /dev/null +++ b/apps/iam/local/yamls/zanzana.yaml @@ -0,0 +1,177 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:15.7 + env: + - name: POSTGRES_USER + value: grafana + - name: POSTGRES_PASSWORD + value: password + - name: POSTGRES_DB + value: grafana + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - containerPort: 5432 + name: postgres + volumeMounts: + - name: postgres-storage + mountPath: /var/lib/postgresql/data + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 15 + periodSeconds: 5 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U $POSTGRES_USER -d $POSTGRES_DB + initialDelaySeconds: 30 + periodSeconds: 10 + volumes: + - name: postgres-storage + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: default +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + name: postgres + type: ClusterIP +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: zanzana + namespace: default +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: zanzana-config + namespace: default + labels: + name: zanzana +data: + grafana.ini: | + app_mode = development + target = zanzana-server + + [log] + level = info + + [database] + type = postgres + host = postgres.default.svc:5432 + name = grafana + user = grafana + password = password + + [feature_toggles] + zanzana = true + authZGRPCServer = true + + [zanzana.server] + allow_insecure = true + check_query_cache = true + http_addr = 0.0.0.0:8080 + + [grpc_server] + enabled = true + address = 0.0.0.0:50051 + enable_logging = true +--- +apiVersion: v1 +kind: Service +metadata: + name: zanzana + namespace: default + labels: + name: zanzana +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + - name: grpc + port: 50051 + targetPort: 50051 + selector: + name: zanzana +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zanzana + namespace: default +spec: + minReadySeconds: 10 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + name: zanzana + template: + metadata: + labels: + name: zanzana + spec: + containers: + - command: + - grafana-server + - target + - --config=/etc/grafana-config/grafana.ini + - --homepath=/usr/share/grafana + env: + - name: GF_PATHS_CONFIG + value: /etc/grafana-config/grafana.ini + image: grafana/grafana-oss-dev:12.2.0-257970 + imagePullPolicy: IfNotPresent + name: zanzana + volumeMounts: + - mountPath: /etc/grafana-config + name: zanzana-config + - mountPath: /var/lib/grafana + name: zanzana-storage + serviceAccount: zanzana + volumes: + - configMap: + name: zanzana-config + name: zanzana-config + - emptyDir: {} + name: zanzana-storage +--- diff --git a/apps/iam/pkg/app/app.go b/apps/iam/pkg/app/app.go new file mode 100644 index 00000000000..edde9404280 --- /dev/null +++ b/apps/iam/pkg/app/app.go @@ -0,0 +1,63 @@ +package app + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/simple" + foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + "github.com/grafana/grafana/apps/iam/pkg/reconcilers" +) + +type AppConfig = reconcilers.AppConfig + +var appManifestData = app.ManifestData{ + AppName: "iam-folder-reconciler", + Group: "iam.grafana.app", +} + +func Provider(appCfg AppConfig) app.Provider { + return simple.NewAppProvider(app.NewEmbeddedManifest(appManifestData), appCfg, New) +} + +func New(cfg app.Config) (app.App, error) { + folderReconciler, err := reconcilers.NewFolderReconciler(cfg) + if err != nil { + return nil, fmt.Errorf("unable to create FolderReconciler: %w", err) + } + + logging.DefaultLogger.Info("FolderReconciler created") + + config := simple.AppConfig{ + Name: cfg.ManifestData.AppName, + KubeConfig: cfg.KubeConfig, + InformerConfig: simple.AppInformerConfig{ + ErrorHandler: func(ctx context.Context, err error) { + // FIXME: add your own error handling here + logging.FromContext(ctx).With("error", err).Error("Informer processing error") + }, + }, + UnmanagedKinds: []simple.AppUnmanagedKind{ + { + Kind: foldersKind.FolderKind(), + Reconciler: folderReconciler, + ReconcileOptions: simple.BasicReconcileOptions{ + Namespace: cfg.SpecificConfig.(AppConfig).FolderReconcilerNamespace, + }, + }, + }, + } + + // Create the App + a, err := simple.NewApp(config) + if err != nil { + return nil, err + } + + // Validate the capabilities against the provided manifest to make sure there isn't a mismatch + err = a.ValidateManifest(cfg.ManifestData) + + return a, err +} diff --git a/apps/iam/pkg/reconcilers/folder_reconciler.go b/apps/iam/pkg/reconcilers/folder_reconciler.go new file mode 100644 index 00000000000..21472487e29 --- /dev/null +++ b/apps/iam/pkg/reconcilers/folder_reconciler.go @@ -0,0 +1,173 @@ +package reconcilers + +import ( + "context" + "fmt" + "time" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/operator" + foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + "github.com/grafana/grafana/pkg/services/authz/zanzana" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// FolderStore interface for retrieving folder information +type FolderStore interface { + GetFolderParent(ctx context.Context, namespace, uid string) (string, error) +} + +// PermissionStore interface for managing folder permissions +type PermissionStore interface { + GetFolderParents(ctx context.Context, namespace, folderUID string) ([]string, error) + SetFolderParent(ctx context.Context, namespace, folderUID, parentUID string) error + DeleteFolderParents(ctx context.Context, namespace, folderUID string) error +} + +// AppConfig represents the app-specific configuration +type AppConfig struct { + ZanzanaAddr string + FolderReconcilerNamespace string +} + +type FolderReconciler struct { + permissionStore PermissionStore + folderStore FolderStore +} + +func NewFolderReconciler(cfg app.Config) (operator.Reconciler, error) { + // Extract Zanzana address from config + appCfg, ok := cfg.SpecificConfig.(AppConfig) + if !ok { + return nil, fmt.Errorf("invalid config type: expected AppConfig, got %T", cfg.SpecificConfig) + } + + // Create Zanzana client + zanzanaClient, err := getZanzanaClient(appCfg.ZanzanaAddr) + if err != nil { + return nil, fmt.Errorf("unable to create zanzana client: %w", err) + } + + // Create dependencies + folderStore := NewAPIFolderStore(&cfg.KubeConfig) + permissionStore := NewZanzanaPermissionStore(zanzanaClient) + + folderReconciler := &FolderReconciler{ + permissionStore: permissionStore, + folderStore: folderStore, + } + + reconciler := &operator.TypedReconciler[*foldersKind.Folder]{ + ReconcileFunc: folderReconciler.reconcile, + } + + return reconciler, nil +} + +func getZanzanaClient(addr string) (zanzana.Client, error) { + transportCredentials := insecure.NewCredentials() + + dialOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(transportCredentials), + } + + conn, err := grpc.NewClient(addr, dialOptions...) + if err != nil { + return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err) + } + + client, err := zanzana.NewClient(conn) + if err != nil { + return nil, fmt.Errorf("failed to initialize zanzana client: %w", err) + } + + return client, nil +} + +func (r *FolderReconciler) reconcile(ctx context.Context, req operator.TypedReconcileRequest[*foldersKind.Folder]) (operator.ReconcileResult, error) { + // Add timeout to prevent hanging operations + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + logger := logging.FromContext(ctx) + logger.Info("Reconciling request", "req", req) + + err := validateFolder(req.Object) + if err != nil { + return operator.ReconcileResult{}, err + } + + switch req.Action { + case operator.ReconcileActionCreated: + return r.handleUpdateFolder(ctx, req.Object) + case operator.ReconcileActionUpdated: + return r.handleUpdateFolder(ctx, req.Object) + case operator.ReconcileActionDeleted: + return r.handleDeleteFolder(ctx, req.Object) + default: + return operator.ReconcileResult{}, nil + } +} + +func (r *FolderReconciler) handleUpdateFolder(ctx context.Context, folder *foldersKind.Folder) (operator.ReconcileResult, error) { + logger := logging.FromContext(ctx) + + folderUID := folder.Name + namespace := folder.Namespace + + parentUID, err := r.folderStore.GetFolderParent(ctx, namespace, folderUID) + if err != nil { + return operator.ReconcileResult{}, err + } + + parents, err := r.permissionStore.GetFolderParents(ctx, namespace, folderUID) + if err != nil { + return operator.ReconcileResult{}, err + } + + if (len(parents) == 0 && parentUID == "") || (len(parents) == 1 && parents[0] == parentUID) { + // Folder is already reconciled + logger.Info("Folder is already reconciled", "folder", folderUID, "parent", parentUID, "namespace", namespace) + return operator.ReconcileResult{}, nil + } + + err = r.permissionStore.SetFolderParent(ctx, namespace, folderUID, parentUID) + if err != nil { + return operator.ReconcileResult{}, err + } + + logger.Info("Folder parent set in permission store", "folder", folderUID, "parent", parentUID, "namespace", namespace) + + return operator.ReconcileResult{}, nil +} + +func (r *FolderReconciler) handleDeleteFolder(ctx context.Context, folder *foldersKind.Folder) (operator.ReconcileResult, error) { + logger := logging.FromContext(ctx) + + namespace := folder.Namespace + folderUID := folder.Name + + err := r.permissionStore.DeleteFolderParents(ctx, namespace, folderUID) + if err != nil { + return operator.ReconcileResult{}, err + } + + logger.Info("Folder deleted from permission store", "folder", folderUID, "namespace", namespace) + + return operator.ReconcileResult{}, nil +} + +func validateFolder(folder *foldersKind.Folder) error { + if folder == nil { + return fmt.Errorf("folder is nil") + } + if folder.Name == "" { + return fmt.Errorf("folder UID (ObjectMeta.Name) is empty") + } + if folder.Namespace == "" { + return fmt.Errorf("folder namespace is empty") + } + return nil +} diff --git a/apps/iam/pkg/reconcilers/folder_service.go b/apps/iam/pkg/reconcilers/folder_service.go new file mode 100644 index 00000000000..00ed59c75b1 --- /dev/null +++ b/apps/iam/pkg/reconcilers/folder_service.go @@ -0,0 +1,50 @@ +package reconcilers + +import ( + "context" + "fmt" + + foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +var _ FolderStore = (*APIFolderStore)(nil) + +func NewAPIFolderStore(config *rest.Config) FolderStore { + return &APIFolderStore{config} +} + +type APIFolderStore struct { + config *rest.Config +} + +func (s *APIFolderStore) GetFolderParent(ctx context.Context, namespace, uid string) (string, error) { + client, err := s.client(namespace) + if err != nil { + return "", fmt.Errorf("create resource client: %w", err) + } + + // Get the folder by UID + unstructuredObj, err := client.Get(ctx, uid, metav1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("get folder %s: %w", uid, err) + } + + object, err := utils.MetaAccessor(unstructuredObj) + if err != nil { + return "", fmt.Errorf("get meta accessor: %w", err) + } + + return object.GetFolder(), nil +} + +func (s *APIFolderStore) client(namespace string) (dynamic.ResourceInterface, error) { + client, err := dynamic.NewForConfig(s.config) + if err != nil { + return nil, err + } + return client.Resource(foldersKind.FolderResourceInfo.GroupVersionResource()).Namespace(namespace), nil +} diff --git a/apps/iam/pkg/reconcilers/zanzana_service.go b/apps/iam/pkg/reconcilers/zanzana_service.go new file mode 100644 index 00000000000..5c93f236b7b --- /dev/null +++ b/apps/iam/pkg/reconcilers/zanzana_service.go @@ -0,0 +1,163 @@ +package reconcilers + +import ( + "context" + "fmt" + "strings" + + authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" + "github.com/grafana/grafana/pkg/services/authz/zanzana" +) + +type ZanzanaPermissionStore struct { + zanzanaClient zanzana.Client +} + +var _ PermissionStore = (*ZanzanaPermissionStore)(nil) + +func NewZanzanaPermissionStore(zanzanaClient zanzana.Client) PermissionStore { + return &ZanzanaPermissionStore{zanzanaClient} +} + +func (c *ZanzanaPermissionStore) SetFolderParent(ctx context.Context, namespace, folderUID, parentUID string) error { + err := c.DeleteFolderParents(ctx, namespace, folderUID) + if err != nil { + return err + } + + user, err := toFolderTuple(parentUID) + if err != nil { + return err + } + + object, err := toFolderTuple(folderUID) + if err != nil { + return err + } + + if err := c.zanzanaClient.Write(ctx, &authzextv1.WriteRequest{ + Namespace: namespace, + Writes: &authzextv1.WriteRequestWrites{ + TupleKeys: []*authzextv1.TupleKey{{ + User: user, + Relation: zanzana.RelationParent, + Object: object, + }}, + }, + }); err != nil { + return err + } + + return nil +} + +func (c *ZanzanaPermissionStore) GetFolderParents(ctx context.Context, namespace, folderUID string) ([]string, error) { + tuples, err := c.listFolderParentRelations(ctx, namespace, folderUID) + if err != nil { + return nil, err + } + + parents := make([]string, 0, len(tuples)) + + for _, t := range tuples { + // Extract UID from format "folder:UID" or "folder:UID#relation" + userParts := strings.Split(t.Key.User, ":") + if len(userParts) == 2 { + // Remove any relation part after # + uidAndRelationParts := strings.Split(userParts[1], "#") + if len(uidAndRelationParts) > 0 { + parents = append(parents, uidAndRelationParts[0]) + } else { + return nil, fmt.Errorf("invalid user format: %s, expected format: folder:UID or folder:UID#relation", t.Key.User) + } + } else { + return nil, fmt.Errorf("invalid user format: %s, expected format: folder:UID or folder:UID#relation", t.Key.User) + } + } + + return parents, nil +} + +func (c *ZanzanaPermissionStore) DeleteFolderParents(ctx context.Context, namespace, folderUID string) error { + tuples, err := c.listFolderParentRelations(ctx, namespace, folderUID) + if err != nil { + return err + } + + if len(tuples) > 0 { + err = c.deleteTuples(ctx, namespace, tuples) + if err != nil { + return err + } + } + + return nil +} + +// listFolderParentRelations lists parent relations where the given folder is the object. +// It returns tuples where other folders are parents of this folder, not children. +func (c *ZanzanaPermissionStore) listFolderParentRelations(ctx context.Context, namespace, folderUID string) ([]*authzextv1.Tuple, error) { + object, err := toFolderTuple(folderUID) + if err != nil { + return nil, err + } + + relation := zanzana.RelationParent + + list, err := c.zanzanaClient.Read(ctx, &authzextv1.ReadRequest{ + Namespace: namespace, + TupleKey: &authzextv1.ReadRequestTupleKey{ + Object: object, + Relation: relation, + }, + }) + + if err != nil { + return nil, err + } + + continuationToken := list.ContinuationToken + for continuationToken != "" { + res, err := c.zanzanaClient.Read(ctx, &authzextv1.ReadRequest{ + ContinuationToken: continuationToken, + Namespace: namespace, + TupleKey: &authzextv1.ReadRequestTupleKey{ + Object: object, + Relation: relation, + }, + }) + if err != nil { + return nil, err + } + + continuationToken = res.ContinuationToken + list.Tuples = append(list.Tuples, res.Tuples...) + } + + return list.Tuples, nil +} + +func (c *ZanzanaPermissionStore) deleteTuples(ctx context.Context, namespace string, tuples []*authzextv1.Tuple) error { + tupleKeys := make([]*authzextv1.TupleKeyWithoutCondition, 0, len(tuples)) + for _, t := range tuples { + tupleKeys = append(tupleKeys, &authzextv1.TupleKeyWithoutCondition{ + User: t.Key.User, + Relation: t.Key.Relation, + Object: t.Key.Object, + }) + } + + return c.zanzanaClient.Write(ctx, &authzextv1.WriteRequest{ + Namespace: namespace, + Deletes: &authzextv1.WriteRequestDeletes{ + TupleKeys: tupleKeys, + }, + }) +} + +func toFolderTuple(UID string) (string, error) { + if strings.ContainsAny(UID, "#:") { + return "", fmt.Errorf("UID contains invalid characters: %s", UID) + } + return zanzana.NewTupleEntry(zanzana.TypeFolder, UID, ""), nil +} diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index f94e2710e12..ab3de62ade8 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -65,13 +65,14 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index c586ffa88d2..2ba66fa1434 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -159,8 +159,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -171,22 +171,22 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 8e1ae857041..5be8ea92b75 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -65,13 +65,14 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index c586ffa88d2..2ba66fa1434 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -159,8 +159,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -171,22 +171,22 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index 45c31ca73c5..79a53eaca86 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -71,14 +71,15 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 4e9f056ba68..0bf5302edb2 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -171,8 +171,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -185,8 +185,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -206,23 +206,23 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -231,8 +231,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index 58b0f331981..31a9edf0440 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -55,12 +55,13 @@ require ( go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index 60de9834b49..a560728dc69 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -130,8 +130,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -140,22 +140,22 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go index 219db2907fb..bc673959164 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go @@ -17,9 +17,23 @@ type Repository struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec RepositorySpec `json:"spec,omitempty"` + Secure SecureValues `json:"secure,omitzero,omitempty"` Status RepositoryStatus `json:"status,omitempty"` } +// NOT YET USED FOR REAL -- testing secure value workflow +type SecureValues struct { + // Token used to connect the configured repository + Token common.InlineSecureValue `json:"token,omitzero,omitempty"` + + // Some webhooks (github) require a secret key value + WebhookSecret common.InlineSecureValue `json:"webhookSecret,omitzero,omitempty"` +} + +func (v SecureValues) IsZero() bool { + return v.Token.IsZero() && v.WebhookSecret.IsZero() +} + type LocalRepositoryConfig struct { Path string `json:"path,omitempty"` } @@ -228,10 +242,22 @@ type RepositoryStatus struct { Webhook *WebhookStatus `json:"webhook"` } +// HealthFailureType represents different types of repository failures +// +enum +type HealthFailureType string + +const ( + HealthFailureHook HealthFailureType = "hook" + HealthFailureHealth HealthFailureType = "health" +) + type HealthStatus struct { // When not healthy, requests will not be executed Healthy bool `json:"healthy"` + // The type of the error + Error HealthFailureType `json:"error,omitempty"` + // When the health was checked last time Checked int64 `json:"checked,omitempty"` diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 868437c9e85..5e09384fc9f 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -665,6 +665,7 @@ func (in *Repository) DeepCopyInto(out *Repository) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + out.Secure = in.Secure in.Status.DeepCopyInto(&out.Status) return } @@ -1085,6 +1086,24 @@ func (in *ResourceWrapper) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecureValues) DeepCopyInto(out *SecureValues) { + *out = *in + out.Token = in.Token + out.WebhookSecret = in.WebhookSecret + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecureValues. +func (in *SecureValues) DeepCopy() *SecureValues { + if in == nil { + return nil + } + out := new(SecureValues) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SyncJobOptions) DeepCopyInto(out *SyncJobOptions) { *out = *in diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 0b4c4e42377..99244e9d5fc 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -57,6 +57,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SecureValues": schema_pkg_apis_provisioning_v0alpha1_SecureValues(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncStatus": schema_pkg_apis_provisioning_v0alpha1_SyncStatus(ref), @@ -570,6 +571,14 @@ func schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref common.ReferenceCall Format: "", }, }, + "error": { + SchemaProps: spec.SchemaProps{ + Description: "The type of the error\n\nPossible enum values:\n - `\"health\"`\n - `\"hook\"`", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"health", "hook"}, + }, + }, "checked": { SchemaProps: spec.SchemaProps{ Description: "When the health was checked last time", @@ -1435,6 +1444,12 @@ func schema_pkg_apis_provisioning_v0alpha1_Repository(ref common.ReferenceCallba Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec"), }, }, + "secure": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SecureValues"), + }, + }, "status": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, @@ -1445,7 +1460,7 @@ func schema_pkg_apis_provisioning_v0alpha1_Repository(ref common.ReferenceCallba }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SecureValues", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -2359,6 +2374,35 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC } } +func schema_pkg_apis_provisioning_v0alpha1_SecureValues(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NOT YET USED FOR REAL -- testing secure value workflow", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "token": { + SchemaProps: spec.SchemaProps{ + Description: "Token used to connect the configured repository", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"), + }, + }, + "webhookSecret": { + SchemaProps: spec.SchemaProps{ + Description: "Some webhooks (github) require a secret key value", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"}, + } +} + func schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go index 6d3b33cb318..813d6649458 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go @@ -4,12 +4,17 @@ package v0alpha1 +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + // HealthStatusApplyConfiguration represents a declarative configuration of the HealthStatus type for use // with apply. type HealthStatusApplyConfiguration struct { - Healthy *bool `json:"healthy,omitempty"` - Checked *int64 `json:"checked,omitempty"` - Message []string `json:"message,omitempty"` + Healthy *bool `json:"healthy,omitempty"` + Error *provisioningv0alpha1.HealthFailureType `json:"error,omitempty"` + Checked *int64 `json:"checked,omitempty"` + Message []string `json:"message,omitempty"` } // HealthStatusApplyConfiguration constructs a declarative configuration of the HealthStatus type for use with @@ -26,6 +31,14 @@ func (b *HealthStatusApplyConfiguration) WithHealthy(value bool) *HealthStatusAp return b } +// WithError sets the Error field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Error field is set to the value of the last call. +func (b *HealthStatusApplyConfiguration) WithError(value provisioningv0alpha1.HealthFailureType) *HealthStatusApplyConfiguration { + b.Error = &value + return b +} + // WithChecked sets the Checked field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Checked field is set to the value of the last call. diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go index 19a4e828600..6541158cb51 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go @@ -16,6 +16,7 @@ type RepositoryApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` Spec *RepositorySpecApplyConfiguration `json:"spec,omitempty"` + Secure *SecureValuesApplyConfiguration `json:"secure,omitempty"` Status *RepositoryStatusApplyConfiguration `json:"status,omitempty"` } @@ -196,6 +197,14 @@ func (b *RepositoryApplyConfiguration) WithSpec(value *RepositorySpecApplyConfig return b } +// WithSecure sets the Secure field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Secure field is set to the value of the last call. +func (b *RepositoryApplyConfiguration) WithSecure(value *SecureValuesApplyConfiguration) *RepositoryApplyConfiguration { + b.Secure = value + return b +} + // WithStatus sets the Status field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Status field is set to the value of the last call. diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/securevalues.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/securevalues.go new file mode 100644 index 00000000000..f4892cab861 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/securevalues.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + commonv0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// SecureValuesApplyConfiguration represents a declarative configuration of the SecureValues type for use +// with apply. +type SecureValuesApplyConfiguration struct { + Token *commonv0alpha1.InlineSecureValue `json:"token,omitempty"` + WebhookSecret *commonv0alpha1.InlineSecureValue `json:"webhookSecret,omitempty"` +} + +// SecureValuesApplyConfiguration constructs a declarative configuration of the SecureValues type for use with +// apply. +func SecureValues() *SecureValuesApplyConfiguration { + return &SecureValuesApplyConfiguration{} +} + +// WithToken sets the Token field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Token field is set to the value of the last call. +func (b *SecureValuesApplyConfiguration) WithToken(value commonv0alpha1.InlineSecureValue) *SecureValuesApplyConfiguration { + b.Token = &value + return b +} + +// WithWebhookSecret sets the WebhookSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the WebhookSecret field is set to the value of the last call. +func (b *SecureValuesApplyConfiguration) WithWebhookSecret(value commonv0alpha1.InlineSecureValue) *SecureValuesApplyConfiguration { + b.WebhookSecret = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go index 8725415c377..dce76cfdc04 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/utils.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go @@ -62,6 +62,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &provisioningv0alpha1.ResourceCountApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ResourceRef"): return &provisioningv0alpha1.ResourceRefApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("SecureValues"): + return &provisioningv0alpha1.SecureValuesApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("SyncJobOptions"): return &provisioningv0alpha1.SyncJobOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("SyncOptions"): diff --git a/apps/secret/go.mod b/apps/secret/go.mod index f153f3be5a5..bd3d4f3542b 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -52,11 +52,11 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 9482a6103bf..f98c5385f61 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -134,8 +134,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -144,14 +144,14 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index d3f050f910c..37c9d61621c 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -66,13 +66,14 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index c586ffa88d2..2ba66fa1434 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -159,8 +159,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -171,22 +171,22 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/contribute/drone-pipeline.md b/contribute/drone-pipeline.md deleted file mode 100644 index 0e743aa5b11..00000000000 --- a/contribute/drone-pipeline.md +++ /dev/null @@ -1,17 +0,0 @@ -# Making changes to the Drone pipeline - -> Only members of the Grafana organization can make changes to the Drone pipeline. - -The Drone pipelines are built with [Starlark](https://github.com/bazelbuild/starlark), a similar language to Python. The Starlark files are located in [`scripts/drone`](https://github.com/grafana/grafana/tree/main/scripts/drone). - -## Drone setup - -1. Set environment variables `DRONE_SERVER` and `DRONE_TOKEN` found in your [Drone account](https://drone.grafana.net/account). These environment variables are used to verify that only Grafana employees can make changes to the pipelines. -1. Install [buildifier](https://github.com/bazelbuild/buildtools/blob/master/buildifier/README.md), and use it to format the Starlark files you want to edit. - -## Drone development - -1. Open a pull request where you can do test runs for your changes. If you need to experiment with secrets, create a pull request in the [`grafana-ci-sandbox repo`](https://github.com/grafana/grafana-ci-sandbox) before opening a pull request in the main repo. -1. Run `make drone` after making changes to the Starlark files. This builds the `.drone.yml` file. - -For further questions, reach out to the `grafana-release-guild` squad. diff --git a/devenv/datasources_docker.yaml b/devenv/datasources_docker.yaml index 61c0b3c7ecd..eb76de4ef5b 100644 --- a/devenv/datasources_docker.yaml +++ b/devenv/datasources_docker.yaml @@ -171,6 +171,51 @@ datasources: - name: gdev-loki type: loki + uid: gdev-loki access: proxy url: http://loki:3100 editable: false + + - name: gdev-pyroscope + type: grafana-pyroscope-datasource + uid: gdev-pyroscope + access: proxy + url: http://pyroscope:4040 + editable: false + + - name: gdev-tempo + type: tempo + uid: gdev-tempo + access: proxy + url: http://tempo:3200 + editable: false + correlations: + - targetUID: gdev-loki + label: 'Logs (correlation)' + description: 'Correlation to logs stored in Loki' + config: + type: query + target: + expr: '{ job="job" }' + field: 'traceID' + jsonData: + tracesToLogsV2: + datasourceUid: gdev-loki + spanStartTimeShift: '5m' + spanEndTimeShift: '-5m' + customQuery: true + query: '{filename="/var/log/grafana/grafana.log"} |="$${__span.traceId}"' + tracesToProfiles: + datasourceUid: gdev-pyroscope + profileTypeId: 'process_cpu:cpu:nanoseconds:cpu:nanoseconds' + tracesToMetrics: + datasourceUid: gdev-prometheus + spanStartTimeShift: '1h' + spanEndTimeShift: '-1h' + tags: [{ key: 'job' }] + queries: + - name: 'Metrics' + query: 'sum(rate({$$__tags}[5m]))' + serviceMap: + datasourceUid: 'gdev-prometheus' + histogramType: 'both' # 'classic' or 'native' or 'both' diff --git a/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json b/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json new file mode 100644 index 00000000000..8f6351e47b8 --- /dev/null +++ b/devenv/dev-dashboards/scenarios/mostly-blank-dashboard.json @@ -0,0 +1,98 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 10, + "links": [], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "maxDataPoints": 10, + "options": { + "counters": { + "dataChanged": true, + "render": true, + "schemaChanged": true + }, + "mode": "events", + "stateView": "" + }, + "pluginVersion": "12.2.0-16975765900", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "refId": "A" + } + ], + "title": "Panel Title", + "type": "debug" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Mostly blank dashboard", + "uid": "bds35fot3cv7kb", + "version": 3 +} diff --git a/devenv/frontend-service/run.sh b/devenv/frontend-service/run.sh new file mode 100644 index 00000000000..9284d218ab4 --- /dev/null +++ b/devenv/frontend-service/run.sh @@ -0,0 +1,13 @@ +# Script called from makefile to auto down the tilt environment on ctrl + c. +# Paths are relative to the makefile + +function tilt_down() +{ + echo "Tearing down Tilt environment... (this might take a little while)" + tilt down -f devenv/frontend-service/Tiltfile +} + + +trap tilt_down SIGINT + +tilt up -f devenv/frontend-service/Tiltfile \ No newline at end of file diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index c050297d430..b26e72e049a 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -69,6 +69,7 @@ "loki_fakedata": (import '../dev-dashboards/datasource-loki/loki_fakedata.json'), "loki_query_splitting": (import '../dev-dashboards/datasource-loki/loki_query_splitting.json'), "migrations": (import '../dev-dashboards/migrations/migrations.json'), + "mostly-blank-dashboard": (import '../dev-dashboards/scenarios/mostly-blank-dashboard.json'), "mssql_fakedata": (import '../dev-dashboards/datasource-mssql/mssql_fakedata.json'), "mssql_unittest": (import '../dev-dashboards/datasource-mssql/mssql_unittest.json'), "mysql_fakedata": (import '../dev-dashboards/datasource-mysql/mysql_fakedata.json'), diff --git a/docs/sources/administration/provisioning/index.md b/docs/sources/administration/provisioning/index.md index 6145d69a761..078afd2858f 100644 --- a/docs/sources/administration/provisioning/index.md +++ b/docs/sources/administration/provisioning/index.md @@ -5,6 +5,7 @@ description: Describes provisioning settings for Grafana using configuration fil keywords: - grafana - provisioning + - provision labels: products: - enterprise @@ -15,8 +16,7 @@ weight: 600 # Provision Grafana -Grafana has an active provisioning system that uses configuration files. -This makes GitOps more natural since data sources and dashboards can be defined using files that can be version controlled. +Grafana has an active provisioning system that uses configuration files. You can define data sources and dashboards using files that can be version controlled, making GitOps more natural. ## Configuration file @@ -25,22 +25,22 @@ Refer to [Configuration](../../setup-grafana/configure-grafana/) for more inform ### Configuration file locations Grafana reads its default configuration from `/conf/defaults.ini`. -By default, Grafana reads custom configuration from `/conf/custom.ini`. -You can override the custom configuration path with the `--config` option. + +Grafana reads custom configuration from `/conf/custom.ini`. You can override the custom configuration path with the `--config` option. {{< admonition type="note" >}} The Deb and RPM packages install the configuration file at `/etc/grafana/grafana.ini`. The Grafana init.d script sets the `--config` option to that path. {{< /admonition >}} -### Use environment variables +## Use environment variables -You can use environment variable lookups in all provisioning configuration. -The syntax for an environment variable is `$ENV_VAR_NAME` or `${ENV_VAR_NAME}`. -If the environment variable value has a `$` in it (for example, `Pa$sw0rd`), use the `$ENV_VAR_NAME` syntax to avoid double expansion. -You can only use environment variables for configuration values and not for keys or bigger parts of the configuration file structure. +You can use environment variable lookups in all provisioning configuration. The syntax for an environment variable is `$ENV_VAR_NAME` or `${ENV_VAR_NAME}`. -You can use environment variables in dashboard provisioning configuration but not the dashboard definition files themselves. +The following applies: + +- Only use environment variables for configuration values. Do not use it for keys or bigger parts of the configuration file structure. +- Use environment variables in dashboard provisioning configuration, but not in the dashboard definition files themselves. The following example looks up the data source URL port, user, and password using environment variables: @@ -53,7 +53,30 @@ datasources: password: $PASSWORD ``` -To escape a literal `$` in your provisioning file values, use `$$`. +### Use of the special character `$` + +Grafana's provisioning system considers any set of characters after an `$` a variable name. + +During the replacement process, Grafana: + +1. Replaces the variables that use the syntax `${ENV_VAR_NAME}`. +1. Next, it replaces the variables that use the syntax `$ENV_VAR_NAME`. + +If your data contains the character `$`, for example `Pa$sw0rd`, and you're using an environment variable, use the `$ENV_VAR_NAME` syntax to avoid double expansion. If you use the `${ENV_VAR_NAME}` syntax, the value will be first replaced as `Pa$sw0rd` and then again as `Pa` since `$sw0rd` will be considered another variable. + +If you want to use the literal value `Pa$sw0rd`, you need to escape the character `$` using a double `$$`: `Pa$$sw0rd`. + +The following example shows how variables are replaced, assuming `PASSWORD=Pa$sw0rd`: + +```yaml +datasources: + - name: Graphite + secureJsonData: + password1: $PASSWORD # Resolved as Pa$sw0rd + password2: ${PASSWORD} # Resolved as Pa + password3: 'Pa$$sw0rd' # Resolved as Pa$sw0rd + password4: 'Pa$sw0rd' # Resolved as Pa +``` ## Configuration management tools diff --git a/docs/sources/developers/plugins/plugin.schema.json b/docs/sources/developers/plugins/plugin.schema.json index d87e542bd05..c2ec4225c03 100644 --- a/docs/sources/developers/plugins/plugin.schema.json +++ b/docs/sources/developers/plugins/plugin.schema.json @@ -685,6 +685,13 @@ } } } + }, + "languages": { + "type": "array", + "description": "The list of languages supported by the plugin. Each entry should be a locale identifier in the format `language-COUNTRY` (e.g., `en-US`, `fr-FR`, `es-ES`).", + "items": { + "type": "string" + } } } } diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 67cba858ef2..cedf7b619e6 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -796,7 +796,7 @@ The following example limits access to the backend of a single plugin: #### `angular_support_enabled` -This is set to false by default, meaning that the angular framework and support components aren't be loaded. +This is set to false by default, meaning that the angular framework and support components aren't loaded. This means that all [plugins](../../developers/angular_deprecation/angular-plugins/) and core features that depend on angular support won't work. The core features that depend on angular are: diff --git a/e2e-playwright/utils/RequestsRecorder.ts b/e2e-playwright/utils/RequestsRecorder.ts new file mode 100644 index 00000000000..f2b4f7f13b1 --- /dev/null +++ b/e2e-playwright/utils/RequestsRecorder.ts @@ -0,0 +1,164 @@ +import { Page, Response, Request } from '@playwright/test'; +import * as prom from 'prom-client'; + +/** + * Records and tracks network request body sizes. + * + * You must call listen() before page.goto() for accurate results - this ensures all responses + * for a page are tracked. + */ +export class RequestsRecorder { + #page: Page; + + #documentUrl: string | undefined; + + #requestsInFlight = 0; + + #currentRequests: Set = new Set(); + + #inflatedSizeBytesCounter: prom.Counter<'type' | 'host_type'>; + #transferSizeBytesCounter: prom.Counter<'type' | 'host_type'>; + #requestCountCounter: prom.Counter<'type' | 'host_type'>; + #resolve?: () => void; + + constructor(page: Page) { + this.#page = page; + + this.#inflatedSizeBytesCounter = new prom.Counter({ + name: 'fe_perf_inflated_size_bytes', + help: 'The size of the inflated response body in bytes', + labelNames: ['type', 'host_type'], + registers: [], + }); + + this.#transferSizeBytesCounter = new prom.Counter({ + name: 'fe_perf_transfer_size_bytes', + help: 'The size of the transfered response body in bytes', + labelNames: ['type', 'host_type'], + registers: [], + }); + + this.#requestCountCounter = new prom.Counter({ + name: 'fe_perf_request_count', + help: 'The number of requests made', + labelNames: ['type', 'host_type'], + registers: [], + }); + } + + listen() { + const handler = this.#handleResponse.bind(this); + const reqHandler = this.#handleRequest.bind(this); + this.#page.on('request', reqHandler); + this.#page.on('response', handler); + + return () => { + this.#page.off('response', handler); + this.#page.off('request', reqHandler); + + if (this.#requestsInFlight === 0) { + return Promise.resolve(); + } + + console.log('waiting for', this.#requestsInFlight, 'requests to finish'); + + return new Promise((resolve) => { + this.#resolve = resolve; + }); + }; + } + + async #handleRequest(request: Request) { + const type = request.resourceType(); + + // Once we've recieved a document response, create a list of requests that we'll count the responses of. + // We also want to count the document request itself. + if (this.#documentUrl || type === 'document') { + this.#currentRequests.add(request); + } + } + + /* + * The Playwright page object has have multiple page loads, and responses for one page may come in after the page + * has been navigated away from. Attempting to get the body of these responses will results in an error, but is also + * not what we want to track. + * + * To solve this, we wait for the 'document' response to come in (a new page has loaded) and then keep track of all + * future requests. We only record responses for requests that were made after the document response. + */ + async #handleResponse(response: Response) { + const request = response.request(); + + const url = response.url(); + const type = response.request().resourceType(); + + // Record when a document response comes in so we can keep track of future requests + if (type === 'document') { + if (this.#documentUrl) { + console.warn('recieved additional document response', url); + } + + this.#documentUrl = url; + } + + // Disregard responses that for requests that were initiated before the current page + if (!this.#currentRequests.has(request)) { + return; + } + + this.#requestsInFlight += 1; + const hostType = getHostType(response.url(), this.#documentUrl ?? ''); + + // Attempting to get the body of an empty response results in an error, so guess if the response will be empty or not + const statusCode = response.status(); + const noBodyStatusCode = statusCode <= 199 || statusCode === 204 || statusCode === 205 || statusCode === 304; + + if (!noBodyStatusCode) { + const body = await response.body(); + this.#inflatedSizeBytesCounter.inc({ type, host_type: hostType }, body.length); + } + + const sizes = await response.request().sizes(); + + this.#transferSizeBytesCounter.inc( + { type, host_type: hostType }, + sizes.responseBodySize + sizes.responseHeadersSize + ); + this.#requestCountCounter.inc({ type, host_type: hostType }); + + this.#requestsInFlight -= 1; + + if (this.#requestsInFlight === 0 && this.#resolve) { + this.#resolve(); + } + } + + getMetrics() { + return [this.#inflatedSizeBytesCounter, this.#transferSizeBytesCounter, this.#requestCountCounter]; + } +} + +/** + * Instead of setting the request host as a label, which may have too high cardinality, we categorise + * the host into a limited set of types. + */ +function getHostType(requestUrl: string, documentUrl: string) { + const url = new URL(requestUrl); + const hostname = url.hostname; // FYI `hostname` doesn't include port, `host` does + + const documentHost = new URL(documentUrl).hostname; + + if (hostname.match(/^grafana-assets\.grafana(-\w+)?\.net$/)) { + return 'assets_cdn'; + } + + if (hostname.match(/^plugins-cdn\.grafana(-\w+)?\.net$/)) { + return 'plugin_cdn'; + } + + if (hostname === documentHost) { + return 'self'; + } + + return 'other'; +} diff --git a/e2e-playwright/various-suite/perf-test.spec.ts b/e2e-playwright/various-suite/perf-test.spec.ts new file mode 100644 index 00000000000..57e93d27f10 --- /dev/null +++ b/e2e-playwright/various-suite/perf-test.spec.ts @@ -0,0 +1,58 @@ +import fs from 'fs'; +import * as prom from 'prom-client'; + +import { test, expect } from '@grafana/plugin-e2e'; + +import { RequestsRecorder } from '../utils/RequestsRecorder'; + +const DASH_PATH = '/d/bds35fot3cv7kb/mostly-blank-dashboard'; + +test('payload-size', { tag: '@performance' }, async ({ page }) => { + const promRegistry = new prom.Registry(); + + const testRunTimeGauge = new prom.Gauge({ + name: 'fe_perf_test_run_time_seconds', + help: 'The time it took for the performance test to run', + registers: [promRegistry], + }); + + const usedJSHeapSizeGauge = new prom.Gauge({ + name: 'fe_perf_used_js_heap_size_bytes', + help: 'The amount of memory used by the JavaScript heap', + registers: [promRegistry], + }); + + const recorder = new RequestsRecorder(page); + const stopListening = recorder.listen(); + + let start = performance.now(); + + await page.goto(DASH_PATH); + + let el = page.getByTestId('data-testid header-container'); + await el.waitFor(); + await expect(el).toBeVisible(); + + let end = performance.now(); + + let client = await page.context().newCDPSession(page); + await client.send('HeapProfiler.collectGarbage'); + let usedJSHeapSize = (await client.send('Runtime.getHeapUsage')).usedSize; + + const responseMetrics = recorder.getMetrics(); + for (const metric of responseMetrics) { + promRegistry.registerMetric(metric); + } + + testRunTimeGauge.set(Math.round(end - start) / 1000); + usedJSHeapSizeGauge.set(+usedJSHeapSize.toFixed(1)); + + const instance = new URL(process.env.GRAFANA_URL || 'http://undefined').host; + promRegistry.setDefaultLabels({ instance }); + const metricsText = await promRegistry.metrics(); + console.log(metricsText); + fs.writeFileSync(process.env.METRICS_OUTPUT_PATH || '/tmp/asset-metrics.txt', metricsText); + + await stopListening(); + page.close(); +}); diff --git a/e2e-playwright/various-suite/solo-route.spec.ts b/e2e-playwright/various-suite/solo-route.spec.ts index 63dd4e6439d..7c09bce8da3 100644 --- a/e2e-playwright/various-suite/solo-route.spec.ts +++ b/e2e-playwright/various-suite/solo-route.spec.ts @@ -35,12 +35,12 @@ test.describe( test('Can view solo repeated panel in scenes', async ({ page, selectors }) => { // open Panel Tests - Graph NG const soloPanelUrl = selectors.pages.SoloPanel.url( - 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true' + 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=A$panel-2&__feature.dashboardSceneSolo=true' ); await page.goto(soloPanelUrl); // Check that the panel title exists - const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('server=B')); + const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('server=A')); await expect(panelTitle).toBeVisible(); // Check that uplot-main-div does not exist @@ -51,7 +51,7 @@ test.describe( test('Can view solo in repeated row and panel in scenes', async ({ page, selectors }) => { // open Panel Tests - Graph NG const soloPanelUrl = selectors.pages.SoloPanel.url( - 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-16-clone-1/grid-item-2/panel-2-clone-1&__feature.dashboardSceneSolo=true' + 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=B$2$panel-2&__feature.dashboardSceneSolo=true' ); await page.goto(soloPanelUrl); diff --git a/e2e/old-arch/various-suite/select-focus.spec.ts b/e2e/old-arch/various-suite/select-focus.spec.ts deleted file mode 100644 index 5ff66306b35..00000000000 --- a/e2e/old-arch/various-suite/select-focus.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { e2e } from '../utils'; - -describe('Select focus/unfocus tests', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it.skip('Tests select focus/unfocus scenarios', () => { - e2e.flows.openDashboard({ uid: '5SdHCadmz' }); - e2e.components.PageToolbar.item('Dashboard settings').click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('be.visible').click(); - }); - - e2e.components.Select.option().should('be.visible').first().click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('exist').should('have.focus'); - }); - - e2e.pages.Dashboard.Settings.General.title().click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('exist').should('not.have.focus'); - }); - }); -}); diff --git a/e2e/old-arch/various-suite/solo-route.spec.ts b/e2e/old-arch/various-suite/solo-route.spec.ts index 2abd2717d5b..df026f66033 100644 --- a/e2e/old-arch/various-suite/solo-route.spec.ts +++ b/e2e/old-arch/various-suite/solo-route.spec.ts @@ -25,17 +25,17 @@ describe('Solo Route', () => { it('Can view solo repeated panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( - 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true' + 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=A$panel-2&__feature.dashboardSceneSolo=true' ); - e2e.components.Panels.Panel.title('server=B').should('exist'); + e2e.components.Panels.Panel.title('server=A').should('exist'); cy.contains('uplot-main-div').should('not.exist'); }); it('Can view solo in repeated row and panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( - 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-16-clone-1/grid-item-2/panel-2-clone-1&__feature.dashboardSceneSolo=true' + 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=B$2$panel-2&__feature.dashboardSceneSolo=true' ); e2e.components.Panels.Panel.title('server = B, pod = Rob').should('exist'); diff --git a/e2e/various-suite/bar-gauge.spec.ts b/e2e/various-suite/bar-gauge.spec.ts deleted file mode 100644 index 7c28c48a146..00000000000 --- a/e2e/various-suite/bar-gauge.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { selectors } from '@grafana/e2e-selectors'; - -import { e2e } from '../utils'; - -describe('Bar Gauge Panel', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Bar Gauge rendering e2e tests', () => { - // open Panel Tests - Bar Gauge - e2e.flows.openDashboard({ uid: 'O6f11TZWk' }); - - cy.get( - `[data-viz-panel-key="panel-6"] [data-testid^="${selectors.components.Panels.Visualization.BarGauge.valueV2}"]` - ) - .should('have.css', 'color', 'rgb(242, 73, 92)') - .contains('100'); - }); -}); diff --git a/e2e/various-suite/bookmarks.spec.ts b/e2e/various-suite/bookmarks.spec.ts deleted file mode 100644 index 95781d1e6e2..00000000000 --- a/e2e/various-suite/bookmarks.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { e2e } from '../utils'; -import { fromBaseUrl } from '../utils/support/url'; - -describe('Pin nav items', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - afterEach(() => { - e2e.flows.setDefaultUserPreferences(); - }); - - it('should pin the selected menu item and add it as a Bookmarks menu item child', () => { - cy.visit(fromBaseUrl('/'), { - onBeforeLoad: (win) => { - win.localStorage.setItem('grafana.navigation.docked', 'true'); // Make sure the menu is docked - }, - }); - - e2e.components.NavMenu.Menu() - .should('be.visible') - .within(() => { - cy.get('ul[aria-label="Navigation"]').should('be.visible').as('navList'); - - // Check if the Bookmark section is visible - cy.get('@navList').children().eq(1).should('be.visible').as('bookmarksItem'); - cy.get('@bookmarksItem').should('contain.text', 'Bookmarks'); - - // Check if the Adminstration section is visible - cy.get('@navList').children().last().should('be.visible').as('adminItem'); - cy.get('@adminItem').should('contain.text', 'Administration'); - cy.get('@adminItem').within(() => { - cy.get('button[aria-label="Add to Bookmarks"]').should('exist').click({ force: true }); - }); - - // Check if the Administration menu item is visible in the Bookmarks section - cy.get('@bookmarksItem').within(() => { - // Expand the Bookmarks section - cy.get('button[aria-label="Expand section: Bookmarks"]').should('exist').click({ force: true }); - cy.get('a').should('contain.text', 'Administration').should('be.visible'); - }); - }); - }); - - it('should unpin the item and remove it from the Bookmarks section', () => { - // Set Administration as a pinned item and reload the page - e2e.flows.setUserPreferences({ navbar: { bookmarkUrls: ['/admin'] } }); - - cy.visit(fromBaseUrl('/'), { - onBeforeLoad: (win) => { - win.localStorage.setItem('grafana.navigation.docked', 'true'); // Make sure the menu is docked - }, - }); - - e2e.components.NavMenu.Menu() - .should('be.visible') - .within(() => { - cy.get('ul[aria-label="Navigation"]').should('be.visible').as('navList'); - - // Check if the Bookmark section is visible - cy.get('@navList').children().eq(1).should('be.visible').as('bookmarksItem'); - cy.get('@bookmarksItem').should('contain.text', 'Bookmarks'); - cy.get('@bookmarksItem').within(() => { - // Expand the Bookmarks section - cy.get('button[aria-label="Expand section: Bookmarks"]').should('exist').click({ force: true }); - cy.get('a').should('contain.text', 'Administration').should('be.visible'); - cy.get('button[aria-label="Remove from Bookmarks"]').should('exist').click({ force: true }); - }); - - cy.get('@bookmarksItem', { timeout: 60000 }).within(() => { - cy.get('a').should('have.length', 1).should('not.contain.text', 'Administration'); - }); - }); - }); -}); diff --git a/e2e/various-suite/exemplars.spec.ts b/e2e/various-suite/exemplars.spec.ts deleted file mode 100644 index 675b83039ba..00000000000 --- a/e2e/various-suite/exemplars.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { e2e } from '../utils'; -import { waitForMonacoToLoad } from '../utils/support/monaco'; - -const dataSourceName = 'PromExemplar'; -const addDataSource = () => { - e2e.flows.addDataSource({ - type: 'Prometheus', - expectedAlertMessage: 'Prometheus', - name: dataSourceName, - form: () => { - e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click(); - e2e.components.DataSource.Prometheus.configPage.internalLinkSwitch().check({ force: true }); - e2e.components.DataSource.Prometheus.configPage.connectionSettings().type('http://prom-url:9090'); - cy.get('[data-testid="data-testid Data source picker select container"]').click(); - - cy.contains('gdev-tempo').scrollIntoView().should('be.visible').click(); - }, - }); -}; -// Skipping due to race conditions with same old arch test e2e/various-suite/exemplars.spec.ts -describe.skip('Exemplars', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - cy.request({ - url: `${Cypress.env('BASE_URL')}/api/datasources/name/${dataSourceName}`, - failOnStatusCode: false, - }).then((response) => { - if (response.isOkStatusCode) { - return; - } - addDataSource(); - }); - }); - - it('should be able to navigate to configured data source', () => { - cy.intercept( - { - pathname: '/api/ds/query', - }, - (req) => { - const datasourceType = req.body.queries[0].datasource.type; - if (datasourceType === 'prometheus') { - req.reply({ fixture: 'exemplars-query-response.json' }); - } else if (datasourceType === 'tempo') { - req.reply({ fixture: 'tempo-response.json' }); - } else { - req.reply({}); - } - } - ); - - e2e.pages.Explore.visit(); - - e2e.components.DataSourcePicker.container().should('be.visible').click(); - cy.contains(dataSourceName).scrollIntoView().should('be.visible').click(); - - // Switch to code editor - e2e.components.RadioButton.container().filter(':contains("Code")').click(); - - // Wait for lazy loading Monaco - waitForMonacoToLoad(); - - e2e.components.TimePicker.openButton().click(); - e2e.components.TimePicker.fromField().clear().type('2021-07-10 17:10:00'); - e2e.components.TimePicker.toField().clear().type('2021-07-10 17:30:00'); - e2e.components.TimePicker.applyTimeRange().click(); - e2e.components.QueryField.container().should('be.visible').type('exemplar-query_bucket{shift}{enter}'); - - cy.get(`[data-testid="time-series-zoom-to-data"]`).click(); - - e2e.components.DataSource.Prometheus.exemplarMarker().first().trigger('mousemove', { force: true }); - cy.contains('Query with gdev-tempo').click(); - e2e.components.TraceViewer.spanBar().should('have.length', 11); - }); -}); diff --git a/e2e/various-suite/explore.spec.ts b/e2e/various-suite/explore.spec.ts deleted file mode 100644 index 69d74c29e98..00000000000 --- a/e2e/various-suite/explore.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { e2e } from '../utils'; - -describe('Explore', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Basic path through Explore.', () => { - e2e.pages.Explore.visit(); - e2e.pages.Explore.General.container().should('have.length', 1); - e2e.components.RefreshPicker.runButtonV2().should('have.length', 1); - - e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer() - .scrollIntoView() - .should('be.visible') - .within(() => { - cy.get('input[id*="test-data-scenario-select-"]').should('be.visible').click(); - }); - - cy.contains('CSV Metric Values').scrollIntoView().should('be.visible').click(); - }); -}); diff --git a/e2e/various-suite/filter-annotations.spec.ts b/e2e/various-suite/filter-annotations.spec.ts deleted file mode 100644 index 35319ee1beb..00000000000 --- a/e2e/various-suite/filter-annotations.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { e2e } from '../utils'; -const DASHBOARD_ID = 'ed155665'; - -// Skipping due to race conditions with same old arch test e2e/various-suite/filter-annotations.spec.ts -describe.skip('Annotations filtering', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Tests switching filter type updates the UI accordingly', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID }); - - e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click(); - e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click(); - - e2e.components.Tab.title('Annotations').click(); - cy.contains('New query').click(); - e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type('Red - Panel two'); - - e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel() - .should('be.visible') - .within(() => { - // All panels - e2e.components.Annotations.annotationsTypeInput().find('input').type('All panels{enter}', { force: true }); - e2e.components.Annotations.annotationsChoosePanelInput().should('not.exist'); - - // All panels except - e2e.components.Annotations.annotationsTypeInput() - .find('input') - .type('All panels except{enter}', { force: true }); - e2e.components.Annotations.annotationsChoosePanelInput().should('be.visible'); - - // Selected panels - e2e.components.Annotations.annotationsTypeInput().find('input').type('Selected panels{enter}', { force: true }); - e2e.components.Annotations.annotationsChoosePanelInput() - .should('be.visible') - .find('input') - .type('Panel two{enter}', { force: true }); - }); - - cy.get('body').click(); - - e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click(); - - e2e.pages.Dashboard.Controls() - .should('be.visible') - .within(() => { - e2e.pages.Dashboard.SubMenu.submenuItemLabels('Red - Panel two') - .should('be.visible') - .parent() - .within((el) => { - cy.get('input') - .should('be.checked') - .uncheck({ force: true }) - .should('not.be.checked') - .check({ force: true }); - }); - - e2e.pages.Dashboard.SubMenu.submenuItemLabels('Red, only panel 1') - .should('be.visible') - .parent() - .within((el) => { - cy.get('input').should('be.checked'); - }); - }); - - e2e.components.Panels.Panel.title('Panel one') - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist').should('have.length', 4); - }); - }); -}); diff --git a/e2e/various-suite/frontend-sandbox-app.spec.ts b/e2e/various-suite/frontend-sandbox-app.spec.ts deleted file mode 100644 index b3e17ece6a9..00000000000 --- a/e2e/various-suite/frontend-sandbox-app.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { e2e } from '../utils'; - -const APP_ID = 'sandbox-app-test'; - -describe('Datasource sandbox', () => { - before(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); - cy.request({ - url: `${Cypress.env('BASE_URL')}/api/plugins/${APP_ID}/settings`, - method: 'POST', - body: { - enabled: true, - }, - }); - }); - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'), true); - }); - - describe('App Page', () => { - describe('Sandbox disabled', () => { - beforeEach(() => { - cy.window().then((win) => { - win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0'); - }); - }); - - it('Loads the app page without the sandbox div wrapper', () => { - cy.visit(`/a/${APP_ID}`); - cy.wait(200); // wait to prevent false positives because cypress checks too fast - cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist'); - cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist'); - }); - - it('Loads the app configuration without the sandbox div wrapper', () => { - cy.visit(`/plugins/${APP_ID}`); - cy.wait(200); // wait to prevent false positives because cypress checks too fast - cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('not.exist'); - cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist'); - }); - }); - - describe('Sandbox enabled', () => { - beforeEach(() => { - cy.window().then((win) => { - win.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1'); - }); - }); - - it('Loads the app page with the sandbox div wrapper', () => { - cy.visit(`/a/${APP_ID}`); - cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist'); - cy.get('div[data-testid="sandbox-app-test-page-one"]').should('exist'); - }); - - it('Loads the app configuration with the sandbox div wrapper', () => { - cy.visit(`/plugins/${APP_ID}`); - cy.get('div[data-plugin-sandbox="sandbox-app-test"]').should('exist'); - cy.get('div[data-testid="sandbox-app-test-config-page"]').should('exist'); - }); - }); - }); - - afterEach(() => { - e2e.flows.revertAllChanges(); - }); - - after(() => { - cy.clearCookies(); - }); -}); diff --git a/e2e/various-suite/gauge.spec.ts b/e2e/various-suite/gauge.spec.ts deleted file mode 100644 index 64e4ce14b61..00000000000 --- a/e2e/various-suite/gauge.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { e2e } from '../utils'; - -describe('Gauge Panel', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Gauge rendering e2e tests', () => { - // open Panel Tests - Gauge - e2e.flows.openDashboard({ uid: '_5rDmaQiz' }); - - // check that gauges are rendered - cy.get('body').find(`.flot-base`).should('have.length', 16); - - // check that no panel errors exist - e2e.components.Panels.Panel.headerCornerInfo('error').should('not.exist'); - }); -}); diff --git a/e2e/various-suite/graph-auto-migrate.spec.ts b/e2e/various-suite/graph-auto-migrate.spec.ts deleted file mode 100644 index cddf07d0660..00000000000 --- a/e2e/various-suite/graph-auto-migrate.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { e2e } from '../utils'; - -const DASHBOARD_ID = 'XMjIZPmik'; -const DASHBOARD_NAME = 'Panel Tests - Graph Time Regions'; -const UPLOT_MAIN_DIV_SELECTOR = '[data-testid="uplot-main-div"]'; - -describe('Auto-migrate graph panel', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Graph panel is auto-migrated', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID }); - cy.contains(DASHBOARD_NAME).should('be.visible'); - cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist'); - - e2e.flows.openDashboard({ uid: DASHBOARD_ID }); - - cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist'); - }); - - it('Annotation markers exist for time regions', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID }); - cy.contains(DASHBOARD_NAME).should('be.visible'); - cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist'); - - e2e.flows.openDashboard({ uid: DASHBOARD_ID }); - - e2e.components.Panels.Panel.title('Business Hours') - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist'); - }); - - e2e.components.Panels.Panel.title("Sunday's 20-23") - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist'); - }); - - e2e.components.Panels.Panel.title('Each day of week') - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist'); - }); - - cy.scrollTo('bottom'); - - e2e.components.Panels.Panel.title('05:00') - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist'); - }); - - e2e.components.Panels.Panel.title('From 22:00 to 00:30 (crossing midnight)') - .should('exist') - .within(() => { - e2e.pages.Dashboard.Annotations.marker().should('exist'); - }); - }); -}); diff --git a/e2e/various-suite/inspect-drawer.spec.ts b/e2e/various-suite/inspect-drawer.spec.ts deleted file mode 100644 index 48746dc7a9f..00000000000 --- a/e2e/various-suite/inspect-drawer.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { e2e } from '../utils'; - -const PANEL_UNDER_TEST = 'Value reducers 1'; - -describe('Inspect drawer tests', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Tests various Inspect Drawer scenarios', () => { - // @ts-ignore some typing issue - cy.on('uncaught:exception', (err) => { - if (err.stack?.indexOf("TypeError: Cannot read property 'getText' of null") !== -1) { - // On occasion monaco editor will not have the time to be properly unloaded when we change the tab - // and then the e2e test fails with the uncaught:exception: - // TypeError: Cannot read property 'getText' of null - // at Object.ai [as getFoldingRanges] (http://localhost:3001/public/build/monaco-json.worker.js:2:215257) - // at e.getFoldingRanges (http://localhost:3001/public/build/monaco-json.worker.js:2:221188) - // at e.fmr (http://localhost:3001/public/build/monaco-json.worker.js:2:116605) - // at e._handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7414) - // at Object.handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7018) - // at e._handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:5038) - // at e.handleMessage (http://localhost:3001/public/build/monaco-json.worker.js:2:4606) - // at e.onmessage (http://localhost:3001/public/build/monaco-json.worker.js:2:7097) - // at Tt.self.onmessage (http://localhost:3001/public/build/monaco-json.worker.js:2:117109) - - // return false to prevent the error from - // failing this test - return false; - } - - return true; - }); - - e2e.flows.openDashboard({ uid: 'wfTJJL5Wz' }); - - e2e.components.Panels.Panel.title(PANEL_UNDER_TEST).scrollIntoView().should('be.visible'); - e2e.components.Panels.Panel.menu(PANEL_UNDER_TEST).click({ force: true }); // force click because menu is hidden and show on hover - e2e.components.Panels.Panel.menuItems('Inspect').trigger('mouseover', { force: true }); - e2e.components.Panels.Panel.menuItems('Data').click({ force: true }); - - expectDrawerTabsAndContent(); - - expectDrawerClose(); - - expectSubMenuScenario('Data'); - expectSubMenuScenario('Query'); - expectSubMenuScenario('Panel JSON', 'JSON'); - - e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST); - - e2e.components.QueryTab.queryInspectorButton().should('be.visible').click(); - - e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`) - .should('be.visible') - .within(() => { - e2e.components.Tab.title('Query').should('be.visible'); - // query should be the active tab - e2e.components.Tab.active().should('have.text', 'Query'); - }); - - e2e.components.PanelInspector.Query.content().should('be.visible'); - }); -}); - -const expectDrawerTabsAndContent = () => { - e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`) - .should('be.visible') - .within(() => { - e2e.components.Tab.title('Data').should('be.visible'); - // data should be the active tab - e2e.components.Tab.active().within((li: JQuery) => { - expect(li.text()).equals('Data'); - }); - e2e.components.PanelInspector.Data.content().should('be.visible'); - e2e.components.PanelInspector.Stats.content().should('not.exist'); - e2e.components.PanelInspector.Json.content().should('not.exist'); - e2e.components.PanelInspector.Query.content().should('not.exist'); - - // other tabs should also be visible, click on each to see if we get any console errors - e2e.components.Tab.title('Stats').should('be.visible').click(); - e2e.components.PanelInspector.Stats.content().should('be.visible'); - e2e.components.PanelInspector.Data.content().should('not.exist'); - e2e.components.PanelInspector.Json.content().should('not.exist'); - e2e.components.PanelInspector.Query.content().should('not.exist'); - - e2e.components.Tab.title('JSON').should('be.visible').click(); - e2e.components.PanelInspector.Json.content().should('be.visible'); - e2e.components.PanelInspector.Data.content().should('not.exist'); - e2e.components.PanelInspector.Stats.content().should('not.exist'); - e2e.components.PanelInspector.Query.content().should('not.exist'); - - e2e.components.Tab.title('Query').should('be.visible').click(); - - e2e.components.PanelInspector.Query.content().should('be.visible'); - e2e.components.PanelInspector.Data.content().should('not.exist'); - e2e.components.PanelInspector.Stats.content().should('not.exist'); - e2e.components.PanelInspector.Json.content().should('not.exist'); - }); -}; - -const expectDrawerClose = () => { - // close using close button - e2e.components.Drawer.General.close().click(); - e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`).should('not.exist'); -}; - -const expectSubMenuScenario = (subMenu: string, tabTitle?: string) => { - tabTitle = tabTitle ?? subMenu; - // testing opening inspect drawer from sub menus under Inspect in header menu - e2e.components.Panels.Panel.title(PANEL_UNDER_TEST).scrollIntoView().should('be.visible'); - e2e.components.Panels.Panel.menu(PANEL_UNDER_TEST).click({ force: true }); // force click because menu is hidden and show on hover - // sub menus are in the DOM but not visible and because there is no hover support in Cypress force click - // https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/testing-dom__hover-hidden-elements/cypress/integration/hover-hidden-elements-spec.js - - // simulate hover on Inspector menu item to display sub menus - e2e.components.Panels.Panel.menuItems('Inspect').trigger('mouseover', { force: true }); - e2e.components.Panels.Panel.menuItems(subMenu).click({ force: true }); - - // data should be the default tab - e2e.components.Tab.title(tabTitle).should('be.visible'); - e2e.components.Tab.active().should('have.text', tabTitle); - - expectDrawerClose(); -}; diff --git a/e2e/various-suite/keybinds.spec.ts b/e2e/various-suite/keybinds.spec.ts deleted file mode 100644 index c3a8b71359d..00000000000 --- a/e2e/various-suite/keybinds.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { e2e } from '../utils'; -import { fromBaseUrl } from '../utils/support/url'; - -// Skipping due to race conditions with same old arch test e2e/various-suite/keybinds.spec.ts -describe.skip('Keyboard shortcuts', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - cy.visit(fromBaseUrl('/')); - - // wait for the page to load - e2e.components.Panels.Panel.title('Latest from the blog').should('be.visible'); - }); - - it('sequence shortcuts should work', () => { - cy.get('body').type('ge'); - e2e.pages.Explore.General.container().should('be.visible'); - - cy.get('body').type('gp'); - e2e.components.UserProfile.preferencesSaveButton().should('be.visible'); - - cy.get('body').type('gh'); - e2e.components.Panels.Panel.title('Latest from the blog').should('be.visible'); - }); - - it('ctrl+z should zoom out the time range', () => { - cy.get('body').type('ge'); - e2e.pages.Explore.General.container().should('be.visible'); - - // Time range is 1 minute, so each shortcut press should jump back or forward by 1 minute - e2e.flows.setTimeRange({ - from: '2024-06-05 10:05:00', - to: '2024-06-05 10:06:00', - zone: 'Browser', - }); - e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query'); - - cy.get('body').type('{ctrl}z'); - e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query'); - let expectedRange = `Time range selected: 2024-06-05 10:03:30 to 2024-06-05 10:07:30`; - e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange); - }); - - it('time range shortcuts should work', () => { - cy.get('body').type('ge'); - e2e.pages.Explore.General.container().should('be.visible'); - - // Time range is 1 minute, so each shortcut press should jump back or forward by 1 minute - e2e.flows.setTimeRange({ - from: '2024-06-05 10:05:00', - to: '2024-06-05 10:06:00', - zone: 'Browser', - }); - e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query'); - let expectedRange = `Time range selected: 2024-06-05 10:05:00 to 2024-06-05 10:06:00`; - e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange); - - cy.log('Trying one shift-left'); - cy.get('body').type('t{leftarrow}'); - e2e.components.RefreshPicker.runButtonV2().should('have.text', 'Run query'); - expectedRange = `Time range selected: 2024-06-05 10:04:00 to 2024-06-05 10:05:00`; // 1 min back - e2e.components.TimePicker.openButton().should('have.attr', 'aria-label', expectedRange); - }); -}); diff --git a/e2e/various-suite/loki-query-builder.spec.ts b/e2e/various-suite/loki-query-builder.spec.ts deleted file mode 100644 index 45b527fca5b..00000000000 --- a/e2e/various-suite/loki-query-builder.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { e2e } from '../utils'; - -const MISSING_LABEL_FILTER_ERROR_MESSAGE = 'Select at least 1 label filter (label and value)'; -const dataSourceName = 'LokiBuilder'; -const addDataSource = () => { - e2e.flows.addDataSource({ - type: 'Loki', - expectedAlertMessage: 'Unable to connect with Loki. Please check the server logs for more details.', - name: dataSourceName, - form: () => { - cy.get('#connection-url').type('http://loki-url:3100'); - }, - }); -}; - -const finalQuery = 'rate({instance=~"instance1|instance2"} | logfmt | __error__=`` [$__auto]'; - -describe('Loki query builder', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - cy.request({ - url: `${Cypress.env('BASE_URL')}/api/datasources/name/${dataSourceName}`, - failOnStatusCode: false, - }).then((response) => { - if (response.isOkStatusCode) { - return; - } - addDataSource(); - }); - }); - - it('should be able to use all modes', () => { - cy.intercept(/labels\?/, (req) => { - req.reply({ status: 'success', data: ['instance', 'job', 'source'] }); - }).as('labelsRequest'); - - cy.intercept(/series?/, (req) => { - req.reply({ status: 'success', data: [{ instance: 'instance1' }] }); - }); - - cy.intercept(/values/, (req) => { - req.reply({ status: 'success', data: ['instance1', 'instance2'] }); - }).as('valuesRequest'); - - cy.intercept(/index\/stats/, (req) => { - req.reply({ streams: 2, chunks: 2660, bytes: 2721792, entries: 14408 }); - }); - - // Go to Explore and choose Loki data source - e2e.pages.Explore.visit(); - e2e.components.DataSourcePicker.container().should('be.visible').click(); - cy.contains(dataSourceName).scrollIntoView().should('be.visible').click(); - - // Start in builder mode, click and choose query pattern - e2e.components.QueryBuilder.queryPatterns().click(); - cy.contains('Log query starters').click(); - cy.contains('Use this query').click(); - cy.contains('No pipeline errors').should('be.visible'); - cy.contains('Logfmt').should('be.visible'); - cy.contains('{} | logfmt | __error__=``').should('be.visible'); - - // Add operation - cy.contains('Operations').should('be.visible').click(); - cy.contains('Range functions').should('be.visible').click(); - cy.contains('Rate').should('be.visible').click(); - cy.contains('rate({} | logfmt | __error__=`` [$__auto]').should('be.visible'); - - // Check for expected error - cy.contains(MISSING_LABEL_FILTER_ERROR_MESSAGE).should('be.visible'); - - // Add labels to remove error - e2e.components.QueryBuilder.labelSelect().should('be.visible').click(); - // wait until labels are loaded and set on the component before starting to type - e2e.components.QueryBuilder.inputSelect().type('i'); - cy.wait('@labelsRequest'); - e2e.components.QueryBuilder.inputSelect().type('nstance{enter}'); - e2e.components.QueryBuilder.matchOperatorSelect() - .should('be.visible') - .click({ force: true }) - .children('div') - .children('input') - .type('=~{enter}', { force: true }); - e2e.components.QueryBuilder.valueSelect().should('be.visible').click(); - e2e.components.QueryBuilder.valueSelect().children('div').children('input').type('instance1{enter}'); - cy.wait('@valuesRequest'); - e2e.components.QueryBuilder.valueSelect().children('div').children('input').type('instance2{enter}'); - - cy.contains(MISSING_LABEL_FILTER_ERROR_MESSAGE).should('not.exist'); - cy.contains(finalQuery).should('be.visible'); - - // Change to code editor - e2e.components.RadioButton.container().filter(':contains("Code")').click(); - - // We need to test this manually because the final query is split into separate DOM elements using cy.contains(finalQuery).should('be.visible'); does not detect the query. - cy.contains('rate').should('be.visible'); - cy.contains('instance1|instance2').should('be.visible'); - cy.contains('logfmt').should('be.visible'); - cy.contains('__error__').should('be.visible'); - cy.contains('$__auto').should('be.visible'); - - // Checks the explain mode toggle - cy.contains('label', 'Explain').click(); - cy.contains('Fetch all log lines matching label filters.').should('be.visible'); - }); -}); diff --git a/e2e/various-suite/loki-table-explore-to-dash.spec.ts b/e2e/various-suite/loki-table-explore-to-dash.spec.ts deleted file mode 100644 index 6de67e4199e..00000000000 --- a/e2e/various-suite/loki-table-explore-to-dash.spec.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { e2e } from '../utils'; - -const dataSourceName = 'LokiEditor'; -const addDataSource = () => { - e2e.flows.addDataSource({ - type: 'Loki', - expectedAlertMessage: 'Unable to connect with Loki. Please check the server logs for more details.', - name: dataSourceName, - form: () => { - cy.get('#connection-url').type('http://loki-url:3100'); - }, - }); -}; - -const lokiQueryResult = { - status: 'success', - results: { - A: { - status: 200, - frames: [ - { - schema: { - refId: 'A', - meta: { - typeVersion: [0, 0], - custom: { - frameType: 'LabeledTimeValues', - }, - stats: [ - { - displayName: 'Summary: bytes processed per second', - unit: 'Bps', - value: 223921, - }, - { - displayName: 'Summary: total bytes processed', - unit: 'decbytes', - value: 4156, - }, - { - displayName: 'Summary: exec time', - unit: 's', - value: 0.01856, - }, - ], - executedQueryString: 'Expr: {targetLabelName="targetLabelValue"}', - }, - fields: [ - { - name: 'labels', - type: 'other', - typeInfo: { - frame: 'json.RawMessage', - }, - }, - { - name: 'Time', - type: 'time', - typeInfo: { - frame: 'time.Time', - }, - }, - { - name: 'Line', - type: 'string', - typeInfo: { - frame: 'string', - }, - }, - { - name: 'tsNs', - type: 'string', - typeInfo: { - frame: 'string', - }, - }, - { - name: 'id', - type: 'string', - typeInfo: { - frame: 'string', - }, - }, - ], - }, - data: { - values: [ - [ - { - targetLabelName: 'targetLabelValue', - instance: 'server\\1', - job: '"grafana/data"', - nonIndexed: 'value', - place: 'moon', - re: 'one.two$three^four', - source: 'data', - }, - ], - [1700077283237], - [ - '{"_entry":"log text with ANSI \\u001b[31mpart of the text\\u001b[0m [149702545]","counter":"22292","float":"NaN","wave":-0.5877852522916832,"label":"val3","level":"info"}', - ], - ['1700077283237000000'], - ['1700077283237000000_9b025d35'], - ], - }, - }, - ], - }, - }, -}; - -describe.skip('Loki Query Editor', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - afterEach(() => { - e2e.flows.revertAllChanges(); - }); - - beforeEach(() => { - cy.setLocalStorage('grafana.featureToggles', 'logsExploreTableVisualisation=1'); - }); - it('Should be able to add explore table to dashboard', () => { - addDataSource(); - - cy.intercept(/labels?/, (req) => { - req.reply({ status: 'success', data: ['instance', 'job', 'source'] }); - }); - - cy.intercept(/series?/, (req) => { - req.reply({ status: 'success', data: [{ instance: 'instance1' }] }); - }); - - cy.intercept(/\/api\/ds\/query\?ds_type=loki?/, (req) => { - req.reply(lokiQueryResult); - }); - - // Go to Explore and choose Loki data source - e2e.pages.Explore.visit(); - e2e.components.DataSourcePicker.container().should('be.visible').click(); - cy.contains(dataSourceName).scrollIntoView().should('be.visible').click(); - - cy.contains('Code').click({ force: true }); - - // Wait for lazy loading - // const monacoLoadingText = 'Loading...'; - - // e2e.components.QueryField.container().should('be.visible').should('have.text', monacoLoadingText); - e2e.components.QueryField.container() - .find('.view-overlays[role="presentation"]') - .get('.cdr') - .then(($el) => { - const win = $el[0].ownerDocument.defaultView; - const after = win.getComputedStyle($el[0], '::after'); - const content = after.getPropertyValue('content'); - expect(content).to.eq('"Enter a Loki query (run with Shift+Enter)"'); - }); - - // Write a simple query - e2e.components.QueryField.container().type('query').type('{instance="instance1"'); - cy.get('.monaco-editor textarea:first').should(($el) => { - expect($el.val()).to.eq('query{instance="instance1"}'); - }); - - // Submit the query - e2e.components.QueryField.container().type('{shift+enter}'); - // Assert the no-data message is not visible - cy.get('[data-testid="explore-no-data"]').should('not.exist'); - - // Click on the table toggle - cy.contains('Table').click({ force: true }); - - // One row with two cells - cy.get('[role="cell"]').should('have.length', 2); - - cy.contains('label', 'targetLabelName').scrollIntoView(); - cy.contains('label', 'targetLabelName').should('be.visible'); - cy.contains('label', 'targetLabelName').click(); - cy.contains('label', 'targetLabelName').within(() => { - cy.get('input[type="checkbox"]').check({ force: true }); - }); - - cy.contains('label', 'targetLabelName').within(() => { - cy.get('input[type="checkbox"]').should('be.checked'); - }); - - const exploreCells = cy.get('[role="cell"]'); - - // Now we should have a row with 3 columns - exploreCells.should('have.length', 3); - // And a value of "targetLabelValue" - exploreCells.should('contain', 'targetLabelValue'); - - const addToButton = cy.get('[aria-label="Add"]'); - addToButton.should('be.visible'); - addToButton.click(); - - const addToDashboardButton = cy.get('[aria-label="Add to dashboard"]'); - - // Now let's add this to a dashboard - addToDashboardButton.should('be.visible'); - addToDashboardButton.click(); - - const addPanelToDashboardButton = cy.contains('Add panel to dashboard'); - addPanelToDashboardButton.should('be.visible'); - - const openDashboardButton = cy.contains('Open dashboard'); - openDashboardButton.should('be.visible'); - openDashboardButton.click(); - - const panel = cy.get('[data-viz-panel-key="panel-1"]'); - panel.should('be.visible'); - - const cells = panel.find('[role="table"] [role="cell"]'); - // Should have 3 columns - cells.should('have.length', 3); - // Cells contain strings found in log line - cells.contains('"wave":-0.5877852522916832'); - - // column has correct value of "targetLabelValue", need to requery the DOM because of the .contains call above - cy.get('[data-viz-panel-key="panel-1"]').find('[role="table"] [role="cell"]').contains('targetLabelValue'); - }); -}); diff --git a/e2e/various-suite/navigation.spec.ts b/e2e/various-suite/navigation.spec.ts deleted file mode 100644 index b422bfcc74a..00000000000 --- a/e2e/various-suite/navigation.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { e2e } from '../utils'; -import { fromBaseUrl } from '../utils/support/url'; - -describe('Docked Navigation', () => { - beforeEach(() => { - // This is a breakpoint where the mega menu can be docked (and docked is the default state) - cy.viewport(1280, 800); - cy.clearAllLocalStorage(); - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - cy.visit(fromBaseUrl('/')); - }); - - it('should remain un-docked when reloading the page', () => { - // Undock the menu - cy.get('[aria-label="Undock menu"]').click(); - - e2e.components.NavMenu.Menu().should('not.exist'); - - cy.reload(); - e2e.components.NavMenu.Menu().should('not.exist'); - }); - - it('Can re-dock after undock', () => { - // Undock the menu - cy.get('[aria-label="Undock menu"]').click(); - cy.get('[aria-label="Open menu"]').click(); - cy.get('[aria-label="Dock menu"]').click(); - - e2e.components.NavMenu.Menu().should('be.visible'); - }); - - it('should remain in same state when navigating to another page', () => { - // Undock the menu - cy.get('[aria-label="Undock menu"]').click(); - - // Navigate - cy.get('[aria-label="Open menu"]').click(); - cy.contains('a', 'Administration').click(); - - // Still undocked - e2e.components.NavMenu.Menu().should('not.exist'); - - // dock the menu - cy.get('[aria-label="Open menu"]').click(); - cy.get('[aria-label="Dock menu"]').click(); - - // Navigate - cy.contains('a', 'Users').click(); - // Still docked - e2e.components.NavMenu.Menu().should('be.visible'); - }); - - it('should undock on smaller viewport sizes', () => { - cy.viewport(1120, 1080); - cy.reload(); - - e2e.components.NavMenu.Menu().should('not.exist'); - }); -}); diff --git a/e2e/various-suite/pie-chart.spec.ts b/e2e/various-suite/pie-chart.spec.ts deleted file mode 100644 index c6d537a40a2..00000000000 --- a/e2e/various-suite/pie-chart.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { selectors } from '@grafana/e2e-selectors'; - -import { e2e } from '../utils'; - -describe('Pie Chart Panel', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Pie Chart rendering e2e tests', () => { - // open Panel Tests - Pie Chart - e2e.flows.openDashboard({ uid: 'lVE-2YFMz' }); - - cy.get( - `[data-viz-panel-key="panel-11"] [data-testid^="${selectors.components.Panels.Visualization.PieChart.svgSlice}"]` - ).should('have.length', 5); - }); -}); diff --git a/e2e/various-suite/prometheus-annotations.spec.ts b/e2e/various-suite/prometheus-annotations.spec.ts deleted file mode 100644 index 135d08bcf9d..00000000000 --- a/e2e/various-suite/prometheus-annotations.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { e2e } from '../utils'; -import { addDashboard } from '../utils/flows'; - -import { createPromDS, getResources } from './helpers/prometheus-helpers'; - -const DATASOURCE_ID = 'Prometheus'; - -const DATASOURCE_NAME = 'aprometheusAnnotationDS'; - -/** - * Click dashboard settings and then the variables tab - * - */ -function navigateToAnnotations() { - e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click(); - e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click(); - e2e.components.Tab.title('Annotations').click(); -} - -function addPrometheusAnnotation(annotationName: string) { - e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click(); - getResources(); - e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type(annotationName); - e2e.components.DataSourcePicker.container().should('be.visible').click(); - cy.contains(DATASOURCE_NAME).scrollIntoView().should('be.visible').click(); -} - -describe('Prometheus annotations', () => { - beforeEach(() => { - createPromDS(DATASOURCE_ID, DATASOURCE_NAME); - }); - - it('should navigate to variable query editor', () => { - const annotationName = 'promAnnotation'; - addDashboard(); - navigateToAnnotations(); - addPrometheusAnnotation(annotationName); - - e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser - .openButton() - .contains('Metrics browser') - .click(); - - e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric().should('exist').type('met'); - - e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser - .metricList() - .should('exist') - .contains('metric1') - .click(); - - e2e.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.useQuery().should('exist').click(); - - e2e.components.DataSource.Prometheus.queryEditor.code.queryField().should('exist').contains('metric1'); - - // check for other parts of the annotations - // min step - e2e.components.DataSource.Prometheus.annotations.minStep().should('exist'); - - // title - e2e.components.DataSource.Prometheus.annotations.title().scrollIntoView().should('exist'); - // tags - e2e.components.DataSource.Prometheus.annotations.tags().scrollIntoView().should('exist'); - // text - e2e.components.DataSource.Prometheus.annotations.text().scrollIntoView().should('exist'); - // series value as timestamp - e2e.components.DataSource.Prometheus.annotations.seriesValueAsTimestamp().scrollIntoView().should('exist'); - - e2e.components.NavToolbar.editDashboard.backToDashboardButton().should('be.visible').click(); - - // check that annotation exists - cy.get('body').contains(annotationName); - }); -}); diff --git a/e2e/various-suite/query-editor.spec.ts b/e2e/various-suite/query-editor.spec.ts deleted file mode 100644 index f9149f7b280..00000000000 --- a/e2e/various-suite/query-editor.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { e2e } from '../utils'; -import { waitForMonacoToLoad } from '../utils/support/monaco'; - -describe('Query editor', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - // x-ing to bypass this flaky test. - // Will rewrite in plugin-e2e with this issue - xit('Undo should work in query editor for prometheus -- test CI.', () => { - e2e.pages.Explore.visit(); - e2e.components.DataSourcePicker.container().should('be.visible').click(); - - cy.contains('gdev-prometheus').scrollIntoView().should('be.visible').click(); - const queryText = `rate(http_requests_total{job="grafana"}[5m])`; - - e2e.components.RadioButton.container().filter(':contains("Code")').should('be.visible').click(); - - waitForMonacoToLoad(); - - e2e.components.QueryField.container().type(queryText, { parseSpecialCharSequences: false }).type('{backspace}'); - - cy.contains(queryText.slice(0, -1)).should('be.visible'); - - e2e.components.QueryField.container().type(e2e.typings.undo()); - - cy.contains(queryText).should('be.visible'); - - e2e.components.Alert.alertV2('error').should('not.be.visible'); - }); -}); diff --git a/e2e/various-suite/return-to-previous.spec.ts b/e2e/various-suite/return-to-previous.spec.ts deleted file mode 100644 index 395c233d76a..00000000000 --- a/e2e/various-suite/return-to-previous.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { e2e } from '../utils'; - -describe('ReturnToPrevious button', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - cy.visit('/alerting/list'); - e2e.components.AlertRules.groupToggle().first().click(); - e2e.components.AlertRules.toggle().click(); - cy.get('a[title="View"]').click(); - cy.url().as('alertRuleUrl'); - cy.get('a').contains('View panel').click(); - }); - - it('should appear when changing context and go back to alert rule when clicking "Back"', () => { - // check whether all elements of RTP are available - e2e.components.ReturnToPrevious.buttonGroup().should('be.visible'); - e2e.components.ReturnToPrevious.dismissButton().should('be.visible'); - e2e.components.ReturnToPrevious.backButton() - .find('span') - .contains('Back to e2e-ReturnToPrevious-test') - .should('be.visible') - .click(); - - // check whether the RTP button leads back to alert rule - cy.get('@alertRuleUrl').then((alertRuleUrl) => { - cy.url().should('eq', alertRuleUrl); - }); - }); - - it('should disappear when clicking "Dismiss"', () => { - e2e.components.ReturnToPrevious.dismissButton().should('be.visible').click(); - e2e.components.ReturnToPrevious.buttonGroup().should('not.exist'); - }); - - it('should not persist when going back to the alert rule details view', () => { - e2e.components.ReturnToPrevious.buttonGroup().should('be.visible'); - - cy.visit('/alerting/list'); - e2e.components.AlertRules.groupToggle().first().click(); - cy.get('a[title="View"]').click(); - e2e.components.ReturnToPrevious.buttonGroup().should('not.exist'); - }); - - it('should override the button label and change the href when user changes alert rules', () => { - e2e.components.ReturnToPrevious.backButton() - .find('span') - .contains('Back to e2e-ReturnToPrevious-test') - .should('be.visible'); - - cy.visit('/alerting/list'); - - e2e.components.AlertRules.groupToggle().last().click(); - cy.get('a[title="View"]').click(); - cy.url().as('alertRule2Url'); - cy.get('a').contains('View panel').click(); - - e2e.components.ReturnToPrevious.backButton() - .find('span') - .contains('Back to e2e-ReturnToPrevious-test-2') - .should('be.visible') - .click(); - - e2e.components.ReturnToPrevious.buttonGroup().should('not.exist'); - - // check whether the RTP button leads back to alert rule - cy.get('@alertRule2Url').then((alertRule2Url) => { - cy.url().should('eq', alertRule2Url); - }); - }); -}); diff --git a/e2e/various-suite/select-focus.spec.ts b/e2e/various-suite/select-focus.spec.ts deleted file mode 100644 index 5ff66306b35..00000000000 --- a/e2e/various-suite/select-focus.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { e2e } from '../utils'; - -describe('Select focus/unfocus tests', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it.skip('Tests select focus/unfocus scenarios', () => { - e2e.flows.openDashboard({ uid: '5SdHCadmz' }); - e2e.components.PageToolbar.item('Dashboard settings').click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('be.visible').click(); - }); - - e2e.components.Select.option().should('be.visible').first().click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('exist').should('have.focus'); - }); - - e2e.pages.Dashboard.Settings.General.title().click(); - - e2e.components.FolderPicker.containerV2() - .should('be.visible') - .within(() => { - cy.get('#dashboard-folder-input').should('exist').should('not.have.focus'); - }); - }); -}); diff --git a/e2e/various-suite/solo-route.spec.ts b/e2e/various-suite/solo-route.spec.ts deleted file mode 100644 index 2abd2717d5b..00000000000 --- a/e2e/various-suite/solo-route.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { e2e } from '../utils'; - -describe('Solo Route', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Can view panels with shared queries in fullscreen', () => { - // open Panel Tests - Bar Gauge - e2e.pages.SoloPanel.visit('ZqZnVvFZz/datasource-tests-shared-queries?orgId=1&panelId=4'); - - cy.get('canvas').should('have.length', 6); - }); - - it('Can view solo panel in scenes', () => { - // open Panel Tests - Graph NG - e2e.pages.SoloPanel.visit( - 'TkZXxlNG3/panel-tests-graph-ng?orgId=1&from=1699954597665&to=1699956397665&panelId=54&__feature.dashboardSceneSolo=true' - ); - - e2e.components.Panels.Panel.title('Interpolation: Step before').should('exist'); - cy.contains('uplot-main-div').should('not.exist'); - }); - - it('Can view solo repeated panel in scenes', () => { - // open Panel Tests - Graph NG - e2e.pages.SoloPanel.visit( - 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true' - ); - - e2e.components.Panels.Panel.title('server=B').should('exist'); - cy.contains('uplot-main-div').should('not.exist'); - }); - - it('Can view solo in repeated row and panel in scenes', () => { - // open Panel Tests - Graph NG - e2e.pages.SoloPanel.visit( - 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-16-clone-1/grid-item-2/panel-2-clone-1&__feature.dashboardSceneSolo=true' - ); - - e2e.components.Panels.Panel.title('server = B, pod = Rob').should('exist'); - cy.contains('uplot-main-div').should('not.exist'); - }); -}); diff --git a/e2e/various-suite/verify-i18n.spec.ts b/e2e/various-suite/verify-i18n.spec.ts deleted file mode 100644 index a664e714e66..00000000000 --- a/e2e/various-suite/verify-i18n.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { e2e } from '../utils'; -import { fromBaseUrl } from '../utils/support/url'; - -describe('Verify i18n', () => { - const I18N_USER = 'i18n-test'; - const I18N_PASSWORD = 'i18n-test'; - - // create a new user to isolate the language changes from other tests - before(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - cy.request({ - method: 'POST', - url: fromBaseUrl('/api/admin/users'), - body: { - email: I18N_USER, - login: I18N_USER, - name: I18N_USER, - password: I18N_PASSWORD, - }, - }).then((response) => { - cy.wrap(response.body.uid).as('uid'); - }); - }); - - // remove the user created in the before hook - after(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - cy.get('@uid').then((uid) => { - cy.request({ - method: 'DELETE', - url: fromBaseUrl(`/api/admin/users/${uid}`), - }); - }); - }); - - beforeEach(() => { - e2e.flows.login(I18N_USER, I18N_PASSWORD); - }); - - // map between languages in the language picker and the corresponding translation of the 'Language' label - const languageMap: Record = { - Deutsch: 'Sprache', - English: 'Language', - Español: 'Idioma', - Français: 'Langue', - 'Português Brasileiro': 'Idioma', - '中文(简体)': '语言', - }; - - // basic test which loops through the defined languages in the picker - // and verifies that the corresponding label is translated correctly - it('loads all the languages correctly', () => { - cy.visit('/profile'); - const LANGUAGE_SELECTOR = '[id="language-preference-select"]'; - cy.wrap(Object.entries(languageMap)).each(([language, label]: [string, string]) => { - cy.get(LANGUAGE_SELECTOR).should('not.be.disabled'); - cy.get(LANGUAGE_SELECTOR).click(); - cy.get(LANGUAGE_SELECTOR).clear().type(language).type('{downArrow}{enter}'); - e2e.components.UserProfile.preferencesSaveButton().click(); - cy.contains('label', label).should('be.visible'); - cy.get(LANGUAGE_SELECTOR).should('have.value', language); - }); - }); -}); diff --git a/e2e/various-suite/visualization-suggestions.spec.ts b/e2e/various-suite/visualization-suggestions.spec.ts deleted file mode 100644 index c5dfb03ef93..00000000000 --- a/e2e/various-suite/visualization-suggestions.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { e2e } from '../utils'; - -describe('Visualization suggestions', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Should be shown and clickable', () => { - e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { '__feature.tableNextGen': false, editPanel: 9 } }); - - // Try visualization suggestions - e2e.components.PanelEditor.toggleVizPicker().click(); - e2e.components.RadioButton.container().filter(':contains("Suggestions")').click(); - - // Verify we see suggestions - e2e.components.VisualizationPreview.card('Line chart').should('be.visible'); - - // Verify search works - cy.get('[placeholder="Search for..."]').type('Table'); - // Should no longer see line chart - e2e.components.VisualizationPreview.card('Line chart').should('not.exist'); - - // Select a visualisation - e2e.components.VisualizationPreview.card('Table').click(); - e2e.components.Panels.Visualization.Table.header().should('be.visible'); - }); -}); diff --git a/eslint.config.js b/eslint.config.js index 075aaaf17e7..664de667de6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,6 +8,8 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const lodashPlugin = require('eslint-plugin-lodash'); const barrelPlugin = require('eslint-plugin-no-barrel-files'); const reactPlugin = require('eslint-plugin-react'); +// FIXME: Remove once eslint-config-grafana is updated to include correct plugin +const hooksPlugin = require('eslint-plugin-react-hooks'); const testingLibraryPlugin = require('eslint-plugin-testing-library'); const unicornPlugin = require('eslint-plugin-unicorn'); @@ -58,13 +60,20 @@ module.exports = [ 'public/build-swagger', // swagger build output ], }, + // FIXME: Remove once eslint-config-grafana is updated to include correct plugin + { + name: 'react-hooks-plugin', + plugins: { + 'react-hooks': hooksPlugin, + }, + }, // Conditionally run the betterer rules if enabled in dev's config ...(enableBettererRules ? bettererConfig : []), - grafanaConfig, + // FIXME: Remove filtering once eslint-config-grafana is updated to include correct plugin + ...grafanaConfig.filter(Boolean), { - name: 'react/jsx-runtime', - // @ts-ignore - not sure why but flat config is typed as a maybe? - ...reactPlugin.configs.flat['jsx-runtime'], + name: 'react/jsx-runtime-rules', + rules: reactPlugin.configs.flat['jsx-runtime'].rules, }, { name: 'grafana/defaults', @@ -165,6 +174,8 @@ module.exports = [ message: 'No bare anchor nodes containing only text. Use `TextLink` instead.', }, ], + // FIXME: Fix these in follow up PR + 'react/no-unescaped-entities': 'off', }, }, { @@ -366,6 +377,14 @@ module.exports = [ 'testing-library/no-container': 'off', }, }, + { + name: 'grafana/test-disables', + files: ['**/*.{spec,test}.{ts,tsx}'], + rules: { + 'react/display-name': 'off', + 'react/no-children-prop': 'off', + }, + }, { name: 'grafana/explore-traceview-overrides', files: ['public/app/features/explore/TraceView/components/demo/**/*.{ts,tsx,js,jsx}'], diff --git a/go.mod b/go.mod index 821527e863d..82fe2410738 100644 --- a/go.mod +++ b/go.mod @@ -197,15 +197,15 @@ require ( go.uber.org/zap v1.27.0 // @grafana/identity-access-team gocloud.dev v0.42.0 // @grafana/grafana-app-platform-squad gocloud.dev/secrets/hashivault v0.42.0 // @grafana/grafana-operator-experience-squad - golang.org/x/crypto v0.40.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.41.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // @grafana/alerting-backend - golang.org/x/mod v0.26.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.42.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/mod v0.27.0 // indirect; @grafana/grafana-backend-group + golang.org/x/net v0.43.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.30.0 // @grafana/identity-access-team golang.org/x/sync v0.16.0 // @grafana/alerting-backend - golang.org/x/text v0.27.0 // @grafana/grafana-backend-group + golang.org/x/text v0.28.0 // @grafana/grafana-backend-group golang.org/x/time v0.11.0 // @grafana/grafana-backend-group - golang.org/x/tools v0.35.0 // indirect; @grafana/grafana-as-code + golang.org/x/tools v0.36.0 // indirect; @grafana/grafana-as-code gonum.org/v1/gonum v0.16.0 // @grafana/oss-big-tent google.golang.org/api v0.235.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.74.2 // @grafana/plugins-platform-backend @@ -603,8 +603,8 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect diff --git a/go.sum b/go.sum index 45bec762338..ff8580c9d8d 100644 --- a/go.sum +++ b/go.sum @@ -2687,8 +2687,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2752,8 +2752,8 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2839,8 +2839,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3036,8 +3036,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -3057,8 +3057,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3081,8 +3081,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3168,8 +3168,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index 9702910b972..cf499b0b363 100644 --- a/go.work.sum +++ b/go.work.sum @@ -529,6 +529,8 @@ github.com/Azure/go-amqp v1.0.5 h1:po5+ljlcNSU8xtapHTe8gIc8yHxCzC03E8afH2g1ftU= github.com/Azure/go-amqp v1.0.5/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4= github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= @@ -629,8 +631,6 @@ github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhi github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atc0005/go-teams-notify/v2 v2.13.0 h1:nbDeHy89NjYlF/PEfLVF6lsserY9O5SnN1iOIw3AxXw= github.com/atc0005/go-teams-notify/v2 v2.13.0/go.mod h1:WSv9moolRsBcpZbwEf6gZxj7h0uJlJskJq5zkEWKO8Y= github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= @@ -641,8 +641,6 @@ github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0 h1:JPXkrQk5OS/+Q81fKH97Ll/V github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0/go.mod h1:dJngkoVMrq0K7QvRkdRZYM4NUp6cdWa2GBdpm8zoY8U= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= -github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= -github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2 h1:vlYXbindmagyVA3RS2SPd47eKZ00GZZQcr+etTviHtc= @@ -679,6 +677,8 @@ github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLs github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg= github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc= github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= @@ -731,6 +731,8 @@ github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJP github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/etcd v3.3.27+incompatible h1:QIudLb9KeBsE5zyYxd1mjzRSkzLg9Wf9QlRwFgd6oTA= @@ -751,6 +753,7 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= @@ -794,6 +797,7 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -806,6 +810,8 @@ github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9X github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= +github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= +github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f h1:/jEs7lulqVO2u1+XI5rW4oFwIIusxuDOVKD9PAzlW2E= github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f/go.mod h1:nDRkX7PHq+p39AD5/usv3KZMerxZTYU/9rfLS5IDspU= github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= @@ -859,6 +865,7 @@ github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYs github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= @@ -973,15 +980,23 @@ github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qY github.com/grafana/authlib/types v0.0.0-20250314102521-a77865c746c0/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I= github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw= +github.com/grafana/cog v0.0.38 h1:V7gRRn/mh7Bg1ptrCxo0bv6K0SnG9TiDZk+3Ppftn6s= +github.com/grafana/cog v0.0.38/go.mod h1:UDstzYqMdgIROmbfkHL8fB9XWQO2lnf5z+4W/eJo4Dc= github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= +github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0UrQVTqtYBU= +github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY= github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk/logging v0.40.3 h1:2VXsXXEQiqAavRP8wusRDB6rDqf5lufP7A6NfjELqPE= +github.com/grafana/grafana-app-sdk/logging v0.40.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk/plugin v0.41.0 h1:ShUvGpAVzM3UxcsfwS6l/lwW4ytDeTbCQXf8w2P8Yp8= +github.com/grafana/grafana-app-sdk/plugin v0.41.0/go.mod h1:YIhimVfAqtOp3kdhxOanaSZjypVKh/bYxf9wfFfhDm0= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= @@ -1193,10 +1208,10 @@ github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= +github.com/moby/moby v27.5.1+incompatible h1:/pN59F/t3U7Q4FPzV88nzqf7Fp0qqCSL2KzhZaiKcKw= +github.com/moby/moby v27.5.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -1205,11 +1220,14 @@ github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I= github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= @@ -1686,6 +1704,7 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1729,6 +1748,8 @@ golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMe golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488 h1:3doPGa+Gg4snce233aCWnbZVFsyFMo/dR40KK/6skyE= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= @@ -1852,6 +1873,8 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizXyllY4= gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= diff --git a/kinds/publicdashboard/public_dashboard_kind.cue b/kinds/publicdashboard/public_dashboard_kind.cue deleted file mode 100644 index f3a569fa334..00000000000 --- a/kinds/publicdashboard/public_dashboard_kind.cue +++ /dev/null @@ -1,25 +0,0 @@ -package kind - -name: "PublicDashboard" -maturity: "merged" -description: "Public dashboard configuration" - -lineage: schemas: [{ - version: [0, 0] - schema: { - spec: { - // Unique public dashboard identifier - uid: string - // Dashboard unique identifier referenced by this public dashboard - dashboardUid: string - // Unique public access token - accessToken?: string - // Flag that indicates if the public dashboard is enabled - isEnabled: bool - // Flag that indicates if annotations are enabled - annotationsEnabled: bool - // Flag that indicates if the time range picker is enabled - timeSelectionEnabled: bool - } @cuetsy(kind="interface") - } -}] diff --git a/nx.json b/nx.json index f5f0322645b..c217e907f44 100644 --- a/nx.json +++ b/nx.json @@ -9,7 +9,8 @@ } }, "tui": { - "autoExit": true + "autoExit": true, + "enabled": false }, "defaultBase": "main" } diff --git a/package.json b/package.json index 9babb485971..42ecab60013 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@crowdin/crowdin-api-client": "^1.42.0", "@cypress/webpack-preprocessor": "6.0.4", "@emotion/eslint-plugin": "11.12.0", - "@grafana/eslint-config": "8.0.0", + "@grafana/eslint-config": "8.1.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", "@grafana/plugin-e2e": "2.1.7", "@grafana/test-utils": "workspace:*", @@ -234,6 +234,7 @@ "postcss-reporter": "7.1.0", "postcss-scss": "4.0.9", "prettier": "3.6.2", + "prom-client": "^15.1.3", "publint": "^0.3.12", "react-refresh": "0.14.0", "react-select-event": "5.5.1", @@ -272,7 +273,7 @@ "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", - "@grafana/assistant": "0.0.14", + "@grafana/assistant": "0.0.16", "@grafana/aws-sdk": "0.7.1", "@grafana/azure-sdk": "0.0.7", "@grafana/data": "workspace:*", @@ -290,8 +291,8 @@ "@grafana/plugin-ui": "0.10.9", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.29.7", - "@grafana/scenes-react": "6.29.7", + "@grafana/scenes": "^6.30.0", + "@grafana/scenes-react": "^6.30.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts index 62e4dd174bf..5f7cf6c8db1 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts @@ -31,6 +31,7 @@ import { ReduceOptions, WindowSizeMode, WindowAlignment, + getNameFromOptions, } from './calculateField'; import { DataTransformerID } from './ids'; @@ -1459,6 +1460,144 @@ describe('calculateField transformer w/ timeseries', () => { }); }); +describe('getNameFromOptions', () => { + it('returns alias when provided', () => { + const options = { + mode: CalculateFieldMode.ReduceRow, + alias: 'My Custom Name', + reduce: { reducer: ReducerID.sum }, + }; + expect(getNameFromOptions(options)).toBe('My Custom Name'); + }); + + it('returns cumulative function name', () => { + const options = { + mode: CalculateFieldMode.CumulativeFunctions, + cumulative: { reducer: ReducerID.sum, field: 'Value' }, + }; + expect(getNameFromOptions(options)).toBe('cumulative sum(Value)'); + }); + + it('returns cumulative function name without field', () => { + const options = { + mode: CalculateFieldMode.CumulativeFunctions, + cumulative: { reducer: ReducerID.mean }, + }; + expect(getNameFromOptions(options)).toBe('cumulative mean'); + }); + + it('returns window function name', () => { + const options = { + mode: CalculateFieldMode.WindowFunctions, + window: { + windowAlignment: WindowAlignment.Trailing, + reducer: ReducerID.mean, + field: 'Temperature', + }, + }; + expect(getNameFromOptions(options)).toBe('trailing moving mean(Temperature)'); + }); + + it('returns window function name without field', () => { + const options = { + mode: CalculateFieldMode.WindowFunctions, + window: { + windowAlignment: WindowAlignment.Centered, + reducer: ReducerID.sum, + }, + }; + expect(getNameFromOptions(options)).toBe('centered moving sum'); + }); + + it('returns unary operation name', () => { + const options = { + mode: CalculateFieldMode.UnaryOperation, + unary: { + operator: UnaryOperationID.Abs, + fieldName: 'Value', + }, + }; + expect(getNameFromOptions(options)).toBe('abs(Value)'); + }); + + it('returns unary operation name without field', () => { + const options = { + mode: CalculateFieldMode.UnaryOperation, + unary: { + operator: UnaryOperationID.Abs, + fieldName: '', + }, + }; + expect(getNameFromOptions(options)).toBe('abs'); + }); + + it('returns binary operation name with field matcher', () => { + const options = { + mode: CalculateFieldMode.BinaryOperation, + binary: { + left: { matcher: { id: FieldMatcherID.byName, options: 'FieldA' } }, + operator: BinaryOperationID.Add, + right: { matcher: { id: FieldMatcherID.byName, options: 'FieldB' } }, + }, + }; + expect(getNameFromOptions(options)).toBe('FieldA + FieldB'); + }); + + it('returns binary operation name with fixed values', () => { + const options = { + mode: CalculateFieldMode.BinaryOperation, + binary: { + left: { fixed: '10' }, + operator: BinaryOperationID.Multiply, + right: { fixed: '5' }, + }, + }; + expect(getNameFromOptions(options)).toBe('10 * 5'); + }); + + it('returns empty string for binary operation with variables', () => { + const options = { + mode: CalculateFieldMode.BinaryOperation, + binary: { + left: { fixed: '$variable1' }, + operator: BinaryOperationID.Add, + right: { fixed: '10' }, + }, + }; + expect(getNameFromOptions(options)).toBe(''); + }); + + it('returns empty string when binary field is not provided', () => { + const options = { + mode: CalculateFieldMode.BinaryOperation, + // No binary field provided at all + }; + expect(getNameFromOptions(options)).toBe(''); + }); + + it('returns reducer name for reduce row mode', () => { + const options = { + mode: CalculateFieldMode.ReduceRow, + reduce: { reducer: ReducerID.mean }, + }; + expect(getNameFromOptions(options)).toBe('Mean'); + }); + + it('returns "Row" for index mode', () => { + const options = { + mode: CalculateFieldMode.Index, + }; + expect(getNameFromOptions(options)).toBe('Row'); + }); + + it('returns "math" as default', () => { + const options = { + mode: 'invalid-mode' as CalculateFieldMode, + }; + expect(getNameFromOptions(options)).toBe('math'); + }); +}); + function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler { const deactivationHandlers: SceneDeactivationHandler[] = []; diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.ts b/packages/grafana-data/src/transformations/transformers/calculateField.ts index 1e9baf3f872..733155a3f10 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.ts @@ -693,11 +693,15 @@ export function getNameFromOptions(options: CalculateFieldTransformerOptions) { } case CalculateFieldMode.BinaryOperation: { const { binary } = options; - const alias = `${binary?.left?.matcher?.options ?? binary?.left?.fixed ?? ''} ${binary?.operator ?? ''} ${binary?.right?.matcher?.options ?? binary?.right?.fixed ?? ''}`; - + const left = binary?.left?.matcher?.options ?? binary?.left?.fixed ?? ''; + const right = binary?.right?.matcher?.options ?? binary?.right?.fixed ?? ''; // binary calculations with variables will be interpolated on the visualization but we don't want to do that here, so just give a blank placeholder - const variableFound = /\$/g.test(alias); - return variableFound ? '' : alias; + if (/\$/.test(left) || /\$/.test(right)) { + return ''; + } + + const operator = binary?.operator ?? BinaryOperationID.Add; + return left && right ? `${left} ${operator} ${right}` : ''; } case CalculateFieldMode.ReduceRow: { diff --git a/packages/grafana-flamegraph/src/AnalyzeFlameGraphButton.tsx b/packages/grafana-flamegraph/src/AnalyzeFlameGraphButton.tsx index 005e1d9b21a..696a1798ad4 100644 --- a/packages/grafana-flamegraph/src/AnalyzeFlameGraphButton.tsx +++ b/packages/grafana-flamegraph/src/AnalyzeFlameGraphButton.tsx @@ -19,6 +19,7 @@ export function AnalyzeFlameGraphButton(props: Props) { className={className} onClick={() => openAssistant({ + origin: 'analyze-flame-graph', prompt: 'Analyze Flame Graph', context: assistantContext, }) diff --git a/packages/grafana-i18n/src/i18n.tsx b/packages/grafana-i18n/src/i18n.tsx index ac22a766d99..160e02e8dcc 100644 --- a/packages/grafana-i18n/src/i18n.tsx +++ b/packages/grafana-i18n/src/i18n.tsx @@ -89,8 +89,15 @@ export async function initPluginTranslations(id: string, loaders?: ResourceLoade return { language }; } -export function getI18nInstance() { - return i18n; +export function getI18nInstance(): typeof i18n { + // in Grafana versions < 12.1.0 the i18n instance is exposed through the default export + // used by plugins that support translations from Grafana >= 11.0.0 + const instance: typeof i18n & { default?: typeof i18n } = i18n; + if (instance && instance.default) { + return instance.default; + } + + return instance; } interface Module { diff --git a/packages/grafana-runtime/src/utils/useFavoriteDatasources.test.ts b/packages/grafana-runtime/src/utils/useFavoriteDatasources.test.ts index 9f589cf8846..ffe37ddbb2e 100644 --- a/packages/grafana-runtime/src/utils/useFavoriteDatasources.test.ts +++ b/packages/grafana-runtime/src/utils/useFavoriteDatasources.test.ts @@ -11,8 +11,8 @@ const mockSetItem = jest.fn(); jest.mock('./userStorage', () => { return { UserStorage: jest.fn().mockImplementation(() => ({ - getItem: mockGetItem, - setItem: mockSetItem, + getItem: (key: string) => mockGetItem(key), + setItem: (key: string, value: string) => mockSetItem(key, value), })), }; }); diff --git a/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts b/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts index bbb47da67e7..20233bbf1c7 100644 --- a/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts +++ b/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts @@ -18,6 +18,9 @@ export type FavoriteDatasources = { isFavoriteDatasource: (dsUid: string) => boolean; }; +// Initialize user storage as a singleton +const userStorage = new UserStorage('grafana-runtime'); + /** * A hook for managing favorite data sources using user storage. * This hook provides functionality to store and retrieve a list of favorite data source UIDs @@ -45,7 +48,6 @@ export function useFavoriteDatasources(): FavoriteDatasources { }; } - const [userStorage] = useState(() => new UserStorage('grafana-runtime')); const [favoriteDatasources, setFavoriteDatasources] = useState([]); const [initialFavoriteDataSources, setInitialFavoriteDataSources] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -64,18 +66,15 @@ export function useFavoriteDatasources(): FavoriteDatasources { }; loadFavorites(); - }, [userStorage]); + }, []); // Helper function to save favorites to storage - const saveFavorites = useCallback( - async (newFavorites: string[]) => { - setIsLoading(true); - await userStorage.setItem(FAVORITE_DATASOURCES_KEY, JSON.stringify(newFavorites)); - setFavoriteDatasources(newFavorites); - setIsLoading(false); - }, - [userStorage] - ); + const saveFavorites = useCallback(async (newFavorites: string[]) => { + setIsLoading(true); + await userStorage.setItem(FAVORITE_DATASOURCES_KEY, JSON.stringify(newFavorites)); + setFavoriteDatasources(newFavorites); + setIsLoading(false); + }, []); const addFavoriteDatasource = useCallback( (ds: DataSourceInstanceSettings) => { diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index 2420ea9c95d..4a7a575e83b 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -110,6 +110,3 @@ export type { // // TODO generate code such that tsc enforces type compatibility between raw and veneer decls export type { LibraryPanel } from './veneer/librarypanel.types'; - -// Raw generated types from PublicDashboard kind. -export type { PublicDashboard } from './raw/publicdashboard/x/publicdashboard_types.gen'; diff --git a/packages/grafana-schema/src/raw/publicdashboard/x/publicdashboard_types.gen.ts b/packages/grafana-schema/src/raw/publicdashboard/x/publicdashboard_types.gen.ts deleted file mode 100644 index b94e4baefe3..00000000000 --- a/packages/grafana-schema/src/raw/publicdashboard/x/publicdashboard_types.gen.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// TSTypesJenny -// LatestMajorsOrXJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -export interface PublicDashboard { - /** - * Unique public access token - */ - accessToken?: string; - /** - * Flag that indicates if annotations are enabled - */ - annotationsEnabled: boolean; - /** - * Dashboard unique identifier referenced by this public dashboard - */ - dashboardUid: string; - /** - * Flag that indicates if the public dashboard is enabled - */ - isEnabled: boolean; - /** - * Flag that indicates if the time range picker is enabled - */ - timeSelectionEnabled: boolean; - /** - * Unique public dashboard identifier - */ - uid: string; -} diff --git a/packages/grafana-ui/src/components/Combobox/ComboboxList.tsx b/packages/grafana-ui/src/components/Combobox/ComboboxList.tsx index 16d1bb11a0c..626418213ff 100644 --- a/packages/grafana-ui/src/components/Combobox/ComboboxList.tsx +++ b/packages/grafana-ui/src/components/Combobox/ComboboxList.tsx @@ -67,7 +67,7 @@ export const ComboboxList = ({ [selectedItems] ); - const allItemsSelected = enableAllOption && selectedItems.length === options.length - 1; + const allItemsSelected = enableAllOption && options.length > 1 && selectedItems.length === options.length - 1; return ( @@ -142,6 +142,7 @@ export const ComboboxList = ({ onClick={(e) => { e.stopPropagation(); }} + data-testid={`${itemId}-checkbox`} /> )} diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx index 4d3c1e0abee..572a9d865a0 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx @@ -281,6 +281,25 @@ describe('MultiCombobox', () => { await user.type(input, 'b'); expect(screen.getByText('A')).toBeInTheDocument(); }); + + it('should not render All when only one option is available and enableAll is true', async () => { + const options = [{ label: 'A', value: 'a' }]; + render(); + const input = screen.getByRole('combobox'); + await user.click(input); + expect(screen.queryByRole('option', { name: 'All' })).not.toBeInTheDocument(); + }); + + it('should not select option when only one option is available and enableAll is true', async () => { + const options = [{ label: 'A', value: 'a' }]; + render(); + const input = screen.getByRole('combobox'); + await user.click(input); + + const checkbox = screen.getByTestId(`combobox-option-${options[0].value}-checkbox`); + expect(checkbox).toBeInTheDocument(); + expect(checkbox).not.toBeChecked(); + }); }); describe('async', () => { diff --git a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx index 157438b65ad..e597c329103 100644 --- a/packages/grafana-ui/src/components/Combobox/ValuePill.tsx +++ b/packages/grafana-ui/src/components/Combobox/ValuePill.tsx @@ -38,6 +38,7 @@ export const ValuePill = forwardRef( ); } ); +ValuePill.displayName = 'ValuePill'; const getValuePillStyles = (theme: GrafanaTheme2, disabled?: boolean) => ({ wrapper: css({ diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index 59135b9a89f..f6bb0645f2f 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -42,65 +42,67 @@ const getIconStyles = (theme: GrafanaTheme2) => { }; }; -export const Icon = React.forwardRef( - ({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => { - const styles = useStyles2(getIconStyles); +export const Icon = React.memo( + React.forwardRef( + ({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => { + const styles = useStyles2(getIconStyles); - if (!isIconName(name)) { - console.warn('Icon component passed an invalid icon name', name); - } - - // handle the deprecated 'fa fa-spinner' - const iconName: IconName = name === 'fa fa-spinner' ? 'spinner' : name; - - const svgSize = getSvgSize(size); - const svgHgt = svgSize; - const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize; - const svgPath = getIconPath(iconName, type); - - const composedClassName = cx( - styles.icon, - className, - type === 'mono' ? { [styles.orange]: name === 'favorite' } : '', - { - [styles.spin]: iconName === 'spinner', + if (!isIconName(name)) { + console.warn('Icon component passed an invalid icon name', name); } - ); - return ( - - } - {...rest} - /> - ); - } + ); + + return ( + + } + {...rest} + /> + ); + } + ) ); Icon.displayName = 'Icon'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx index 55b9d3e19ba..57001a4176e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx @@ -61,6 +61,17 @@ describe('PillCell', () => { ` ); }); + + it('non-string values', () => { + expectHTML( + render(), + ` + 100 + 200 + 300 + ` + ); + }); }); describe('Color by value mappings', () => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx index 2b85560c792..4ba20f36605 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx @@ -49,12 +49,12 @@ interface Pill { const SPLIT_RE = /\s*,\s*/; const TRANSPARENT = 'rgba(0,0,0,0)'; -function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2): Pill[] { +function createPills(pillValues: unknown[], field: Field, theme: GrafanaTheme2): Pill[] { return pillValues.map((pill, index) => { const bgColor = getPillColor(pill, field, theme); const textColor = colorManipulator.getContrastRatio('#FFFFFF', bgColor) >= 4.5 ? '#FFFFFF' : '#000000'; return { - value: pill, + value: String(pill), key: `${pill}-${index}`, bgColor, color: textColor, @@ -62,7 +62,7 @@ function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2): }); } -export function inferPills(rawValue: TableCellValue): string[] { +export function inferPills(rawValue: TableCellValue): unknown[] { if (rawValue === '' || rawValue == null) { return []; } @@ -81,7 +81,7 @@ export function inferPills(rawValue: TableCellValue): string[] { } // FIXME: this does not yet support "shades of a color" -function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string { +function getPillColor(value: unknown, field: Field, theme: GrafanaTheme2): string { const cfg = field.config; if (cfg.mappings?.length ?? 0 > 0) { @@ -101,7 +101,7 @@ function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string } } - return getColorByStringHash(colors, value); + return getColorByStringHash(colors, String(value)); } export const getStyles: TableCellStyles = (theme, { textWrap, shouldOverflow }) => diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx index 3c4e5421c4c..48b444839a6 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx @@ -73,46 +73,46 @@ describe('TableNG Cells renderers', () => { } // Helper function to render a cell and get the test ID - const renderCell = (field: Field, cellOptions: TableCellOptions) => - render( - getCellRenderer( - field, - cellOptions - )({ - field, - value: 'test-value', - rowIdx: 0, - frame: createDataFrame({ fields: [field] }), - height: 100, - width: 100, - theme: createTheme(), - cellOptions, - cellInspect: false, - showFilters: false, - getActions: jest.fn(() => [ + const renderCell = (field: Field, cellOptions: TableCellOptions) => { + // eslint-disable-next-line testing-library/render-result-naming-convention + const CellComponent = getCellRenderer(field, cellOptions); + return render( + [ { title: 'Action', onClick: jest.fn(() => {}), confirmation: jest.fn(), style: {} }, - ]), - }) + ])} + /> ); + }; // Performance test helper const benchmarkCellPerformance = (field: Field, cellOptions: TableCellOptions, iterations = 100) => { // eslint-disable-next-line testing-library/render-result-naming-convention - const r = getCellRenderer(field, cellOptions); + const CellComponent = getCellRenderer(field, cellOptions); return measurePerformance(() => { render( - r({ - field, - value: 'test-value', - rowIdx: 0, - frame: createDataFrame({ fields: [field] }), - height: 100, - width: 100, - theme: createTheme(), - cellOptions, - cellInspect: false, - showFilters: false, - }) + ); }, iterations); }; @@ -289,6 +289,22 @@ describe('TableNG Cells renderers', () => { expect(container).toBeInTheDocument(); expect(container.childNodes).toHaveLength(1); }); + + it('should use AutoCell when attempting to render a field with an unsupported type', () => { + // confirm that a real pill cell has spans. + const stringField = createField(FieldType.string, ['42']); + const { container: stringFieldContainer } = renderCell(stringField, { type: TableCellDisplayMode.Pill }); + expect(stringFieldContainer).toBeInTheDocument(); + expect(stringFieldContainer.childNodes).toHaveLength(1); + expect(stringFieldContainer.querySelector('span')).toBeInTheDocument(); + + // confirm that number pill cell doesn't actually render a pill cell. + const numberField = createField(FieldType.number, [42]); + const { container: numberFieldContainer } = renderCell(numberField, { type: TableCellDisplayMode.Pill }); + expect(numberFieldContainer).toBeInTheDocument(); + expect(numberFieldContainer.childNodes).toHaveLength(1); + expect(numberFieldContainer.querySelector('span')).toBeNull(); + }); }); describe.skip('performance benchmarks', () => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx index c0e2801d7f7..ba8df94ac5c 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx @@ -1,9 +1,10 @@ import { clsx } from 'clsx'; +import { memo, MemoExoticComponent } from 'react'; import { Field, FieldType, GrafanaTheme2, isDataFrame, isTimeSeriesFrame } from '@grafana/data'; import { TableCellDisplayMode, TableCellOptions, TableCustomCellOptions } from '../../types'; -import { TableCellRenderer, TableCellStyleOptions, TableCellStyles } from '../types'; +import { TableCellRenderer, TableCellRendererProps, TableCellStyleOptions, TableCellStyles } from '../types'; import { getCellOptions } from '../utils'; import { ActionsCell, getStyles as getActionsCellStyles } from './ActionsCell'; @@ -16,49 +17,10 @@ import { MarkdownCell, getStyles as getMarkdownCellStyles } from './MarkdownCell import { PillCell, getStyles as getPillStyles } from './PillCell'; import { SparklineCell, getStyles as getSparklineCellStyles } from './SparklineCell'; -const GAUGE_RENDERER: TableCellRenderer = (props) => ( - -); - -const AUTO_RENDERER: TableCellRenderer = (props) => ( +const AutoCellRenderer = memo((props: TableCellRendererProps) => ( -); - -const SPARKLINE_RENDERER: TableCellRenderer = (props) => ( - -); - -const GEO_RENDERER: TableCellRenderer = (props) => ; - -const IMAGE_RENDERER: TableCellRenderer = (props) => ( - -); - -const DATA_LINKS_RENDERER: TableCellRenderer = (props) => ; - -const ACTIONS_RENDERER: TableCellRenderer = ({ field, rowIdx, getActions = () => [] }) => ( - -); - -const MARKDOWN_RENDERER: TableCellRenderer = (props) => ( - -); - -const PILL_RENDERER: TableCellRenderer = (props) => ; +)); +AutoCellRenderer.displayName = 'AutoCellRenderer'; function isCustomCellOptions(options: TableCellOptions): options is TableCustomCellOptions { return options.type === TableCellDisplayMode.Custom; @@ -71,73 +33,110 @@ function mixinAutoCellStyles(fn: TableCellStyles): TableCellStyles { }; } -const CUSTOM_RENDERER: TableCellRenderer = (props) => { - if (!isCustomCellOptions(props.cellOptions) || !props.cellOptions.cellComponent) { - return null; // nonsensical case, but better to typeguard it than throw. - } - const CustomCellComponent = props.cellOptions.cellComponent; - return ; -}; +interface CellRegistryEntry { + renderer: MemoExoticComponent; + getStyles?: TableCellStyles; + testField?: (field: Field) => boolean; +} -const CELL_RENDERERS: Record = { - [TableCellDisplayMode.Actions]: { - renderer: ACTIONS_RENDERER, - getStyles: getActionsCellStyles, - }, +const CELL_REGISTRY: Record = { [TableCellDisplayMode.Auto]: { - renderer: AUTO_RENDERER, + renderer: AutoCellRenderer, getStyles: getAutoCellStyles, }, [TableCellDisplayMode.ColorBackground]: { - renderer: AUTO_RENDERER, + renderer: AutoCellRenderer, getStyles: getAutoCellStyles, }, [TableCellDisplayMode.ColorText]: { - renderer: AUTO_RENDERER, + renderer: AutoCellRenderer, getStyles: getAutoCellStyles, }, - [TableCellDisplayMode.Custom]: { - renderer: CUSTOM_RENDERER, + [TableCellDisplayMode.JSONView]: { + renderer: AutoCellRenderer, + getStyles: mixinAutoCellStyles(getJsonCellStyles), + }, + [TableCellDisplayMode.Actions]: { + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + [])} /> + )), + getStyles: getActionsCellStyles, }, [TableCellDisplayMode.DataLinks]: { - renderer: DATA_LINKS_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ), getStyles: getDataLinksStyles, }, [TableCellDisplayMode.Gauge]: { - renderer: GAUGE_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + + )), + }, + [TableCellDisplayMode.Sparkline]: { + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + + )), + getStyles: getSparklineCellStyles, }, [TableCellDisplayMode.Geo]: { - renderer: GEO_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ), getStyles: getGeoCellStyles, }, [TableCellDisplayMode.Image]: { - renderer: IMAGE_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + + )), getStyles: getImageStyles, }, - [TableCellDisplayMode.JSONView]: { - renderer: AUTO_RENDERER, - getStyles: mixinAutoCellStyles(getJsonCellStyles), - }, [TableCellDisplayMode.Pill]: { - renderer: PILL_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + + )), getStyles: getPillStyles, - }, - [TableCellDisplayMode.Sparkline]: { - renderer: SPARKLINE_RENDERER, - getStyles: getSparklineCellStyles, + testField: (field: Field) => field.type === FieldType.string, }, [TableCellDisplayMode.Markdown]: { - renderer: MARKDOWN_RENDERER, + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => ( + + )), getStyles: getMarkdownCellStyles, + testField: (field: Field) => field.type === FieldType.string, + }, + [TableCellDisplayMode.Custom]: { + // eslint-disable-next-line react/display-name + renderer: memo((props: TableCellRendererProps) => { + if (!isCustomCellOptions(props.cellOptions) || !props.cellOptions.cellComponent) { + return null; // nonsensical case, but better to typeguard it than throw. + } + const CustomCellComponent = props.cellOptions.cellComponent; + return ( + + ); + }), }, }; -// TODO: come up with a more elegant way to handle this. -const STRING_ONLY_RENDERERS = new Set([ - TableCellDisplayMode.Markdown, - TableCellDisplayMode.Pill, -]); - /** @internal */ export function getCellRenderer( field: Field, @@ -145,15 +144,16 @@ export function getCellRenderer( ): TableCellRenderer { const cellType = cellOptions?.type ?? TableCellDisplayMode.Auto; if (cellType === TableCellDisplayMode.Auto) { - return CELL_RENDERERS[getAutoRendererDisplayMode(field)].renderer; + return CELL_REGISTRY[getAutoRendererDisplayMode(field)].renderer; } - if (STRING_ONLY_RENDERERS.has(cellType) && field.type !== FieldType.string) { - return AUTO_RENDERER; + // if the field fails the test for a specific renderer, fallback to Auto + if (CELL_REGISTRY[cellType]?.testField && CELL_REGISTRY[cellType].testField(field) !== true) { + return AutoCellRenderer; } // cautious fallback to Auto renderer in case some garbage cell type has been provided. - return CELL_RENDERERS[cellType]?.renderer ?? AUTO_RENDERER; + return CELL_REGISTRY[cellType]?.renderer ?? AutoCellRenderer; } /** @internal */ @@ -166,7 +166,7 @@ export function getCellSpecificStyles( if (cellType === TableCellDisplayMode.Auto) { return getAutoRendererStyles(theme, options, field); } - return CELL_RENDERERS[cellType]?.getStyles?.(theme, options); + return CELL_REGISTRY[cellType]?.getStyles?.(theme, options); } /** @internal */ @@ -177,7 +177,7 @@ export function getAutoRendererStyles( ): string | undefined { const impliedDisplayMode = getAutoRendererDisplayMode(field); if (impliedDisplayMode !== TableCellDisplayMode.Auto) { - return CELL_RENDERERS[impliedDisplayMode]?.getStyles?.(theme, options); + return CELL_REGISTRY[impliedDisplayMode]?.getStyles?.(theme, options); } return getAutoCellStyles(theme, options); } diff --git a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx index 443053e8134..ed8e3c88a8d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/MaybeWrapWithLink.tsx @@ -19,7 +19,7 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit // as real, single link if (linksCount === 1 && actionsCount === 0) { let link = (getCellLinks(field, rowIdx) ?? [])[0]; - return renderSingleLink(link, children); + return link != null ? renderSingleLink(link, children) : children; } // as faux link that acts as hit-area for tooltip activation else if (linksCount + actionsCount > 0) { @@ -30,3 +30,4 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit // raw value return children; }); +MaybeWrapWithLink.displayName = 'MaybeWrapWithLink'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index c461dd23a33..e53a1d9e827 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -111,10 +111,7 @@ export function TableNG(props: TableNGProps) { } = props; const theme = useTheme2(); - const styles = useStyles2(getGridStyles, { - enablePagination, - transparent, - }); + const styles = useStyles2(getGridStyles, enablePagination, transparent); const panelContext = usePanelContext(); const getCellActions = useCallback( @@ -328,7 +325,7 @@ export function TableNG(props: TableNGProps) { const footerStyles = getFooterStyles(justifyContent); const displayName = getDisplayName(field); const headerCellClass = getHeaderCellStyles(theme, justifyContent); - const renderFieldCell = getCellRenderer(field, cellOptions); + const CellType = getCellRenderer(field, cellOptions); const cellInspect = isCellInspectEnabled(field); const showFilters = Boolean(field.config.filterable && onCellFilterAdded != null); @@ -409,21 +406,21 @@ export function TableNG(props: TableNGProps) { return ( <> - {renderFieldCell({ - cellOptions, - frame, - field, - height, - rowIdx, - theme, - value, - width, - timeRange, - cellInspect, - showFilters, - getActions: getCellActions, - disableSanitizeHtml, - })} + {showActions && ( ): JSX.Element => { // cached so we don't care about multiple calls. - const height = rowHeightFn(props.row); + const tooltipHeight = rowHeightFn(props.row); let tooltipStyle: CSSProperties | undefined; if (tooltipCanBeColorized) { const tooltipDisplayValue = tooltipField.display!(props.row[tooltipDisplayName]); // this is yet another call to field.display() for the tooltip field @@ -504,7 +501,7 @@ export function TableNG(props: TableNGProps) { } return ( - + {renderBasicCellContent(props)} ); @@ -790,6 +787,7 @@ export function TableNG(props: TableNGProps) { */ const renderRowFactory = (fields: Field[], panelContext: PanelContext, expandedRows: Set, enableSharedCrosshair: boolean) => + // eslint-disable-next-line react/display-name (key: React.Key, props: RenderRowProps): React.ReactNode => { const { row } = props; const rowIdx = row.__index; diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx index cdbcde443c1..2f0181672cc 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx @@ -1,5 +1,6 @@ import WKT from 'ol/format/WKT'; import Geometry from 'ol/geom/Geometry'; +import { memo } from 'react'; import { FieldType } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -9,8 +10,8 @@ import { TableCellInspectorMode } from '../../TableCellInspector'; import { TableCellDisplayMode } from '../../types'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellActionsProps } from '../types'; -export function TableCellActions(props: TableCellActionsProps) { - const { +export const TableCellActions = memo( + ({ field, value, cellOptions, @@ -20,9 +21,7 @@ export function TableCellActions(props: TableCellActionsProps) { className, cellInspect, showFilters, - } = props; - - return ( + }: TableCellActionsProps) => ( // stopping propagation to prevent clicks within the actions menu from triggering the cell click events // for things like the data links tooltip. // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions @@ -80,5 +79,6 @@ export function TableCellActions(props: TableCellActionsProps) { )} - ); -} + ) +); +TableCellActions.displayName = 'TableCellActions'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx index a81f083a2f1..156854bcfd9 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx @@ -1,4 +1,4 @@ -import { CSSProperties, ReactElement, useMemo, useState, useRef, useEffect, RefObject } from 'react'; +import { CSSProperties, ReactElement, useMemo, useState, useRef, useEffect, memo, RefObject } from 'react'; import { DataGridHandle } from 'react-data-grid'; import { ActionModel, DataFrame, Field, GrafanaTheme2 } from '@grafana/data'; @@ -10,7 +10,7 @@ import { TableCellOptions } from '../../types'; import { getTooltipStyles } from '../styles'; import { TableCellRenderer, TableCellRendererProps } from '../types'; -export interface Props { +export interface TableCellTooltipProps { cellOptions: TableCellOptions; children: ReactElement; classes: ReturnType; @@ -30,130 +30,131 @@ export interface Props { width?: number; } -export function TableCellTooltip({ - cellOptions, - children, - classes, - className, - data, - disableSanitizeHtml, - field, - getActions, - gridRef, - height, - placement, - renderer, - rowIdx, - style, - theme, - tooltipField, - width = 300, -}: Props) { - const rawValue = field.values[rowIdx]; - const tooltipCaretRef = useRef(null); +export const TableCellTooltip = memo( + ({ + cellOptions, + children, + classes, + className, + data, + disableSanitizeHtml, + field, + getActions, + gridRef, + height, + placement, + renderer: CellRenderer, + rowIdx, + style, + theme, + tooltipField, + width = 300, + }: TableCellTooltipProps) => { + const rawValue = field.values[rowIdx]; + const tooltipCaretRef = useRef(null); - const [hovered, setHovered] = useState(false); - const [pinned, setPinned] = useState(false); + const [hovered, setHovered] = useState(false); + const [pinned, setPinned] = useState(false); - const show = hovered || pinned; - const dynamicHeight = tooltipField.config.custom?.cellOptions?.dynamicHeight; + const show = hovered || pinned; + const dynamicHeight = tooltipField.config.custom?.cellOptions?.dynamicHeight; - useEffect(() => { - if (pinned) { - const gridRoot = gridRef.current?.element; + useEffect(() => { + if (pinned) { + const gridRoot = gridRef.current?.element; - const windowListener = (ev: Event) => { - if (ev.target === tooltipCaretRef.current) { - return; - } + const windowListener = (ev: Event) => { + if (ev.target === tooltipCaretRef.current) { + return; + } - setPinned(false); - window.removeEventListener('click', windowListener); - }; + setPinned(false); + window.removeEventListener('click', windowListener); + }; - window.addEventListener('click', windowListener); + window.addEventListener('click', windowListener); - // right now, we kill the pinned tooltip on any form of scrolling to avoid awkward rendering - // where the tooltip bumps up against the edge of the scrollable container. we could try to - // kill the tooltip when it hits these boundaries rather than when scrolling starts. - const scrollListener = () => { - setPinned(false); - }; - gridRoot?.addEventListener('scroll', scrollListener, { once: true }); + // right now, we kill the pinned tooltip on any form of scrolling to avoid awkward rendering + // where the tooltip bumps up against the edge of the scrollable container. we could try to + // kill the tooltip when it hits these boundaries rather than when scrolling starts. + const scrollListener = () => { + setPinned(false); + }; + gridRoot?.addEventListener('scroll', scrollListener, { once: true }); - return () => { - window.removeEventListener('click', windowListener); - gridRoot?.removeEventListener('scroll', scrollListener); - }; + return () => { + window.removeEventListener('click', windowListener); + gridRoot?.removeEventListener('scroll', scrollListener); + }; + } + + return; + }, [pinned, gridRef]); + + const rendererProps = useMemo( + () => + ({ + cellInspect: false, + cellOptions, + disableSanitizeHtml, + field, + frame: data, + getActions, + height, + rowIdx, + showFilters: false, + theme, + value: rawValue, + width, + }) satisfies TableCellRendererProps, + [cellOptions, data, disableSanitizeHtml, field, getActions, height, rawValue, rowIdx, theme, width] + ); + + const cellElement = tooltipCaretRef.current?.closest('.rdg-cell'); + + if (rawValue === null || rawValue === undefined) { + return children; } - return; - }, [pinned, gridRef]); + // TODO: perist the hover if you mouse out of the trigger and into the popover + const onMouseLeave = () => setHovered(false); + const onMouseEnter = () => setHovered(true); - const rendererProps = useMemo( - () => - ({ - cellInspect: false, - cellOptions, - disableSanitizeHtml, - field, - frame: data, - getActions, - height, - rowIdx, - showFilters: false, - theme, - value: rawValue, - width, - }) satisfies TableCellRendererProps, - [cellOptions, data, disableSanitizeHtml, field, getActions, height, rawValue, rowIdx, theme, width] - ); + return ( + <> + {cellElement && ( + } + show={show} + placement={placement} + wrapperClassName={classes.tooltipWrapper} + className={className} + style={{ ...style, minWidth: width, ...(!dynamicHeight && { height }) }} + referenceElement={cellElement} + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + onClick={(ev) => ev.stopPropagation()} // prevent click from bubbling to the global click listener for un-pinning + data-testid={selectors.components.Panels.Visualization.TableNG.Tooltip.Wrapper} + /> + )} - const cellElement = tooltipCaretRef.current?.closest('.rdg-cell'); - - if (rawValue === null || rawValue === undefined) { - return children; - } - - const body = <>{renderer(rendererProps)}; - - // TODO: perist the hover if you mouse out of the trigger and into the popover - const onMouseLeave = () => setHovered(false); - const onMouseEnter = () => setHovered(true); - - return ( - <> - {cellElement && ( - setPinned((prev) => !prev)} onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - onClick={(ev) => ev.stopPropagation()} // prevent click from bubbling to the global click listener for un-pinning - data-testid={selectors.components.Panels.Visualization.TableNG.Tooltip.Wrapper} + onBlur={onMouseLeave} + onFocus={onMouseEnter} /> - )} - {/* TODO: figure out an accessible way to trigger the tooltip. */} - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} -
setPinned((prev) => !prev)} - onMouseLeave={onMouseLeave} - onMouseEnter={onMouseEnter} - onBlur={onMouseLeave} - onFocus={onMouseEnter} - /> - - {children} - - ); -} + {children} + + ); + } +); +TableCellTooltip.displayName = 'TableCellTooltip'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts index ce378961589..ad06992984e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts @@ -477,7 +477,7 @@ describe('TableNG hooks', () => { }), columnWidths: [100, 100, 100], enabled: true, - typographyCtx: { ...typographyCtx, avgCharWidth: 5, wrappedCount: jest.fn(() => 2) }, + typographyCtx: { ...typographyCtx, avgCharWidth: 5, measureHeight: jest.fn(() => 38) }, sortColumns: [], }); }); @@ -486,7 +486,7 @@ describe('TableNG hooks', () => { }); it('should calculate the available width for a header cell based on the icons rendered within it', () => { - const countFn = jest.fn(() => 1); + const heightFn = jest.fn(() => 20); const { fields } = setupData(); @@ -512,13 +512,13 @@ describe('TableNG hooks', () => { fields: modifiedFields, columnWidths: [100, 100, 100], enabled: true, - typographyCtx: { ...typographyCtx, wrappedCount: countFn }, + typographyCtx: { ...typographyCtx, measureHeight: heightFn }, sortColumns: [], showTypeIcons: false, }); }); - expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86, modifiedFields[0], -1); + expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86, modifiedFields[0], -1, 6); modifiedFields = fields.map((field) => { if (field.name === 'name') { @@ -543,13 +543,13 @@ describe('TableNG hooks', () => { fields: modifiedFields, columnWidths: [100, 100, 100], enabled: true, - typographyCtx: { ...typographyCtx, wrappedCount: countFn }, + typographyCtx: { ...typographyCtx, measureHeight: heightFn }, sortColumns: [{ columnKey: 'Longer name that needs wrapping', direction: 'ASC' }], showTypeIcons: true, }); }); - expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26, modifiedFields[0], -1); + expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26, modifiedFields[0], -1, 6); }); }); @@ -685,9 +685,9 @@ describe('TableNG hooks', () => { }); }); - // we test the lineCounters and getRowHeight directly to check that all of that - // math is working correctly. we mainly want to confirm here that the - // cache is clearing and that the local logic in this hook works. + // we test the cell height measurerers and getRowHeight directly to check + //that all of that math is working correctly. we mainly want to confirm that + // the cache is clearing and that the local logic in this hook works. describe('wrapped columns', () => { let rows: TableRow[]; let fieldsWithWrappedText: Field[]; @@ -749,14 +749,14 @@ describe('TableNG hooks', () => { it('adjusts the width of the columns based on the cell padding and border', () => { fieldsWithWrappedText[0].values[0] = 'Annie Lennox'; - const wrappedCountFn = jest.fn(() => 2); - const estimateLinesFn = jest.fn(() => 2); + const measureHeightFn = jest.fn(() => 40); + const estimateHeightFn = jest.fn(() => 40); const { result } = renderHook(() => { const rowHeight = useRowHeight({ fields: fieldsWithWrappedText, columnWidths: [100, 100, 100], defaultHeight: 40, - typographyCtx: { ...typographyCtx, wrappedCount: wrappedCountFn, estimateLines: estimateLinesFn }, + typographyCtx: { ...typographyCtx, measureHeight: measureHeightFn, estimateHeight: estimateHeightFn }, hasNestedFrames: false, expandedRows: new Set(), }); @@ -768,11 +768,12 @@ describe('TableNG hooks', () => { expect(result.current(rows[0])).toEqual(expect.any(Number)); - expect(estimateLinesFn).toHaveBeenCalledWith( + expect(measureHeightFn).toHaveBeenCalledWith( 'Annie Lennox', 100 - TABLE.CELL_PADDING * 2 - TABLE.BORDER_RIGHT, fieldsWithWrappedText[0], - 0 + 0, + 22 ); }); }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts index 84b9a277dad..5162aac82f8 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts @@ -3,7 +3,7 @@ import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-gr import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data'; -import { TableCellDisplayMode, TableColumnResizeActionCallback } from '../types'; +import { TableColumnResizeActionCallback } from '../types'; import { TABLE } from './constants'; import { FilterType, TableFooterCalc, TableRow, TableSortByFieldState, TableSummaryRow, TypographyCtx } from './types'; @@ -13,9 +13,8 @@ import { applySort, getColumnTypes, getRowHeight, - buildHeaderLineCounters, - buildRowLineCounters, - getCellOptions, + buildHeaderHeightMeasurers, + buildCellHeightMeasurers, } from './utils'; // Helper function to get displayed value @@ -341,7 +340,7 @@ export function useHeaderHeight({ }: UseHeaderHeightOptions): number { const perIconSpace = ICON_WIDTH + ICON_GAP; - const lineCounters = useMemo(() => buildHeaderLineCounters(fields, typographyCtx), [fields, typographyCtx]); + const measurers = useMemo(() => buildHeaderHeightMeasurers(fields, typographyCtx), [fields, typographyCtx]); const columnAvailableWidths = useMemo( () => @@ -369,16 +368,8 @@ export function useHeaderHeight({ if (!enabled) { return 0; } - return getRowHeight( - fields, - -1, - columnAvailableWidths, - TABLE.HEADER_HEIGHT, - lineCounters, - TABLE.LINE_HEIGHT, - TABLE.CELL_PADDING - ); - }, [fields, enabled, columnAvailableWidths, lineCounters]); + return getRowHeight(fields, -1, columnAvailableWidths, TABLE.HEADER_HEIGHT, measurers, TABLE.CELL_PADDING); + }, [fields, enabled, columnAvailableWidths, measurers]); return headerHeight; } @@ -400,8 +391,8 @@ export function useRowHeight({ expandedRows, typographyCtx, }: UseRowHeightOptions): NonNullable | ((row: TableRow) => number) { - const lineCounters = useMemo(() => buildRowLineCounters(fields, typographyCtx), [fields, typographyCtx]); - const hasWrappedCols = useMemo(() => lineCounters?.length ?? 0 > 0, [lineCounters]); + const measurers = useMemo(() => buildCellHeightMeasurers(fields, typographyCtx), [fields, typographyCtx]); + const hasWrappedCols = useMemo(() => measurers?.length ?? 0 > 0, [measurers]); const colWidths = useMemo(() => { const columnWidthAffordance = 2 * TABLE.CELL_PADDING + TABLE.BORDER_RIGHT; @@ -437,27 +428,11 @@ export function useRowHeight({ // regular rows let result = cache[row.__index]; if (!result) { - result = cache[row.__index] = getRowHeight( - fields, - row.__index, - colWidths, - defaultHeight, - lineCounters, - TABLE.LINE_HEIGHT, - (field, numLines) => { - // Pill cells have vertical padding between each row - if (getCellOptions(field).type === TableCellDisplayMode.Pill) { - return TABLE.CELL_PADDING * (numLines - 1) + TABLE.CELL_PADDING * 2; - } - - // default vertical padding for cells - return TABLE.CELL_PADDING * 2; - } - ); + result = cache[row.__index] = getRowHeight(fields, row.__index, colWidths, defaultHeight, measurers); } return result; }; - }, [hasNestedFrames, hasWrappedCols, defaultHeight, fields, colWidths, lineCounters, expandedRows]); + }, [hasNestedFrames, hasWrappedCols, defaultHeight, fields, colWidths, measurers, expandedRows]); return rowHeight; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/styles.ts b/packages/grafana-ui/src/components/Table/TableNG/styles.ts index ca837f66dbc..b21bb702041 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/styles.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/styles.ts @@ -7,10 +7,7 @@ import { COLUMN, TABLE } from './constants'; import { TableCellStyles } from './types'; import { getJustifyContent, TextAlign } from './utils'; -export const getGridStyles = ( - theme: GrafanaTheme2, - { enablePagination, transparent }: { enablePagination?: boolean; transparent?: boolean } -) => { +export const getGridStyles = (theme: GrafanaTheme2, enablePagination?: boolean, transparent?: boolean) => { const bgColor = transparent ? theme.colors.background.canvas : theme.colors.background.primary; // this needs to be pre-calc'd since the theme colors have alpha and the border color becomes // unpredictable for background color cells diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index 03705ef669d..0f1883456a7 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -1,4 +1,4 @@ -import { ReactNode, SyntheticEvent } from 'react'; +import { FC, SyntheticEvent } from 'react'; import { Column } from 'react-data-grid'; import { @@ -150,7 +150,7 @@ export interface BaseTableProps { /* ---------------------------- Table cell props ---------------------------- */ export interface TableNGProps extends BaseTableProps {} -export type TableCellRenderer = (props: TableCellRendererProps) => ReactNode; +export type TableCellRenderer = FC; export interface TableCellRendererProps { rowIdx: number; @@ -279,24 +279,30 @@ export interface TypographyCtx { fontFamily: string; letterSpacing: number; avgCharWidth: number; - estimateLines: LineCounter; - wrappedCount: LineCounter; + estimateHeight: MeasureCellHeight; + measureHeight: MeasureCellHeight; } -export type LineCounter = (value: unknown, width: number, field: Field, rowIdx: number) => number; -export interface LineCounterEntry { +export type MeasureCellHeight = ( + value: unknown, + width: number, + field: Field, + rowIdx: number, + lineHeight: number +) => number; +export interface MeasureCellHeightEntry { /** * given a values and the available width, returns the line count for that value */ - counter: LineCounter; + measure: MeasureCellHeight; /** * if getting an accurate line count is expensive, you can provide an estimate method - * which will be used when looping over the row. the counter method will only be invoked + * which will be used when looping over the row. the method will only be invoked * for the cell which is the maximum line count for the row. */ - estimate?: LineCounter; + estimate?: MeasureCellHeight; /** - * indicates which field indexes of the visible fields this line counter applies to. + * indicates which field indexes of the visible fields this measurer applies to. */ fieldIdxs: number[]; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts index 5ebd14336b9..8ca2ba7870f 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts @@ -18,7 +18,7 @@ import { BarGaugeDisplayMode, TableCellBackgroundDisplayMode, TableCellHeight } import { TableCellDisplayMode } from '../types'; import { COLUMN, TABLE } from './constants'; -import { LineCounterEntry } from './types'; +import { MeasureCellHeightEntry } from './types'; import { extractPixelValue, frameToRecords, @@ -34,15 +34,15 @@ import { getColumnTypes, computeColWidths, getRowHeight, - buildRowLineCounters, - buildHeaderLineCounters, - getTextLineEstimator, + buildCellHeightMeasurers, + buildHeaderHeightMeasurers, + getTextHeightEstimator, createTypographyContext, applySort, SINGLE_LINE_ESTIMATE_THRESHOLD, - wrapUwrapCount, - getDataLinksCounter, - getPillLineCounter, + getTextHeightMeasurerFromUwrapCount, + getDataLinksHeightMeasurer, + getPillCellHeightMeasurer, getDefaultRowHeight, getDisplayName, predicateByName, @@ -850,49 +850,49 @@ describe('TableNG utils', () => { ctx: expect.any(CanvasRenderingContext2D), fontFamily: 'sans-serif', letterSpacing: 0.15, - wrappedCount: expect.any(Function), - estimateLines: expect.any(Function), + measureHeight: expect.any(Function), + estimateHeight: expect.any(Function), avgCharWidth: expect.any(Number), }) ); - expect(ctx.wrappedCount('the quick brown fox jumps over the lazy dog', 100, field, 0)).toEqual( + expect(ctx.measureHeight('the quick brown fox jumps over the lazy dog', 100, field, 0, 20)).toEqual( expect.any(Number) ); - expect(ctx.estimateLines('the quick brown fox jumps over the lazy dog', 100, field, 0)).toEqual( + expect(ctx.estimateHeight('the quick brown fox jumps over the lazy dog', 100, field, 0, 20)).toEqual( expect.any(Number) ); }); }); - describe('wrapUwrapCount', () => { + describe('getTextHeightMeasurerFromUwrapCount', () => { const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['foo', 'bar', 'baz'] }; it('wraps the uwrap count function', () => { - const wrappedCount = wrapUwrapCount(jest.fn(() => 2)); - expect(wrappedCount('test string', 100, field, 0)).toBe(2); + const measureHeight = getTextHeightMeasurerFromUwrapCount(jest.fn(() => 2)); + expect(measureHeight('test string', 100, field, 0, 20)).toBe(40); }); - it('returns 1 for null or undefined values', () => { - const wrappedCount = wrapUwrapCount(jest.fn(() => 2)); - expect(wrappedCount(null, 100, field, 0)).toBe(1); - expect(wrappedCount(undefined, 100, field, 0)).toBe(1); + it("returns a single line's height for null or undefined values", () => { + const measureHeight = getTextHeightMeasurerFromUwrapCount(jest.fn(() => 2)); + expect(measureHeight(null, 100, field, 0, 20)).toBe(20); + expect(measureHeight(undefined, 100, field, 0, 20)).toBe(20); }); }); - describe('getTextLineEstimator', () => { - const counter = getTextLineEstimator(10); + describe('getTextHeightEstimator', () => { + const estimator = getTextHeightEstimator(10); const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['foo', 'bar', 'baz'] }; it('returns -1 if there are no strings or dashes within the string', () => { - expect(counter('asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf', 5, field, 0)).toBe(-1); + expect(estimator('asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf', 5, field, 0, 22)).toBe(-1); }); it('calculates an approximate rendered height for the text based on the width and avgCharWidth', () => { - expect(counter('asdfas dfasdfasdf asdfasdfasdfa sdfasdfasdfasdf 23', 200, field, 0)).toBe(2.5); + expect(estimator('asdfas dfasdfasdf asdfasdfasdfa sdfasdfasdfasdf 23', 200, field, 0, 20)).toBe(60); }); }); - describe('getDataLinksCounter', () => { + describe('getDataLinksHeightMeasurer', () => { it('counts number of valid links using getCellLinks', () => { const field: Field = { name: 'test', @@ -911,58 +911,58 @@ describe('TableNG utils', () => { values: ['value1'], }; - const counter = getDataLinksCounter(); - expect(counter('my value', 100, field, 0)).toBe(2); + const measurer = getDataLinksHeightMeasurer(); + expect(measurer('my value', 100, field, 0, 20)).toBe(40); }); }); - describe('getPillLineCounter', () => { + describe('getPillCellHeightMeasurer', () => { it('counts up the number of lines using the pill measuring method', () => { - const counter = getPillLineCounter(jest.fn((str) => str.length * 5)); - expect(counter('tag1,tag2', 100, {} as Field, 0)).toBe(1); - expect(counter('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0)).toBe(3); + const measurer = getPillCellHeightMeasurer(jest.fn((str) => str.length * 5)); + expect(measurer('tag1,tag2', 100, {} as Field, 0, 20)).toBe(20); + expect(measurer('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0, 20)).toBe(68); }); it('returns 0 if value is null', () => { - const counter = getPillLineCounter(jest.fn((str) => str.length * 5)); - expect(counter(null, 100, {} as Field, 0)).toBe(0); + const measurer = getPillCellHeightMeasurer(jest.fn((str) => str.length * 5)); + expect(measurer(null, 100, {} as Field, 0, 20)).toBe(0); }); it('returns 0 if no pills are inferred', () => { - const counter = getPillLineCounter(jest.fn((str) => str.length * 5)); - expect(counter('', 100, {} as Field, 0)).toBe(0); + const measurer = getPillCellHeightMeasurer(jest.fn((str) => str.length * 5)); + expect(measurer('', 100, {} as Field, 0, 20)).toBe(0); }); it('caches the width measurement for the same value', () => { const widthMeasurement = jest.fn((str) => str.length * 5); - const counter = getPillLineCounter(widthMeasurement); - counter('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0); - counter('tag1,tag2', 100, {} as Field, 0); - counter('tag2', 200, {} as Field, 0); - counter('tag2,tag3,tag2,tag4,tag4,tag2,tag5', 300, {} as Field, 0); + const measurer = getPillCellHeightMeasurer(widthMeasurement); + measurer('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0, 20); + measurer('tag1,tag2', 100, {} as Field, 0, 20); + measurer('tag2', 200, {} as Field, 0, 20); + measurer('tag2,tag3,tag2,tag4,tag4,tag2,tag5', 300, {} as Field, 0, 20); expect(widthMeasurement).toHaveBeenCalledTimes(6); // Should only call for unique values }); }); - describe('buildHeaderLineCounters', () => { + describe('buildHeaderHeightMeasurers', () => { const ctx = { fontFamily: 'sans-serif', letterSpacing: 0.15, ctx: {} as CanvasRenderingContext2D, count: jest.fn(() => 2), avgCharWidth: 7, - wrappedCount: jest.fn(() => 2), - estimateLines: jest.fn(() => 2), + measureHeight: jest.fn(() => 2), + estimateHeight: jest.fn(() => 2), }; - it('returns an array of line counters for each column', () => { + it('returns an array of measurers for each column', () => { const fields: Field[] = [ { name: 'Name', type: FieldType.string, values: [], config: { custom: { wrapHeaderText: true } } }, { name: 'Age', type: FieldType.number, values: [], config: { custom: { wrapHeaderText: true } } }, ]; - const counters = buildHeaderLineCounters(fields, ctx); - expect(counters![0].counter).toEqual(expect.any(Function)); - expect(counters![0].fieldIdxs).toEqual([0, 1]); + const measurers = buildHeaderHeightMeasurers(fields, ctx); + expect(measurers![0].measure).toEqual(expect.any(Function)); + expect(measurers![0].fieldIdxs).toEqual([0, 1]); }); it('does not return the index of columns which are not wrapped', () => { @@ -971,8 +971,8 @@ describe('TableNG utils', () => { { name: 'Age', type: FieldType.number, values: [], config: { custom: { wrapHeaderText: true } } }, ]; - const counters = buildHeaderLineCounters(fields, ctx); - expect(counters![0].fieldIdxs).toEqual([1]); + const measurers = buildHeaderHeightMeasurers(fields, ctx); + expect(measurers![0].fieldIdxs).toEqual([1]); }); it('returns undefined if no columns are wrapped', () => { @@ -981,22 +981,22 @@ describe('TableNG utils', () => { { name: 'Age', type: FieldType.number, values: [], config: { custom: {} } }, ]; - const counters = buildHeaderLineCounters(fields, ctx); - expect(counters).toBeUndefined(); + const measurers = buildHeaderHeightMeasurers(fields, ctx); + expect(measurers).toBeUndefined(); }); }); - describe('buildRowLineCounters', () => { + describe('buildCellHeightMeasurers', () => { const ctx = { fontFamily: 'sans-serif', letterSpacing: 0.15, ctx: {} as CanvasRenderingContext2D, - wrappedCount: jest.fn(() => 2), - estimateLines: jest.fn(() => 2), + measureHeight: jest.fn(() => 2), + estimateHeight: jest.fn(() => 2), avgCharWidth: 7, }; - it('sets up text line counters for each text column if wrapping is on', () => { + it('sets up text height measurers for each text column if wrapping is on', () => { const fields: Field[] = [ { name: 'Name', type: FieldType.string, values: [], config: { custom: { cellOptions: { wrapText: true } } } }, { @@ -1006,9 +1006,9 @@ describe('TableNG utils', () => { config: { custom: { cellOptions: { wrapText: true } } }, }, ]; - const counters = buildRowLineCounters(fields, ctx); - expect(counters![0].counter).toEqual(expect.any(Function)); - expect(counters![0].fieldIdxs).toEqual([0, 1]); + const measurers = buildCellHeightMeasurers(fields, ctx); + expect(measurers![0].measure).toEqual(expect.any(Function)); + expect(measurers![0].fieldIdxs).toEqual([0, 1]); }); it('does not return the index of columns which are not wrapped', () => { @@ -1022,8 +1022,8 @@ describe('TableNG utils', () => { }, ]; - const counters = buildRowLineCounters(fields, ctx); - expect(counters![0].fieldIdxs).toEqual([1]); + const measurers = buildCellHeightMeasurers(fields, ctx); + expect(measurers![0].fieldIdxs).toEqual([1]); }); it('sets up line counting for pills if present and wrapping is on', () => { @@ -1035,12 +1035,12 @@ describe('TableNG utils', () => { config: { custom: { cellOptions: { type: TableCellDisplayMode.Pill, wrapText: true } } }, }, ]; - const counters = buildRowLineCounters(fields, ctx); - expect(counters![0].estimate).toEqual(expect.any(Function)); - expect(counters![0].estimate!('tag1,tag2', 100, fields[0], 0)).toEqual(expect.any(Number)); - expect(counters![0].counter).toEqual(expect.any(Function)); - expect(counters![0].counter('tag1,tag2', 100, fields[0], 0)).toEqual(expect.any(Number)); - expect(counters![0].fieldIdxs).toEqual([0]); + const measurers = buildCellHeightMeasurers(fields, ctx); + expect(measurers![0].estimate).toEqual(expect.any(Function)); + expect(measurers![0].estimate!('tag1,tag2', 100, fields[0], 0, 22)).toEqual(expect.any(Number)); + expect(measurers![0].measure).toEqual(expect.any(Function)); + expect(measurers![0].measure('tag1,tag2', 100, fields[0], 0, 22)).toEqual(expect.any(Number)); + expect(measurers![0].fieldIdxs).toEqual([0]); }); it('sets up line counting for datalinks if present and wrapping is on', () => { @@ -1056,10 +1056,10 @@ describe('TableNG utils', () => { ]), }, ]; - const counters = buildRowLineCounters(fields, ctx); - expect(counters![0].counter).toEqual(expect.any(Function)); - expect(counters![0].counter('http://example.com/1', 100, fields[0], 0)).toEqual(expect.any(Number)); - expect(counters![0].fieldIdxs).toEqual([0]); + const measurers = buildCellHeightMeasurers(fields, ctx); + expect(measurers![0].measure).toEqual(expect.any(Function)); + expect(measurers![0].measure('http://example.com/1', 100, fields[0], 0, 22)).toEqual(expect.any(Number)); + expect(measurers![0].fieldIdxs).toEqual([0]); }); it('does not enable text counting for non-string fields', () => { @@ -1068,9 +1068,9 @@ describe('TableNG utils', () => { { name: 'Age', type: FieldType.number, values: [], config: { custom: { cellOptions: { wrapText: true } } } }, ]; - const counters = buildRowLineCounters(fields, ctx); + const measurers = buildCellHeightMeasurers(fields, ctx); // empty array - we had one column that indicated it wraps, but it was numeric, so we just ignore it - expect(counters).toEqual([]); + expect(measurers).toEqual([]); }); it('returns an undefined if no columns are wrapped', () => { @@ -1079,14 +1079,14 @@ describe('TableNG utils', () => { { name: 'Age', type: FieldType.number, values: [], config: { custom: {} } }, ]; - const counters = buildRowLineCounters(fields, ctx); - expect(counters).toBeUndefined(); + const measurers = buildCellHeightMeasurers(fields, ctx); + expect(measurers).toBeUndefined(); }); }); describe('getRowHeight', () => { let fields: Field[]; - let counters: LineCounterEntry[]; + let measurers: MeasureCellHeightEntry[]; beforeEach(() => { fields = [ @@ -1103,91 +1103,109 @@ describe('TableNG utils', () => { config: { custom: { cellOptions: { wrapText: true } } }, }, ]; - counters = [ - { counter: jest.fn((value, _length: number) => String(value).split(' ').length), fieldIdxs: [0] }, // Mocked to count words as lines - { counter: jest.fn((value, _length: number) => Math.ceil(String(value).length / 3)), fieldIdxs: [1] }, // Mocked to return a line for every 3 digits of a number + measurers = [ + { + measure: jest.fn( + (value, _length, _field, _rowIdx, lineHeight) => String(value).split(' ').length * lineHeight + ), + fieldIdxs: [0], + }, // Mocked to count words as lines + { + measure: jest.fn( + (value, _length, _field, _rowIdx, lineHeight) => Math.ceil(String(value).length / 3) * lineHeight + ), + fieldIdxs: [1], + }, // Mocked to return a line for every 3 digits of a number ]; }); it('should use the default height for single-line rows', () => { // 1 line @ 20px, 10px vertical padding = 30, minimum is 36 - expect(getRowHeight(fields, 0, [30, 30], 36, counters, 20, 10)).toBe(36); + expect(getRowHeight(fields, 0, [30, 30], 36, measurers, 20, 10)).toBe(36); }); it('should use the default height for multi-line rows which are shorter than the default height', () => { // 3 lines @ 5px, 5px vertical padding = 20, minimum is 36 - expect(getRowHeight(fields, 3, [30, 30], 36, counters, 5, 5)).toBe(36); + expect(getRowHeight(fields, 3, [30, 30], 36, measurers, 5, 5)).toBe(36); }); - it('should return the row height using line counters for multi-line', () => { + it('should return the row height using line measurers for multi-line', () => { // 3 lines @ 20px ('longer', 'one', 'here'), 10px vertical padding - expect(getRowHeight(fields, 3, [30, 30], 36, counters, 20, 10)).toBe(70); + expect(getRowHeight(fields, 3, [30, 30], 36, measurers, 20, 10)).toBe(70); // 4 lines @ 15px (789 122 349 932), 15px vertical padding - expect(getRowHeight(fields, 4, [30, 30], 36, counters, 15, 15)).toBe(75); + expect(getRowHeight(fields, 4, [30, 30], 36, measurers, 15, 15)).toBe(75); }); it('should take colWidths into account when calculating max wrap cell', () => { - getRowHeight(fields, 3, [50, 60], 36, counters, 20, 10); - expect(counters[0].counter).toHaveBeenCalledWith('longer one here', 50, fields[0], 3); - expect(counters[1].counter).toHaveBeenCalledWith(123456, 60, fields[1], 3); + getRowHeight(fields, 3, [50, 60], 36, measurers, 20, 10); + expect(measurers[0].measure).toHaveBeenCalledWith('longer one here', 50, fields[0], 3, 20); + expect(measurers[1].measure).toHaveBeenCalledWith(123456, 60, fields[1], 3, 20); }); // this is used to calc wrapped header height it('should use the display name if the rowIdx is -1', () => { - getRowHeight(fields, -1, [50, 60], 36, counters, 20, 10); - expect(counters[0].counter).toHaveBeenCalledWith('Name', 50, fields[0], -1); - expect(counters[1].counter).toHaveBeenCalledWith('Age', 60, fields[1], -1); + getRowHeight(fields, -1, [50, 60], 36, measurers, 20, 10); + expect(measurers[0].measure).toHaveBeenCalledWith('Name', 50, fields[0], -1, 20); + expect(measurers[1].measure).toHaveBeenCalledWith('Age', 60, fields[1], -1, 20); }); - it('should ignore columns which do not have line counters', () => { - const height = getRowHeight(fields, 3, [30, 30], 36, [counters[1]], 20, 10); + it('should ignore columns which do not have measurers', () => { + const height = getRowHeight(fields, 3, [30, 30], 36, [measurers[1]], 20, 10); // 2 lines @ 20px, 10px vertical padding (not 3 lines, since we don't line count Name) expect(height).toBe(50); }); - it('should return the default height if there are no counters to apply', () => { + it('should return the default height if there are no measurers to apply', () => { const height = getRowHeight(fields, 3, [30, 30], 36, [], 20, 10); expect(height).toBe(36); }); describe('estimations vs. precise counts', () => { beforeEach(() => { - counters = [ - { counter: jest.fn((value, _length: number) => String(value).split(' ').length), fieldIdxs: [0] }, // Mocked to count words as lines + measurers = [ { - estimate: jest.fn((value) => String(value).length), // Mocked to return a line for every digits of a number - counter: jest.fn((value, _length: number) => Math.ceil(String(value).length / 3)), + measure: jest.fn( + (value, _length, _field, _rowIdx, lineHeight) => String(value).split(' ').length * lineHeight + ), + fieldIdxs: [0], + }, // Mocked to count words as lines + { + measure: jest.fn( + (value, _length, _field, _rowIdx, lineHeight) => Math.ceil(String(value).length / 3) * lineHeight + ), + estimate: jest.fn((value, _length, _field, _rowIdx, lineHeight) => String(value).length * lineHeight), // Mocked to return a line for every digits of a number fieldIdxs: [1], - }, + }, // Mocked to return a line for every 3 digits of a number ]; }); // 2 lines @ 20px (123,456), 10px vertical padding. when we did this before, 'longer one here' would win, making it 70px. - // the `estimate` function is picking `123456` as the longer one now (6 lines), then the `counter` function is used + // the `estimate` function is picking `123456` as the longer one now (6 lines), then the `measure` function is used // to calculate the height (2 lines). this is a very forced case, but we just want to prove that it actually works. it('uses the estimate value rather than the precise value to select the row height', () => { - expect(getRowHeight(fields, 3, [30, 30], 36, counters, 20, 10)).toBe(50); + expect(getRowHeight(fields, 3, [30, 30], 36, measurers, 20, 10)).toBe(50); }); it('returns doesnt bother getting the precise count if the estimates are all below the threshold', () => { - jest.mocked(counters[0].counter).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - 0.3); - jest.mocked(counters[1].estimate!).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - 0.1); + jest.mocked(measurers[0].measure).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - 0.3); + jest.mocked(measurers[1].estimate!).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - 0.1); - expect(getRowHeight(fields, 3, [30, 30], 36, counters, 20, 10)).toBe(36); + expect(getRowHeight(fields, 3, [30, 30], 36, measurers, 20, 10)).toBe(36); - // this is what we really care about - we want to save on performance by not calling the counter in this case. - expect(counters[1].counter).not.toHaveBeenCalled(); + // this is what we really care about - we want to save on performance by not calling the measure in this case. + expect(measurers[1].measure).not.toHaveBeenCalled(); }); it('uses the precise count if the estimate is above the threshold, even if its below 1', () => { - // NOTE: if this fails, just change the test to use a different value besides 0.1 - expect(SINGLE_LINE_ESTIMATE_THRESHOLD + 0.1).toBeLessThan(1); + // NOTE: if this fails, just change the test to use a different value besides 1 + const thresholdOffset = 1; + expect(SINGLE_LINE_ESTIMATE_THRESHOLD + thresholdOffset).toBeLessThan(TABLE.LINE_HEIGHT); - jest.mocked(counters[0].counter).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - 0.3); - jest.mocked(counters[1].estimate!).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD + 0.1); + jest.mocked(measurers[0].measure).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD - thresholdOffset); + jest.mocked(measurers[1].estimate!).mockReturnValue(SINGLE_LINE_ESTIMATE_THRESHOLD + thresholdOffset); - expect(getRowHeight(fields, 3, [30, 30], 36, counters, 20, 10)).toBe(50); + expect(getRowHeight(fields, 3, [30, 30], 36, measurers, 20, 10)).toBe(50); }); }); }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index 3c36e150a3c..3eb89230a58 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -34,8 +34,8 @@ import { FrameToRowsConverter, Comparator, TypographyCtx, - LineCounter, - LineCounterEntry, + MeasureCellHeight, + MeasureCellHeightEntry, } from './types'; /* ---------------------------- Cell calculations --------------------------- */ @@ -112,29 +112,30 @@ export function createTypographyContext(fontSize: number, fontFamily: string, le fontFamily, letterSpacing, avgCharWidth, - estimateLines: getTextLineEstimator(avgCharWidth), - wrappedCount: wrapUwrapCount(count), + estimateHeight: getTextHeightEstimator(avgCharWidth), + measureHeight: getTextHeightMeasurerFromUwrapCount(count), }; } /** * @internal wraps the uwrap count function to ensure that it is given a string. */ -export function wrapUwrapCount(count: Count): LineCounter { - return (value, width) => { +export function getTextHeightMeasurerFromUwrapCount(count: Count): MeasureCellHeight { + return (value, width, _field, _rowIdx, lineHeight) => { if (value == null) { - return 1; + return lineHeight; } - return count(String(value), width); + const lines = count(String(value), width); + return lines * lineHeight; }; } /** - * @internal returns a line counter which guesstimates a number of lines in a text cell based on the typography context's avgCharWidth. + * @internal returns a measurer which guesstimates a number of lines in a text cell based on the typography context's avgCharWidth. */ -export function getTextLineEstimator(avgCharWidth: number): LineCounter { - return (value, width) => { +export function getTextHeightEstimator(avgCharWidth: number): MeasureCellHeight { + return (value, width, _field, _rowIdx, lineHeight) => { if (!value) { return -1; } @@ -147,20 +148,21 @@ export function getTextLineEstimator(avgCharWidth: number): LineCounter { } const charsPerLine = width / avgCharWidth; - return strValue.length / charsPerLine; + const lines = Math.ceil(strValue.length / charsPerLine); + return lines * lineHeight; }; } /** * @internal */ -export function getDataLinksCounter(): LineCounter { +export function getDataLinksHeightMeasurer(): MeasureCellHeight { const linksCountCache: Record = {}; // when we render links, we need to filter out the invalid links. since the call to `getLinks` is expensive, // we'll cache the result and reuse it for every row in the table. this cache is cleared when line counts are // rebuilt anytime from the `useRowHeight` hook, and that includes adding and removing data links. - return (_value, _width, field) => { + return (_value, _width, field, _rowIdx, lineHeight) => { const cacheKey = getDisplayName(field); if (linksCountCache[cacheKey] === undefined) { let count = 0; @@ -172,7 +174,7 @@ export function getDataLinksCounter(): LineCounter { linksCountCache[cacheKey] = count; } - return linksCountCache[cacheKey]; + return linksCountCache[cacheKey] * lineHeight; }; } @@ -180,10 +182,10 @@ const PILLS_FONT_SIZE = 12; const PILLS_SPACING = 12; // 6px horizontal padding on each side const PILLS_GAP = 4; // gap between pills -export function getPillLineCounter(measureWidth: (value: string) => number): LineCounter { +export function getPillCellHeightMeasurer(measureWidth: (value: string) => number): MeasureCellHeight { const widthCache: Record = {}; - return (value, width) => { + return (value, width, _field, _rowIdx, lineHeight) => { if (value == null) { return 0; } @@ -197,10 +199,11 @@ export function getPillLineCounter(measureWidth: (value: string) => number): Lin let currentLineUse = width; for (const pillValue of pillValues) { - let rawWidth = widthCache[pillValue]; + const strPill = String(pillValue); + let rawWidth = widthCache[strPill]; if (rawWidth === undefined) { - rawWidth = measureWidth(pillValue); - widthCache[pillValue] = rawWidth; + rawWidth = measureWidth(strPill); + widthCache[strPill] = rawWidth; } const pillWidth = rawWidth + PILLS_SPACING; @@ -212,14 +215,19 @@ export function getPillLineCounter(measureWidth: (value: string) => number): Lin } } - return lines; + // default line height happens to be the height of a pill, but maybe we need a custom + // const here to make sure this doesn't get out of sync with the actual pill height. + return lines * lineHeight + (lines - 1) * PILLS_GAP; }; } /** - * @internal return a text line counter for every field which has wrapHeaderText enabled. + * @internal return a text measurer for every field which has wrapHeaderText enabled. */ -export function buildHeaderLineCounters(fields: Field[], typographyCtx: TypographyCtx): LineCounterEntry[] | undefined { +export function buildHeaderHeightMeasurers( + fields: Field[], + typographyCtx: TypographyCtx +): MeasureCellHeightEntry[] | undefined { const wrappedColIdxs = fields.reduce((acc: number[], field, idx) => { if (field.config?.custom?.wrapHeaderText) { acc.push(idx); @@ -233,19 +241,55 @@ export function buildHeaderLineCounters(fields: Field[], typographyCtx: Typograp // don't bother with estimating the line counts for the headers, because it's punishing // when we get it wrong and there won't be that many compared to how many rows a table might contain. - return [{ counter: typographyCtx.wrappedCount, fieldIdxs: wrappedColIdxs }]; + return [{ measure: typographyCtx.measureHeight, fieldIdxs: wrappedColIdxs }]; } const spaceRegex = /[\s-]/; /** - * @internal return a text line counter for every field which has wrapHeaderText enabled. we do this once as we're rendering + * @internal return a text height measurer for every field which has wrapHeaderText enabled. we do this once as we're rendering * the table, and then getRowHeight uses the output of this to caluclate the height of each row. */ -export function buildRowLineCounters(fields: Field[], typographyCtx: TypographyCtx): LineCounterEntry[] | undefined { - const result: Record = {}; +export function buildCellHeightMeasurers( + fields: Field[], + typographyCtx: TypographyCtx +): MeasureCellHeightEntry[] | undefined { + const result: Record = {}; let wrappedFields = 0; + const measurerFactory: Record< + TableCellDisplayMode.Auto | TableCellDisplayMode.DataLinks | TableCellDisplayMode.Pill, + () => [MeasureCellHeight, MeasureCellHeight?] + > = { + // for string fields, we estimate the length of a line using `avgCharWidth` to limit expensive calls `count`. + [TableCellDisplayMode.Auto]: () => [typographyCtx.measureHeight, typographyCtx.estimateHeight], + [TableCellDisplayMode.DataLinks]: () => [getDataLinksHeightMeasurer(), undefined], + // pills use a different font size, so they require their own typography context. + [TableCellDisplayMode.Pill]: () => { + const pillTypographyCtx = createTypographyContext( + PILLS_FONT_SIZE, + typographyCtx.fontFamily, + typographyCtx.letterSpacing + ); + return [ + getPillCellHeightMeasurer((value) => pillTypographyCtx.ctx.measureText(value).width), + getPillCellHeightMeasurer((value) => value.length * pillTypographyCtx.avgCharWidth), + ]; + }, + } as const; + + const setupMeasurerForIdx = (measurerFactoryKey: keyof typeof measurerFactory, fieldIdx: number) => { + if (!result[measurerFactoryKey]) { + const [measure, estimate] = measurerFactory[measurerFactoryKey](); + result[measurerFactoryKey] = { + measure, + estimate, + fieldIdxs: [], + }; + } + result[measurerFactoryKey].fieldIdxs.push(fieldIdx); + }; + for (let fieldIdx = 0; fieldIdx < fields.length; fieldIdx++) { const field = fields[fieldIdx]; if (shouldTextWrap(field)) { @@ -253,36 +297,11 @@ export function buildRowLineCounters(fields: Field[], typographyCtx: TypographyC const cellType = getCellOptions(field).type; if (cellType === TableCellDisplayMode.DataLinks) { - result.dataLinksCounter = result.dataLinksCounter ?? { - counter: getDataLinksCounter(), - fieldIdxs: [], - }; - result.dataLinksCounter.fieldIdxs.push(fieldIdx); + setupMeasurerForIdx(TableCellDisplayMode.DataLinks, fieldIdx); } else if (cellType === TableCellDisplayMode.Pill) { - if (!result.pillCounter) { - const pillTypographyCtx = createTypographyContext( - PILLS_FONT_SIZE, - typographyCtx.fontFamily, - typographyCtx.letterSpacing - ); - - result.pillCounter = { - estimate: getPillLineCounter((value) => value.length * pillTypographyCtx.avgCharWidth), - counter: getPillLineCounter((value) => pillTypographyCtx.ctx.measureText(value).width), - fieldIdxs: [], - }; - } - result.pillCounter.fieldIdxs.push(fieldIdx); - } - - // for string fields, we estimate the length of a line using `avgCharWidth` to limit expensive calls `count`. - else if (field.type === FieldType.string) { - result.textCounter = result.textCounter ?? { - counter: typographyCtx.wrappedCount, - estimate: typographyCtx.estimateLines, - fieldIdxs: [], - }; - result.textCounter.fieldIdxs.push(fieldIdx); + setupMeasurerForIdx(TableCellDisplayMode.Pill, fieldIdx); + } else if (field.type === FieldType.string) { + setupMeasurerForIdx(TableCellDisplayMode.Auto, fieldIdx); } } } @@ -294,10 +313,10 @@ export function buildRowLineCounters(fields: Field[], typographyCtx: TypographyC return Object.values(result); } -// in some cases, the estimator might return a value that is less than 1, but when measured by the counter, it actually +// in some cases, the estimator might return a value that is less than 1, but when calculated by the measurer, it actually // realizes that it's a multi-line cell. to avoid this, we want to give a little buffer away from 1 before we fully trust // the estimator to have told us that a cell is single-line. -export const SINGLE_LINE_ESTIMATE_THRESHOLD = 0.85; +export const SINGLE_LINE_ESTIMATE_THRESHOLD = 18.5; /** * @internal @@ -309,27 +328,25 @@ export function getRowHeight( rowIdx: number, columnWidths: number[], defaultHeight: number, - lineCounters?: LineCounterEntry[], + measurers?: MeasureCellHeightEntry[], lineHeight = TABLE.LINE_HEIGHT, - // when this is a function, the field which was measured as the maximum size will be returned, as well as the - // calculated number of lines, so that the consumer can use it in case the vertical padding value differs field-by-field. - verticalPadding: number | ((field: Field, numLines: number) => number) = TABLE.CELL_PADDING + verticalPadding = TABLE.CELL_PADDING * 2 ): number { - if (!lineCounters?.length) { + if (!measurers?.length) { return defaultHeight; } - let maxLines = -1; + let maxHeight = -1; let maxValue = ''; let maxWidth = 0; let maxField: Field | undefined; - let preciseCounter: LineCounter | undefined; + let preciseMeasurer: MeasureCellHeight | undefined; - for (const { estimate, counter, fieldIdxs } of lineCounters) { - // for some of the line counters, getting the precise count of the lines is expensive. those line counters - // set both an "estimate" and a "counter" function. if the cell we find to be the max was estimated, we will - // get the "true" value right before calculating the row height by hanging onto a reference to the counter fn. - const count = estimate ?? counter; + for (const { estimate, measure, fieldIdxs } of measurers) { + // for some of the cell height measurers, getting the precise height is expensive. those entries set + // both "estimate" and "measure" functions. if the cell we find to be the max was estimated, we will + // get the "true" value right before calculating the row height by keeping a reference to the measure fn. + const measurer = (estimate ?? measure) satisfies MeasureCellHeight; const isEstimating = estimate !== undefined; for (const fieldIdx of fieldIdxs) { @@ -338,38 +355,32 @@ export function getRowHeight( const cellValueRaw = rowIdx === -1 ? getDisplayName(field) : field.values[rowIdx]; if (cellValueRaw != null) { const colWidth = columnWidths[fieldIdx]; - const approxLines = count(cellValueRaw, colWidth, field, rowIdx); - if (approxLines > maxLines) { - maxLines = approxLines; + const estimatedHeight = measurer(cellValueRaw, colWidth, field, rowIdx, lineHeight); + if (estimatedHeight > maxHeight) { + maxHeight = estimatedHeight; maxValue = cellValueRaw; maxWidth = colWidth; maxField = field; - preciseCounter = isEstimating ? counter : undefined; + preciseMeasurer = isEstimating ? measure : undefined; } } } } // if the value is -1 or the estimate for the max cell was less than the SINGLE_LINE_ESTIMATE_THRESHOLD, we trust - // that the estimator correctly identified that no text wrapping is needed for this row, skipping the preciseCounter. - if (maxField === undefined || maxLines < SINGLE_LINE_ESTIMATE_THRESHOLD) { + // that the estimator correctly identified that no text wrapping is needed for this row, skipping the preciseMeasurer. + if (maxField === undefined || maxHeight < SINGLE_LINE_ESTIMATE_THRESHOLD) { return defaultHeight; } // if we finished this row height loop with an estimate, we need to call - // the `preciseCounter` method to get the exact line count. - if (preciseCounter !== undefined) { - maxLines = preciseCounter(maxValue, maxWidth, maxField, rowIdx); + // the `preciseMeasurer` method to get the exact line count. + if (preciseMeasurer !== undefined) { + maxHeight = preciseMeasurer(maxValue, maxWidth, maxField, rowIdx, lineHeight); } - // round up to the nearest line before doing math - maxLines = Math.ceil(maxLines); - - // adjust for vertical padding and line height, and clamp to a minimum default height - const verticalPaddingValue = - typeof verticalPadding === 'function' ? verticalPadding(maxField, maxLines) : verticalPadding; - const totalHeight = maxLines * lineHeight + verticalPaddingValue; - return Math.max(totalHeight, defaultHeight); + // adjust for vertical padding, and clamp to a minimum default height + return Math.max(maxHeight + verticalPadding, defaultHeight); } /** diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index c6da7f2336d..51ddc319f3f 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -137,17 +137,17 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.41.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 90bafdd4714..bec1d40e6bb 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -403,8 +403,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= @@ -414,8 +414,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -425,8 +425,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= @@ -451,14 +451,14 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -472,8 +472,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 78854c60506..ce231290790 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -43,11 +43,11 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index df48bc9bb77..45a22c473b8 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -96,8 +96,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -110,8 +110,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -129,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -142,8 +142,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 41f81051b82..d45b4d47541 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -82,15 +82,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 88081713de9..3e0c96a17c5 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -240,8 +240,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -264,8 +264,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= @@ -291,23 +291,23 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -322,8 +322,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/build/action.go b/pkg/build/action.go deleted file mode 100644 index 5c93a935341..00000000000 --- a/pkg/build/action.go +++ /dev/null @@ -1 +0,0 @@ -package build diff --git a/pkg/build/cmd/fetchimages.go b/pkg/build/cmd/fetchimages.go deleted file mode 100644 index 26260aedb19..00000000000 --- a/pkg/build/cmd/fetchimages.go +++ /dev/null @@ -1,127 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os/exec" - "path/filepath" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/grafana/grafana/pkg/build/docker" - "github.com/grafana/grafana/pkg/build/gcloud" -) - -const ( - alpine = "alpine" - ubuntu = "ubuntu" -) - -// GetImageFiles returns the list of image (.img, but should be .tar because they are tar archives) files that are -// created in the 'tag' process and stored in the prerelease bucket, waiting to be released. -func GetImageFiles(grafana string, version string, architectures []config.Architecture) []string { - bases := []string{alpine, ubuntu} - images := []string{} - for _, base := range bases { - for _, arch := range architectures { - image := fmt.Sprintf("%s-%s-%s.img", grafana, version, arch) - if base == "ubuntu" { - image = fmt.Sprintf("%s-%s-ubuntu-%s.img", grafana, version, arch) - } - - images = append(images, image) - } - } - - return images -} - -func FetchImages(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - metadata, err := config.GenerateMetadata(c) - if err != nil { - return err - } - - buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) - if err != nil { - return err - } - - cfg := docker.Config{ - Archs: buildConfig.Docker.Architectures, - Distribution: buildConfig.Docker.Distribution, - Bucket: buildConfig.Docker.PrereleaseBucket, - Edition: c.String("edition"), - Tag: metadata.GrafanaVersion, - } - - grafana := "grafana" - if cfg.Edition == "enterprise" { - grafana = "grafana-enterprise" - } - if cfg.Edition == "enterprise2" { - grafana = "grafana-enterprise2" - } - if cfg.Edition == "grafana" || cfg.Edition == "oss" { - grafana = "grafana-oss" - } - - baseURL := fmt.Sprintf("gs://%s/%s/", cfg.Bucket, cfg.Tag) - images := GetImageFiles(grafana, cfg.Tag, cfg.Archs) - - log.Printf("Fetching images [%v]", images) - - if err := gcloud.ActivateServiceAccount(); err != nil { - return err - } - if err := DownloadImages(baseURL, images, "."); err != nil { - return err - } - if err := LoadImages(images, "."); err != nil { - return err - } - return nil -} - -// LoadImages uses the `docker load -i` command to load the image tar file into the docker daemon so that it can be -// tagged and pushed. -func LoadImages(images []string, source string) error { - p := filepath.Clean(source) - for _, image := range images { - image := filepath.Join(p, image) - log.Println("Loading image", image) - //nolint:gosec - cmd := exec.Command("docker", "load", "-i", image) - cmd.Dir = "." - out, err := cmd.CombinedOutput() - if err != nil { - log.Printf("out: %s\n", out) - return fmt.Errorf("error loading image: %q", err) - } - log.Println("Loaded image", image) - } - log.Println("Images successfully loaded!") - return nil -} - -func DownloadImages(baseURL string, images []string, destination string) error { - for _, image := range images { - p := baseURL + image - log.Println("Downloading image", p) - //nolint:gosec - cmd := exec.Command("gsutil", "-m", "cp", "-r", p, destination) - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to download: %w\n%s", err, out) - } - } - return nil -} diff --git a/pkg/build/cmd/fetchimages_test.go b/pkg/build/cmd/fetchimages_test.go deleted file mode 100644 index 9452b011a66..00000000000 --- a/pkg/build/cmd/fetchimages_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "testing" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/stretchr/testify/require" -) - -func TestGetImageFiles(t *testing.T) { - var ( - architectures = []config.Architecture{ - config.ArchAMD64, - config.ArchARM64, - config.ArchARMv7, - } - ) - - t.Run("1.2.3", func(t *testing.T) { - expect := []string{ - "grafana-oss-1.2.3-amd64.img", - "grafana-oss-1.2.3-arm64.img", - "grafana-oss-1.2.3-armv7.img", - "grafana-oss-1.2.3-ubuntu-amd64.img", - "grafana-oss-1.2.3-ubuntu-arm64.img", - "grafana-oss-1.2.3-ubuntu-armv7.img", - } - - res := GetImageFiles("grafana-oss", "1.2.3", architectures) - - require.Equal(t, expect, res) - }) - - t.Run("1.2.3+example-01", func(t *testing.T) { - expect := []string{ - "grafana-oss-1.2.3+example-01-amd64.img", - "grafana-oss-1.2.3+example-01-arm64.img", - "grafana-oss-1.2.3+example-01-armv7.img", - "grafana-oss-1.2.3+example-01-ubuntu-amd64.img", - "grafana-oss-1.2.3+example-01-ubuntu-arm64.img", - "grafana-oss-1.2.3+example-01-ubuntu-armv7.img", - } - - res := GetImageFiles("grafana-oss", "1.2.3+example-01", architectures) - - require.Equal(t, expect, res) - }) -} diff --git a/pkg/build/cmd/flags.go b/pkg/build/cmd/flags.go deleted file mode 100644 index f0fb0ee1168..00000000000 --- a/pkg/build/cmd/flags.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import "github.com/urfave/cli/v2" - -var ( - jobsFlag = cli.IntFlag{ - Name: "jobs", - Usage: "Number of parallel jobs", - } - buildIDFlag = cli.StringFlag{ - Name: "build-id", - Usage: "Optionally supply a build ID to be part of the version", - } - editionFlag = cli.StringFlag{ - Name: "edition", - Usage: "The edition of Grafana to build (oss or enterprise)", - Value: "oss", - } - triesFlag = cli.IntFlag{ - Name: "tries", - Usage: "Specify number of tries before failing", - Value: 1, - } - tagFlag = cli.StringFlag{ - Name: "tag", - Usage: "Grafana version tag", - } - securityFlag = cli.BoolFlag{ - Name: "security", - Usage: "Security release", - } - srcFlag = cli.StringFlag{ - Name: "src-bucket", - Value: "grafana-prerelease", - Usage: "Google Cloud Storage bucket", - } - securityDestBucketFlag = cli.StringFlag{ - Name: "security-dest-bucket", - Usage: "Google Cloud Storage bucket for security packages (or $SECURITY_DEST_BUCKET)", - } - destFlag = cli.StringFlag{ - Name: "dest-bucket", - Value: "grafana-downloads", - Usage: "Google Cloud Storage bucket for published packages", - } -) diff --git a/pkg/build/cmd/grafanacom.go b/pkg/build/cmd/grafanacom.go deleted file mode 100644 index 934d888d4ce..00000000000 --- a/pkg/build/cmd/grafanacom.go +++ /dev/null @@ -1,406 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "log" - "net" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "time" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/grafana/grafana/pkg/build/gcloud" - "github.com/grafana/grafana/pkg/build/gcloud/storage" - "github.com/grafana/grafana/pkg/build/gcom" - "github.com/grafana/grafana/pkg/build/packaging" - "github.com/grafana/grafana/pkg/build/versions" -) - -const grafanaAPI = "https://grafana.com/api" - -var httpClient = http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { - return dialer.DialContext - }(&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }), - ForceAttemptHTTP2: true, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - }, -} - -// GrafanaCom implements the sub-command "grafana-com". -func GrafanaCom(c *cli.Context) error { - bucketStr := c.String("src-bucket") - edition := config.Edition(c.String("edition")) - - if err := gcloud.ActivateServiceAccount(); err != nil { - return fmt.Errorf("couldn't activate service account, err: %w", err) - } - - metadata, err := config.GenerateMetadata(c) - if err != nil { - return err - } - - releaseMode, err := metadata.GetReleaseMode() - if err != nil { - return err - } - - version := metadata.GrafanaVersion - semver := versions.ParseSemver(version) - if releaseMode.Mode == config.Cronjob { - gcs, err := storage.New() - if err != nil { - return err - } - bucket := gcs.Bucket(bucketStr) - latestMainVersion, err := storage.GetLatestMainBuild(c.Context, bucket, filepath.Join(string(edition), "main")) - if err != nil { - return err - } - version = latestMainVersion - } - - dryRun := c.Bool("dry-run") - simulateRelease := c.Bool("simulate-release") - // Test release mode and dryRun imply simulateRelease - if releaseMode.IsTest || dryRun { - simulateRelease = true - } - - grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY")) - if grafanaAPIKey == "" { - return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1) - } - - pkgjson, err := getPackageJSON() - if err != nil { - return cli.Exit(err.Error(), 1) - } - - whatsNewURL, releaseNotesURL, err := getReleaseURLs(semver, pkgjson) - if err != nil { - return cli.Exit(err.Error(), 1) - } - - // TODO: Verify config values - cfg := packaging.PublishConfig{ - Config: config.Config{ - Version: version, - }, - Edition: edition, - ReleaseMode: releaseMode, - GrafanaAPIKey: grafanaAPIKey, - WhatsNewURL: whatsNewURL, - ReleaseNotesURL: releaseNotesURL, - DryRun: dryRun, - TTL: c.String("ttl"), - SimulateRelease: simulateRelease, - } - - if err := publishPackages(cfg); err != nil { - return cli.Exit(err.Error(), 1) - } - - log.Println("Successfully published packages to grafana.com!") - return nil -} - -type grafanaConf struct { - WhatsNewURL string `json:"whatsNewUrl"` - ReleaseNotesURL string `json:"releaseNotesUrl"` -} - -type packageConf struct { - Grafana grafanaConf `json:"grafana"` -} - -func getPackageJSON() (*packageConf, error) { - pkgB, err := os.ReadFile("package.json") - if err != nil { - return nil, fmt.Errorf("failed to read package.json: %w", err) - } - - var pconf packageConf - if err := json.Unmarshal(pkgB, &pconf); err != nil { - return nil, fmt.Errorf("failed to decode package.json: %w", err) - } - - return &pconf, nil -} - -func getReleaseURLs(semver versions.Semver, pconf *packageConf) (string, string, error) { - u := fmt.Sprintf(pconf.Grafana.WhatsNewURL, semver.Major, semver.Minor, semver.Patch) - if _, err := url.ParseRequestURI(u); err != nil { - return "", "", fmt.Errorf("grafana.whatsNewUrl is invalid in package.json: %q", pconf.Grafana.WhatsNewURL) - } - if _, err := url.ParseRequestURI(pconf.Grafana.ReleaseNotesURL); err != nil { - return "", "", fmt.Errorf("grafana.releaseNotesUrl is invalid in package.json: %q", - pconf.Grafana.ReleaseNotesURL) - } - - return u, pconf.Grafana.ReleaseNotesURL, nil -} - -func Builds(baseURL *url.URL, grafana, version string, packages []packaging.BuildArtifact) ([]GCOMPackage, error) { - builds := make([]GCOMPackage, len(packages)) - for i, v := range packages { - var ( - os = v.Distro - arch = v.Arch - ) - - if v.Distro == "windows" { - os = "win" - if v.Ext == "msi" { - os = "win-installer" - } - } - - if v.Distro == "rhel" { - if arch == "aarch64" { - arch = "arm64" - } - if arch == "x86_64" { - arch = "amd64" - } - } - - if v.Distro == "deb" { - if arch == "armhf" { - arch = "armv7" - if v.RaspberryPi { - log.Println(v.Distro, arch, "raspberrypi == true") - arch = "armv6" - } - } - } - - u := gcom.GetURL(baseURL, version, grafana, v.Distro, v.Arch, v.Ext, v.Musl, v.RaspberryPi) - builds[i] = GCOMPackage{ - OS: os, - URL: u.String(), - Arch: arch, - } - } - - return builds, nil -} - -// publishPackages publishes packages to grafana.com. -func publishPackages(cfg packaging.PublishConfig) error { - log.Printf("Publishing Grafana packages, version %s, %s edition, %s mode, dryRun: %v, simulating: %v...\n", - cfg.Version, cfg.Edition, cfg.ReleaseMode.Mode, cfg.DryRun, cfg.SimulateRelease) - - versionStr := fmt.Sprintf("v%s", cfg.Version) - log.Printf("Creating release %s at grafana.com...\n", versionStr) - - var ( - pth string - grafana = "grafana" - ) - - switch cfg.Edition { - case config.EditionOSS: - pth = "oss" - case config.EditionEnterprise: - grafana = "grafana-enterprise" - pth = "enterprise" - default: - return fmt.Errorf("unrecognized edition %q", cfg.Edition) - } - - switch cfg.ReleaseMode.Mode { - case config.MainMode, config.DownstreamMode, config.CronjobMode: - pth = path.Join(pth, packaging.MainFolder) - default: - pth = path.Join(pth, packaging.ReleaseFolder) - } - - pth = path.Join(pth) - baseArchiveURL := &url.URL{ - Scheme: "https", - Host: "dl.grafana.com", - Path: pth, - } - - builds, err := Builds(baseArchiveURL, grafana, cfg.Version, packaging.ArtifactConfigs) - if err != nil { - return err - } - - r := Release{ - Version: cfg.Version, - ReleaseDate: time.Now().UTC(), - Builds: builds, - Stable: cfg.ReleaseMode.Mode == config.TagMode && !cfg.ReleaseMode.IsPreview && !cfg.ReleaseMode.IsTest, - Beta: cfg.ReleaseMode.IsPreview, - Nightly: cfg.ReleaseMode.Mode == config.CronjobMode, - } - if cfg.ReleaseMode.Mode == config.TagMode || r.Beta { - r.WhatsNewURL = cfg.WhatsNewURL - r.ReleaseNotesURL = cfg.ReleaseNotesURL - } - - if err := postRequest(cfg, "versions", r, fmt.Sprintf("create release %s", r.Version)); err != nil { - return err - } - - if err := postRequest(cfg, fmt.Sprintf("versions/%s", cfg.Version), r, - fmt.Sprintf("update release %s", cfg.Version)); err != nil { - return err - } - - for i, v := range r.Builds { - sha, err := getSHA256(v.URL) - if err != nil { - return err - } - - r.Builds[i].SHA256 = string(sha) - } - - for _, b := range r.Builds { - if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages", cfg.Version), b, - fmt.Sprintf("create build %s %s", b.OS, b.Arch)); err != nil { - return err - } - if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages/%s/%s", cfg.Version, b.Arch, b.OS), b, - fmt.Sprintf("update build %s %s", b.OS, b.Arch)); err != nil { - return err - } - } - - return nil -} - -func getSHA256(u string) ([]byte, error) { - shaURL := fmt.Sprintf("%s.sha256", u) - - // nolint:gosec - resp, err := http.Get(shaURL) - if err != nil { - return nil, err - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Println("failed to close response body, err: %w", err) - } - }() - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, fmt.Errorf("failed downloading %s: %s", u, resp.Status) - } - - sha256, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return sha256, nil -} - -func postRequest(cfg packaging.PublishConfig, pth string, body any, descr string) error { - var sfx string - switch cfg.Edition { - case config.EditionOSS: - case config.EditionEnterprise: - sfx = packaging.EnterpriseSfx - default: - return fmt.Errorf("unrecognized edition %q", cfg.Edition) - } - product := fmt.Sprintf("grafana%s", sfx) - - jsonB, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("failed to JSON encode release: %w", err) - } - - u, err := constructURL(product, pth) - if err != nil { - return err - } - req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(jsonB)) - if err != nil { - return err - } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", cfg.GrafanaAPIKey)) - req.Header.Add("Content-Type", "application/json") - - log.Printf("Posting to grafana.com API, %s - JSON: %s\n", u, string(jsonB)) - if cfg.SimulateRelease { - log.Println("Only simulating request") - return nil - } - - resp, err := httpClient.Do(req) - if err != nil { - return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Println("failed to close response body, err: %w", err) - } - }() - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - body, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") { - log.Printf("Already exists: %s\n", descr) - return nil - } - - return fmt.Errorf("failed posting to %s (%s): %s", u, descr, resp.Status) - } - - log.Printf("Successfully posted to grafana.com API, %s\n", u) - - return nil -} - -func constructURL(product string, pth string) (string, error) { - productPath := filepath.Clean(filepath.Join("/", product, pth)) - u, err := url.Parse(grafanaAPI) - if err != nil { - return "", err - } - u.Path = path.Join(u.Path, productPath) - return u.String(), err -} - -type GCOMPackage struct { - OS string `json:"os"` - URL string `json:"url"` - SHA256 string `json:"sha256"` - Arch string `json:"arch"` -} - -type Release struct { - Version string `json:"version"` - ReleaseDate time.Time `json:"releaseDate"` - Stable bool `json:"stable"` - Beta bool `json:"beta"` - Nightly bool `json:"nightly"` - WhatsNewURL string `json:"whatsNewUrl"` - ReleaseNotesURL string `json:"releaseNotesUrl"` - Builds []GCOMPackage `json:"-"` -} diff --git a/pkg/build/cmd/grafanacom_test.go b/pkg/build/cmd/grafanacom_test.go deleted file mode 100644 index 1b0eb0bcf20..00000000000 --- a/pkg/build/cmd/grafanacom_test.go +++ /dev/null @@ -1,284 +0,0 @@ -package main - -import ( - "fmt" - "net/url" - "path" - "testing" - - "github.com/grafana/grafana/pkg/build/packaging" - "github.com/grafana/grafana/pkg/build/versions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_constructURL(t *testing.T) { - type args struct { - product string - pth string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - {name: "cleans .. sequence", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false}, - {name: "doesn't clean anything - non malicious url", args: args{"foo", "bar"}, want: "https://grafana.com/api/foo/bar", wantErr: false}, - {name: "doesn't clean anything - three dots", args: args{"...", "..."}, want: "https://grafana.com/api/.../...", wantErr: false}, - {name: "cleans .", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := constructURL(tt.args.product, tt.args.pth) - if (err != nil) != tt.wantErr { - t.Errorf("constructURL() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("constructURL() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestBuilds(t *testing.T) { - baseURL := &url.URL{ - Scheme: "https", - Host: "dl.example.com", - Path: path.Join("oss", "release"), - } - - version := "1.2.3" - grafana := "grafana" - packages := []packaging.BuildArtifact{ - { - Distro: "deb", - Arch: "arm64", - Ext: "deb", - }, - { - Distro: "rhel", - Arch: "aarch64", - Ext: "rpm", - }, - { - Distro: "linux", - Arch: "arm64", - Ext: "tar.gz", - }, - { - Distro: "deb", - Arch: "armhf", - Ext: "deb", - RaspberryPi: true, - }, - { - Distro: "deb", - Arch: "armhf", - Ext: "deb", - }, - { - Distro: "linux", - Arch: "armv7", - Ext: "tar.gz", - }, - { - Distro: "windows", - Arch: "amd64", - Ext: "zip", - }, - { - Distro: "windows", - Arch: "amd64", - Ext: "msi", - }, - } - - expect := []GCOMPackage{ - { - URL: "https://dl.example.com/oss/release/grafana_1.2.3_arm64.deb", - OS: "deb", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3-1.aarch64.rpm", - OS: "rhel", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-arm64.tar.gz", - OS: "linux", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3_armhf.deb", - OS: "deb", - Arch: "armv6", - }, - { - URL: "https://dl.example.com/oss/release/grafana_1.2.3_armhf.deb", - OS: "deb", - Arch: "armv7", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-armv7.tar.gz", - OS: "linux", - Arch: "armv7", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.zip", - OS: "win", - Arch: "amd64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.msi", - OS: "win-installer", - Arch: "amd64", - }, - } - - builds, err := Builds(baseURL, grafana, version, packages) - require.NoError(t, err) - require.Equal(t, len(expect), len(builds)) - - for i := range builds { - t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) { - assert.Equal(t, expect[i].URL, builds[i].URL) - assert.Equal(t, expect[i].OS, builds[i].OS) - assert.Equal(t, expect[i].Arch, builds[i].Arch) - }) - } -} - -func TestBuildsWithPlus(t *testing.T) { - baseURL := &url.URL{ - Scheme: "https", - Host: "dl.example.com", - Path: path.Join("oss", "release"), - } - - version := "1.2.3+example-01" - grafana := "grafana" - packages := []packaging.BuildArtifact{ - { - Distro: "deb", - Arch: "arm64", - Ext: "deb", - }, - { - Distro: "rhel", - Arch: "aarch64", - Ext: "rpm", - }, - { - Distro: "linux", - Arch: "arm64", - Ext: "tar.gz", - }, - { - Distro: "deb", - Arch: "armhf", - Ext: "deb", - RaspberryPi: true, - }, - { - Distro: "deb", - Arch: "armhf", - Ext: "deb", - }, - { - Distro: "linux", - Arch: "armv7", - Ext: "tar.gz", - }, - { - Distro: "windows", - Arch: "amd64", - Ext: "zip", - }, - { - Distro: "windows", - Arch: "amd64", - Ext: "msi", - }, - } - - expect := []GCOMPackage{ - { - URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_arm64.deb", - OS: "deb", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3+example~01-1.aarch64.rpm", - OS: "rhel", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-arm64.tar.gz", - OS: "linux", - Arch: "arm64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3+example~01_armhf.deb", - OS: "deb", - Arch: "armv6", - }, - { - URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_armhf.deb", - OS: "deb", - Arch: "armv7", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-armv7.tar.gz", - OS: "linux", - Arch: "armv7", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.zip", - OS: "win", - Arch: "amd64", - }, - { - URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.msi", - OS: "win-installer", - Arch: "amd64", - }, - } - - builds, err := Builds(baseURL, grafana, version, packages) - require.NoError(t, err) - require.Equal(t, len(expect), len(builds)) - - for i := range builds { - t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) { - assert.Equal(t, expect[i].URL, builds[i].URL) - assert.Equal(t, expect[i].OS, builds[i].OS) - assert.Equal(t, expect[i].Arch, builds[i].Arch) - }) - } -} - -func TestReleaseURLs(t *testing.T) { - f := "https://grafana.com/whats-new-in-v%[1]s-%[2]s" - - smv := versions.Semver{ - Major: "1", - Minor: "2", - Patch: "3", - } - - conf := packageConf{ - Grafana: grafanaConf{ - WhatsNewURL: f, - ReleaseNotesURL: "https://example.com", - }, - } - - expect := "https://grafana.com/whats-new-in-v1-2" - - a, _, err := getReleaseURLs(smv, &conf) - require.NoError(t, err) - require.Equal(t, expect, a) -} diff --git a/pkg/build/cmd/main.go b/pkg/build/cmd/main.go index 8b2152c42a5..3fb66dfd67e 100644 --- a/pkg/build/cmd/main.go +++ b/pkg/build/cmd/main.go @@ -4,18 +4,9 @@ import ( "log" "os" - "github.com/grafana/grafana/pkg/build/cmd/util" "github.com/grafana/grafana/pkg/build/daggerbuild/cmd" - "github.com/urfave/cli/v2" ) -var additionalCommands []*cli.Command = make([]*cli.Command, 0, 5) - -//nolint:unused -func registerAppCommand(c *cli.Command) { - additionalCommands = append(additionalCommands, c) -} - func main() { // TODO change the registerer if the user is running using a JSON file etc for k, v := range cmd.Artifacts { @@ -25,272 +16,6 @@ func main() { } app := cmd.GlobalCLI.App() - artifactsCommand := cmd.GlobalCLI.ArtifactsCommand() - artifactsCommand.Subcommands = cli.Commands{ - { - Name: "storybook", - Usage: "[ARCHIVED] Publish Grafana storybook", - Action: PublishStorybookAction, - Flags: []cli.Flag{ - &editionFlag, - &tagFlag, - &srcFlag, - &cli.StringFlag{ - Name: "storybook-bucket", - Value: "grafana-storybook", - Usage: "Google Cloud Storage bucket for storybooks", - }, - }, - }, - { - Name: "static-assets", - Usage: "[ARCHIVED] Publish Grafana static assets", - Action: PublishStaticAssetsAction, - Flags: []cli.Flag{ - &editionFlag, - &securityFlag, - &securityDestBucketFlag, - &tagFlag, - &srcFlag, - &destFlag, - &cli.StringFlag{ - Name: "static-assets-bucket", - Value: "grafana-static-assets", - Usage: "Google Cloud Storage bucket for static assets", - }, - &cli.StringSliceFlag{ - Name: "static-asset-editions", - Usage: "All the editions of the static assets (or $STATIC_ASSET_EDITIONS)", - }, - }, - }, - { - Name: "packages", - Usage: "[ARCHIVED] Publish Grafana packages", - Action: PublishArtifactsAction, - Flags: []cli.Flag{ - &editionFlag, - &securityFlag, - &securityDestBucketFlag, - &tagFlag, - &srcFlag, - &destFlag, - &cli.StringSliceFlag{ - Name: "artifacts-editions", - Value: cli.NewStringSlice("oss", "enterprise", "enterprise2"), - Usage: "Editions for which the artifacts should be delivered (oss,enterprise,enterprise2), (or $ARTIFACTS_EDITIONS)", - }, - &cli.StringFlag{ - Name: "enterprise2-dest-bucket", - Value: "grafana-downloads-enterprise2", - Usage: "Google Cloud Storage bucket for published packages", - }, - &cli.StringFlag{ - Name: "enterprise2-security-prefix", - Usage: "Bucket path prefix for enterprise2 security releases (or $ENTERPRISE2_SECURITY_PREFIX)", - }, - }, - }, - { - Name: "docker", - Usage: "[ARCHIVED] Handle Grafana Docker images", - Subcommands: cli.Commands{ - { - Name: "fetch", - Usage: "Fetch Grafana Docker images", - ArgsUsage: "[version]", - Action: util.MaxArgCountWrapper(1, FetchImages), - Flags: []cli.Flag{ - &editionFlag, - }, - }, - }, - }, - { - Name: "npm", - Usage: "[ARCHIVED] Handle Grafana npm packages", - Subcommands: cli.Commands{ - { - Name: "release", - Usage: "Release npm packages", - ArgsUsage: "[version]", - Action: NpmReleaseAction, - Flags: []cli.Flag{ - &tagFlag, - }, - }, - { - Name: "store", - Usage: "Store npm packages tarball", - Action: NpmStoreAction, - Flags: []cli.Flag{ - &tagFlag, - }, - }, - { - Name: "retrieve", - Usage: "Retrieve npm packages tarball", - Action: NpmRetrieveAction, - Flags: []cli.Flag{ - &tagFlag, - }, - }, - }, - }, - } - app.Commands = append(app.Commands, []*cli.Command{ - { - Name: "e2e-tests", - Usage: "[ARCHIVED] Run Grafana e2e tests", - Action: EndToEndTests, - Flags: []cli.Flag{ - &triesFlag, - &cli.IntFlag{ - Name: "port", - Value: 3001, - Usage: "Specify the server port", - }, - &cli.StringFlag{ - Name: "suite", - Usage: "Specify the end-to-end tests suite to be used", - }, - &cli.StringFlag{ - Name: "host", - Value: "grafana-server", - Usage: "Specify the server host", - }, - &cli.StringFlag{ - Name: "video", - Value: "true", - Usage: "Specify if videos should be recorded", - }, - }, - }, - { - Name: "upload-cdn", - Usage: "[ARCHIVED] Upload public/* to a cdn bucket", - Action: UploadCDN, - Flags: []cli.Flag{ - &editionFlag, - }, - }, - { - Name: "publish-metrics", - Usage: "[ARCHIVED] Publish a set of metrics from stdin", - ArgsUsage: "", - Action: util.MaxArgCountWrapper(1, PublishMetrics), - }, - { - Name: "verify-drone", - Usage: "[ARCHIVED] Verify Drone configuration", - Action: VerifyDrone, - }, - { - Name: "store-storybook", - Usage: "[ARCHIVED] Stores storybook to GCS buckets", - Action: StoreStorybook, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "deployment", - Usage: "Kind of deployment (e.g. canary/latest)", - }, - }, - }, - { - Name: "verify-storybook", - Usage: "[ARCHIVED] Integrity check for storybook build", - Action: VerifyStorybook, - }, - { - Name: "upload-packages", - Usage: "[ARCHIVED] Upload Grafana packages", - Action: UploadPackages, - Flags: []cli.Flag{ - &jobsFlag, - &editionFlag, - &cli.BoolFlag{ - Name: "enterprise2", - Usage: "Declare if the edition is enterprise2", - }, - }, - }, - artifactsCommand, - { - Name: "publish", - Usage: "[ARCHIVED] Publish packages to Grafana com and repositories", - Subcommands: cli.Commands{ - { - Name: "grafana-com", - Usage: "Publish packages to grafana.com", - Action: GrafanaCom, - Flags: []cli.Flag{ - &editionFlag, - &buildIDFlag, - &util.DryRunFlag, - &cli.StringFlag{ - Name: "src-bucket", - Value: "grafana-downloads", - Usage: "Google Cloud Storage bucket", - }, - }, - }, - { - Name: "github", - Usage: "Publish packages to GitHub releases", - Action: PublishGithub, - Flags: []cli.Flag{ - &util.DryRunFlag, - &cli.StringFlag{ - Name: "path", - Usage: "Path to the asset to be published", - }, - &cli.StringFlag{ - Name: "repo", - Required: true, - Usage: "GitHub repository", - }, - &cli.StringFlag{ - Name: "tag", - Usage: "Release tag (default from metadata)", - }, - &cli.BoolFlag{ - Name: "create", - Usage: "Create release if it doesn't exist", - }, - }, - }, - { - Name: "aws", - Usage: "Publish image to AWS Marketplace releases", - Action: PublishAwsMarketplace, - Flags: []cli.Flag{ - &util.DryRunFlag, - &cli.StringFlag{ - Name: "version", - Usage: "Release version (default from metadata)", - }, - &cli.StringFlag{ - Name: "image", - Required: true, - Usage: "Name of the image to be released", - }, - &cli.StringFlag{ - Name: "repo", - Required: true, - Usage: "AWS Marketplace ECR repository", - }, - &cli.StringFlag{ - Name: "product", - Required: true, - Usage: "AWS Marketplace product identifier", - }, - }, - }, - }, - }, - }...) - - app.Commands = append(app.Commands, additionalCommands...) if err := app.Run(os.Args); err != nil { log.Fatalln(err) diff --git a/pkg/build/cmd/npm.go b/pkg/build/cmd/npm.go deleted file mode 100644 index 4660b22b636..00000000000 --- a/pkg/build/cmd/npm.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "strings" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/npm" -) - -func NpmRetrieveAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - tag := c.String("tag") - if tag == "" { - return fmt.Errorf("no tag version specified, exitting") - } - - if strings.Contains(tag, "security") { - log.Printf("skipping npm publish because version '%s' has 'security'", tag) - return nil - } - - prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET")) - if prereleaseBucket == "" { - return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1) - } - - err := npm.FetchNpmPackages(c.Context, tag, prereleaseBucket) - if err != nil { - return err - } - return nil -} - -func NpmStoreAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - tag := c.String("tag") - if tag == "" { - return fmt.Errorf("no tag version specified, exiting") - } - - if strings.Contains(tag, "security") { - log.Printf("skipping npm publish because version '%s' has 'security'", tag) - return nil - } - - prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET")) - if prereleaseBucket == "" { - return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1) - } - - err := npm.StoreNpmPackages(c.Context, tag, prereleaseBucket) - if err != nil { - return err - } - return nil -} - -func NpmReleaseAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - tag := c.String("tag") - if tag == "" { - return fmt.Errorf("no tag version specified, exitting") - } - - if strings.Contains(tag, "security") { - log.Printf("skipping npm publish because version '%s' has 'security'", tag) - return nil - } - - err := npm.PublishNpmPackages(c.Context, tag) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/build/cmd/publishartifacts.go b/pkg/build/cmd/publishartifacts.go deleted file mode 100644 index bf3f162e362..00000000000 --- a/pkg/build/cmd/publishartifacts.go +++ /dev/null @@ -1,112 +0,0 @@ -package main - -import ( - "fmt" - "log" - "path/filepath" - "strings" - - "github.com/grafana/grafana/pkg/build/env" - "github.com/grafana/grafana/pkg/build/gcloud" - "github.com/grafana/grafana/pkg/build/gcloud/storage" - "github.com/urfave/cli/v2" -) - -// PublishArtifactsAction Action implements the sub-command "publish-artifacts". -func PublishArtifactsAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - security := c.Bool("security") - var securityDestBucket, enterprise2SecurityPrefix string - - artifactsEditions, err := env.RequireListWithEnvFallback(c, "artifacts-editions", "ARTIFACTS_EDITIONS") - if err != nil { - return err - } - - if security { - securityDestBucket, err = env.RequireStringWithEnvFallback(c, "security-dest-bucket", "SECURITY_DEST_BUCKET") - if err != nil { - return err - } - enterprise2SecurityPrefix, err = env.RequireStringWithEnvFallback(c, "enterprise2-security-prefix", "ENTERPRISE2_SECURITY_PREFIX") - if err != nil { - return err - } - } - - if err := gcloud.ActivateServiceAccount(); err != nil { - return fmt.Errorf("error connecting to gcp, %q", err) - } - - cfg := publishConfig{ - srcBucket: c.String("src-bucket"), - destBucket: c.String("dest-bucket"), - enterprise2DestBucket: c.String("enterprise2-dest-bucket"), - enterprise2SecurityPrefix: enterprise2SecurityPrefix, - security: security, - tag: strings.TrimPrefix(c.String("tag"), "v"), - } - - if cfg.security { - cfg.destBucket = securityDestBucket - } - - gcs, err := storage.New() - if err != nil { - return err - } - - for _, edition := range artifactsEditions { - switch edition { - case "oss", "enterprise": - err = copyArtifacts(c, gcs, cfg, edition) - if err != nil { - return err - } - case "enterprise2": - err = copyEnterprise2Artifacts(c, gcs, cfg) - if err != nil { - return err - } - default: - log.Printf("unrecognised artifacts edition: %s\n", edition) - } - } - - return nil -} - -func copyArtifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig, edition string) error { - bucket := gcs.Bucket(cfg.destBucket) - destURL := edition - if !cfg.security { - destURL = filepath.Join(destURL, "release") - } - log.Printf("Copying downloads for %s, from %s bucket to %s bucket", edition, cfg.srcBucket, destURL) - if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads/v%s/%s/release", cfg.tag, edition), bucket, destURL); err != nil { - return err - } - - log.Printf("Successfully copied downloads.") - return nil -} - -func copyEnterprise2Artifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig) error { - bucket := gcs.Bucket(cfg.enterprise2DestBucket) - var prefix string - if cfg.security { - prefix = cfg.enterprise2SecurityPrefix - } - destURL := fmt.Sprintf("enterprise2/%srelease", prefix) - log.Printf("Copying downloads for enterprise2, from %s bucket to %s bucket", cfg.srcBucket, destURL) - if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads-enterprise2/v%s/enterprise2/release", cfg.tag), bucket, destURL); err != nil { - return err - } - return nil -} diff --git a/pkg/build/cmd/publishaws.go b/pkg/build/cmd/publishaws.go deleted file mode 100644 index f48f821a0ed..00000000000 --- a/pkg/build/cmd/publishaws.go +++ /dev/null @@ -1,310 +0,0 @@ -package main - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/aws/aws-sdk-go/service/marketplacecatalog" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/client" - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" -) - -const ( - marketplaceChangeSetName = "Add new version" - marketplaceCatalogId = "AWSMarketplace" - marketplaceRegistryId = "709825985650" - marketplaceRegistryRegion = "us-east-1" - marketplaceRegistryUrl = "709825985650.dkr.ecr.us-east-1.amazonaws.com" - marketplaceRequestsUrl = "https://aws.amazon.com/marketplace/management/requests/" - releaseNotesTemplateUrl = "https://grafana.com/docs/grafana/latest/release-notes/release-notes-${TAG}/" - helmChartsUrl = "https://grafana.github.io/helm-charts/" - docsUrl = "https://grafana.com/docs/grafana/latest/enterprise/license/" - imagePlatform = "linux/amd64" - - publishAwsMarketplaceTestKey publishAwsMarketplaceTestKeyType = "test-client" -) - -var ( - errEmptyVersion = errors.New(`failed to retrieve release version from metadata, use "--version" to set it manually`) -) - -type publishAwsMarketplaceTestKeyType string - -type publishAwsMarketplaceFlags struct { - dryRun bool - version string - repo string - image string - product string -} - -type AwsMarketplacePublishingService struct { - auth string - docker AwsMarketplaceDocker - ecr AwsMarketplaceRegistry - mkt AwsMarketplaceCatalog -} - -type AwsMarketplaceDocker interface { - ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) - ImageTag(ctx context.Context, source string, target string) error - ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) -} - -type AwsMarketplaceRegistry interface { - GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) -} - -type AwsMarketplaceCatalog interface { - DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) - StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) -} - -func PublishAwsMarketplace(ctx *cli.Context) error { - f, err := getPublishAwsMarketplaceFlags(ctx) - if err != nil { - return err - } - - if f.version == "" { - return errEmptyVersion - } - - svc, err := getAwsMarketplacePublishingService() - if err != nil { - return err - } - - if ctx.Context.Value(publishAwsMarketplaceTestKey) != nil { - svc = ctx.Context.Value(publishAwsMarketplaceTestKey).(*AwsMarketplacePublishingService) - } - - fmt.Println("Logging in to AWS Marketplace registry") - err = svc.Login(ctx.Context) - if err != nil { - return err - } - - fmt.Printf("Retrieving image '%s:%s' from Docker Hub\n", f.image, f.version) - err = svc.PullImage(ctx.Context, f.image, f.version) - if err != nil { - return err - } - - fmt.Printf("Renaming image '%s:%s' to '%s/%s:%s'\n", f.image, f.version, marketplaceRegistryUrl, f.repo, f.version) - err = svc.TagImage(ctx.Context, f.image, f.repo, f.version) - if err != nil { - return err - } - - if !f.dryRun { - fmt.Printf("Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version) - err = svc.PushToMarketplace(ctx.Context, f.repo, f.version) - if err != nil { - return err - } - } else { - fmt.Printf("Dry-Run: Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version) - } - - fmt.Printf("Retrieving product identifier for product '%s'\n", f.product) - pid, err := svc.GetProductIdentifier(ctx.Context, f.product) - if err != nil { - return err - } - - if !f.dryRun { - fmt.Printf("Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl) - return svc.ReleaseToProduct(ctx.Context, pid, f.repo, f.version) - } else { - fmt.Printf("Dry-Run: Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl) - } - - return nil -} - -func getAwsMarketplacePublishingService() (*AwsMarketplacePublishingService, error) { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - - mySession := session.Must(session.NewSession()) - ecr := ecr.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion)) - mkt := marketplacecatalog.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion)) - return &AwsMarketplacePublishingService{ - docker: cli, - ecr: ecr, - mkt: mkt, - }, nil -} - -func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error { - out, err := s.ecr.GetAuthorizationTokenWithContext(ctx, &ecr.GetAuthorizationTokenInput{}) - if err != nil { - return err - } - s.auth = *out.AuthorizationData[0].AuthorizationToken - authData, err := base64.StdEncoding.DecodeString(s.auth) - if err != nil { - return err - } - authString := strings.Split(string(authData), ":") - authData, err = json.Marshal(registry.AuthConfig{ - Username: authString[0], - Password: authString[1], - }) - s.auth = base64.StdEncoding.EncodeToString(authData) - return err -} - -func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, img string, version string) error { - reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", img, version), image.PullOptions{ - Platform: imagePlatform, - }) - if err != nil { - return err - } - - _, err = io.Copy(os.Stdout, reader) - if err != nil { - return err - } - - err = reader.Close() - if err != nil { - return err - } - return nil -} - -func (s *AwsMarketplacePublishingService) TagImage(ctx context.Context, image string, repo string, version string) error { - err := s.docker.ImageTag(ctx, fmt.Sprintf("%s:%s", image, version), fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version)) - if err != nil { - return err - } - return nil -} - -func (s *AwsMarketplacePublishingService) PushToMarketplace(ctx context.Context, repo string, version string) error { - reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), image.PushOptions{ - RegistryAuth: s.auth, - }) - if err != nil { - return err - } - - _, err = io.Copy(os.Stdout, reader) - if err != nil { - return err - } - - err = reader.Close() - if err != nil { - return err - } - return nil -} - -func (s *AwsMarketplacePublishingService) GetProductIdentifier(ctx context.Context, product string) (string, error) { - out, err := s.mkt.DescribeEntityWithContext(ctx, &marketplacecatalog.DescribeEntityInput{ - EntityId: aws.String(product), - Catalog: aws.String(marketplaceCatalogId), - }) - if err != nil { - return "", err - } - return *out.EntityIdentifier, nil -} - -func (s *AwsMarketplacePublishingService) ReleaseToProduct(ctx context.Context, pid string, repo string, version string) error { - _, err := s.mkt.StartChangeSetWithContext(ctx, &marketplacecatalog.StartChangeSetInput{ - Catalog: aws.String(marketplaceCatalogId), - ChangeSetName: aws.String(marketplaceChangeSetName), - ChangeSet: []*marketplacecatalog.Change{ - buildAwsMarketplaceChangeSet(pid, repo, version), - }, - }) - return err -} - -func getPublishAwsMarketplaceFlags(ctx *cli.Context) (*publishAwsMarketplaceFlags, error) { - metadata, err := config.GenerateMetadata(ctx) - if err != nil { - return nil, err - } - version := ctx.String("version") - if version == "" && metadata.GrafanaVersion != "" { - version = metadata.GrafanaVersion - } - image := ctx.String("image") - repo := ctx.String("repo") - product := ctx.String("product") - dryRun := ctx.Bool("dry-run") - return &publishAwsMarketplaceFlags{ - dryRun: dryRun, - version: version, - image: image, - repo: repo, - product: product, - }, nil -} - -func buildAwsMarketplaceReleaseNotesUrl(version string) string { - sanitizedVersion := strings.ReplaceAll(version, ".", "-") - return strings.ReplaceAll(releaseNotesTemplateUrl, "${TAG}", sanitizedVersion) -} - -func buildAwsMarketplaceChangeSet(entityId string, repo string, version string) *marketplacecatalog.Change { - return &marketplacecatalog.Change{ - ChangeType: aws.String("AddDeliveryOptions"), - Entity: &marketplacecatalog.Entity{ - Type: aws.String("ContainerProduct@1.0"), - Identifier: aws.String(entityId), - }, - Details: aws.String(buildAwsMarketplaceVersionDetails(repo, version)), - } -} - -func buildAwsMarketplaceVersionDetails(repo string, version string) string { - releaseNotesUrl := buildAwsMarketplaceReleaseNotesUrl(version) - return fmt.Sprintf(`{ - "Version": { - "ReleaseNotes": "Release notes are available on the website %s", - "VersionTitle": "v%s" - }, - "DeliveryOptions": [ - { - "Details": { - "EcrDeliveryOptionDetails": { - "DeploymentResources": [ - { - "Name": "Helm Charts", - "Url": "%s" - } - ], - "CompatibleServices": ["EKS", "ECS", "ECS-Anywhere", "EKS-Anywhere"], - "ContainerImages": ["%s/%s:%s"], - "Description": "Grafana Enterprise can be installed using the official Grafana Helm chart repository. The repository is available on Github: %s", - "UsageInstructions": "You can apply your Grafana Enterprise license to a new or existing Grafana Enterprise deployment by updating a configuration setting or environment variable. Your Grafana instance must be deployed on AWS, or have network access to AWS. For more information, see %s" - } - }, - "DeliveryOptionTitle": "Helm Chart" - } - ] - }`, releaseNotesUrl, version, helmChartsUrl, marketplaceRegistryUrl, repo, version, helmChartsUrl, docsUrl) -} diff --git a/pkg/build/cmd/publishaws_test.go b/pkg/build/cmd/publishaws_test.go deleted file mode 100644 index af3782b1571..00000000000 --- a/pkg/build/cmd/publishaws_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - "io" - "os" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/aws/aws-sdk-go/service/marketplacecatalog" - "github.com/docker/docker/api/types/image" - "github.com/grafana/grafana/pkg/build/cmd/util" - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" -) - -type awsPublishTestCase struct { - name string - args []string - expectedError error - errorContains string - expectedOutput string - mockedService *AwsMarketplacePublishingService -} - -func TestPublishAwsMarketplace(t *testing.T) { - t.Setenv("DRONE_BUILD_EVENT", "promote") - t.Setenv("DRONE_TAG", "v1.0.0") - t.Setenv("DRONE_COMMIT", "abcdefgh") - testApp := setupPublishAwsMarketplaceTests(t) - errShouldNotCallMock := errors.New("shouldn't call") - - testCases := []awsPublishTestCase{ - { - name: "try to publish without required flags", - errorContains: `Required flags "image, repo, product" not set`, - }, - { - name: "try to publish without credentials", - args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, - mockedService: &AwsMarketplacePublishingService{ - ecr: &mockAwsMarketplaceRegistry{ - GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain, - }, - }, - expectedError: credentials.ErrNoValidProvidersFoundInChain, - }, - { - name: "try to publish with valid credentials and nonexisting version", - args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, - mockedService: &AwsMarketplacePublishingService{ - ecr: &mockAwsMarketplaceRegistry{}, - docker: &mockAwsMarketplaceDocker{}, - mkt: &mockAwsMarketplaceCatalog{}, - }, - expectedOutput: "Releasing to product", - }, - { - name: "try to publish with valid credentials and existing version", - args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, - mockedService: &AwsMarketplacePublishingService{ - ecr: &mockAwsMarketplaceRegistry{}, - docker: &mockAwsMarketplaceDocker{}, - mkt: &mockAwsMarketplaceCatalog{}, - }, - expectedOutput: "Releasing to product", - }, - { - name: "dry run with invalid credentials", - args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, - mockedService: &AwsMarketplacePublishingService{ - ecr: &mockAwsMarketplaceRegistry{ - GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain, - }, - }, - expectedError: credentials.ErrNoValidProvidersFoundInChain, - }, - { - name: "dry run with valid credentials", - args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"}, - mockedService: &AwsMarketplacePublishingService{ - ecr: &mockAwsMarketplaceRegistry{}, - docker: &mockAwsMarketplaceDocker{ - ImagePushError: errShouldNotCallMock, - }, - mkt: &mockAwsMarketplaceCatalog{ - StartChangeSetWithContextError: errShouldNotCallMock, - }, - }, - expectedOutput: "Dry-Run: Releasing to product", - }, - } - - if os.Getenv("DRONE_COMMIT") == "" { - // this test only works locally due to Drone environment - testCases = append(testCases, - awsPublishTestCase{ - name: "try to publish without version", - args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test"}, - expectedError: errEmptyVersion, - }, - ) - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - ctx := context.WithValue(context.Background(), publishAwsMarketplaceTestKey, test.mockedService) - args := []string{"run"} - args = append(args, test.args...) - out, err := captureStdout(t, func() error { - return testApp.RunContext(ctx, args) - }) - if test.expectedOutput != "" { - assert.Contains(t, out, test.expectedOutput) - } - if test.expectedError != nil || test.errorContains != "" { - assert.Error(t, err) - if test.expectedError != nil { - assert.ErrorIs(t, err, test.expectedError) - } - if test.errorContains != "" { - assert.ErrorContains(t, err, test.errorContains) - } - } else { - assert.NoError(t, err) - } - }) - } -} - -func setupPublishAwsMarketplaceTests(t *testing.T) *cli.App { - t.Helper() - testApp := cli.NewApp() - testApp.Action = PublishAwsMarketplace - testApp.Flags = []cli.Flag{ - &util.DryRunFlag, - &cli.StringFlag{ - Name: "version", - Usage: "Release version (default from metadata)", - }, - &cli.StringFlag{ - Name: "image", - Required: true, - Usage: "Name of the image to be released", - }, - &cli.StringFlag{ - Name: "repo", - Required: true, - Usage: "AWS Marketplace ECR repository", - }, - &cli.StringFlag{ - Name: "product", - Required: true, - Usage: "AWS Marketplace product identifier", - }, - } - return testApp -} - -type mockAwsMarketplaceDocker struct { - ImagePullError error - ImageTagError error - ImagePushError error -} - -func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError -} - -func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error { - return m.ImageTagError -} - -func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError -} - -type mockAwsMarketplaceRegistry struct { - GetAuthorizationTokenWithContextError error -} - -func (m *mockAwsMarketplaceRegistry) GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) { - return &ecr.GetAuthorizationTokenOutput{ - AuthorizationData: []*ecr.AuthorizationData{ - { - AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString([]byte("username:password"))), - }, - }, - }, m.GetAuthorizationTokenWithContextError -} - -type mockAwsMarketplaceCatalog struct { - DescribeEntityWithContextError error - StartChangeSetWithContextError error -} - -func (m *mockAwsMarketplaceCatalog) DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) { - return &marketplacecatalog.DescribeEntityOutput{ - EntityIdentifier: aws.String("productid"), - }, m.DescribeEntityWithContextError -} - -func (m *mockAwsMarketplaceCatalog) StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) { - return &marketplacecatalog.StartChangeSetOutput{}, m.StartChangeSetWithContextError -} diff --git a/pkg/build/cmd/publishconfig.go b/pkg/build/cmd/publishconfig.go deleted file mode 100644 index 4715fce25e7..00000000000 --- a/pkg/build/cmd/publishconfig.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -type publishConfig struct { - tag string - srcBucket string - destBucket string - enterprise2DestBucket string - enterprise2SecurityPrefix string - staticAssetsBucket string - staticAssetEditions []string - storybookBucket string - security bool -} diff --git a/pkg/build/cmd/publishgithub.go b/pkg/build/cmd/publishgithub.go deleted file mode 100644 index 86dcc714fc7..00000000000 --- a/pkg/build/cmd/publishgithub.go +++ /dev/null @@ -1,191 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "os" - "path" - "strings" - - "github.com/google/go-github/v70/github" - "github.com/urfave/cli/v2" - "golang.org/x/oauth2" - - "github.com/grafana/grafana/pkg/build/config" -) - -type githubRepositoryService interface { - GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) - CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) - UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) - DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) - ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opt *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error) -} - -type githubRepo struct { - owner string - name string -} - -type publishGithubFlags struct { - create bool - dryRun bool - tag string - repo *githubRepo - artifactPath string -} - -var ( - newGithubClient = githubRepositoryClient - errTokenIsEmpty = errors.New("the environment variable GH_TOKEN must be set") - errTagIsEmpty = errors.New(`failed to retrieve release tag from metadata, use "--tag" to set it manually`) - errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`) -) - -func PublishGithub(c *cli.Context) error { - ctx := c.Context - token := os.Getenv("GH_TOKEN") - f, err := getPublishGithubFlags(c) - if err != nil { - return err - } - - if f.tag == "" { - return errTagIsEmpty - } - - if token == "" { - return errTokenIsEmpty - } - - if f.dryRun { - return runPublishGithubDryRun(f, token, c) - } - - client := newGithubClient(ctx, token) - release, res, err := client.GetReleaseByTag(ctx, f.repo.owner, f.repo.name, f.tag) - if err != nil && res.StatusCode != 404 { - return err - } - - if release == nil { - if f.create { - release, _, err = client.CreateRelease(ctx, f.repo.owner, f.repo.name, &github.RepositoryRelease{TagName: &f.tag}) - if err != nil { - return err - } - } else { - return errReleaseNotFound - } - } - - artifactName := path.Base(f.artifactPath) - file, err := os.Open(f.artifactPath) - if err != nil { - return err - } - - assetsPage := 1 - foundAsset := false - for { - assets, resp, err := client.ListReleaseAssets(ctx, f.repo.owner, f.repo.name, *release.ID, &github.ListOptions{ - Page: assetsPage, - }) - if err != nil { - return err - } - for _, asset := range assets { - if asset.GetName() == artifactName { - fmt.Printf("Found existing artifact with the name '%s'. Deleting that now.\n", artifactName) - if _, err := client.DeleteReleaseAsset(ctx, f.repo.owner, f.repo.name, asset.GetID()); err != nil { - return fmt.Errorf("failed to delete already existing asset: %w", err) - } - foundAsset = true - break - } - } - if resp.NextPage <= assetsPage || foundAsset { - break - } - assetsPage = resp.NextPage - } - - asset, _, err := client.UploadReleaseAsset(ctx, f.repo.owner, f.repo.name, *release.ID, &github.UploadOptions{Name: artifactName}, file) - if err != nil { - return err - } - fmt.Printf("Asset '%s' uploaded to release '%s' on repository '%s/%s'\nDownload: %s\n", *asset.Name, f.tag, f.repo.owner, f.repo.name, *asset.BrowserDownloadURL) - return nil -} - -func githubRepositoryClient(ctx context.Context, token string) githubRepositoryService { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: token}, - ) - tc := oauth2.NewClient(ctx, ts) - - client := github.NewClient(tc) - return client.Repositories -} - -func getPublishGithubFlags(c *cli.Context) (*publishGithubFlags, error) { - metadata, err := config.GenerateMetadata(c) - if err != nil { - return nil, err - } - tag := c.Value("tag").(string) - if tag == "" && metadata.GrafanaVersion != "" { - tag = fmt.Sprintf("v%s", metadata.GrafanaVersion) - } - fullRepo := c.Value("repo").(string) - dryRun := c.Value("dry-run").(bool) - owner := strings.Split(fullRepo, "/")[0] - name := strings.Split(fullRepo, "/")[1] - create := c.Value("create").(bool) - artifactPath := c.Value("path").(string) - if artifactPath == "" { - artifactPath = fmt.Sprintf("grafana-enterprise2-%s-amd64.img", metadata.GrafanaVersion) - fmt.Printf("path argument is not provided, resolving to default %s...\n", artifactPath) - } - return &publishGithubFlags{ - artifactPath: artifactPath, - create: create, - dryRun: dryRun, - tag: tag, - repo: &githubRepo{ - owner: owner, - name: name, - }, - }, nil -} - -func runPublishGithubDryRun(f *publishGithubFlags, token string, c *cli.Context) error { - client := newGithubClient(c.Context, token) - fmt.Println("Dry-Run: Retrieving release on repository by tag") - release, res, err := client.GetReleaseByTag(c.Context, f.repo.owner, f.repo.name, f.tag) - if err != nil && res.StatusCode != 404 { - fmt.Println("Dry-Run: Github communication error:\n", err) - return nil - } - - if release == nil { - if f.create { - fmt.Println("Dry-Run: Release doesn't exist and --create is enabled, so it would try to create the release") - } else { - fmt.Println("Dry-Run: Release doesn't exist and --create is disabled, so it would fail with error") - return nil - } - } - - artifactName := path.Base(f.artifactPath) - fmt.Printf("Dry-Run: Opening file for release: %s\n", f.artifactPath) - _, err = os.Open(f.artifactPath) - if err != nil { - fmt.Println("Dry-Run: Error opening file\n", err) - return nil - } - - fmt.Printf("Dry-Run: Would upload asset '%s' to release '%s' on repo '%s/%s' and return download URL if successful\n", artifactName, f.tag, f.repo.owner, f.repo.name) - return nil -} diff --git a/pkg/build/cmd/publishgithub_test.go b/pkg/build/cmd/publishgithub_test.go deleted file mode 100644 index 24c77fef501..00000000000 --- a/pkg/build/cmd/publishgithub_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package main - -import ( - "context" - "errors" - "io" - "net/http" - "os" - "path/filepath" - "testing" - - "github.com/google/go-github/v70/github" - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/cmd/util" -) - -type githubPublishTestCases struct { - name string - args []string - token string - expectedError error - errorContains string - expectedOutput string - mockedService *mockGithubRepositoryServiceImpl -} - -var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} - -func mockGithubRepositoryClient(context.Context, string) githubRepositoryService { - return mockGithubRepositoryService -} - -func TestPublishGithub(t *testing.T) { - t.Setenv("DRONE_BUILD_EVENT", "promote") - t.Setenv("DRONE_TAG", "v1.0.0") - t.Setenv("DRONE_COMMIT", "abcdefgh") - testApp, testPath := setupPublishGithubTests(t) - mockErrUnauthorized := errors.New("401") - - testCases := []githubPublishTestCases{ - { - name: "try to publish without required flags", - errorContains: `Required flags "path, repo" not set`, - }, - { - name: "try to publish without token", - args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - expectedError: errTokenIsEmpty, - }, - { - name: "try to publish with invalid token", - token: "invalid", - args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, - expectedError: mockErrUnauthorized, - }, - { - name: "try to publish with valid token and nonexisting tag with create disabled", - token: "valid", - args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, - expectedError: errReleaseNotFound, - }, - { - name: "try to publish with valid token and nonexisting tag with create enabled", - token: "valid", - args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, - }, - { - name: "try to publish with valid token and existing tag", - token: "valid", - args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - }, - { - name: "dry run with invalid token", - token: "invalid", - args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized}, - expectedOutput: "Github communication error", - }, - { - name: "dry run with valid token and nonexisting tag with create disabled", - token: "valid", - args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, - expectedOutput: "Release doesn't exist", - }, - { - name: "dry run with valid token and nonexisting tag with create enabled", - token: "valid", - args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"}, - mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound}, - expectedOutput: "Would upload asset", - }, - { - name: "dry run with valid token and existing tag", - token: "valid", - args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"}, - expectedOutput: "Would upload asset", - }, - } - - if os.Getenv("DRONE_COMMIT") == "" { - // this test only works locally due to Drone environment - testCases = append(testCases, - githubPublishTestCases{ - name: "try to publish without tag", - args: []string{"--path", testPath, "--repo", "test/test"}, - expectedError: errTagIsEmpty, - }, - ) - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - if test.token != "" { - t.Setenv("GH_TOKEN", test.token) - } - if test.mockedService != nil { - mockGithubRepositoryService = test.mockedService - } else { - mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{} - } - args := []string{"run"} - args = append(args, test.args...) - out, err := captureStdout(t, func() error { - return testApp.Run(args) - }) - if test.expectedOutput != "" { - assert.Contains(t, out, test.expectedOutput) - } - if test.expectedError != nil || test.errorContains != "" { - assert.Error(t, err) - if test.expectedError != nil { - assert.ErrorIs(t, err, test.expectedError) - } - if test.errorContains != "" { - assert.ErrorContains(t, err, test.errorContains) - } - } else { - assert.NoError(t, err) - } - }) - } -} - -func setupPublishGithubTests(t *testing.T) (*cli.App, string) { - t.Helper() - ex, err := os.Executable() - if err != nil { - panic(err) - } - testPath := filepath.Dir(ex) - - newGithubClient = mockGithubRepositoryClient - - testApp := cli.NewApp() - testApp.Action = PublishGithub - testApp.Flags = []cli.Flag{ - &util.DryRunFlag, - &cli.StringFlag{ - Name: "path", - Required: true, - Usage: "Path to the asset to be published", - }, - &cli.StringFlag{ - Name: "repo", - Required: true, - Usage: "Github repository", - }, - &cli.StringFlag{ - Name: "tag", - Usage: "Release tag (default from metadata)ß", - }, - &cli.BoolFlag{ - Name: "create", - Usage: "Create release if it doesn't exist", - }, - } - return testApp, testPath -} - -func captureStdout(t *testing.T, fn func() error) (string, error) { - t.Helper() - rescueStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - err := fn() - werr := w.Close() - if werr != nil { - return "", err - } - out, _ := io.ReadAll(r) - os.Stdout = rescueStdout - return string(out), err -} - -type mockGithubRepositoryServiceImpl struct { - tagErr error - createErr error - uploadErr error -} - -func (m *mockGithubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) { - var release *github.RepositoryRelease - res := &github.Response{Response: &http.Response{}} - if m.tagErr == nil { - releaseID := int64(1) - release = &github.RepositoryRelease{ID: &releaseID} - } else if errors.Is(m.tagErr, errReleaseNotFound) { - res.StatusCode = 404 - } - return release, res, m.tagErr -} - -func (m *mockGithubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) { - releaseID := int64(1) - return &github.RepositoryRelease{ID: &releaseID}, &github.Response{}, m.createErr -} - -func (m *mockGithubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) { - assetName := "test" - assetUrl := "testurl.com.br" - return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr -} - -func (m *mockGithubRepositoryServiceImpl) DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) { - return nil, nil -} - -func (m *mockGithubRepositoryServiceImpl) ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opts *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error) { - resp := github.Response{} - resp.LastPage = 1 - resp.FirstPage = 1 - resp.NextPage = 0 - return []*github.ReleaseAsset{}, &resp, nil -} diff --git a/pkg/build/cmd/publishmetrics.go b/pkg/build/cmd/publishmetrics.go deleted file mode 100644 index 9d0576891fd..00000000000 --- a/pkg/build/cmd/publishmetrics.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "log" - "os" - "regexp" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/metrics" -) - -func PublishMetrics(c *cli.Context) error { - apiKey := c.Args().Get(0) - - input, err := io.ReadAll(os.Stdin) - if err != nil { - return cli.Exit(fmt.Sprintf("Reading from stdin failed: %s", err), 1) - } - - reMetrics := regexp.MustCompile(`(?ms)^Metrics: (\{.+\})`) - ms := reMetrics.FindSubmatch(input) - if len(ms) == 0 { - return cli.Exit(fmt.Sprintf("Input on wrong format: %q", string(input)), 1) - } - - m := map[string]string{} - if err := json.Unmarshal(ms[1], &m); err != nil { - return cli.Exit(fmt.Sprintf("decoding metrics failed: %s", err), 1) - } - - log.Printf("Received metrics %+v", m) - - if err := metrics.Publish(m, apiKey); err != nil { - return cli.Exit(fmt.Sprintf("publishing metrics failed: %s", err), 1) - } - - return nil -} diff --git a/pkg/build/cmd/publishstaticassets.go b/pkg/build/cmd/publishstaticassets.go deleted file mode 100644 index 5b6eff9f12a..00000000000 --- a/pkg/build/cmd/publishstaticassets.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/grafana/grafana/pkg/build/env" - "github.com/grafana/grafana/pkg/build/gcloud" - "github.com/grafana/grafana/pkg/build/gcloud/storage" - "github.com/urfave/cli/v2" -) - -// PublishStaticAssetsAction Action implements the sub-command "artifacts static-assets". -func PublishStaticAssetsAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - staticAssetEditions, err := env.RequireListWithEnvFallback(c, "static-asset-editions", "STATIC_ASSET_EDITIONS") - if err != nil { - return err - } - - if err := gcloud.ActivateServiceAccount(); err != nil { - return fmt.Errorf("error connecting to gcp, %q", err) - } - - cfg := publishConfig{ - srcBucket: c.String("src-bucket"), - staticAssetsBucket: c.String("static-assets-bucket"), - staticAssetEditions: staticAssetEditions, - tag: strings.TrimPrefix(c.String("tag"), "v"), - } - - gcs, err := storage.New() - if err != nil { - return err - } - bucket := gcs.Bucket(cfg.staticAssetsBucket) - - for _, edition := range staticAssetEditions { - if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/static-assets/%s/%s", edition, cfg.tag), bucket, fmt.Sprintf("%s/%s", edition, cfg.tag)); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/build/cmd/publishstorybook.go b/pkg/build/cmd/publishstorybook.go deleted file mode 100644 index e33366d8e4b..00000000000 --- a/pkg/build/cmd/publishstorybook.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strings" - - "github.com/grafana/grafana/pkg/build/gcloud" - "github.com/grafana/grafana/pkg/build/gcloud/storage" - "github.com/grafana/grafana/pkg/build/versions" - "github.com/urfave/cli/v2" -) - -// PublishStorybookAction Action implements the sub-command "publish-artifacts". -func PublishStorybookAction(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - if err := gcloud.ActivateServiceAccount(); err != nil { - return fmt.Errorf("error connecting to gcp, %q", err) - } - - cfg := publishConfig{ - srcBucket: c.String("src-bucket"), - storybookBucket: c.String("storybook-bucket"), - tag: strings.TrimPrefix(c.String("tag"), "v"), - } - - gcs, err := storage.New() - if err != nil { - return err - } - bucket := gcs.Bucket(cfg.storybookBucket) - if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, cfg.tag); err != nil { - return err - } - - if latest, err := isLatest(cfg); err == nil && latest { - log.Printf("Copying storybooks to latest...") - if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, "latest"); err != nil { - return err - } - } else { - return err - } - - return nil -} - -func isLatest(cfg publishConfig) (bool, error) { - stableVersion, err := versions.GetLatestVersion(versions.LatestStableVersionURL) - if err != nil { - return false, err - } - isLatest, err := versions.IsGreaterThanOrEqual(cfg.tag, stableVersion) - if err != nil { - return false, err - } - return isLatest, nil -} diff --git a/pkg/build/cmd/storestorybook.go b/pkg/build/cmd/storestorybook.go deleted file mode 100644 index 13970e6a32e..00000000000 --- a/pkg/build/cmd/storestorybook.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "log" - "path/filepath" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/grafana/grafana/pkg/build/gcloud/storage" -) - -// StoreStorybook implements the sub-command "store-storybook". -func StoreStorybook(c *cli.Context) error { - deployment := c.String("deployment") - - metadata, err := config.GenerateMetadata(c) - if err != nil { - return err - } - - buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) - if err != nil { - return err - } - - storybookBucket := buildConfig.Buckets.Storybook - srcPath := buildConfig.Buckets.StorybookSrcDir - srcPath = filepath.Join(srcPath, deployment) - - gcs, err := storage.New() - if err != nil { - return err - } - bucket := gcs.Bucket(storybookBucket) - - if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil { - return err - } - - log.Printf("Successfully cleaned source: %s/%s\n", storybookBucket, srcPath) - - if err := gcs.CopyLocalDir(c.Context, "packages/grafana-ui/dist/storybook", bucket, srcPath, true); err != nil { - return err - } - - log.Printf("Successfully stored storybook to: %s/%s!\n", storybookBucket, srcPath) - - return nil -} diff --git a/pkg/build/cmd/uploadcdn.go b/pkg/build/cmd/uploadcdn.go deleted file mode 100644 index e18e1f65e16..00000000000 --- a/pkg/build/cmd/uploadcdn.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "path/filepath" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/grafana/grafana/pkg/build/gcloud/storage" -) - -// UploadCDN implements the sub-command "upload-cdn". -func UploadCDN(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - metadata, err := config.GenerateMetadata(c) - if err != nil { - return err - } - - version := metadata.GrafanaVersion - if err != nil { - return cli.Exit(err.Error(), 1) - } - - buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) - if err != nil { - return err - } - - edition := os.Getenv("EDITION") - log.Printf("Uploading Grafana CDN Assets, version %s, %s edition...", version, edition) - - editionPath := "" - - switch config.Edition(edition) { - case config.EditionOSS: - editionPath = "grafana-oss" - case config.EditionEnterprise: - editionPath = "grafana" - case config.EditionEnterprise2: - editionPath = os.Getenv("ENTERPRISE2_CDN_PATH") - default: - panic(fmt.Sprintf("unrecognized edition %q", edition)) - } - - gcs, err := storage.New() - if err != nil { - return err - } - - bucket := gcs.Bucket(buildConfig.Buckets.CDNAssets) - srcPath := buildConfig.Buckets.CDNAssetsDir - srcPath = filepath.Join(srcPath, editionPath, version) - - if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil { - return err - } - log.Printf("Successfully cleaned source: %s/%s\n", buildConfig.Buckets.CDNAssets, srcPath) - - if err := gcs.CopyLocalDir(c.Context, "./public", bucket, srcPath, false); err != nil { - return err - } - - log.Printf("Successfully uploaded cdn static assets to: %s/%s!\n", buildConfig.Buckets.CDNAssets, srcPath) - - return nil -} diff --git a/pkg/build/cmd/uploadpackages.go b/pkg/build/cmd/uploadpackages.go deleted file mode 100644 index 5801192c62c..00000000000 --- a/pkg/build/cmd/uploadpackages.go +++ /dev/null @@ -1,234 +0,0 @@ -package main - -import ( - "encoding/base64" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/config" - "github.com/grafana/grafana/pkg/build/droneutil" - "github.com/grafana/grafana/pkg/build/gcloud" -) - -// PackageRegexp returns a regexp for matching packages corresponding to a certain Grafana edition. -func PackageRegexp(edition config.Edition) *regexp.Regexp { - var sfx string - switch edition { - case config.EditionOSS: - case config.EditionEnterprise: - sfx = "-enterprise" - case config.EditionEnterprise2: - sfx = "-enterprise2" - default: - panic(fmt.Sprintf("unrecognized edition %q", edition)) - } - rePkg, err := regexp.Compile(fmt.Sprintf(`^grafana%s(?:-rpi)?[-_][^-_]+.*$`, sfx)) - if err != nil { - panic(fmt.Sprintf("Failed to compile regexp: %s", err)) - } - - return rePkg -} - -const releaseFolder = "release" -const mainFolder = "main" -const releaseBranchFolder = "prerelease" - -type uploadConfig struct { - config.Config - - edition config.Edition - versionMode config.VersionMode - gcpKey string - distDir string - versionFolder string -} - -// UploadPackages implements the sub-command "upload-packages". -func UploadPackages(c *cli.Context) error { - if c.NArg() > 0 { - if err := cli.ShowSubcommandHelp(c); err != nil { - return cli.Exit(err.Error(), 1) - } - return cli.Exit("", 1) - } - - gcpKeyB64 := strings.TrimSpace(os.Getenv("GCP_KEY")) - if gcpKeyB64 == "" { - return cli.Exit("the environment variable GCP_KEY must be set", 1) - } - gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKeyB64) - if err != nil { - return cli.Exit("failed to base64 decode $GCP_KEY", 1) - } - gcpKey := string(gcpKeyB) - - distDir, err := filepath.Abs("dist") - if err != nil { - return cli.Exit(err.Error(), 1) - } - - metadata, err := config.GenerateMetadata(c) - if err != nil { - return err - } - - version := metadata.GrafanaVersion - - releaseMode, err := metadata.GetReleaseMode() - if err != nil { - return cli.Exit(err.Error(), 1) - } - - releaseModeConfig, err := config.GetBuildConfig(releaseMode.Mode) - if err != nil { - return cli.Exit(err.Error(), 1) - } - - var edition config.Edition - if e, ok := os.LookupEnv("EDITION"); ok { - edition = config.Edition(e) - } - - if c.Bool("enterprise2") { - edition = config.EditionEnterprise2 - } - - if edition == "" { - return fmt.Errorf("both EDITION envvar and '--enterprise2' flag are missing. At least one of those is required") - } - - // TODO: Verify config values - cfg := uploadConfig{ - Config: config.Config{ - Version: version, - Bucket: releaseModeConfig.Buckets.Artifacts, - }, - edition: edition, - versionMode: releaseMode.Mode, - gcpKey: gcpKey, - distDir: distDir, - } - - event, err := droneutil.GetDroneEventFromEnv() - if err != nil { - return err - } - - if cfg.edition == config.EditionEnterprise2 { - cfg.Bucket, err = bucketForEnterprise2(releaseModeConfig, event) - if err != nil { - return err - } - } - - cfg.versionFolder, err = getVersionFolder(cfg, event) - if err != nil { - return err - } - - if err := uploadPackages(cfg); err != nil { - return cli.Exit(err.Error(), 1) - } - - log.Println("Successfully uploaded packages!") - return nil -} - -// Corner case for custom enterprise2 mode -func bucketForEnterprise2(releaseModeConfig *config.BuildConfig, event string) (string, error) { - if event == config.Custom { - buildConfig, err := config.GetBuildConfig(config.ReleaseBranchMode) - if err != nil { - return "", err - } - return buildConfig.Buckets.ArtifactsEnterprise2, nil - } - - if releaseModeConfig.Buckets.ArtifactsEnterprise2 != "" { - return releaseModeConfig.Buckets.ArtifactsEnterprise2, nil - } - - return "", fmt.Errorf("enterprise2 bucket var doesn't exist") -} - -func getVersionFolder(cfg uploadConfig, event string) (string, error) { - switch cfg.versionMode { - case config.TagMode, config.CloudMode: - return releaseFolder, nil - case config.MainMode, config.DownstreamMode: - return mainFolder, nil - case config.ReleaseBranchMode: - return releaseBranchFolder, nil - default: - // Corner case for custom enterprise2 mode - if event == config.Custom && cfg.versionMode == config.Enterprise2Mode { - return releaseFolder, nil - } - return "", fmt.Errorf("unrecognized version mode: %s", cfg.versionMode) - } -} - -func uploadPackages(cfg uploadConfig) error { - log.Printf("Uploading Grafana packages, version %s, %s edition, %s mode...\n", cfg.Version, cfg.edition, - cfg.versionMode) - - if err := gcloud.ActivateServiceAccount(); err != nil { - return fmt.Errorf("couldn't activate service account, err: %w", err) - } - - edition := strings.ToLower(string(cfg.edition)) - - var sfx string - switch cfg.edition { - case config.EditionOSS: - case config.EditionEnterprise: - sfx = "-enterprise" - case config.EditionEnterprise2: - sfx = "-enterprise2" - default: - panic(fmt.Sprintf("unrecognized edition %q", cfg.edition)) - } - matches, err := filepath.Glob(filepath.Join(cfg.distDir, fmt.Sprintf("grafana%s*", sfx))) - if err != nil { - return fmt.Errorf("failed to list packages: %w", err) - } - fpaths := []string{} - rePkg := PackageRegexp(cfg.edition) - for _, fpath := range matches { - fname := filepath.Base(fpath) - if strings.Contains(fname, "latest") || !rePkg.MatchString(fname) { - log.Printf("Ignoring file %q\n", fpath) - continue - } - - fpaths = append(fpaths, fpath) - } - - var tag, gcsPath string - droneTag := strings.TrimSpace(os.Getenv("DRONE_TAG")) - if droneTag != "" { - tag = droneTag - gcsPath = fmt.Sprintf("gs://%s/%s/%s/%s", cfg.Bucket, tag, edition, cfg.versionFolder) - } else { - gcsPath = fmt.Sprintf("gs://%s/%s/%s/", cfg.Bucket, edition, cfg.versionFolder) - } - log.Printf("Uploading %d file(s) to GCS (%s)...\n", len(fpaths), gcsPath) - - args := []string{"-m", "cp"} - args = append(args, fpaths...) - args = append(args, gcsPath) - cmd := exec.Command("gsutil", args...) - if output, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to upload files to GCS: %s", output) - } - - return nil -} diff --git a/pkg/build/cmd/uploadpackages_test.go b/pkg/build/cmd/uploadpackages_test.go deleted file mode 100644 index 9d3bc7b52e1..00000000000 --- a/pkg/build/cmd/uploadpackages_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/build/config" -) - -func Test_getVersionFolder(t *testing.T) { - type args struct { - cfg uploadConfig - event string - versionFolder string - } - tests := []struct { - name string - args args - err error - }{ - {"tag mode", args{uploadConfig{versionMode: config.TagMode}, "", releaseFolder}, nil}, - {"main mode", args{uploadConfig{versionMode: config.MainMode}, "", mainFolder}, nil}, - {"downstream mode", args{uploadConfig{versionMode: config.DownstreamMode}, "", mainFolder}, nil}, - {"release branch mode", args{uploadConfig{versionMode: config.ReleaseBranchMode}, "", releaseBranchFolder}, nil}, - {"enterprise pro mode", args{uploadConfig{versionMode: config.Enterprise2Mode}, config.Custom, releaseFolder}, nil}, - {"cloud mode", args{uploadConfig{versionMode: config.CloudMode}, "", releaseFolder}, nil}, - {"unrecognised version mode", args{uploadConfig{versionMode: "foo"}, config.Custom, ""}, errors.New("")}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - versionMode, err := getVersionFolder(tt.args.cfg, tt.args.event) - if tt.err != nil { - require.Error(t, err) - } - require.Equal(t, versionMode, tt.args.versionFolder) - }) - } -} - -func Test_checkForEnterprise2Edition(t *testing.T) { - type args struct { - releaseModeConfig *config.BuildConfig - event string - } - tests := []struct { - name string - args args - want string - err error - }{ - {"event is not custom", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: "dummy"}}}, "dummy", nil}, - {"event is not custom and string is empty", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: ""}}}, "", fmt.Errorf("enterprise2 bucket var doesn't exist")}, - {"event is custom", args{releaseModeConfig: nil, event: "custom"}, "grafana-downloads-enterprise2", nil}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := bucketForEnterprise2(tt.args.releaseModeConfig, tt.args.event) - if tt.err != nil { - require.Error(t, err) - } - assert.Equalf(t, tt.want, got, "bucketForEnterprise2(%v, %v)", tt.args.releaseModeConfig, tt.args.event) - }) - } -} diff --git a/pkg/build/cmd/verifydrone.go b/pkg/build/cmd/verifydrone.go deleted file mode 100644 index 14596377110..00000000000 --- a/pkg/build/cmd/verifydrone.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "log" - "os" - - "github.com/drone/drone-cli/drone/lint" - "github.com/drone/drone-cli/drone/starlark" - "github.com/google/go-cmp/cmp" - cliv1 "github.com/urfave/cli" - "github.com/urfave/cli/v2" - "gopkg.in/yaml.v3" - - "github.com/grafana/grafana/pkg/build/fsutil" -) - -func VerifyDrone(c *cli.Context) error { - const yml = ".drone.yml" - const backup = ".drone.yml.bak" - - if err := fsutil.CopyFile(yml, backup); err != nil { - return cli.Exit(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1) - } - defer func() { - if err := os.Remove(yml); err != nil { - log.Printf("Failed to rename %s to %s", backup, yml) - } - if err := os.Rename(backup, yml); err != nil { - log.Printf("Failed to rename %s to %s", backup, yml) - } - }() - - flags := &flag.FlagSet{} - for _, flag := range starlark.Command.Flags { - flag.Apply(flags) - } - if err := flags.Set("format", "true"); err != nil { - return err - } - cStarlark := cliv1.NewContext(cliv1.NewApp(), flags, nil) - action := starlark.Command.Action.(func(*cliv1.Context)) - action(cStarlark) - - if err := verifyYAML(yml, backup); err != nil { - return err - } - - flags = &flag.FlagSet{} - for _, flag := range lint.Command.Flags { - flag.Apply(flags) - } - err := flags.Set("trusted", "true") - if err != nil { - return err - } - cLint := cliv1.NewContext(cliv1.NewApp(), flags, nil) - actionE := lint.Command.Action.(func(*cliv1.Context) error) - if err := actionE(cLint); err != nil { - return err - } - - log.Printf("%s is valid", yml) - - return nil -} - -func readConfig(fpath string) ([]map[string]any, error) { - //nolint:gosec - f, err := os.Open(fpath) - if err != nil { - return nil, cli.Exit(fmt.Sprintf("failed to read %s: %s", fpath, err), 1) - } - defer func() { - if err := f.Close(); err != nil { - log.Println("error closing file", err) - } - }() - - // The YAML stream may contain multiple pipeline configurations, read them all - dec := yaml.NewDecoder(f) - var c []map[string]any - for { - var m map[string]any - if err := dec.Decode(&m); err != nil { - if errors.Is(err, io.EOF) { - break - } - return nil, cli.Exit(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1) - } - - if m["kind"] == "signature" { - log.Printf("Ignoring a signature") - continue - } - - c = append(c, m) - } - - return c, nil -} - -func verifyYAML(yml, backup string) error { - log.Printf("Comparing %s and %s", yml, backup) - - c1, err := readConfig(yml) - if err != nil { - return err - } - - c2, err := readConfig(backup) - if err != nil { - return err - } - - if !cmp.Equal(c1, c2) { - return cli.Exit(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert", - yml), 1) - } - - return nil -} diff --git a/pkg/build/cmd/verifystorybook.go b/pkg/build/cmd/verifystorybook.go deleted file mode 100644 index 109a0cdd619..00000000000 --- a/pkg/build/cmd/verifystorybook.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package verifystorybook contains the sub-command "verify-storybook". -package main - -import ( - "fmt" - "log" - "path/filepath" - - "github.com/grafana/grafana/pkg/build/fsutil" - "github.com/urfave/cli/v2" -) - -// VerifyStorybook Action implements the sub-command "verify-storybook". -func VerifyStorybook(c *cli.Context) error { - const grafanaDir = "." - - paths := []string{ - "packages/grafana-ui/dist/storybook/index.html", - "packages/grafana-ui/dist/storybook/iframe.html"} - for _, p := range paths { - exists, err := fsutil.Exists(filepath.Join(grafanaDir, p)) - if err != nil { - return cli.Exit(fmt.Sprintf("failed to verify Storybook build: %s", err), 1) - } - if !exists { - return fmt.Errorf("failed to verify Storybook build, missing %q", p) - } - } - - log.Printf("Successfully verified Storybook integrity") - return nil -} diff --git a/pkg/build/config/config.go b/pkg/build/config/config.go deleted file mode 100644 index 7aa4176e13a..00000000000 --- a/pkg/build/config/config.go +++ /dev/null @@ -1,18 +0,0 @@ -package config - -type Config struct { - Version string - Bucket string - DebRepoBucket string - DebDBBucket string - RPMRepoBucket string - GPGPassPath string - GPGPrivateKey string - GPGPublicKey string - NumWorkers int - GitHubUser string - GitHubToken string - PullEnterprise bool - PackageVersion string - SignPackages bool -} diff --git a/pkg/build/config/edition.go b/pkg/build/config/edition.go deleted file mode 100644 index 48d7375f1cd..00000000000 --- a/pkg/build/config/edition.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -type Edition string - -const ( - EditionOSS Edition = "oss" - EditionEnterprise Edition = "enterprise" - EditionEnterprise2 Edition = "enterprise2" -) diff --git a/pkg/build/config/genmetadata.go b/pkg/build/config/genmetadata.go deleted file mode 100644 index 1ff0565bc17..00000000000 --- a/pkg/build/config/genmetadata.go +++ /dev/null @@ -1,103 +0,0 @@ -package config - -import ( - "fmt" - "os" - "strings" - - "github.com/urfave/cli/v2" - - "github.com/grafana/grafana/pkg/build/droneutil" -) - -func GenerateMetadata(c *cli.Context) (Metadata, error) { - var metadata Metadata - version := "" - - event, err := droneutil.GetDroneEventFromEnv() - if err != nil { - return Metadata{}, err - } - - tag, ok := os.LookupEnv("DRONE_TAG") - if !ok { - fmt.Println("DRONE_TAG envvar not present, %w", err) - } - - var releaseMode ReleaseMode - switch event { - case string(PullRequestMode): - releaseMode = ReleaseMode{Mode: PullRequestMode} - case Push: - mode, err := CheckDroneTargetBranch() - if err != nil { - return Metadata{}, err - } - releaseMode = ReleaseMode{Mode: mode} - case Custom: - if edition, _ := os.LookupEnv("EDITION"); edition == string(EditionEnterprise2) { - releaseMode = ReleaseMode{Mode: Enterprise2Mode} - if tag != "" { - version = strings.TrimPrefix(tag, "v") - } - break - } - mode, err := CheckDroneTargetBranch() - if err != nil { - return Metadata{}, err - } - // if there is a custom event targeting the main branch, that's an enterprise downstream build - if mode == MainBranch { - releaseMode = ReleaseMode{Mode: DownstreamMode} - } else { - releaseMode = ReleaseMode{Mode: mode} - } - case Tag, Promote: - if tag == "" { - return Metadata{}, fmt.Errorf("DRONE_TAG envvar not present for a tag/promotion event, %w", err) - } - version = strings.TrimPrefix(tag, "v") - mode, err := CheckSemverSuffix() - if err != nil { - return Metadata{}, err - } - releaseMode = mode - case Cronjob: - releaseMode = ReleaseMode{Mode: CronjobMode} - } - - if version == "" { - version, err = generateVersionFromBuildID() - if err != nil { - return Metadata{}, err - } - } - - currentCommit, err := GetDroneCommit() - if err != nil { - return Metadata{}, err - } - metadata = Metadata{ - GrafanaVersion: version, - ReleaseMode: releaseMode, - GrabplVersion: c.App.Version, - CurrentCommit: currentCommit, - } - - fmt.Printf("building Grafana version: %s, release mode: %+v\n", metadata.GrafanaVersion, metadata.ReleaseMode) - - return metadata, nil -} - -func generateVersionFromBuildID() (string, error) { - buildID, ok := os.LookupEnv("DRONE_BUILD_NUMBER") - if !ok { - return "", fmt.Errorf("unable to get DRONE_BUILD_NUMBER environmental variable") - } - var err error - version, err := GenerateGrafanaVersion(buildID, ".") - if err != nil { - return "", err - } - return version, nil -} diff --git a/pkg/build/config/genmetadata_test.go b/pkg/build/config/genmetadata_test.go deleted file mode 100644 index d8eeb06bb6a..00000000000 --- a/pkg/build/config/genmetadata_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package config - -import ( - "flag" - "os" - "testing" - - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" -) - -const ( - DroneBuildEvent = "DRONE_BUILD_EVENT" - DroneTargetBranch = "DRONE_TARGET_BRANCH" - DroneTag = "DRONE_TAG" - DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE" - DroneBuildNumber = "DRONE_BUILD_NUMBER" -) - -const ( - hashedGrafanaVersion = "9.2.0-12345" - versionedBranch = "v9.2.x" -) - -func TestGetMetadata(t *testing.T) { - tcs := []struct { - envMap map[string]string - expVersion string - mode ReleaseMode - }{ - {map[string]string{DroneBuildEvent: PullRequest, DroneTargetBranch: "", DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: PullRequestMode}}, - {map[string]string{DroneBuildEvent: Push, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}}, - {map[string]string{DroneBuildEvent: Push, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: MainMode}}, - {map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}}, - {map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: DownstreamMode}}, - {map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345", "EDITION": string(EditionEnterprise2)}, hashedGrafanaVersion, ReleaseMode{Mode: Enterprise2Mode}}, - {map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: false}}, - {map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-preview", DroneSemverPrerelease: "preview", DroneBuildNumber: "12345"}, "9.2.0-preview", ReleaseMode{Mode: TagMode, IsPreview: true, IsTest: false}}, - {map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: true}}, - {map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: false}}, - {map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-preview", DroneSemverPrerelease: "preview", DroneBuildNumber: "12345"}, "9.2.0-preview", ReleaseMode{Mode: TagMode, IsPreview: true, IsTest: false}}, - {map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsPreview: false, IsTest: true}}, - } - - ctx := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil) - for _, tc := range tcs { - t.Run("Should return valid metadata, ", func(t *testing.T) { - setUpEnv(t, tc.envMap) - testMetadata(t, ctx, tc.expVersion, tc.mode) - }) - } -} - -func testMetadata(t *testing.T, ctx *cli.Context, version string, releaseMode ReleaseMode) { - t.Helper() - - metadata, err := GenerateMetadata(ctx) - require.NoError(t, err) - t.Run("with a valid version", func(t *testing.T) { - expVersion := metadata.GrafanaVersion - require.Equal(t, expVersion, version) - }) - - t.Run("with a valid release mode from the built-in list", func(t *testing.T) { - expMode := metadata.ReleaseMode - require.NoError(t, err) - require.Equal(t, expMode, releaseMode) - }) -} - -func setUpEnv(t *testing.T, envMap map[string]string) { - t.Helper() - - os.Clearenv() - t.Setenv("DRONE_COMMIT", "abcd12345") - for k, v := range envMap { - t.Setenv(k, v) - } -} diff --git a/pkg/build/config/package.json b/pkg/build/config/package.json deleted file mode 100644 index 1a5184b05c7..00000000000 --- a/pkg/build/config/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "9.2.0-pre" -} diff --git a/pkg/build/config/variant.go b/pkg/build/config/variant.go deleted file mode 100644 index 6b476ed95e6..00000000000 --- a/pkg/build/config/variant.go +++ /dev/null @@ -1,64 +0,0 @@ -package config - -// Variant is the OS / Architecture combination that Grafana can be compiled for. -type Variant string - -const ( - VariantLinuxAmd64 Variant = "linux-amd64" - VariantLinuxAmd64Musl Variant = "linux-amd64-musl" - VariantArmV6 Variant = "linux-armv6" - VariantArmV7 Variant = "linux-armv7" - VariantArmV7Musl Variant = "linux-armv7-musl" - VariantArm64 Variant = "linux-arm64" - VariantArm64Musl Variant = "linux-arm64-musl" - VariantDarwinAmd64 Variant = "darwin-amd64" - VariantWindowsAmd64 Variant = "windows-amd64" -) - -var AllVariants = []Variant{ - // https://github.com/golang/go/issues/58425 disabling arm builds until go issue is resolved - // VariantArmV6, - // VariantArmV7, - // VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, -} - -// Architecture is an allowed value in the GOARCH environment variable. -type Architecture string - -const ( - ArchAMD64 Architecture = "amd64" - ArchARMv6 Architecture = "armv6" - ArchARMv7 Architecture = "armv7" - ArchARM64 Architecture = "arm64" - ArchARMHF Architecture = "armhf" - ArchARMHFP Architecture = "armhfp" - ArchARM Architecture = "arm" -) - -type OS string - -const ( - OSWindows OS = "windows" - OSDarwin OS = "darwin" - OSLinux OS = "linux" -) - -type LibC string - -const ( - LibCMusl = "musl" -) - -// Distribution is the base os image where the Grafana image is built on. -type Distribution string - -const ( - Ubuntu Distribution = "ubuntu" - Alpine Distribution = "alpine" -) diff --git a/pkg/build/config/version.go b/pkg/build/config/version.go index 1533231a43b..dd1b0405119 100644 --- a/pkg/build/config/version.go +++ b/pkg/build/config/version.go @@ -5,72 +5,9 @@ import ( "fmt" "os" "path/filepath" - "regexp" "strings" - - "github.com/grafana/grafana/pkg/build/git" ) -type Metadata struct { - GrafanaVersion string `json:"version,omitempty"` - ReleaseMode ReleaseMode `json:"releaseMode,omitempty"` - GrabplVersion string `json:"grabplVersion,omitempty"` - CurrentCommit string `json:"currentCommit,omitempty"` -} - -type ReleaseMode struct { - Mode VersionMode `json:"mode,omitempty"` - IsPreview bool `json:"IsPreview,omitempty"` - IsTest bool `json:"isTest,omitempty"` -} - -type PluginSignature struct { - Sign bool `json:"sign,omitempty"` - AdminSign bool `json:"adminSign,omitempty"` -} - -type Docker struct { - ShouldSave bool `json:"shouldSave,omitempty"` - Distribution []Distribution `json:"distribution,omitempty"` - Architectures []Architecture `json:"archs,omitempty"` - PrereleaseBucket string `json:"prereleaseBucket,omitempty"` -} - -type Buckets struct { - Artifacts string `json:"artifacts,omitempty"` - ArtifactsEnterprise2 string `json:"artifactsEnterprise2,omitempty"` - CDNAssets string `json:"CDNAssets,omitempty"` - CDNAssetsDir string `json:"CDNAssetsDir,omitempty"` - Storybook string `json:"storybook,omitempty"` - StorybookSrcDir string `json:"storybookSrcDir,omitempty"` -} - -// BuildConfig represents the struct that defines all of the different variables used to build Grafana -type BuildConfig struct { - Variants []Variant `json:"variants,omitempty"` - PluginSignature PluginSignature `json:"pluginSignature,omitempty"` - Docker Docker `json:"docker,omitempty"` - Buckets Buckets `json:"buckets,omitempty"` -} - -func (md *Metadata) GetReleaseMode() (ReleaseMode, error) { - return md.ReleaseMode, nil -} - -// VersionMap is a map of versions. Each key of the Versions map is an event that uses the config as the value for that key. -// For example, the 'pull_request' key will have data in it that might cause Grafana to be built differently in a pull request, -// than the way it will be built in 'main' -type VersionMap map[VersionMode]BuildConfig - -// GetBuildConfig reads the embedded config.json and decodes it. -func GetBuildConfig(mode VersionMode) (*BuildConfig, error) { - if v, ok := Versions[mode]; ok { - return &v, nil - } - - return nil, fmt.Errorf("mode '%s' not found in version list", mode) -} - // GenerateGrafanaVersion gets the Grafana version from the package.json func GenerateGrafanaVersion(buildID, grafanaDir string) (string, error) { version, err := GetPackageJSONVersion(grafanaDir) @@ -106,55 +43,6 @@ func GetPackageJSONVersion(grafanaDir string) (string, error) { return version, nil } -func CheckDroneTargetBranch() (VersionMode, error) { - rePRCheckBranch := git.PRCheckRegexp() - reRlsBranch := regexp.MustCompile(`^v\d+\.\d+\.x$`) - target := os.Getenv("DRONE_TARGET_BRANCH") - if target == "" { - return "", fmt.Errorf("failed to get DRONE_TARGET_BRANCH environmental variable") - } else if target == string(MainMode) { - return MainMode, nil - } - if reRlsBranch.MatchString(target) { - return ReleaseBranchMode, nil - } - if rePRCheckBranch.MatchString(target) { - return PullRequestMode, nil - } - fmt.Printf("unrecognized target branch: %s, defaulting to %s\n", target, PullRequestMode) - return PullRequestMode, nil -} - -func CheckSemverSuffix() (ReleaseMode, error) { - rePreviewRls := regexp.MustCompile(`preview.*`) - reTestRls := regexp.MustCompile(`test.*`) - reCloudRls := regexp.MustCompile(`cloud.*`) - tagSuffix, ok := os.LookupEnv("DRONE_SEMVER_PRERELEASE") - if !ok || tagSuffix == "" { - fmt.Println("DRONE_SEMVER_PRERELEASE doesn't exist for a tag, this is a release event...") - return ReleaseMode{Mode: TagMode}, nil - } - switch { - case rePreviewRls.MatchString(tagSuffix): - return ReleaseMode{Mode: TagMode, IsPreview: true}, nil - case reTestRls.MatchString(tagSuffix): - return ReleaseMode{Mode: TagMode, IsTest: true}, nil - case reCloudRls.MatchString(tagSuffix): - return ReleaseMode{Mode: CloudMode}, nil - default: - fmt.Printf("DRONE_SEMVER_PRERELEASE is custom string, release event with %s suffix\n", tagSuffix) - return ReleaseMode{Mode: TagMode}, nil - } -} - -func GetDroneCommit() (string, error) { - commit := strings.TrimSpace(os.Getenv("DRONE_COMMIT")) - if commit == "" { - return "", fmt.Errorf("the environment variable DRONE_COMMIT is missing") - } - return commit, nil -} - func shortenBuildID(buildID string) string { buildID = strings.ReplaceAll(buildID, "-", "") if len(buildID) < 9 { diff --git a/pkg/build/config/version_mode.go b/pkg/build/config/version_mode.go deleted file mode 100644 index d7734bb7d01..00000000000 --- a/pkg/build/config/version_mode.go +++ /dev/null @@ -1,28 +0,0 @@ -package config - -// VersionMode defines the source event that created a release or published version -type VersionMode string - -const ( - MainMode VersionMode = "main" - TagMode VersionMode = "release" - ReleaseBranchMode VersionMode = "branch" - PullRequestMode VersionMode = "pull_request" - DownstreamMode VersionMode = "downstream" - Enterprise2Mode VersionMode = "enterprise2" - CronjobMode VersionMode = "cron" - CloudMode VersionMode = "cloud" -) - -const ( - Tag = "tag" - PullRequest = "pull_request" - Push = "push" - Custom = "custom" - Promote = "promote" - Cronjob = "cron" -) - -const ( - MainBranch = "main" -) diff --git a/pkg/build/config/versions.go b/pkg/build/config/versions.go deleted file mode 100644 index d87c60d7ff0..00000000000 --- a/pkg/build/config/versions.go +++ /dev/null @@ -1,237 +0,0 @@ -package config - -var Versions = VersionMap{ - PullRequestMode: { - Variants: []Variant{ - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantArm64, - VariantArm64Musl, - }, - PluginSignature: PluginSignature{ - Sign: false, - AdminSign: false, - }, - Docker: Docker{ - ShouldSave: false, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - }, - Distribution: []Distribution{ - Alpine, - }, - }, - }, - MainMode: { - Variants: []Variant{ - VariantArmV6, - VariantArmV7, - VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: false, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - ArchARMv7, // GOARCH=ARM is used for both armv6 and armv7. They are differentiated by the GOARM variable. - }, - Distribution: []Distribution{ - Alpine, - Ubuntu, - }, - }, - Buckets: Buckets{ - Artifacts: "grafana-downloads", - ArtifactsEnterprise2: "grafana-downloads-enterprise2", - CDNAssets: "grafana-static-assets", - Storybook: "grafana-storybook", - }, - }, - DownstreamMode: { - Variants: []Variant{ - VariantArmV6, - VariantArmV7, - VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: true, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - ArchARMv7, // GOARCH=ARM is used for both armv6 and armv7. They are differentiated by the GOARM variable. - }, - Distribution: []Distribution{ - Alpine, - Ubuntu, - }, - }, - Buckets: Buckets{ - Artifacts: "grafana-downloads", - ArtifactsEnterprise2: "grafana-downloads-enterprise2", - CDNAssets: "grafana-static-assets", - }, - }, - ReleaseBranchMode: { - Variants: []Variant{ - VariantArmV6, - VariantArmV7, - VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: true, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - ArchARMv7, - }, - Distribution: []Distribution{ - Alpine, - Ubuntu, - }, - PrereleaseBucket: "grafana-prerelease/artifacts/docker", - }, - Buckets: Buckets{ - Artifacts: "grafana-downloads", - ArtifactsEnterprise2: "grafana-downloads-enterprise2", - CDNAssets: "grafana-static-assets", - }, - }, - TagMode: { - Variants: []Variant{ - VariantArmV6, - VariantArmV7, - VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: true, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - ArchARMv7, - }, - Distribution: []Distribution{ - Alpine, - Ubuntu, - }, - PrereleaseBucket: "grafana-prerelease/artifacts/docker", - }, - Buckets: Buckets{ - Artifacts: "grafana-prerelease/artifacts/downloads", - ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2", - CDNAssets: "grafana-prerelease", - CDNAssetsDir: "artifacts/static-assets", - Storybook: "grafana-prerelease", - StorybookSrcDir: "artifacts/storybook", - }, - }, - Enterprise2Mode: { - Variants: []Variant{ - VariantArmV6, - VariantArmV7, - VariantArmV7Musl, - VariantArm64, - VariantArm64Musl, - VariantDarwinAmd64, - VariantWindowsAmd64, - VariantLinuxAmd64, - VariantLinuxAmd64Musl, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: true, - Architectures: []Architecture{ - ArchAMD64, - ArchARM64, - ArchARMv7, - }, - Distribution: []Distribution{ - Alpine, - Ubuntu, - }, - PrereleaseBucket: "grafana-prerelease/artifacts/docker", - }, - Buckets: Buckets{ - Artifacts: "grafana-prerelease/artifacts/downloads", - ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2", - CDNAssets: "grafana-prerelease", - CDNAssetsDir: "artifacts/static-assets", - Storybook: "grafana-prerelease", - StorybookSrcDir: "artifacts/storybook", - }, - }, - CloudMode: { - Variants: []Variant{ - VariantLinuxAmd64Musl, - // We still need this variant to build the .deb file - VariantLinuxAmd64, - }, - PluginSignature: PluginSignature{ - Sign: true, - AdminSign: true, - }, - Docker: Docker{ - ShouldSave: true, - Architectures: []Architecture{ - ArchAMD64, - }, - Distribution: []Distribution{ - Alpine, - }, - PrereleaseBucket: "grafana-prerelease/artifacts/docker", - }, - Buckets: Buckets{ - Artifacts: "grafana-prerelease/artifacts/downloads", - ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2", - CDNAssets: "grafana-prerelease", - CDNAssetsDir: "artifacts/static-assets", - Storybook: "grafana-prerelease", - StorybookSrcDir: "artifacts/storybook", - }, - }, -} diff --git a/pkg/build/daggerbuild/backend/builder.go b/pkg/build/daggerbuild/backend/builder.go index 4b49efa1ba9..104a42687f7 100644 --- a/pkg/build/daggerbuild/backend/builder.go +++ b/pkg/build/daggerbuild/backend/builder.go @@ -106,7 +106,9 @@ func GolangContainer( WithExec([]string{"wget", "-q", "http://dl.grafana.com/ci/arm-linux-musleabihf-cross.tgz", "-P", "/toolchain"}). WithExec([]string{"tar", "-xf", "/toolchain/arm-linux-musleabihf-cross.tgz", "-C", "/toolchain"}). WithExec([]string{"wget", "-q", "https://dl.grafana.com/ci/s390x-linux-musl-cross.tgz", "-P", "/toolchain"}). - WithExec([]string{"tar", "-xf", "/toolchain/s390x-linux-musl-cross.tgz", "-C", "/toolchain"}) + WithExec([]string{"tar", "-xf", "/toolchain/s390x-linux-musl-cross.tgz", "-C", "/toolchain"}). + WithExec([]string{"wget", "-q", "https://dl.grafana.com/ci/riscv64-linux-musl-cross.tgz", "-P", "/toolchain"}). + WithExec([]string{"tar", "-xf", "/toolchain/riscv64-linux-musl-cross.tgz", "-C", "/toolchain"}) return WithGoEnv(log, container, distro, opts) } diff --git a/pkg/build/daggerbuild/backend/distributions.go b/pkg/build/daggerbuild/backend/distributions.go index 05322192a63..31ac6e63140 100644 --- a/pkg/build/daggerbuild/backend/distributions.go +++ b/pkg/build/daggerbuild/backend/distributions.go @@ -264,6 +264,22 @@ func BuildOptsStaticS390X(distro Distribution, experiments []string, tags []stri } } +// BuildOptsStaticS390X builds Grafana statically for the s390x arch +func BuildOptsStaticRiscv64(distro Distribution, experiments []string, tags []string) *GoBuildOpts { + var ( + os, _ = OSAndArch(distro) + ) + + return &GoBuildOpts{ + CC: "/toolchain/riscv64-linux-musl-cross/bin/riscv64-linux-musl-gcc", + CXX: "/toolchain/riscv64-linux-musl-cross/bin/riscv64-linux-musl-cpp", + ExperimentalFlags: experiments, + OS: os, + Arch: "riscv64", + CGOEnabled: true, + } +} + func StdZigBuildOpts(distro Distribution, experiments []string, tags []string) *GoBuildOpts { var ( os, arch = OSAndArch(distro) @@ -331,7 +347,7 @@ var DistributionGoOpts = map[Distribution]DistroBuildOptsFunc{ DistLinuxAMD64: StdZigBuildOpts, DistLinuxAMD64Dynamic: StdZigBuildOpts, DistPlan9AMD64: StdZigBuildOpts, - DistLinuxRISCV64: StdZigBuildOpts, + DistLinuxRISCV64: BuildOptsStaticRiscv64, // Non-Linux distros can have whatever they want in CC and CXX; it'll get overridden // but it's probably not best to rely on that. diff --git a/pkg/build/daggerbuild/cmd/app.go b/pkg/build/daggerbuild/cmd/app.go index f63dcea934c..ac4d1f61bbf 100644 --- a/pkg/build/daggerbuild/cmd/app.go +++ b/pkg/build/daggerbuild/cmd/app.go @@ -26,27 +26,7 @@ func (c *CLI) App() *cli.App { Name: "grafana-build", Usage: "A build tool for Grafana", Commands: []*cli.Command{ - // Legacy commands, should eventually be completely replaced by what's in "artifacts" - { - Name: "package", - Subcommands: []*cli.Command{ - PackagePublishCommand, - }, - }, - { - Name: "docker", - Subcommands: []*cli.Command{ - DockerPublishCommand, - }, - }, - ProImageCommand, - { - Name: "npm", - Subcommands: []*cli.Command{ - PublishNPMCommand, - }, - }, - GCOMCommand, + c.ArtifactsCommand(), }, } } diff --git a/pkg/build/daggerbuild/cmd/docker_publish.go b/pkg/build/daggerbuild/cmd/docker_publish.go deleted file mode 100644 index a809e28eb80..00000000000 --- a/pkg/build/daggerbuild/cmd/docker_publish.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -var DockerPublishCommand = &cli.Command{ - Name: "publish", - Action: PipelineActionWithPackageInput(pipelines.PublishDocker), - Usage: "Using a grafana.docker.tar.gz as input (ideally one built using the 'package' command), publish a docker image and manifest", - Flags: JoinFlagsWithDefault( - PackageInputFlags, - DockerFlags, - DockerPublishFlags, - GCPFlags, - ConcurrencyFlags, - ), -} diff --git a/pkg/build/daggerbuild/cmd/gcom.go b/pkg/build/daggerbuild/cmd/gcom.go deleted file mode 100644 index 6414cba99a6..00000000000 --- a/pkg/build/daggerbuild/cmd/gcom.go +++ /dev/null @@ -1,11 +0,0 @@ -package cmd - -import ( - "github.com/urfave/cli/v2" -) - -var GCOMCommand = &cli.Command{ - Name: "gcom", - Description: "Executes requests to grafana.com", - Subcommands: []*cli.Command{GCOMPublishCommand}, -} diff --git a/pkg/build/daggerbuild/cmd/gcom_publish.go b/pkg/build/daggerbuild/cmd/gcom_publish.go deleted file mode 100644 index 0d41aa05096..00000000000 --- a/pkg/build/daggerbuild/cmd/gcom_publish.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -var GCOMPublishCommand = &cli.Command{ - Name: "publish", - Action: PipelineActionWithPackageInput(pipelines.PublishGCOM), - Description: "Publishes a grafana.tar.gz (ideally one built using the 'package' command) to grafana.com (--destination will be the download path)", - Flags: JoinFlagsWithDefault( - GCOMFlags, - PackageInputFlags, - PublishFlags, - ConcurrencyFlags, - ), -} diff --git a/pkg/build/daggerbuild/cmd/main.go b/pkg/build/daggerbuild/cmd/main.go deleted file mode 100644 index 6b31194b684..00000000000 --- a/pkg/build/daggerbuild/cmd/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "os" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -// Deprecated: use the Artifact type instead -func PipelineActionWithPackageInput(pf pipelines.PipelineFuncWithPackageInput) cli.ActionFunc { - return func(c *cli.Context) error { - var ( - ctx = c.Context - opts = []dagger.ClientOpt{} - ) - if c.Bool("verbose") { - opts = append(opts, dagger.WithLogOutput(os.Stderr)) - } - client, err := dagger.Connect(ctx, opts...) - if err != nil { - return err - } - defer func(c *dagger.Client) { - if err := c.Close(); err != nil { - fmt.Println("error closing dagger client:", err) - } - }(client) - - args, err := pipelines.PipelineArgsFromContext(ctx, c) - if err != nil { - return err - } - - if len(args.PackageInputOpts.Packages) == 0 { - return errors.New("expected at least one package from a '--package' flag") - } - - if err := pf(ctx, client, args); err != nil { - return err - } - return nil - } -} diff --git a/pkg/build/daggerbuild/cmd/npm_publish.go b/pkg/build/daggerbuild/cmd/npm_publish.go deleted file mode 100644 index 4a764034cca..00000000000 --- a/pkg/build/daggerbuild/cmd/npm_publish.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -var PublishNPMCommand = &cli.Command{ - Name: "publish", - Action: PipelineActionWithPackageInput(pipelines.PublishNPM), - Usage: "Using a grafana.tar.gz as input (ideally one built using the 'package' command), take the npm artifacts and publish them on NPM.", - Flags: JoinFlagsWithDefault( - PackageInputFlags, - NPMFlags, - GCPFlags, - ConcurrencyFlags, - ), -} diff --git a/pkg/build/daggerbuild/cmd/package_publish.go b/pkg/build/daggerbuild/cmd/package_publish.go deleted file mode 100644 index 095a848ad12..00000000000 --- a/pkg/build/daggerbuild/cmd/package_publish.go +++ /dev/null @@ -1,18 +0,0 @@ -package cmd - -import ( - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -var PackagePublishCommand = &cli.Command{ - Name: "publish", - Action: PipelineActionWithPackageInput(pipelines.PublishPackage), - Description: "Publishes a grafana.tar.gz (ideally one built using the 'package' command) in the destination directory (--destination)", - Flags: JoinFlagsWithDefault( - PackageInputFlags, - PublishFlags, - GCPFlags, - ConcurrencyFlags, - ), -} diff --git a/pkg/build/daggerbuild/cmd/pro_image.go b/pkg/build/daggerbuild/cmd/pro_image.go deleted file mode 100644 index 43001452244..00000000000 --- a/pkg/build/daggerbuild/cmd/pro_image.go +++ /dev/null @@ -1,13 +0,0 @@ -package cmd - -import ( - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" - "github.com/urfave/cli/v2" -) - -var ProImageCommand = &cli.Command{ - Name: "pro-image", - Action: PipelineActionWithPackageInput(pipelines.ProImage), - Description: "Creates a hosted grafana pro image", - Flags: JoinFlagsWithDefault(ProImageFlags, GCPFlags, PackageInputFlags), -} diff --git a/pkg/build/daggerbuild/containers/google_cloud.go b/pkg/build/daggerbuild/containers/google_cloud.go deleted file mode 100644 index 3463aaec270..00000000000 --- a/pkg/build/daggerbuild/containers/google_cloud.go +++ /dev/null @@ -1,132 +0,0 @@ -package containers - -import ( - "fmt" - "math/rand" - "os" - "path/filepath" - "strconv" - - "dagger.io/dagger" -) - -const GoogleCloudImage = "google/cloud-sdk:alpine" - -// GCPAuthenticator injects authentication information into the provided container. -type GCPAuthenticator interface { - Authenticate(*dagger.Client, *dagger.Container) (*dagger.Container, error) -} - -// GCPServiceAccount satisfies GCPAuthenticator and injects the provided ServiceAccount into the filesystem and adds a 'gcloud auth activate-service-account' -type GCPServiceAccount struct { - DaggerFile *dagger.File - JSONFile string -} - -func (a *GCPServiceAccount) Authenticate(d *dagger.Client, c *dagger.Container) (*dagger.Container, error) { - if a.DaggerFile == nil && a.JSONFile == "" { - return nil, fmt.Errorf("GCPServiceAccount authentication missed JSONFile AND DaggerFile") - } - var container *dagger.Container - - if a.JSONFile != "" { - container = c.WithMountedFile( - "/opt/service_account.json", - d.Host().Directory(filepath.Dir(a.JSONFile)).File(filepath.Base(a.JSONFile)), - ) - } - - if a.DaggerFile != nil { - container = c.WithMountedFile("/opt/service_account.json", a.DaggerFile) - } - - return container.WithExec([]string{"gcloud", "auth", "activate-service-account", "--key-file", "/opt/service_account.json"}), nil -} - -func NewGCPServiceAccount(filepath string) *GCPServiceAccount { - return &GCPServiceAccount{ - JSONFile: filepath, - } -} - -func NewGCPServiceAccountWithFile(file *dagger.File) *GCPServiceAccount { - return &GCPServiceAccount{ - DaggerFile: file, - } -} - -// InheritedServiceAccount uses `gcloud` command in the current shell to get the GCS credentials. -// This type should really only be used when running locally. -type GCPInheritedAuth struct{} - -func (a *GCPInheritedAuth) Authenticate(d *dagger.Client, c *dagger.Container) (*dagger.Container, error) { - if val, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok { - return c.WithMountedDirectory("/auth/credentials.json", d.Host().Directory(val)).WithEnvVariable("GOOGLE_APPLICATION_CREDENTIALS", "/auth/credentials.json"), nil - } - - cfg, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - return c.WithMountedDirectory("/root/.config/gcloud", d.Host().Directory(filepath.Join(cfg, ".config", "gcloud"))), nil -} - -func GCSUploadDirectory(d *dagger.Client, image string, auth GCPAuthenticator, dir *dagger.Directory, dst string) (*dagger.Container, error) { - container := d.Container().From(image). - WithMountedDirectory("/src", dir) - - var err error - container, err = auth.Authenticate(d, container) - if err != nil { - return nil, err - } - - secret := d.SetSecret("gcs-destination", dst) - container = container.WithSecretVariable("GCS_DESTINATION", secret) - - return container.WithExec([]string{"/bin/sh", "-c", "gcloud storage cp -r /src/* ${GCS_DESTINATION}"}), nil -} - -func GCSDownloadFile(d *dagger.Client, image string, auth GCPAuthenticator, url string) (*dagger.File, error) { - var ( - container = d.Container().From(image) - err error - r = rand.Int() - ) - - container, err = auth.Authenticate(d, container) - if err != nil { - return nil, err - } - secret := d.SetSecret("gcs-download-url", url) - file := container. - WithEnvVariable("RAND", strconv.Itoa(r)). - WithSecretVariable("GCS_DOWNLOAD_URL", secret). - WithExec([]string{"/bin/sh", "-c", "gcloud storage cp ${GCS_DOWNLOAD_URL} /src/file"}). - File("/src/file") - - return file, nil -} - -func GCSAuth(d *dagger.Client, opts *GCPOpts) GCPAuthenticator { - var auth GCPAuthenticator = &GCPInheritedAuth{} - // The order of operations: - // 1. Try to use base64 key. - // 2. Try to use gcp-service-account-key (path to a file). - // 3. Try mounting $XDG_CONFIG_HOME/gcloud - if key := opts.ServiceAccountKeyBase64; key != "" { - secret := d.SetSecret("gcp-sa-key-base64", key) - // Write key to a file in an alpine container... - file := d.Container().From("alpine"). - WithSecretVariable("GCP_SERVICE_ACCOUNT_KEY_BASE64", secret). - WithExec([]string{"/bin/sh", "-c", "echo $GCP_SERVICE_ACCOUNT_KEY_BASE64 | base64 -d > /key.json"}). - File("/key.json") - - auth = NewGCPServiceAccountWithFile(file) - } else if key := opts.ServiceAccountKey; key != "" { - auth = NewGCPServiceAccount(key) - } - - return auth -} diff --git a/pkg/build/daggerbuild/containers/opts_pro_image.go b/pkg/build/daggerbuild/containers/opts_pro_image.go deleted file mode 100644 index 8affbc6f753..00000000000 --- a/pkg/build/daggerbuild/containers/opts_pro_image.go +++ /dev/null @@ -1,42 +0,0 @@ -package containers - -import "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil" - -type ProImageOpts struct { - // Github token used to clone private repositories. - GitHubToken string - - // The path to a Grafana debian package. - Deb string - - // The Grafana version. - GrafanaVersion string - - // The docker image tag. - ImageTag string - - // The docker image repo. - Repo string - - // The release type. - ReleaseType string - - // True if the pro image should be pushed to the container registry. - Push bool - - // The container registry that the image should be pushed to. Required if Push is true. - ContainerRegistry string -} - -func ProImageOptsFromFlags(c cliutil.CLIContext) *ProImageOpts { - return &ProImageOpts{ - GitHubToken: c.String("github-token"), - Deb: c.String("deb"), - GrafanaVersion: c.String("grafana-version"), - ImageTag: c.String("image-tag"), - Repo: c.String("repo"), - ReleaseType: c.String("release-type"), - Push: c.Bool("push"), - ContainerRegistry: c.String("registry"), - } -} diff --git a/pkg/build/daggerbuild/containers/package_input.go b/pkg/build/daggerbuild/containers/package_input.go deleted file mode 100644 index 13b4e53117f..00000000000 --- a/pkg/build/daggerbuild/containers/package_input.go +++ /dev/null @@ -1,73 +0,0 @@ -package containers - -import ( - "context" - "fmt" - "net/url" - "path/filepath" - "strings" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil" -) - -type PackageInputOpts struct { - // Name is used when overriding the artifact that is being produced. This is used in very specific scenarios where - // the source package's name does not match the package's metadata name. - Name string - Packages []string -} - -func PackageInputOptsFromFlags(c cliutil.CLIContext) *PackageInputOpts { - return &PackageInputOpts{ - Name: c.String("name"), - Packages: c.StringSlice("package"), - } -} - -// GetPackage uses the PackageInputOpts to get a Grafana package, either from the local filesystem (if the package is of type 'file://...') -// or Google Cloud Storage if the package is a 'gs://' URL. -func GetPackages(ctx context.Context, d *dagger.Client, packageOpts *PackageInputOpts, gcpOpts *GCPOpts) ([]*dagger.File, error) { - files := make([]*dagger.File, len(packageOpts.Packages)) - for i, pkg := range packageOpts.Packages { - u, err := url.Parse(pkg) - if err != nil { - return nil, err - } - - var file *dagger.File - switch u.Scheme { - case "file", "fs": - p := strings.TrimPrefix(u.String(), u.Scheme+"://") - f, err := getLocalPackage(ctx, d, p) - if err != nil { - return nil, err - } - - file = f - case "gs": - f, err := getGCSPackage(ctx, d, gcpOpts, u.String()) - if err != nil { - return nil, err - } - - file = f - default: - return nil, fmt.Errorf("%w: %s", ErrorUnrecognizedScheme, u.Scheme) - } - - files[i] = file - } - - return files, nil -} - -func getLocalPackage(ctx context.Context, d *dagger.Client, file string) (*dagger.File, error) { - // pending https://github.com/dagger/dagger/issues/4745 - return d.Host().Directory(filepath.Dir(file)).File(filepath.Base(file)), nil -} - -func getGCSPackage(ctx context.Context, d *dagger.Client, opts *GCPOpts, gcsURL string) (*dagger.File, error) { - auth := GCSAuth(d, opts) - return GCSDownloadFile(d, GoogleCloudImage, auth, gcsURL) -} diff --git a/pkg/build/daggerbuild/containers/publish_dir.go b/pkg/build/daggerbuild/containers/publish_dir.go deleted file mode 100644 index abe1ad0bbdd..00000000000 --- a/pkg/build/daggerbuild/containers/publish_dir.go +++ /dev/null @@ -1,62 +0,0 @@ -package containers - -import ( - "context" - "fmt" - "log" - "net/url" - "strings" - - "dagger.io/dagger" -) - -func publishLocalDir(ctx context.Context, dir *dagger.Directory, dst string) error { - if _, err := dir.Export(ctx, dst); err != nil { - return err - } - - return nil -} - -func publishGCSDir(ctx context.Context, d *dagger.Client, dir *dagger.Directory, opts *GCPOpts, dst string) error { - auth := GCSAuth(d, opts) - uploader, err := GCSUploadDirectory(d, GoogleCloudImage, auth, dir, dst) - if err != nil { - return err - } - - if _, err := ExitError(ctx, uploader); err != nil { - return err - } - - return nil -} - -// PublishDirectory publishes a directory to the given destination. -func PublishDirectory(ctx context.Context, d *dagger.Client, dir *dagger.Directory, opts *GCPOpts, dst string) (string, error) { - log.Println("Publishing directory", dst) - u, err := url.Parse(dst) - if err != nil { - // If the destination URL is not a URL then we can assume that it's just a filepath. - if err := publishLocalDir(ctx, dir, dst); err != nil { - return "", err - } - return "", err - } - - switch u.Scheme { - case "file", "fs": - dst := strings.TrimPrefix(u.String(), u.Scheme+"://") - if err := publishLocalDir(ctx, dir, dst); err != nil { - return "", err - } - case "gs": - if err := publishGCSDir(ctx, d, dir, opts, dst); err != nil { - return "", err - } - default: - return "", fmt.Errorf("%w: '%s'", ErrorUnrecognizedScheme, u.Scheme) - } - - return dst, nil -} diff --git a/pkg/build/daggerbuild/gcom/opts.go b/pkg/build/daggerbuild/gcom/opts.go deleted file mode 100644 index b2a97839e0b..00000000000 --- a/pkg/build/daggerbuild/gcom/opts.go +++ /dev/null @@ -1,34 +0,0 @@ -package gcom - -import ( - "net/url" - - "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil" -) - -// GCOMOpts are options used when making requests to grafana.com. -type GCOMOpts struct { - URL *url.URL - DownloadURL *url.URL - ApiKey string - Beta bool - Nightly bool -} - -func GCOMOptsFromFlags(c cliutil.CLIContext) (*GCOMOpts, error) { - apiUrl, err := url.Parse(c.String("api-url")) - if err != nil { - return nil, err - } - downloadUrl, err := url.Parse(c.String("download-url")) - if err != nil { - return nil, err - } - return &GCOMOpts{ - URL: apiUrl, - DownloadURL: downloadUrl, - ApiKey: c.String("api-key"), - Beta: c.Bool("beta"), - Nightly: c.Bool("nightly"), - }, nil -} diff --git a/pkg/build/daggerbuild/gcom/publish.go b/pkg/build/daggerbuild/gcom/publish.go deleted file mode 100644 index 8c656f0acf9..00000000000 --- a/pkg/build/daggerbuild/gcom/publish.go +++ /dev/null @@ -1,60 +0,0 @@ -package gcom - -import ( - "context" - "encoding/json" - "fmt" - - "dagger.io/dagger" -) - -type GCOMVersionPayload struct { - Version string `json:"version"` - ReleaseDate string `json:"releaseDate"` - Stable bool `json:"stable"` - Beta bool `json:"beta"` - Nightly bool `json:"nightly"` - WhatsNewURL string `json:"whatsNewUrl"` - ReleaseNotesURL string `json:"releaseNotesUrl"` -} - -type GCOMPackagePayload struct { - OS string `json:"os"` - URL string `json:"url"` - Sha256 string `json:"sha256"` - Arch string `json:"arch"` -} - -// PublishGCOMVersion publishes a version to grafana.com. -func PublishGCOMVersion(ctx context.Context, d *dagger.Client, versionPayload *GCOMVersionPayload, opts *GCOMOpts) (string, error) { - versionApiUrl := opts.URL.JoinPath("/versions") - - jsonVersionPayload, err := json.Marshal(versionPayload) - if err != nil { - return "", err - } - - apiKeySecret := d.SetSecret("gcom-api-key", opts.ApiKey) - - return d.Container().From("alpine/curl"). - WithSecretVariable("GCOM_API_KEY", apiKeySecret). - WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonVersionPayload), versionApiUrl.String())}). - Stdout(ctx) -} - -// PublishGCOMPackage publishes a package to grafana.com. -func PublishGCOMPackage(ctx context.Context, d *dagger.Client, packagePayload *GCOMPackagePayload, opts *GCOMOpts, version string) (string, error) { - packagesApiUrl := opts.URL.JoinPath("/versions/", version, "/packages") - - jsonPackagePayload, err := json.Marshal(packagePayload) - if err != nil { - return "", err - } - - apiKeySecret := d.SetSecret("gcom-api-key", opts.ApiKey) - - return d.Container().From("alpine/curl"). - WithSecretVariable("GCOM_API_KEY", apiKeySecret). - WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`curl -H "Content-Type: application/json" -H "Authorization: Bearer $GCOM_API_KEY" -d '%s' %s`, string(jsonPackagePayload), packagesApiUrl.String())}). - Stdout(ctx) -} diff --git a/pkg/build/daggerbuild/pipelines/docker_publish.go b/pkg/build/daggerbuild/pipelines/docker_publish.go deleted file mode 100644 index 1c714026b53..00000000000 --- a/pkg/build/daggerbuild/pipelines/docker_publish.go +++ /dev/null @@ -1,141 +0,0 @@ -package pipelines - -import ( - "context" - "fmt" - "log" - "strings" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/docker" - "golang.org/x/sync/errgroup" - "golang.org/x/sync/semaphore" -) - -func ImageManifest(tag string) string { - log.Println(tag) - log.Println(tag) - log.Println(tag) - log.Println(tag) - manifest := strings.ReplaceAll(tag, "-image-tags", "") - lastDash := strings.LastIndex(manifest, "-") - return manifest[:lastDash] -} - -func LatestManifest(tag string) string { - suffix := "" - if strings.Contains(tag, "ubuntu") { - suffix = "-ubuntu" - } - - manifest := strings.ReplaceAll(tag, "-image-tags", "") - manifestImage := strings.Split(manifest, ":")[0] - return strings.Join([]string{manifestImage, fmt.Sprintf("latest%s", suffix)}, ":") -} - -// PublishDocker is a pipeline that uses a grafana.docker.tar.gz as input and publishes a Docker image to a container registry or repository. -// Grafana's Dockerfile should support supplying a tar.gz using a --build-arg. -func PublishDocker(ctx context.Context, d *dagger.Client, args PipelineArgs) error { - opts := args.DockerOpts - packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts) - if err != nil { - return err - } - - var ( - wg = &errgroup.Group{} - sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel) - ) - - manifestTags := make(map[string][]string) - for i, name := range args.PackageInputOpts.Packages { - // For each package we retrieve the tags grafana-image-tags and grafana-oss-image-tags, or grafana-enterprise-image-tags - format := opts.TagFormat - if strings.Contains(name, "ubuntu") { - format = opts.UbuntuTagFormat - } - - tarOpts := TarOptsFromFileName(name) - - tags, err := docker.Tags(opts.Org, opts.Registry, []string{opts.Repository}, format, tarOpts.NameOpts()) - if err != nil { - return err - } - log.Println(tags) - for _, tag := range tags { - // For each tag we publish an image and add the tag to the list of tags for a specific manifest - // Since each package has a maximum of 2 tags, this for loop will only run twice on a worst case scenario - manifest := ImageManifest(tag) - manifestTags[manifest] = append(manifestTags[manifest], tag) - - if opts.Latest { - manifest := LatestManifest(tag) - manifestTags[manifest] = append(manifestTags[manifest], tag) - } - - wg.Go(PublishPackageImageFunc(ctx, sm, d, packages[i], tag, opts)) - } - } - - if err := wg.Wait(); err != nil { - // Wait for all images to be published - return err - } - - for manifest, tags := range manifestTags { - // Publish each manifest - wg.Go(PublishDockerManifestFunc(ctx, sm, d, manifest, tags, opts)) - } - - return wg.Wait() -} - -func PublishPackageImageFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, pkg *dagger.File, tag string, opts *docker.DockerOpts) func() error { - return func() error { - log.Printf("[%s] Attempting to publish image", tag) - log.Printf("[%s] Acquiring semaphore", tag) - if err := sm.Acquire(ctx, 1); err != nil { - return fmt.Errorf("failed to acquire semaphore: %w", err) - } - defer sm.Release(1) - log.Printf("[%s] Acquired semaphore", tag) - - log.Printf("[%s] Publishing image", tag) - out, err := docker.PublishPackageImage(ctx, d, pkg, tag, opts.Username, opts.Password, opts.Registry) - if err != nil { - return fmt.Errorf("[%s] error: %w", tag, err) - } - log.Printf("[%s] Done publishing image", tag) - - if _, err := fmt.Fprintln(Stdout, out); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - - return nil - } -} - -func PublishDockerManifestFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, manifest string, tags []string, opts *docker.DockerOpts) func() error { - return func() error { - log.Printf("[%s] Attempting to publish manifest", manifest) - log.Printf("[%s] Acquiring semaphore", manifest) - if err := sm.Acquire(ctx, 1); err != nil { - return fmt.Errorf("failed to acquire semaphore: %w", err) - } - defer sm.Release(1) - log.Printf("[%s] Acquired semaphore", manifest) - - log.Printf("[%s] Publishing manifest", manifest) - out, err := docker.PublishManifest(ctx, d, manifest, tags, opts.Username, opts.Password, opts.Registry) - if err != nil { - return fmt.Errorf("[%s] error: %w", manifest, err) - } - log.Printf("[%s] Done publishing manifest", manifest) - - if _, err := fmt.Fprintln(Stdout, out); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - return nil - } -} diff --git a/pkg/build/daggerbuild/pipelines/docker_publish_test.go b/pkg/build/daggerbuild/pipelines/docker_publish_test.go deleted file mode 100644 index 4bcfe540ad1..00000000000 --- a/pkg/build/daggerbuild/pipelines/docker_publish_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package pipelines_test - -import ( - "testing" - - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" -) - -func TestImageManifest(t *testing.T) { - manifests := map[string]string{ - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana:1.2.3-test.1.2.3-ubuntu", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3-ubuntu", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana:1.2.3-test.1.2.3-ubuntu", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-oss:1.2.3-test.1.2.3-ubuntu", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3-ubuntu", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-enterprise:1.2.3-test.1.2.3-ubuntu", - } - - for k, v := range manifests { - if n := pipelines.ImageManifest(k); n != v { - t.Errorf("Expected '%s' manifest to equal '%s' but got '%s'", k, v, n) - } - } -} - -func TestLatestManifest(t *testing.T) { - manifests := map[string]string{ - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana:latest", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-oss:latest", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana:latest", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-oss:latest", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana:latest-ubuntu", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-oss:latest-ubuntu", - "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana:latest-ubuntu", - "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-oss:latest-ubuntu", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64": "docker.io/grafana/grafana-enterprise:latest", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64": "docker.io/grafana/grafana-enterprise:latest", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64": "docker.io/grafana/grafana-enterprise:latest-ubuntu", - "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64": "docker.io/grafana/grafana-enterprise:latest-ubuntu", - } - - for k, v := range manifests { - if n := pipelines.LatestManifest(k); n != v { - t.Errorf("Expected '%s' manifest to equal '%s' but got '%s'", k, v, n) - } - } -} diff --git a/pkg/build/daggerbuild/pipelines/docker_test.go b/pkg/build/daggerbuild/pipelines/docker_test.go deleted file mode 100644 index 4d6aea01b94..00000000000 --- a/pkg/build/daggerbuild/pipelines/docker_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package pipelines_test - -// -// func TestImageName(t *testing.T) { -// // Normally I don't advocate for abstracting tests using test cases -// // but I think in this case I would really like to get a clearer view into what docker image tags will be produced. -// // Be sure that if you add additional test cases to this that you don't use formatting or concatenation; it should be obvious when looking at the test -// // what the expected output should be. And that value should not change based on another value. -// type tc struct { -// Description string -// Tags []string -// BaseImage pipelines.BaseImage -// DockerOpts *containers.DockerOpts -// TarOpts pipelines.TarFileOpts -// } -// -// var ( -// version = "v1.2.3-test.1.2.3" -// ) -// -// cases := []tc{ -// { -// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-amd64", -// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64", -// }, -// }, -// { -// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. ARM64 images have a -arm64 suffix. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/arm64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-arm64", -// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-arm64", -// }, -// }, -// { -// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. Ubuntu images have a '-ubuntu' suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageUbuntu, -// Tags: []string{ -// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-amd64", -// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-amd64", -// }, -// }, -// { -// Description: "Grafana docker images are created for both the 'docker.io/grafana/grafana-image-tags' and 'docker.io/grafana/grafana-oss-image-tags' repositories. ARM64 images have an -arm64 suffix. Ubuntu images have a '-ubuntu' suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/arm64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageUbuntu, -// Tags: []string{ -// "docker.io/grafana/grafana-image-tags:1.2.3-test.1.2.3-ubuntu-arm64", -// "docker.io/grafana/grafana-oss-image-tags:1.2.3-test.1.2.3-ubuntu-arm64", -// }, -// }, -// { -// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "enterprise", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-amd64", -// }, -// }, -// { -// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. ARM64 images have an -arm64 suffix. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "enterprise", -// Distro: "linux/arm64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-arm64", -// }, -// }, -// { -// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. Ubuntu images have a '-ubuntu' suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "enterprise", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageUbuntu, -// Tags: []string{ -// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-amd64", -// }, -// }, -// { -// Description: "Enterprise docker images are created for only the docker.io/grafana/grafana-enterprise-image-tags repository. ARM64 images have an -arm64 suffix. Ubuntu images have a '-ubuntu' suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "enterprise", -// Distro: "linux/arm64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "grafana", -// Registry: "docker.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageUbuntu, -// Tags: []string{ -// "docker.io/grafana/grafana-enterprise-image-tags:1.2.3-test.1.2.3-ubuntu-arm64", -// }, -// }, -// { -// Description: "Grafana docker images are created for both the 'registry.io/org/grafana-image-tags' and 'registry.io/org/grafana-oss-image-tags' repositories. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "org", -// Registry: "registry.io", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "registry.io/org/grafana-image-tags:1.2.3-test.1.2.3-amd64", -// "registry.io/org/grafana-oss-image-tags:1.2.3-test.1.2.3-amd64", -// }, -// }, -// { -// Description: "Grafana docker images are created for only the 'registry.io/org/grafana-dev' repository. Alpine images have no suffix.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "org", -// Registry: "registry.io", -// Repository: "grafana-dev", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageAlpine, -// Tags: []string{ -// "registry.io/org/grafana-dev:1.2.3-test.1.2.3-amd64", -// }, -// }, -// { -// Description: "Grafana docker images are created for only the 'registry.io/org/grafana-dev' repository.", -// TarOpts: pipelines.TarFileOpts{ -// Edition: "", -// Distro: "linux/amd64", -// Version: version, -// }, -// DockerOpts: &containers.DockerOpts{ -// Org: "org", -// Registry: "registry.io", -// Repository: "grafana-dev", -// TagFormat: pipelines.DefaultTagFormat, -// UbuntuTagFormat: pipelines.DefaultUbuntuTagFormat, -// }, -// BaseImage: pipelines.BaseImageUbuntu, -// Tags: []string{ -// "registry.io/org/grafana-dev:1.2.3-test.1.2.3-ubuntu-amd64", -// }, -// }, -// } -// -// for n, test := range cases { -// t.Run(fmt.Sprintf("[%d / %d] %s", n+1, len(cases), test.Description), func(t *testing.T) { -// expect := sort.StringSlice(test.Tags) -// res, err := pipelines.GrafanaImageTags(test.BaseImage, test.DockerOpts, test.TarOpts) -// if err != nil { -// t.Fatal("Unexpected error:", err.Error()) -// } -// -// for i := range expect { -// e := expect[i] -// r := res[i] -// if e != r { -// t.Errorf("[%d / %d]\nExpected '%s'\nReceived '%s'", i+1, len(expect), e, r) -// } -// } -// }) -// } -// } diff --git a/pkg/build/daggerbuild/pipelines/gcom_publish.go b/pkg/build/daggerbuild/pipelines/gcom_publish.go deleted file mode 100644 index 7fc28d39201..00000000000 --- a/pkg/build/daggerbuild/pipelines/gcom_publish.go +++ /dev/null @@ -1,154 +0,0 @@ -package pipelines - -import ( - "context" - "fmt" - "log" - "path/filepath" - "strings" - "time" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/backend" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/gcom" - "golang.org/x/sync/errgroup" - "golang.org/x/sync/semaphore" -) - -func VersionPayloadFromFileName(name string, opts *gcom.GCOMOpts) *gcom.GCOMVersionPayload { - var ( - tarOpts = TarOptsFromFileName(name) - splitVersion = strings.Split(tarOpts.Version, ".") - stable = true - nightly = false - beta = false - ) - - if opts.Beta { - stable = false - beta = true - } - if opts.Nightly { - stable = false - beta = false - nightly = true - } - - return &gcom.GCOMVersionPayload{ - Version: tarOpts.Version, - ReleaseDate: time.Now().Format(time.RFC3339Nano), - Stable: stable, - Beta: beta, - Nightly: nightly, - WhatsNewURL: fmt.Sprintf("https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%s-%s/", splitVersion[0], splitVersion[1]), - ReleaseNotesURL: "https://grafana.com/docs/grafana/next/release-notes/", - } -} - -func PackagePayloadFromFile(ctx context.Context, d *dagger.Client, name string, file *dagger.File, opts *gcom.GCOMOpts) (*gcom.GCOMPackagePayload, error) { - tarOpts := TarOptsFromFileName(name) - ext := filepath.Ext(name) - os, _ := backend.OSAndArch(tarOpts.Distro) - arch := strings.ReplaceAll(backend.FullArch(tarOpts.Distro), "/", "") - - if os == "windows" { - os = "win" - } - - if ext == ".deb" { - os = "deb" - } - if ext == ".rpm" { - os = "rhel" - } - if ext == ".exe" { - os = "win-installer" - } - - sha256, err := containers.Sha256(d, file).Contents(ctx) - if err != nil { - return nil, err - } - - return &gcom.GCOMPackagePayload{ - OS: os, - URL: opts.DownloadURL.JoinPath(name).String(), - Sha256: sha256, - Arch: arch, - }, nil -} - -func PublishGCOM(ctx context.Context, d *dagger.Client, args PipelineArgs) error { - var ( - opts = args.GCOMOpts - wg = &errgroup.Group{} - sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel) - ) - - packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts) - if err != nil { - return err - } - - // Extract the package versions - versionPayloads := make(map[string]*gcom.GCOMVersionPayload) - for _, name := range args.PackageInputOpts.Packages { - tarOpts := TarOptsFromFileName(name) - if _, ok := versionPayloads[tarOpts.Version]; !ok { - log.Printf("[%s] Building version payload", tarOpts.Version) - versionPayloads[tarOpts.Version] = VersionPayloadFromFileName(name, opts) - } - } - - // Publish each version only once - for _, p := range versionPayloads { - log.Printf("[%s] Attempting to publish version", p.Version) - out, err := gcom.PublishGCOMVersion(ctx, d, p, opts) - if err != nil { - return err - } - log.Printf("[%s] Done publishing version", p.Version) - if _, err := fmt.Fprintln(Stdout, strings.ReplaceAll(out, "\n", "")); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - } - - // Publish the package(s) - for i, name := range args.PackageInputOpts.Packages { - wg.Go(PublishGCOMPackageFunc(ctx, sm, d, opts, name, packages[i])) - } - return wg.Wait() -} - -func PublishGCOMPackageFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, opts *gcom.GCOMOpts, path string, file *dagger.File) func() error { - return func() error { - name := filepath.Base(path) - tarOpts := TarOptsFromFileName(name) - log.Printf("[%s] Attempting to publish package", name) - log.Printf("[%s] Acquiring semaphore", name) - if err := sm.Acquire(ctx, 1); err != nil { - return fmt.Errorf("failed to acquire semaphore: %w", err) - } - defer sm.Release(1) - log.Printf("[%s] Acquired semaphore", name) - - log.Printf("[%s] Building package payload", name) - packagePayload, err := PackagePayloadFromFile(ctx, d, name, file, opts) - if err != nil { - return fmt.Errorf("[%s] error: %w", name, err) - } - - log.Printf("[%s] Publishing package", name) - out, err := gcom.PublishGCOMPackage(ctx, d, packagePayload, opts, tarOpts.Version) - if err != nil { - return fmt.Errorf("[%s] error: %w", name, err) - } - log.Printf("[%s] Done publishing package", name) - - if _, err := fmt.Fprintln(Stdout, strings.ReplaceAll(out, "\n", "")); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - return nil - } -} diff --git a/pkg/build/daggerbuild/pipelines/npm_publish.go b/pkg/build/daggerbuild/pipelines/npm_publish.go deleted file mode 100644 index aab5366818b..00000000000 --- a/pkg/build/daggerbuild/pipelines/npm_publish.go +++ /dev/null @@ -1,69 +0,0 @@ -package pipelines - -import ( - "context" - "fmt" - "log" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/frontend" - "golang.org/x/sync/errgroup" - "golang.org/x/sync/semaphore" -) - -func PublishNPM(ctx context.Context, d *dagger.Client, args PipelineArgs) error { - var ( - wg = &errgroup.Group{} - sm = semaphore.NewWeighted(args.ConcurrencyOpts.Parallel) - ) - - packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts) - if err != nil { - return err - } - - // Extract the package(s) - for i := range args.PackageInputOpts.Packages { - var ( - // name = ReplaceExt(v, "") - targz = packages[i] - ) - - artifacts := containers.ExtractedArchive(d, targz).Directory("npm-artifacts") - - entries, err := artifacts.Entries(ctx) - if err != nil { - return err - } - - for _, path := range entries { - wg.Go(PublishNPMFunc(ctx, sm, d, artifacts.File(path), path, args.NpmToken, args.NpmRegistry, args.NpmTags)) - } - } - return wg.Wait() -} - -func PublishNPMFunc(ctx context.Context, sm *semaphore.Weighted, d *dagger.Client, pkg *dagger.File, path, token, registry string, tags []string) func() error { - return func() error { - log.Printf("[%s] Attempting to publish package", path) - log.Printf("[%s] Acquiring semaphore", path) - if err := sm.Acquire(ctx, 1); err != nil { - return fmt.Errorf("failed to acquire semaphore: %w", err) - } - defer sm.Release(1) - log.Printf("[%s] Acquired semaphore", path) - - log.Printf("[%s] Publishing package", path) - out, err := frontend.PublishNPM(ctx, d, pkg, token, registry, tags) - if err != nil { - return fmt.Errorf("[%s] error: %w", path, err) - } - log.Printf("[%s] Done publishing package", path) - - if _, err := fmt.Fprintln(Stdout, out); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - return nil - } -} diff --git a/pkg/build/daggerbuild/pipelines/package_names.go b/pkg/build/daggerbuild/pipelines/package_names.go deleted file mode 100644 index 55b4898345b..00000000000 --- a/pkg/build/daggerbuild/pipelines/package_names.go +++ /dev/null @@ -1,87 +0,0 @@ -package pipelines - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/grafana/grafana/pkg/build/daggerbuild/backend" - "github.com/grafana/grafana/pkg/build/daggerbuild/packages" -) - -type TarFileOpts struct { - // Name is the name of the product in the package. 99% of the time, this will be "grafana" or "grafana-enterprise". - Name string - Version string - BuildID string - // Edition is the flavor text after "grafana-", like "enterprise". - Edition string - Distro backend.Distribution - Suffix string -} - -func (opts *TarFileOpts) NameOpts() packages.NameOpts { - return packages.NameOpts{ - // Name is the name of the product in the package. 99% of the time, this will be "grafana" or "grafana-enterprise". - Name: packages.Name(opts.Name), - Version: opts.Version, - BuildID: opts.BuildID, - Distro: opts.Distro, - } -} - -func WithoutExt(name string) string { - ext := filepath.Ext(name) - n := strings.TrimSuffix(name, ext) - - // Explicitly handle `.gz` which might will also probably have a `.tar` extension as well. - if ext == ".gz" { - n = strings.TrimSuffix(n, ".ubuntu.docker.tar") - n = strings.TrimSuffix(n, ".docker.tar") - n = strings.TrimSuffix(n, ".tar") - } - - return n -} - -func TarOptsFromFileName(filename string) TarFileOpts { - filename = filepath.Base(filename) - n := WithoutExt(filename) - components := strings.Split(n, "_") - if len(components) != 5 { - return TarFileOpts{} - } - - var ( - name = components[0] - version = components[1] - buildID = components[2] - os = components[3] - arch = components[4] - ) - if archv := strings.Split(arch, "-"); len(archv) == 2 { - // The reverse operation of removing the 'v' for 'arm' because the golang environment variable - // GOARM doesn't want it, but the docker --platform flag either doesn't care or does want it. - if archv[0] == "arm" { - archv[1] = "v" + archv[1] - } - - // arm-7 should become arm/v7 - arch = strings.Join([]string{archv[0], archv[1]}, "/") - } - edition := "" - suffix := "" - if n := strings.Split(name, "-"); len(n) != 1 { - edition = strings.Join(n[1:], "-") - suffix = fmt.Sprintf("-%s", n[1]) - } - - return TarFileOpts{ - Name: name, - Edition: edition, - Version: version, - BuildID: buildID, - Distro: backend.Distribution(strings.Join([]string{os, arch}, "/")), - Suffix: suffix, - } -} diff --git a/pkg/build/daggerbuild/pipelines/package_names_test.go b/pkg/build/daggerbuild/pipelines/package_names_test.go deleted file mode 100644 index d4f050178fa..00000000000 --- a/pkg/build/daggerbuild/pipelines/package_names_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package pipelines_test - -import ( - "testing" - - "github.com/grafana/grafana/pkg/build/daggerbuild/backend" - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" -) - -func TestWithoutExt(t *testing.T) { - names := map[string]string{ - "grafana_v1.0.1-test_333_plan9_amd64.tar.gz": "grafana_v1.0.1-test_333_plan9_amd64", - "grafana-enterprise_v1.0.1-test_333_plan9_amd64.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_amd64", - "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6", - "grafana-enterprise_v1.0.1-test_333_plan9_amd64.deb": "grafana-enterprise_v1.0.1-test_333_plan9_amd64", - "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.deb": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6", - "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.docker.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6", - "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.ubuntu.docker.tar.gz": "grafana-enterprise_v1.0.1-test_333_plan9_arm-6", - } - - for k, v := range names { - if n := pipelines.WithoutExt(k); n != v { - t.Errorf("Expected '%s' without file name to equal '%s' but got '%s'", k, v, n) - } - } -} - -func TestOptsFromFile(t *testing.T) { - t.Run("It should get the correct tar file opts from a valid name", func(t *testing.T) { - name := "grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz" - distro := backend.Distribution("plan9/arm/v6") - expect := pipelines.TarFileOpts{ - Edition: "enterprise", - Version: "v1.0.1-test", - BuildID: "333", - Distro: distro, - } - got := pipelines.TarOptsFromFileName(name) - if got.Edition != expect.Edition { - t.Errorf("got.Edition != expect.Edition, expected '%s'", expect.Edition) - } - if got.Version != expect.Version { - t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version) - } - if got.BuildID != expect.BuildID { - t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID) - } - if got.Distro != expect.Distro { - t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro) - } - }) - t.Run("It should consider only the basename", func(t *testing.T) { - name := "somewhere/grafana-enterprise_v1.0.1-test_333_plan9_arm-6.tar.gz" - distro := backend.Distribution("plan9/arm/v6") - expect := pipelines.TarFileOpts{ - Edition: "enterprise", - Version: "v1.0.1-test", - BuildID: "333", - Distro: distro, - } - got := pipelines.TarOptsFromFileName(name) - if got.Edition != expect.Edition { - t.Errorf("got.Edition != expect.Edition, expected '%s'", expect.Edition) - } - if got.Version != expect.Version { - t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version) - } - if got.BuildID != expect.BuildID { - t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID) - } - if got.Distro != expect.Distro { - t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro) - } - }) - t.Run("It should support editions with multiple hyphens", func(t *testing.T) { - name := "somewhere/grafana-enterprise-rpi_v1.0.1-test_333_plan9_arm-6.tar.gz" - distro := backend.Distribution("plan9/arm/v6") - expect := pipelines.TarFileOpts{ - Edition: "enterprise-rpi", - Version: "v1.0.1-test", - BuildID: "333", - Distro: distro, - } - got := pipelines.TarOptsFromFileName(name) - if got.Edition != expect.Edition { - t.Errorf("got.Edition != expect.Edition, expected '%s', got '%s'", expect.Edition, got.Edition) - } - if got.Version != expect.Version { - t.Errorf("got.Version != expect.Version, expected '%s', got '%s'", expect.Version, got.Version) - } - if got.BuildID != expect.BuildID { - t.Errorf("got.BuildID != expect.BuildID, expected '%s', got '%s'", expect.BuildID, got.BuildID) - } - if got.Distro != expect.Distro { - t.Errorf("got.Distro != expect.Distro, expected '%s', got '%s'", expect.Distro, got.Distro) - } - }) -} diff --git a/pkg/build/daggerbuild/pipelines/package_publish.go b/pkg/build/daggerbuild/pipelines/package_publish.go deleted file mode 100644 index 017500dd7f5..00000000000 --- a/pkg/build/daggerbuild/pipelines/package_publish.go +++ /dev/null @@ -1,34 +0,0 @@ -package pipelines - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" -) - -// PublishPackage takes one or multiple grafana.tar.gz as input and publishes it to a set destination. -func PublishPackage(ctx context.Context, d *dagger.Client, args PipelineArgs) error { - packages, err := containers.GetPackages(ctx, d, args.PackageInputOpts, args.GCPOpts) - if err != nil { - return err - } - - c := d.Container().From("alpine") - for i, name := range args.PackageInputOpts.Packages { - c = c.WithFile("/dist/"+filepath.Base(name), packages[i]) - } - - dst, err := containers.PublishDirectory(ctx, d, c.Directory("dist"), args.GCPOpts, args.PublishOpts.Destination) - if err != nil { - return err - } - if _, err := fmt.Fprintln(os.Stdout, dst); err != nil { - return fmt.Errorf("error writing to stdout: %w", err) - } - - return nil -} diff --git a/pkg/build/daggerbuild/pipelines/package_test.go b/pkg/build/daggerbuild/pipelines/package_test.go deleted file mode 100644 index 75ee9bcf220..00000000000 --- a/pkg/build/daggerbuild/pipelines/package_test.go +++ /dev/null @@ -1 +0,0 @@ -package pipelines_test diff --git a/pkg/build/daggerbuild/pipelines/pipeline_args.go b/pkg/build/daggerbuild/pipelines/pipeline_args.go deleted file mode 100644 index 00352368027..00000000000 --- a/pkg/build/daggerbuild/pipelines/pipeline_args.go +++ /dev/null @@ -1,147 +0,0 @@ -// package pipelines has functions and types that orchestrate containers. -package pipelines - -import ( - "context" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/docker" - "github.com/grafana/grafana/pkg/build/daggerbuild/gcom" - "github.com/grafana/grafana/pkg/build/daggerbuild/gpg" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -type PipelineFunc func(context.Context, *dagger.Client, *dagger.Directory, PipelineArgs) error -type PipelineFuncWithPackageInput func(context.Context, *dagger.Client, PipelineArgs) error - -func DockerOptsFromFlags(c cliutil.CLIContext) *docker.DockerOpts { - return &docker.DockerOpts{ - Registry: c.String("registry"), - AlpineBase: c.String("alpine-base"), - UbuntuBase: c.String("ubuntu-base"), - Username: c.String("username"), - Password: c.String("password"), - Org: c.String("org"), - Repository: c.String("repo"), - Latest: c.Bool("latest"), - TagFormat: c.String("tag-format"), - UbuntuTagFormat: c.String("ubuntu-tag-format"), - } -} - -type ConcurrencyOpts struct { - Parallel int64 -} - -func ConcurrencyOptsFromFlags(c cliutil.CLIContext) *ConcurrencyOpts { - return &ConcurrencyOpts{ - Parallel: c.Int64("parallel"), - } -} - -type PipelineArgs struct { - // These arguments are ones that are available at the global level. - Verbose bool - - // Platform, where applicable, specifies what platform (linux/arm64, for example) to run the dagger containers on. - // This should really only be used if you know what you're doing. misusing this flag can result in really slow builds. - // Some example scenarios where you might want to use this: - // * You're on linux/amd64 and you're building a docker image for linux/armv7 or linux/arm64 - // * You're on linux/arm64 and you're building a package for linux/arm64 - Platform dagger.Platform - - // Context is available for all sub-commands that define their own flags. - Context cliutil.CLIContext - - // GrafanaOpts will be populated if the GrafanaFlags are enabled on the current sub-command. - // GrafanaOpts *containers.GrafanaOpts - - // PackageOpts will be populated if the PackageFlags are enabled on the current sub-command. - // PackageOpts *containers.PackageOpts - - // PublishOpts will be populated if the PublishFlags flags are enabled on the current sub-command - // This is set for pipelines that publish artifacts. - PublishOpts *containers.PublishOpts - - // PackageInputOpts will be populated if the PackageInputFlags are enabled on current sub-command. - // This is set for pipelines that accept a package as input. - PackageInputOpts *containers.PackageInputOpts - GPGOpts *gpg.GPGOpts - DockerOpts *docker.DockerOpts - GCPOpts *containers.GCPOpts - ConcurrencyOpts *ConcurrencyOpts - - // ProImageOpts will be populated if ProImageFlags are enabled on the current sub-command. - ProImageOpts *containers.ProImageOpts - - // NPMOpts will be populated if NPMFlags are enabled on the current sub-command. - NpmToken string - NpmRegistry string - NpmTags []string - - // GCOMOpts will be populated if GCOMFlags are enabled on the current sub-command. - GCOMOpts *gcom.GCOMOpts -} - -// PipelineArgsFromContext populates a pipelines.PipelineArgs from a CLI context. -func PipelineArgsFromContext(ctx context.Context, c cliutil.CLIContext) (PipelineArgs, error) { - // Global flags - var ( - verbose = c.Bool("v") - platform = c.String("platform") - ) - // grafanaOpts, err := containers.GrafanaOptsFromFlags(ctx, c) - // if err != nil { - // return PipelineArgs{}, err - // } - gcomOpts, err := gcom.GCOMOptsFromFlags(c) - if err != nil { - return PipelineArgs{}, err - } - - return PipelineArgs{ - Context: c, - Verbose: verbose, - Platform: dagger.Platform(platform), - // GrafanaOpts: grafanaOpts, - GPGOpts: &gpg.GPGOpts{}, - // PackageOpts: containers.PackageOptsFromFlags(c), - PublishOpts: containers.PublishOptsFromFlags(c), - PackageInputOpts: containers.PackageInputOptsFromFlags(c), - DockerOpts: DockerOptsFromFlags(c), - GCPOpts: containers.GCPOptsFromFlags(c), - ConcurrencyOpts: ConcurrencyOptsFromFlags(c), - ProImageOpts: containers.ProImageOptsFromFlags(c), - GCOMOpts: gcomOpts, - NpmToken: c.String("token"), - NpmRegistry: c.String("registry"), - NpmTags: c.StringSlice("tag"), - }, nil -} - -// InjectPipelineArgsIntoSpan is used to copy some of the arguments passed to -// the pipeline into a top-level OpenTelemtry span. Fields that might contain -// secrets are left out. -func InjectPipelineArgsIntoSpan(span trace.Span, args PipelineArgs) { - attributes := make([]attribute.KeyValue, 0, 10) - attributes = append(attributes, attribute.String("platform", string(args.Platform))) - // if args.GrafanaOpts != nil { - // attributes = append(attributes, attribute.String("go-version", args.GrafanaOpts.GoVersion)) - // attributes = append(attributes, attribute.String("version", args.GrafanaOpts.Version)) - // attributes = append(attributes, attribute.String("grafana-dir", args.GrafanaOpts.GrafanaDir)) - // attributes = append(attributes, attribute.String("grafana-ref", args.GrafanaOpts.GrafanaRef)) - // attributes = append(attributes, attribute.String("enterprise-dir", args.GrafanaOpts.EnterpriseDir)) - // attributes = append(attributes, attribute.String("enterprise-ref", args.GrafanaOpts.EnterpriseRef)) - // } - // if args.PackageOpts != nil { - // distros := []string{} - // for _, distro := range args.PackageOpts.Distros { - // distros = append(distros, string(distro)) - // } - // attributes = append(attributes, attribute.StringSlice("package-distros", distros)) - // } - span.SetAttributes(attributes...) -} diff --git a/pkg/build/daggerbuild/pipelines/pipeline_args_test.go b/pkg/build/daggerbuild/pipelines/pipeline_args_test.go deleted file mode 100644 index 661daa56dec..00000000000 --- a/pkg/build/daggerbuild/pipelines/pipeline_args_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package pipelines_test - -// type TestCLIContext struct { -// Data map[string]interface{} -// } -// -// func (t *TestCLIContext) Bool(key string) bool { -// if _, ok := t.Data[key]; !ok { -// return false -// } -// -// return t.Data[key].(bool) -// } -// -// func (t *TestCLIContext) String(key string) string { -// if _, ok := t.Data[key]; !ok { -// return "" -// } -// -// return t.Data[key].(string) -// } -// -// func (t *TestCLIContext) Set(key string, val string) error { -// t.Data[key] = val -// -// return nil -// } -// -// func (t *TestCLIContext) StringSlice(key string) []string { -// if _, ok := t.Data[key]; !ok { -// return nil -// } -// return t.Data[key].([]string) -// } -// -// func (t *TestCLIContext) Path(key string) string { -// return t.Data[key].(string) -// } -// -// func (t *TestCLIContext) Int64(key string) int64 { -// if _, ok := t.Data[key]; !ok { -// return 0 -// } -// -// return t.Data[key].(int64) -// } -// -// func TestPipelineArgsFromContext(t *testing.T) { -// enterpriseDir, err := os.MkdirTemp("", "grafana-enterprise-*") -// if err != nil { -// t.Fatal(err) -// } -// -// validData := map[string]interface{}{ -// "v": true, -// "version": "v1.0.0", -// "grafana": true, -// "grafana-dir": "/grafana", -// "grafana-ref": "asdf", -// "enterprise": true, -// "enterprise-dir": enterpriseDir, -// "enterprise-ref": "1234", -// "build-id": "build-1234", -// "github-token": "", -// "sign": false, -// } -// -// // t.Run("It should return a PipelineArgs object if there are no errors", func(t *testing.T) { -// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{ -// // Data: validData, -// // }) -// // if err != nil { -// // t.Fatal(err) -// // } -// -// // if args.Verbose != true { -// // t.Error("args.Verbose should be true") -// // } -// // // opts := args.GrafanaOpts -// // // if opts.Version != "v1.0.0" { -// // // t.Error("args.Version should be v1.0.0") -// // // } -// -// // if opts.BuildGrafana != true { -// // t.Error("args.BuildGrafana should be true") -// // } -// -// // if opts.GrafanaDir != "/grafana" { -// // t.Error("args.GrafanaDir should be /grafana") -// // } -// -// // if opts.GrafanaRef != "asdf" { -// // t.Error("args.GrafanaRef should be asdf") -// // } -// -// // if opts.BuildEnterprise != true { -// // t.Error("args.Enterprise should be true") -// // } -// -// // if opts.EnterpriseDir != enterpriseDir { -// // t.Errorf("args.EnterpriseDir should be %s", enterpriseDir) -// // } -// -// // if opts.EnterpriseRef != "1234" { -// // t.Error("args.EnterpriseRef should be 1234") -// // } -// // }) -// -// // t.Run("If no build ID is provided, a random 12-character string should be given", func(t *testing.T) { -// // data := validData -// // data["build-id"] = "" -// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{ -// // Data: data, -// // }) -// // if err != nil { -// // t.Fatal(err) -// // } -// // opts := args.GrafanaOpts -// // if opts.BuildID == "" { -// // t.Fatal("BuildID should not be empty") -// // } -// // if len(opts.BuildID) != 12 { -// // t.Fatal("BuildID should be a 12-character string") -// // } -// // }) -// -// // t.Run("If the --enterprise-ref is set to a non-default value, it should set the enterprise flag to true", func(t *testing.T) { -// // data := validData -// // data["enterprise"] = false -// // data["enterprise-ref"] = "ref-1234" -// -// // args, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{ -// // Data: data, -// // }) -// // if err != nil { -// // t.Fatal(err) -// // } -// // opts := args.GrafanaOpts -// // if opts.BuildEnterprise != true { -// // t.Fatal("args.BuildEnterprise should be true") -// // } -// // }) -// -// t.Run("If the --enterprise-ref is set to a non-default value, it should set the enterprise flag to true", func(t *testing.T) { -// data := validData -// data["enterprise"] = false -// data["enterprise-ref"] = "" -// data["enterprise-dir"] = filepath.Join(enterpriseDir, "does-not-exist") -// -// _, err := pipelines.PipelineArgsFromContext(context.Background(), &TestCLIContext{ -// Data: data, -// }) -// if err == nil { -// t.Fatal("error should not be empty") -// } -// }) -// } diff --git a/pkg/build/daggerbuild/pipelines/pro_image.go b/pkg/build/daggerbuild/pipelines/pro_image.go deleted file mode 100644 index 45d886fbde9..00000000000 --- a/pkg/build/daggerbuild/pipelines/pro_image.go +++ /dev/null @@ -1,85 +0,0 @@ -package pipelines - -import ( - "context" - "fmt" - "log" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/git" -) - -func ProImage(ctx context.Context, dc *dagger.Client, args PipelineArgs) error { - if len(args.PackageInputOpts.Packages) > 1 { - return fmt.Errorf("only one package is allowed: packages=%+v", args.PackageInputOpts.Packages) - } - packages, err := containers.GetPackages(ctx, dc, args.PackageInputOpts, args.GCPOpts) - if err != nil { - return fmt.Errorf("getting packages: packages=%+v %w", args.PackageInputOpts.Packages, err) - } - - debianPackageFile := packages[0] - - log.Printf("Cloning hosted Grafana...") - hostedGrafanaRepo, err := git.CloneWithGitHubToken(dc, args.ProImageOpts.GitHubToken, "https://github.com/grafana/hosted-grafana.git", "main") - if err != nil { - return fmt.Errorf("cloning hosted-grafana repo: %w", err) - } - - socketPath := "/var/run/docker.sock" - socket := dc.Host().UnixSocket(socketPath) - - hostedGrafanaImage := fmt.Sprintf("%s/%s:%s", args.ProImageOpts.ContainerRegistry, args.ProImageOpts.Repo, args.ProImageOpts.ImageTag) - - log.Printf("Building hosted Grafana image: %s", hostedGrafanaImage) - container := dc.Container().From("google/cloud-sdk:433.0.0-alpine"). - WithExec([]string{ - "/bin/sh", "-c", - "gcloud auth configure-docker --quiet", - }). - WithUnixSocket(socketPath, socket). - WithDirectory("/src", hostedGrafanaRepo). - WithFile("/src/grafana.deb", debianPackageFile). - WithWorkdir("/src"). - WithExec([]string{ - "/bin/sh", "-c", - "docker build --platform=linux/amd64 . -f ./cmd/hgrun/Dockerfile -t hgrun:latest", - }). - WithExec([]string{ - "/bin/sh", "-c", - fmt.Sprintf("docker build --platform=linux/amd64 --build-arg=RELEASE_TYPE=%s --build-arg=GRAFANA_VERSION=%s --build-arg=HGRUN_IMAGE=hgrun:latest . -f ./docker/hosted-grafana-all/Dockerfile -t %s", - args.ProImageOpts.ReleaseType, - args.ProImageOpts.GrafanaVersion, - hostedGrafanaImage, - ), - }) - - if args.ProImageOpts.Push { - if args.ProImageOpts.ContainerRegistry == "" { - return fmt.Errorf("--registry= is required") - } - - authenticator := containers.GCSAuth(dc, &containers.GCPOpts{ - ServiceAccountKey: args.GCPOpts.ServiceAccountKey, - ServiceAccountKeyBase64: args.GCPOpts.ServiceAccountKeyBase64, - }) - - authenticatedContainer, err := authenticator.Authenticate(dc, container) - if err != nil { - return fmt.Errorf("authenticating container with gcs auth: %w", err) - } - - log.Printf("Pushing hosted Grafana image to registry...") - container = authenticatedContainer.WithExec([]string{ - "/bin/sh", "-c", - fmt.Sprintf("docker push %s", hostedGrafanaImage), - }) - } - - if _, err := containers.ExitError(ctx, container); err != nil { - return fmt.Errorf("container did not exit successfully: %w", err) - } - - return nil -} diff --git a/pkg/build/daggerbuild/pipelines/publish.go b/pkg/build/daggerbuild/pipelines/publish.go deleted file mode 100644 index d7ec8de3d1f..00000000000 --- a/pkg/build/daggerbuild/pipelines/publish.go +++ /dev/null @@ -1,29 +0,0 @@ -package pipelines - -import ( - "io" - "os" - "sync" -) - -type SyncWriter struct { - Writer io.Writer - - mutex *sync.Mutex -} - -func NewSyncWriter(w io.Writer) *SyncWriter { - return &SyncWriter{ - Writer: w, - mutex: &sync.Mutex{}, - } -} - -func (w *SyncWriter) Write(b []byte) (int, error) { - w.mutex.Lock() - defer w.mutex.Unlock() - - return w.Writer.Write(b) -} - -var Stdout = NewSyncWriter(os.Stdout) diff --git a/pkg/build/daggerbuild/scripts/drone_build_main.sh b/pkg/build/daggerbuild/scripts/drone_build_main.sh deleted file mode 100755 index 4c584cdc3a1..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_main.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env sh - -local_dst="dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:grafana:linux/amd64 \ - -a targz:grafana:linux/arm64 \ - -a targz:grafana:linux/arm/v6 \ - -a targz:grafana:linux/arm/v7 \ - -a targz:grafana:windows/amd64 \ - -a targz:grafana:darwin/amd64 \ - -a deb:grafana:linux/amd64 \ - -a deb:grafana:linux/arm64 \ - -a deb:grafana:linux/arm/v6 \ - -a deb:grafana:linux/arm/v7 \ - -a docker:grafana:linux/amd64 \ - -a docker:grafana:linux/arm64 \ - -a docker:grafana:linux/arm/v7 \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --checksum \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-dir=${GRAFANA_DIR} \ - --github-token=${GITHUB_TOKEN} \ - --ubuntu-base=${UBUNTU_BASE} \ - --alpine-base=${ALPINE_BASE} \ - --destination=${local_dst} > assets.txt - -echo "Final list of artifacts:" -cat assets.txt - -# Move the tar.gz packages to their expected locations -cat assets.txt | IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main diff --git a/pkg/build/daggerbuild/scripts/drone_build_main_enterprise.sh b/pkg/build/daggerbuild/scripts/drone_build_main_enterprise.sh deleted file mode 100755 index 4e04d60343c..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_main_enterprise.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env sh -local_dst="dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:enterprise:linux/amd64 \ - -a targz:enterprise:linux/arm64 \ - -a targz:enterprise:linux/arm/v6 \ - -a targz:enterprise:linux/arm/v7 \ - -a deb:enterprise:linux/amd64 \ - -a deb:enterprise:linux/arm64 \ - -a deb:enterprise:linux/arm/v6 \ - -a deb:enterprise:linux/arm/v7 \ - -a docker:enterprise:linux/amd64 \ - -a docker:enterprise:linux/arm64 \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --checksum \ - --verify \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-ref=${SOURCE_COMMIT} \ - --grafana-repo="https://github.com/grafana/grafana.git" \ - --enterprise-ref=${DRONE_COMMIT} \ - --github-token=${GITHUB_TOKEN} \ - --ubuntu-base=${UBUNTU_BASE} \ - --alpine-base=${ALPINE_BASE} \ - --patches-repo=${PATCHES_REPO} \ - --patches-path=${PATCHES_PATH} \ - --destination=${local_dst} > assets.txt - -cat assets.txt - -# Move the tar.gz packages to their expected locations -cat assets.txt | DESTINATION=gs://grafana-downloads IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main diff --git a/pkg/build/daggerbuild/scripts/drone_build_main_pro.sh b/pkg/build/daggerbuild/scripts/drone_build_main_pro.sh deleted file mode 100755 index 932c6420d08..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_main_pro.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env sh -local_dst="./dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all -# Build all of the grafana.tar.gz packages. -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:pro:linux/amd64 \ - -a targz:pro:linux/arm64 \ - -a deb:pro:linux/amd64 \ - -a deb:pro:linux/arm64 \ - -a frontend:enterprise \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --checksum \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-ref=${SOURCE_COMMIT} \ - --grafana-repo="https://github.com/grafana/grafana.git" \ - --enterprise-ref=${DRONE_COMMIT} \ - --github-token=${GITHUB_TOKEN} \ - --ubuntu-base=${UBUNTU_BASE} \ - --alpine-base=${ALPINE_BASE} \ - --patches-repo=${PATCHES_REPO} \ - --patches-path=${PATCHES_PATH} \ - --destination=${local_dst} > assets.txt - -echo "Final list of artifacts:" -# Move the tar.gz packages to their expected locations -cat assets.txt | grep -v "public" | DESTINATION=gs://grafana-downloads-enterprise2 IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main -cat assets.txt | grep "public" | DESTINATION=gs://grafana-static-assets IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/cdn diff --git a/pkg/build/daggerbuild/scripts/drone_build_nightly_enterprise.sh b/pkg/build/daggerbuild/scripts/drone_build_nightly_enterprise.sh deleted file mode 100755 index 2f616bd5055..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_nightly_enterprise.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env sh -set -e -local_dst="${DRONE_WORKSPACE}/dist" - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - - # -a targz:enterprise:linux/arm/v6 \ - # -a targz:enterprise:linux/arm/v7 \ - # -a deb:enterprise:linux/arm/v6:nightly \ - # -a deb:enterprise:linux/arm/v7:nightly \ - # -a docker:enterprise:linux/arm/v7 \ - # -a docker:enterprise:linux/arm/v7:ubuntu \ - -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:enterprise:linux/amd64 \ - -a targz:enterprise:linux/arm64 \ - -a targz:enterprise:linux/arm/v7 \ - -a targz:enterprise:linux/arm/v6 \ - -a deb:enterprise:linux/amd64:nightly \ - -a deb:enterprise:linux/arm64:nightly \ - -a deb:enterprise:linux/arm/v6:nightly \ - -a deb:enterprise:linux/arm/v7:nightly \ - -a rpm:enterprise:linux/amd64:sign:nightly \ - -a rpm:enterprise:linux/arm64:sign:nightly \ - -a targz:enterprise:windows/amd64 \ - -a targz:enterprise:windows/arm64 \ - -a targz:enterprise:darwin/amd64 \ - -a targz:enterprise:darwin/arm64 \ - -a zip:enterprise:windows/amd64 \ - -a msi:enterprise:windows/amd64 \ - -a docker:enterprise:linux/amd64 \ - -a docker:enterprise:linux/arm64 \ - -a docker:enterprise:linux/arm/v7 \ - -a docker:enterprise:linux/amd64:ubuntu \ - -a docker:enterprise:linux/arm64:ubuntu \ - -a docker:enterprise:linux/arm/v7:ubuntu \ - --checksum \ - --verify=false \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-ref=main \ - --enterprise-ref=main \ - --grafana-repo=https://github.com/grafana/grafana.git \ - --github-token=${GITHUB_TOKEN} \ - --destination=${local_dst} \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --ubuntu-base="${UBUNTU_BASE}" \ - --alpine-base="${ALPINE_BASE}" > assets.txt - -cat assets.txt diff --git a/pkg/build/daggerbuild/scripts/drone_build_nightly_grafana.sh b/pkg/build/daggerbuild/scripts/drone_build_nightly_grafana.sh deleted file mode 100755 index 495eda0e282..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_nightly_grafana.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env sh -set -e -local_dst="${DRONE_WORKSPACE}/dist" - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:grafana:linux/amd64 \ - -a targz:grafana:linux/arm64 \ - -a targz:grafana:linux/arm/v7 \ - -a targz:grafana:linux/arm/v6 \ - -a deb:grafana:linux/amd64:nightly \ - -a deb:grafana:linux/arm64:nightly \ - -a deb:grafana:linux/arm/v6:nightly \ - -a deb:grafana:linux/arm/v7:nightly \ - -a rpm:grafana:linux/amd64:sign:nightly \ - -a rpm:grafana:linux/arm64:sign:nightly \ - -a targz:grafana:windows/amd64 \ - -a targz:grafana:windows/arm64 \ - -a targz:grafana:darwin/amd64 \ - -a targz:grafana:darwin/arm64 \ - -a zip:grafana:windows/amd64 \ - -a msi:grafana:windows/amd64 \ - -a docker:grafana:linux/amd64 \ - -a docker:grafana:linux/arm64 \ - -a docker:grafana:linux/arm/v7 \ - -a docker:grafana:linux/amd64:ubuntu \ - -a docker:grafana:linux/arm64:ubuntu \ - -a docker:grafana:linux/arm/v7:ubuntu \ - --checksum \ - --verify \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-dir=${GRAFANA_DIR} \ - --github-token=${GITHUB_TOKEN} \ - --destination=${local_dst} \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --ubuntu-base="${UBUNTU_BASE}" \ - --alpine-base="${ALPINE_BASE}" > assets.txt - -cat assets.txt diff --git a/pkg/build/daggerbuild/scripts/drone_build_tag_enterprise.sh b/pkg/build/daggerbuild/scripts/drone_build_tag_enterprise.sh deleted file mode 100755 index 43240ed7bec..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_tag_enterprise.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env sh -local_dst="dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - -# Build all of the grafana.tar.gz packages. -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a targz:enterprise:linux/amd64 \ - -a targz:enterprise:linux/arm64 \ - -a targz:enterprise:linux/arm/v6 \ - -a targz:enterprise:linux/arm/v7 \ - -a deb:enterprise:linux/amd64 \ - -a deb:enterprise:linux/arm64 \ - -a deb:enterprise:linux/arm/v6 \ - -a deb:enterprise:linux/arm/v7 \ - -a rpm:enterprise:linux/amd64:sign \ - -a rpm:enterprise:linux/arm64:sign \ - -a targz:enterprise:windows/amd64 \ - -a targz:enterprise:windows/arm64 \ - -a targz:enterprise:darwin/amd64 \ - -a targz:enterprise:darwin/arm64 \ - -a targz:boring:linux/amd64/dynamic \ - -a zip:enterprise:windows/amd64 \ - -a msi:enterprise:windows/amd64 \ - -a docker:enterprise:linux/amd64 \ - -a docker:enterprise:linux/arm64 \ - -a docker:enterprise:linux/arm/v7 \ - -a docker:enterprise:linux/amd64:ubuntu \ - -a docker:enterprise:linux/arm64:ubuntu \ - -a docker:enterprise:linux/arm/v7:ubuntu \ - -a docker:boring:linux/amd64/dynamic \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --verify \ - --checksum \ - --parallel=5 \ - --build-id=${DRONE_BUILD_NUMBER} \ - --enterprise-ref=${DRONE_TAG} \ - --grafana-ref=${DRONE_TAG} \ - --grafana-repo=https://github.com/grafana/grafana-security-mirror.git \ - --github-token=${GITHUB_TOKEN} \ - --ubuntu-base="${UBUNTU_BASE}" \ - --alpine-base="${ALPINE_BASE}" \ - --version=${DRONE_TAG} \ - --destination=${local_dst} > assets.txt - -# Move the tar.gz packages to their expected locations -cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease diff --git a/pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh b/pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh deleted file mode 100755 index e21a705b380..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -dst="${DESTINATION}/${DRONE_BUILD_EVENT}" -local_dst="file://dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a npm:grafana \ - -a storybook \ - -a targz:grafana:linux/amd64 \ - -a targz:grafana:linux/arm64 \ - -a targz:grafana:linux/arm/v6 \ - -a targz:grafana:linux/arm/v7 \ - -a deb:grafana:linux/amd64 \ - -a deb:grafana:linux/arm64 \ - -a deb:grafana:linux/arm/v6 \ - -a deb:grafana:linux/arm/v7 \ - -a rpm:grafana:linux/amd64:sign \ - -a rpm:grafana:linux/arm64:sign \ - -a docker:grafana:linux/amd64 \ - -a docker:grafana:linux/arm64 \ - -a docker:grafana:linux/arm/v7 \ - -a docker:grafana:linux/amd64:ubuntu \ - -a docker:grafana:linux/arm64:ubuntu \ - -a docker:grafana:linux/arm/v7:ubuntu \ - -a targz:grafana:windows/amd64 \ - -a targz:grafana:windows/arm64 \ - -a targz:grafana:darwin/amd64 \ - -a targz:grafana:darwin/arm64 \ - -a zip:grafana:windows/amd64 \ - -a msi:grafana:windows/amd64 \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --checksum \ - --verify \ - --build-id=${DRONE_BUILD_NUMBER} \ - --grafana-dir=${GRAFANA_DIR} \ - --github-token=${GITHUB_TOKEN} \ - --ubuntu-base="${UBUNTU_BASE}" \ - --alpine-base="${ALPINE_BASE}" \ - --version=${DRONE_TAG} \ - --destination=${local_dst} > assets.txt - -cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease diff --git a/pkg/build/daggerbuild/scripts/drone_build_tag_pro.sh b/pkg/build/daggerbuild/scripts/drone_build_tag_pro.sh deleted file mode 100755 index 8023e2a0f0c..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_build_tag_pro.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env sh -local_dst="dist/${DRONE_BUILD_EVENT}" -set -e - -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*' -# This command enables qemu emulators for building Docker images for arm64/armv6/armv7/etc on the host. -docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all - -# Build all of the grafana.tar.gz packages. -dagger run --silent go run ./pkg/build/cmd \ - artifacts \ - -a frontend:enterprise \ - -a targz:pro:linux/amd64 \ - -a targz:pro:linux/arm64 \ - -a targz:pro:linux/arm/v6 \ - -a targz:pro:linux/arm/v7 \ - -a deb:pro:linux/amd64 \ - -a deb:pro:linux/arm64 \ - -a targz:pro:darwin/amd64 \ - -a targz:pro:windows/amd64 \ - -a docker:pro:linux/amd64 \ - -a docker:pro:linux/arm64 \ - -a docker:pro:linux/arm/v7 \ - -a docker:pro:linux/amd64:ubuntu \ - -a docker:pro:linux/arm64:ubuntu \ - -a docker:pro:linux/arm/v7:ubuntu \ - --checksum \ - --parallel=2 \ - --yarn-cache=${YARN_CACHE_FOLDER} \ - --build-id=${DRONE_BUILD_NUMBER} \ - --enterprise-ref=${DRONE_TAG} \ - --grafana-ref=${DRONE_TAG} \ - --grafana-repo=https://github.com/grafana/grafana-security-mirror.git \ - --github-token=${GITHUB_TOKEN} \ - --version=${DRONE_TAG} \ - --ubuntu-base="${UBUNTU_BASE}" \ - --alpine-base="${ALPINE_BASE}" \ - --destination=${local_dst} > assets.txt - -# Move the tar.gz packages to their expected locations -cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease diff --git a/pkg/build/daggerbuild/scripts/drone_publish_nightly_enterprise.sh b/pkg/build/daggerbuild/scripts/drone_publish_nightly_enterprise.sh deleted file mode 100755 index 3c7ab2f2e78..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_publish_nightly_enterprise.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env sh -set -e -local_dir="${DRONE_WORKSPACE}/dist" - -# Publish the docker images present in the bucket -dagger run --silent go run ./pkg/build/cmd docker publish \ - $(find $local_dir | grep docker.tar.gz | grep -v sha256 | awk '{print "--package=file://"$0}') \ - --username=${DOCKER_USERNAME} \ - --password=${DOCKER_PASSWORD} \ - --latest \ - --repo="grafana-enterprise-dev" - -# Publish packages to the downloads bucket -dagger run --silent go run ./pkg/build/cmd package publish \ - $(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | awk '{print "--package=file://"$0}') \ - --gcp-service-account-key-base64=${GCP_KEY_BASE64} \ - --destination="${DOWNLOADS_DESTINATION}/enterprise/release" - -# Publish packages to grafana.com -dagger run --silent go run ./pkg/build/cmd gcom publish \ - $(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \ - --api-key=${GCOM_API_KEY} \ - --api-url="https://grafana.com/api/grafana-enterprise" \ - --download-url="https://dl.grafana.com/enterprise/release" \ - --nightly diff --git a/pkg/build/daggerbuild/scripts/drone_publish_nightly_grafana.sh b/pkg/build/daggerbuild/scripts/drone_publish_nightly_grafana.sh deleted file mode 100755 index 85ef11a1920..00000000000 --- a/pkg/build/daggerbuild/scripts/drone_publish_nightly_grafana.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env sh -set -e -# ver=$(cat ${GRAFANA_DIR}/package.json | jq -r .version | sed -E "s/$/-/" | sed -E "s/-.*/-${DRONE_BUILD_NUMBER}/") -local_dir="${DRONE_WORKSPACE}/dist" - -# Publish the docker images present in the bucket -dagger run --silent go run ./pkg/build/cmd docker publish \ - $(find $local_dir | grep docker.tar.gz | grep -v sha256 | awk '{print "--package=file://"$0}') \ - --username=${DOCKER_USERNAME} \ - --password=${DOCKER_PASSWORD} \ - --repo="grafana-dev" - -# Publish packages to the downloads bucket -dagger run --silent go run ./pkg/build/cmd package publish \ - $(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | awk '{print "--package=file://"$0}') \ - --gcp-service-account-key-base64=${GCP_KEY_BASE64} \ - --destination="${DOWNLOADS_DESTINATION}/oss/release" - -# Publish only the linux/amd64 edition storybook into the storybook bucket -# dagger run --silent go run ./pkg/build/cmd storybook \ -# $(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \ -# --gcp-service-account-key-base64=${GCP_KEY_BASE64} \ -# --destination="${STORYBOOK_DESTINATION}/${ver}" - -# # Publish only the linux/amd64 edition static assets into the static assets bucket -# dagger run --silent go run ./pkg/build/cmd cdn \ -# $(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \ -# --gcp-service-account-key-base64=${GCP_KEY_BASE64} \ -# --destination="${CDN_DESTINATION}/${ver}/public" - -# Publish only the linux/amd64 edition npm packages to npm -dagger run --silent go run ./pkg/build/cmd npm publish \ - $(find $local_dir | grep tar.gz | grep linux | grep amd64 | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \ - --token=${NPM_TOKEN} \ - --tag="nightly" - -# Publish packages to grafana.com -dagger run --silent go run ./pkg/build/cmd gcom publish \ - $(find $local_dir | grep -e .rpm -e .tar.gz -e .exe -e .zip -e .deb | grep -v sha256 | grep -v docker | awk '{print "--package=file://"$0}') \ - --api-key=${GCOM_API_KEY} \ - --api-url="https://grafana.com/api/grafana" \ - --download-url="https://dl.grafana.com/oss/release" \ - --nightly diff --git a/pkg/build/daggerbuild/scripts/move_packages.go b/pkg/build/daggerbuild/scripts/move_packages.go deleted file mode 100644 index 1d90852f459..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages.go +++ /dev/null @@ -1,495 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "dagger.io/dagger" - "github.com/grafana/grafana/pkg/build/daggerbuild/backend" - "github.com/grafana/grafana/pkg/build/daggerbuild/containers" - "github.com/grafana/grafana/pkg/build/daggerbuild/pipelines" -) - -const ( - proName = "enterprise2" - // 1: The version (with a v prefix) - // 2: The "edition". Options: 'oss', 'pro', 'enterprise'. - // 3: The full name. 'grafana', 'grafana-enterprise', 'grafana-pro - // 4: The 'ersion', or 'version' without the 'v'. - // 5: The OS: 'windows', 'linux', 'darwin' - // 6: The architecture: 'amd64', 'armv6', 'armv7', 'arm64'. - // 7: -musl, sometimes. - // 8: '.sha256', sometimes. - tarGzFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s-%[4]s.%[5]s-%[6]s%[7]s.tar.gz%[8]s" - debFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.deb%[8]s" - rpmFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s-%[4]s-1.%[6]s.rpm%[8]s" - exeFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.exe%[8]s" - msiFormat = "artifacts/downloads%[9]s/%[1]s/%[2]s/release/%[3]s_%[4]s_%[6]s.msi%[8]s" - - tarGzMainFormat = "%[2]s/main/%[3]s-%[4]s.%[5]s-%[6]s%[7]s.tar.gz%[8]s" - debMainFormat = "%[2]s/main/%[3]s_%[4]s_%[6]s.deb%[8]s" - - // 1: ersion - // 2. name (grafana-oss | grafana-enterprise) - // 3: '-ubuntu', if set - // 4: arch - // 5: '.sha256', if set - dockerFormat = "artifacts/docker/%[1]s/%[2]s-%[1]s%[3]s-%[4]s.img%[5]s" - - // 1: ersion - // 2. name (grafana-oss | grafana-enterprise) - cdnFormat = "artifacts/static-assets/%[2]s/%[1]s/public" - cdnMainFormat = "grafana/%s/public" - - // 1: ersion - storybookFormat = "artifacts/storybook/v%[1]s" - - // 1: version - // 2: package name (@grafana-ui-10.0.0.tgz) - npmFormat = "artifacts/npm/v%[1]s/npm-artifacts" - - sha256Ext = ".sha256" - grafana = "grafana" -) - -// One artifact and be copied to multiple different locations (like armv7 tar.gz packages should be copied to tar.gz and -musl.tar.gz) -type HandlerFunc func(name string) []string - -var Handlers = map[string]HandlerFunc{ - ".tar.gz": TarGZHandler, - ".deb": DebHandler, - ".rpm": RPMHandler, - ".docker.tar.gz": DockerHandler, - ".exe": EXEHandler, - ".msi": MSIHandler, - ".zip": ZipHandler, -} - -func IsMain() bool { - return os.Getenv("IS_MAIN") != "" -} - -func NPMHandler(name string) []string { - var ( - version = strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v") - ) - - return []string{fmt.Sprintf(npmFormat, version)} -} - -func ZipHandler(name string) []string { - files := EXEHandler(strings.ReplaceAll(name, "zip", "exe")) - - for i, v := range files { - files[i] = strings.ReplaceAll(v, "exe", "zip") - } - - return files -} - -func MSIHandler(name string) []string { - files := EXEHandler(strings.ReplaceAll(name, "msi", "exe")) - - for i, v := range files { - files[i] = strings.ReplaceAll(v, "exe", "msi") - } - - return files -} - -func RPMHandler(name string) []string { - ext := filepath.Ext(name) - - // If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template - // or just give it emptystring if it's not the sha256 file - sha256 := "" - if ext == sha256Ext { - sha256 = sha256Ext - } - - n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls - opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(strings.ReplaceAll(n, sha256Ext, ""), "rpm", "tar.gz")) - - // In grafana-build we just use "" to refer to "oss" - edition := "oss" - fullName := grafana - if opts.Edition != "" { - edition = opts.Edition - fullName += "-" + opts.Edition - } - - goos, arch := backend.OSAndArch(opts.Distro) - arm := backend.ArchVersion(opts.Distro) - if arch == "arm" { - if arm == "7" { - arch = "armhfp" - } - } - - if arch == "arm64" { - arch = "aarch64" - } - - if arch == "amd64" { - arch = "x86_64" - } - - enterprise2 := "" - version := opts.Version - ersion := strings.Replace(strings.TrimPrefix(version, "v"), "-", "~", 1) - - if edition == "pro" { - // "pro" in this case is called "enterprise2" - fullName = "grafana-enterprise2" - edition = proName - // and is in the 'downloads-enterprise2' folder instead of 'downloads' - enterprise2 = "-enterprise2" - // and has an period separator {version}.{arch} instead of {version}_{arch} - } - dst := fmt.Sprintf(rpmFormat, version, edition, fullName, ersion, goos, arch, edition, sha256, enterprise2) - - return []string{ - dst, - } -} - -func EXEHandler(name string) []string { - packages := DebHandler(strings.ReplaceAll(name, "exe", "deb")) - for i, v := range packages { - v = strings.ReplaceAll(v, "deb", "exe") - v = strings.ReplaceAll(v, "amd64", "windows-amd64") - v = strings.ReplaceAll(v, "_", "-") - v = strings.ReplaceAll(v, "~", "-") - v = strings.ReplaceAll(v, "-windows", ".windows") - packages[i] = v - } - - return packages -} - -func DebHandler(name string) []string { - ext := filepath.Ext(name) - format := debFormat - if IsMain() { - format = debMainFormat - } - - // If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template - // or just give it emptystring if it's not the sha256 file - sha256 := "" - if ext == sha256Ext { - sha256 = sha256Ext - } - - n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls - opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(strings.ReplaceAll(n, sha256Ext, ""), "deb", "tar.gz")) - - // In grafana-build we just use "" to refer to "oss" - edition := "oss" - fullName := grafana - version := opts.Version - ersion := strings.TrimPrefix(version, "v") - ersion = strings.Replace(ersion, "-", "~", 1) - enterprise2 := "" - if opts.Edition != "" { - edition = opts.Edition - fullName += "-" + opts.Edition - if edition == "pro" { - // "pro" in this case is called "enterprise2" - fullName = "grafana-enterprise2" - edition = proName - // and is in the 'downloads-enterprise2' folder instead of 'downloads' - enterprise2 = "-enterprise2" - } - - if edition == "pro-rpi" { - // "pro" in this case is called "enterprise2" - fullName = "grafana-enterprise2-rpi" - edition = proName - // and is in the 'downloads-enterprise2' folder instead of 'downloads' - enterprise2 = "-enterprise2" - } - - if edition == "rpi" { - edition = "oss" - } - - if edition == "enterprise-rpi" { - edition = "enterprise" - } - } - - names := []string{fullName} - goos, arch := backend.OSAndArch(opts.Distro) - if arch == "arm" { - arch = "armhf" - // If we're building for arm then we also copy the same thing, but with the name '-rpi'. for osme reason? - names = []string{fullName} - } - - dst := []string{} - for _, n := range names { - dst = append(dst, fmt.Sprintf(format, opts.Version, edition, n, ersion, goos, arch, edition, sha256, enterprise2)) - } - - return dst -} - -func TarGZHandler(name string) []string { - ext := filepath.Ext(name) - - // If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template - // or just give it emptystring if it's not the sha256 file - sha256 := "" - if ext == sha256Ext { - sha256 = sha256Ext - } - - n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls - opts := pipelines.TarOptsFromFileName(strings.ReplaceAll(n, sha256Ext, "")) - - // In grafana-build we just use "" to refer to "oss" - edition := "oss" - fullName := grafana - version := opts.Version - ersion := strings.TrimPrefix(version, "v") - enterprise2 := "" - if opts.Edition != "" { - edition = opts.Edition - fullName += "-" + opts.Edition - if edition == "pro" { - enterprise2 = "-enterprise2" - fullName = "grafana-enterprise2" - edition = proName - } - } - - libc := []string{""} - goos, arch := backend.OSAndArch(opts.Distro) - - if arch == "arm64" || arch == "arm" || arch == "amd64" && goos == "linux" { - libc = []string{"", "-musl"} - } - - arm := backend.ArchVersion(opts.Distro) - if arch == "arm" { - arch += "v" + arm - // I guess we don't create an arm-6-musl? - if arm == "6" { - libc = []string{""} - } - } - format := tarGzFormat - if IsMain() { - format = tarGzMainFormat - } - dst := []string{} - for _, m := range libc { - dst = append(dst, fmt.Sprintf(format, opts.Version, edition, fullName, ersion, goos, arch, m, sha256, enterprise2)) - } - - return dst -} - -func DockerHandler(name string) []string { - ext := filepath.Ext(name) - - // If we're copying a sha256 file and not a tar.gz then we want to add .sha256 to the template - // or just give it emptystring if it's not the sha256 file - sha256 := "" - if ext == sha256Ext { - sha256 = sha256Ext - } - - n := filepath.Base(name) // Surprisingly still works even with 'gs://' urls - - // try to get .ubuntu.docker.tar.gz.sha256 / .ubuntu.docker.tar.gz / docker.tar.gz to all just end in 'tar.gz' - normalized := strings.ReplaceAll(n, sha256Ext, "") - normalized = strings.ReplaceAll(normalized, ".ubuntu", "") - normalized = strings.ReplaceAll(normalized, ".docker", "") - - opts := pipelines.TarOptsFromFileName(normalized) - - // In grafana-build we just use "" to refer to "oss" - edition := "oss" - fullName := grafana - if opts.Edition != "" { - edition = opts.Edition - if edition == "pro" { - edition = proName - } - } - - fullName += "-" + edition - ubuntu := "" - if strings.Contains(name, "ubuntu") { - ubuntu = "-ubuntu" - } - - _, arch := backend.OSAndArch(opts.Distro) - if arch == "arm" { - arch += "v" + backend.ArchVersion(opts.Distro) - } - return []string{ - fmt.Sprintf(dockerFormat, strings.TrimPrefix(opts.Version, "v"), fullName, ubuntu, arch, sha256), - } -} - -func CDNHandler(name string) []string { - if IsMain() { - // This folder is is always ${dist}/${version}/${name}/${public} - dist, err := filepath.Rel(".", filepath.Join(name, "../../../")) - if err != nil { - panic(err) - } - - path, err := filepath.Rel(dist, name) - if err != nil { - panic(err) - } - s := strings.Split(path, string(os.PathSeparator)) - return []string{fmt.Sprintf(cdnMainFormat, s[0])} - } - version := strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v") - return []string{fmt.Sprintf(cdnFormat, version, grafana)} -} - -func StorybookHandler(name string) []string { - version := strings.TrimPrefix(os.Getenv("DRONE_TAG"), "v") - return []string{fmt.Sprintf(storybookFormat, version)} -} - -// A hopefully temporary script that prints the gsutil commands that will move these artifacts into the location where they were expected previously. -// Just pipe this into bash or exec or whatever to do the actual copying. -// Run without redirecting stdout to verify the operations. -func main() { - prefix := os.Args[1] - - ctx := context.Background() - client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr)) - if err != nil { - panic(err) - } - - var ( - scanner = bufio.NewScanner(os.Stdin) - authenticator = containers.GCSAuth(client, &containers.GCPOpts{ - ServiceAccountKeyBase64: os.Getenv("GCP_KEY_BASE64"), - }) - - container = client.Container().From("google/cloud-sdk:alpine") - ) - // - if c, err := authenticator.Authenticate(client, container); err == nil { - container = c - } else { - panic(err) - } - - for scanner.Scan() { - var ( - name = scanner.Text() - ) - handler, ext := getHandler(name, Handlers) - destinations := handler(name) - if ext == "" { - for _, v := range destinations { - dir := filepath.Join(prefix, filepath.Dir(v)) - v := filepath.Join(prefix, v) - - log.Println("Creating dir", dir) - if err := os.MkdirAll(dir, 0700); err != nil { - panic(err) - } - log.Println("Copying", name, "to", v) - //nolint:gosec - cmd := exec.Command("cp", "-r", strings.TrimPrefix(name, "file://"), v) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - panic(err) - } - } - continue - } - - log.Println("File:", name, "to be copied as", destinations) - for _, v := range destinations { - dir := filepath.Join(prefix, filepath.Dir(v)) - v := filepath.Join(prefix, v) - log.Println("Creating directory", dir) - if err := os.MkdirAll(dir, 0700); err != nil { - panic(err) - } - - log.Println("Copying", name, "to", dir, "as", v) - - //nolint:gosec - cmd := exec.Command("cp", strings.TrimPrefix(name, "file://"), v) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - panic(err) - } - } - } - - log.Println("Copying", prefix, "to gcs") - dst := os.Getenv("DESTINATION") - container = container.WithMountedDirectory("dist", client.Host().Directory(prefix)). - WithExec([]string{"gcloud", "storage", "cp", "-r", "/dist/*", dst}) - - stdout, err := container.Stdout(ctx) - if err != nil { - panic(err) - } - - stderr, err := container.Stdout(ctx) - if err != nil { - panic(err) - } - - fmt.Fprint(os.Stdout, stdout) - fmt.Fprint(os.Stderr, stderr) -} - -func getHandler(name string, handlers map[string]HandlerFunc) (HandlerFunc, string) { - ext := filepath.Ext(name) - // sha256 extensions should be handled the same way what precedes the extension - if ext == sha256Ext { - ext = filepath.Ext(strings.ReplaceAll(name, sha256Ext, "")) - } - - // tar.gz extensions can also have docker.tar.gz so we need to make sure we don't skip that - if ext == ".gz" { - ext = ".tar.gz" - if filepath.Ext(strings.ReplaceAll(name, ".tar.gz", "")) == ".docker" || - filepath.Ext(strings.ReplaceAll(name, ".tar.gz.sha256", "")) == ".docker" { - ext = ".docker.tar.gz" - } - } - - handler := handlers[ext] - // If there is no extension, then we are either dealing with public assets - // or the storybook, which both require some extra handling: - if ext != "" { - return handler, ext - } - - if filepath.Base(name) == "public" { - return CDNHandler, "" - } - if filepath.Base(name) == "storybook" { - return StorybookHandler, "" - } - if filepath.Base(name) == "npm-packages" { - return NPMHandler, "" - } - panic("no handler found") -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_cdn_test.go b/pkg/build/daggerbuild/scripts/move_packages_cdn_test.go deleted file mode 100644 index d4b1711d355..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_cdn_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -var cdnMapping = map[string]m{ - "OSS: Linux AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64/public", - output: []string{ - "artifacts/static-assets/grafana/1.2.3/public", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, - "ENT: Linux AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64/public", - output: []string{ - "artifacts/static-assets/grafana/1.2.3/public", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, - "PRO: Linux AMD64": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64/public", - output: []string{ - "artifacts/static-assets/grafana/1.2.3/public", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, - "main": { - input: "dist/10.3.0-62960/grafana-enterprise/public", - output: []string{ - "grafana/10.3.0-62960/public", - }, - env: map[string]string{"IS_MAIN": "true"}, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_deb_test.go b/pkg/build/daggerbuild/scripts/move_packages_deb_test.go deleted file mode 100644 index 09c882a4177..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_deb_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -var debMapping = map[string]m{ - "OSS: Linux AMD64 on main": { - env: map[string]string{ - "IS_MAIN": "true", - }, - input: "file://dist/grafana_v1.2.3_102_linux_amd64.deb", - output: []string{ - "oss/main/grafana_1.2.3_amd64.deb", - }, - }, - "OSS: Linux AMD64 on main with - in version": { - env: map[string]string{ - "IS_MAIN": "true", - }, - input: "file://dist/grafana_v1.2.3-102_102_linux_amd64.deb", - output: []string{ - "oss/main/grafana_1.2.3~102_amd64.deb", - }, - }, - "OSS: Linux AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.deb", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_amd64.deb", - }, - }, - "OSS: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_amd64.deb.sha256", - }, - }, - "OSS: Linux ARM7": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.deb", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_armhf.deb", - }, - }, - "OSS: RPI ARM7": { - input: "gs://bucket/tag/grafana-rpi_v1.2.3_102_linux_arm-7.deb", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-rpi_1.2.3_armhf.deb", - }, - }, - "OSS: RPI ARM6": { - input: "gs://bucket/tag/grafana-rpi_v1.2.3_102_linux_arm-6.deb", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-rpi_1.2.3_armhf.deb", - }, - }, - "OSS: Linux ARM7 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_armhf.deb.sha256", - }, - }, - "OSS: Linux ARM64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.deb", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_arm64.deb", - }, - }, - "OSS: Linux ARM64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana_1.2.3_arm64.deb.sha256", - }, - }, - "ENT: Linux AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.deb", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_amd64.deb", - }, - }, - "ENT: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_amd64.deb.sha256", - }, - }, - "ENT: Linux ARM64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.deb", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_arm64.deb", - }, - }, - "ENT: Linux ARM64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_arm64.deb.sha256", - }, - }, - "ENT: Linux ARM7": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.deb", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_armhf.deb", - }, - }, - "ENT: RPI ARM7": { - input: "gs://bucket/tag/grafana-enterprise-rpi_v1.2.3_102_linux_arm-7.deb", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-rpi_1.2.3_armhf.deb", - }, - }, - "ENT: ARM7 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.deb.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise_1.2.3_armhf.deb.sha256", - }, - }, - "ENT2: RPI ARM7": { - input: "gs://bucket/tag/grafana-pro-rpi_v1.2.3_102_linux_arm-7.deb", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3/enterprise2/release/grafana-enterprise2-rpi_1.2.3_armhf.deb", - }, - }, - "ENT2: Pre-release AMD64": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.deb", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2_1.2.3~pre.4_amd64.deb", - }, - }, - "ENT2: Pre-release AMD64 SHA256": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.deb.sha256", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2_1.2.3~pre.4_amd64.deb.sha256", - }, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_docker_test.go b/pkg/build/daggerbuild/scripts/move_packages_docker_test.go deleted file mode 100644 index 70660c8e04f..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_docker_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package main - -var dockerMapping = map[string]m{ - "ENT: Linux AMD64 Ubuntu": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-amd64.img", - }, - }, - "ENT: Linux AMD64 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-amd64.img.sha256", - }, - }, - "ENT: Linux ARM64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-arm64.img", - }, - }, - "ENT: Linux ARM64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-arm64.img.sha256", - }, - }, - "ENT: Linux ARM7": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-armv7.img", - }, - }, - "ENT: Linux ARM7 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-armv7.img.sha256", - }, - }, - "ENT: Linux ARM7 Ubuntu": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-armv7.img", - }, - }, - "ENT: Linux AR7 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-armv7.img.sha256", - }, - }, - "ENT: Linux AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-amd64.img", - }, - }, - "ENT: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-amd64.img.sha256", - }, - }, - "ENT: Linux ARM64 Ubuntu": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-arm64.img", - }, - }, - "ENT: Linux ARM64 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise-1.2.3-ubuntu-arm64.img.sha256", - }, - }, - "OSS: Linux ARM7": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-armv7.img", - }, - }, - "OSS: Linux ARM7 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-armv7.img.sha256", - }, - }, - "OSS: Linux ARM7 Ubuntu": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-armv7.img", - }, - }, - "OSS: Linux AR7 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-armv7.img.sha256", - }, - }, - "OSS: Linux AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-amd64.img", - }, - }, - "OSS: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-amd64.img.sha256", - }, - }, - "OSS: Linux AMD64 Ubuntu": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-amd64.img", - }, - }, - "OSS: Linux AMD64 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-amd64.img.sha256", - }, - }, - "OSS: Linux ARM64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-arm64.img", - }, - }, - "OSS: Linux ARM64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-arm64.img.sha256", - }, - }, - "OSS: Linux ARM64 Ubuntu": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-arm64.img", - }, - }, - "OSS: Linux ARM64 Ubuntu SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz.sha256", - output: []string{ - "artifacts/docker/1.2.3/grafana-oss-1.2.3-ubuntu-arm64.img.sha256", - }, - }, - "PRO: Linux AMD64": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-amd64.img", - }, - }, - "PRO: Linux ARM64": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm64.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-arm64.img", - }, - }, - "PRO: Linux ARM7": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm-7.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-armv7.img", - }, - }, - "PRO: Linux AMD64 Ubuntu": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-amd64.img", - }, - }, - "PRO: Linux ARM64 Ubuntu": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm64.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-arm64.img", - }, - }, - "PRO: Linux ARM7 Ubuntu": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_arm-7.ubuntu.docker.tar.gz", - output: []string{ - "artifacts/docker/1.2.3/grafana-enterprise2-1.2.3-ubuntu-armv7.img", - }, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_exe_test.go b/pkg/build/daggerbuild/scripts/move_packages_exe_test.go deleted file mode 100644 index 32499c805c7..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_exe_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -var exeMapping = map[string]m{ - "ENT": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.exe", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.exe", - }, - }, - "ENT SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.exe.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.exe.sha256", - }, - }, - "OSS": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.exe", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.exe", - }, - }, - "OSS SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.exe.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.exe.sha256", - }, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_msi_test.go b/pkg/build/daggerbuild/scripts/move_packages_msi_test.go deleted file mode 100644 index 77db73d4b58..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_msi_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -var msiMapping = map[string]m{ - "ENT": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.msi", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.msi", - }, - }, - "ENT SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.msi.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.msi.sha256", - }, - }, - "OSS": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.msi", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.msi", - }, - }, - "OSS SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.msi.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.msi.sha256", - }, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_npm_test.go b/pkg/build/daggerbuild/scripts/move_packages_npm_test.go deleted file mode 100644 index 146a1b1219b..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_npm_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -var npmMapping = map[string]m{ - "Grafana data": { - input: "file://dist/tag/grafana-10.2.0-pre/npm-packages", - output: []string{ - "artifacts/npm/v10.2.0-pre/npm-artifacts", - }, - env: map[string]string{"DRONE_TAG": "10.2.0-pre"}, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_rpm_test.go b/pkg/build/daggerbuild/scripts/move_packages_rpm_test.go deleted file mode 100644 index 22b1d8ac8a9..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_rpm_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -var rpmMapping = map[string]m{ - "OSS: Linux AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.x86_64.rpm", - }, - }, - "OSS: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.x86_64.rpm.sha256", - }, - }, - "OSS: Linux ARM7": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.armhfp.rpm", - }, - }, - "OSS: Linux ARM7 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.armhfp.rpm.sha256", - }, - }, - "OSS: Linux aarch64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_aarch64.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.aarch64.rpm", - }, - }, - "OSS: Linux aarch64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3-1.aarch64.rpm.sha256", - }, - }, - "ENT: Linux AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.x86_64.rpm", - }, - }, - "ENT: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.x86_64.rpm.sha256", - }, - }, - "ENT: Linux ARM64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.aarch64.rpm", - }, - }, - "ENT: Linux ARM64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.aarch64.rpm.sha256", - }, - }, - "ENT: Linux ARM7": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.rpm", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.armhfp.rpm", - }, - }, - "ENT: Linux ARM7 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.rpm.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3-1.armhfp.rpm.sha256", - }, - }, - "PRO: Linux AMD64": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.rpm", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3~pre.4-1.x86_64.rpm", - }, - }, - "PRO: Linux AMD64 SHA256": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.rpm.sha256", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3~pre.4-1.x86_64.rpm.sha256", - }, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_storybook_test.go b/pkg/build/daggerbuild/scripts/move_packages_storybook_test.go deleted file mode 100644 index 6616d3027a2..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_storybook_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -var storybookMapping = map[string]m{ - "OSS": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64/storybook", - output: []string{ - "artifacts/storybook/v1.2.3", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, - "ENT": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64/storybook", - output: []string{ - "artifacts/storybook/v1.2.3", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, - "PRO": { - input: "gs://bucket/tag/grafana-pro_v1.2.3_102_linux_amd64/storybook", - output: []string{ - "artifacts/storybook/v1.2.3", - }, - env: map[string]string{"DRONE_TAG": "1.2.3"}, - }, -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_test.go b/pkg/build/daggerbuild/scripts/move_packages_test.go deleted file mode 100644 index 5b1d310f386..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package main - -import ( - "sort" - "testing" - - "github.com/stretchr/testify/require" -) - -type m struct { - input string - output []string - env map[string]string -} - -var targzMapping = map[string]m{ - "ENT: Darwin AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_darwin_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.darwin-amd64.tar.gz", - }, - }, - "ENT: Darwin AMD64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_darwin_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.darwin-amd64.tar.gz.sha256", - }, - }, - "ENT: AMD64 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64-musl.tar.gz", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64.tar.gz", - }, - }, - "ENT: AMD64 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-amd64.tar.gz.sha256", - }, - }, - "ENT: ARM64 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64-musl.tar.gz", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64.tar.gz", - }, - }, - "ENT: ARM64 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-arm64.tar.gz.sha256", - }, - }, - "ENT: ARM6": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-6.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv6.tar.gz", - }, - }, - "ENT: ARM6 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-6.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv6.tar.gz.sha256", - }, - }, - "ENT: ARM7 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7-musl.tar.gz", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7.tar.gz", - }, - }, - "ENT: ARM7 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_linux_arm-7.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.linux-armv7.tar.gz.sha256", - }, - }, - "ENT: Windows AMD64": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.tar.gz", - }, - }, - "ENT: Windows AMD64 SHA256": { - input: "gs://bucket/tag/grafana-enterprise_v1.2.3_102_windows_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/enterprise/release/grafana-enterprise-1.2.3.windows-amd64.tar.gz.sha256", - }, - }, - "OSS: ARM6": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-6.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv6.tar.gz", - }, - }, - "OSS: ARM6 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-6.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv6.tar.gz.sha256", - }, - }, - "OSS: ARM7 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7-musl.tar.gz", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7.tar.gz", - }, - }, - "OSS: ARM7 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm-7.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-armv7.tar.gz.sha256", - }, - }, - "OSS: Windows AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.tar.gz", - }, - }, - "OSS: Windows AMD64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_windows_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.windows-amd64.tar.gz.sha256", - }, - }, - "OSS: Darwin AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3_102_darwin_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.darwin-amd64.tar.gz", - }, - }, - "OSS: Darwin AMD64 SHA256": { - input: "gs://bucket/tag/grafana_v1.2.3_102_darwin_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.darwin-amd64.tar.gz.sha256", - }, - }, - "OSS: Linux AMD64 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64-musl.tar.gz", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64.tar.gz", - }, - }, - "OSS: Linux AMD64 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-amd64.tar.gz.sha256", - }, - }, - "OSS: Linux ARM64 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.tar.gz", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64-musl.tar.gz", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64.tar.gz", - }, - }, - "OSS: Linux ARM64 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana_v1.2.3_102_linux_arm64.tar.gz.sha256", - output: []string{ - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64-musl.tar.gz.sha256", - "artifacts/downloads/v1.2.3/oss/release/grafana-1.2.3.linux-arm64.tar.gz.sha256", - }, - }, - "ENT2: Linux AMD64 with MUSL copy": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.tar.gz", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64-musl.tar.gz", - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64.tar.gz", - }, - }, - "ENT2: Linux AMD64 SHA256 with MUSL copy": { - input: "gs://bucket/tag/grafana-pro_v1.2.3-pre.4_102_linux_amd64.tar.gz.sha256", - output: []string{ - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64-musl.tar.gz.sha256", - "artifacts/downloads-enterprise2/v1.2.3-pre.4/enterprise2/release/grafana-enterprise2-1.2.3-pre.4.linux-amd64.tar.gz.sha256", - }, - }, -} - -func TestGetHandler(t *testing.T) { - runTests(t, "TARGZ: ", targzMapping) - runTests(t, "DOCKER: ", dockerMapping) - runTests(t, "CDN: ", cdnMapping) - runTests(t, "ZIP: ", zipMapping) - runTests(t, "MSI: ", msiMapping) - runTests(t, "NPM: ", npmMapping) - runTests(t, "DEB: ", debMapping) - runTests(t, "RPM: ", rpmMapping) - runTests(t, "EXE: ", exeMapping) - runTests(t, "STORYBOOK: ", storybookMapping) -} - -func runTests(t *testing.T, namePrefix string, tests map[string]m) { - t.Helper() - for testname, testcase := range tests { - t.Run(namePrefix+testname, func(t *testing.T) { - for envName, envValue := range testcase.env { - t.Setenv(envName, envValue) - } - handler, _ := getHandler(testcase.input, Handlers) - output := handler(testcase.input) - sort.Strings(output) - require.Equal(t, testcase.output, output) - }) - } -} diff --git a/pkg/build/daggerbuild/scripts/move_packages_zip_test.go b/pkg/build/daggerbuild/scripts/move_packages_zip_test.go deleted file mode 100644 index af121e1e7f5..00000000000 --- a/pkg/build/daggerbuild/scripts/move_packages_zip_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -var zipMapping = map[string]m{ - "OSS: Windows AMD64": { - input: "gs://bucket/tag/grafana_v1.2.3-test.1_102_windows_amd64.zip", - output: []string{ - "artifacts/downloads/v1.2.3-test.1/oss/release/grafana-1.2.3-test.1.windows-amd64.zip", - }, - }, - "OSS: Windows AMD64 from file://": { - input: "file://bucket/tag/grafana_v1.2.3-test.1_102_windows_amd64.zip", - output: []string{ - "artifacts/downloads/v1.2.3-test.1/oss/release/grafana-1.2.3-test.1.windows-amd64.zip", - }, - }, - "OSS: Windows AMD64 main from file://": { - input: "file://bucket/tag/grafana_main_102_windows_amd64.zip", - output: []string{ - "artifacts/downloads/main/oss/release/grafana-main.windows-amd64.zip", - }, - }, -} diff --git a/pkg/build/docker/dockerconfig.go b/pkg/build/docker/dockerconfig.go deleted file mode 100644 index 7a971dc2ba5..00000000000 --- a/pkg/build/docker/dockerconfig.go +++ /dev/null @@ -1,15 +0,0 @@ -package docker - -import ( - "github.com/grafana/grafana/pkg/build/config" -) - -type Config struct { - Bucket string - Edition string - Tag string - Distribution []config.Distribution - Archs []config.Architecture - DockerHubRepo string - Security bool -} diff --git a/pkg/build/droneutil/docs.go b/pkg/build/droneutil/docs.go deleted file mode 100644 index 8bf3753936e..00000000000 --- a/pkg/build/droneutil/docs.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package droneutil provides utility functions for working with Drone. -package droneutil diff --git a/pkg/build/droneutil/event.go b/pkg/build/droneutil/event.go deleted file mode 100644 index 707b87383c2..00000000000 --- a/pkg/build/droneutil/event.go +++ /dev/null @@ -1,34 +0,0 @@ -package droneutil - -import ( - "fmt" - "os" - "strings" -) - -// Lookup is the equivalent of os.LookupEnv, but also accepts a list of strings rather than only checking os.Environ() -func Lookup(values []string, val string) (string, bool) { - for _, v := range values { - prefix := val + "=" - if strings.HasPrefix(v, prefix) { - return strings.TrimPrefix(v, prefix), true - } - } - - return "", false -} - -// GetDroneEvent looks for the "DRONE_BUILD_EVENT" in the provided env list and returns the value. -// if it was not found, then an error is returned. -func GetDroneEvent(env []string) (string, error) { - event, ok := Lookup(env, "DRONE_BUILD_EVENT") - if !ok { - return "", fmt.Errorf("failed to get DRONE_BUILD_EVENT environmental variable") - } - return event, nil -} - -// GetDroneEventFromEnv returns the value of DRONE_BUILD_EVENT from os.Environ() -func GetDroneEventFromEnv() (string, error) { - return GetDroneEvent(os.Environ()) -} diff --git a/pkg/build/droneutil/event_test.go b/pkg/build/droneutil/event_test.go deleted file mode 100644 index b69ce50fc54..00000000000 --- a/pkg/build/droneutil/event_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package droneutil_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/build/droneutil" -) - -func TestGetDroneEvent(t *testing.T) { - t.Run("Should return the Drone Event", func(t *testing.T) { - env := []string{"DRONE_BUILD_EVENT=pull_request"} - droneEvent, err := droneutil.GetDroneEvent(env) - require.NoError(t, err) - require.Equal(t, droneEvent, "pull_request") - }) - t.Run("Should return error, Drone Event env var is missing", func(t *testing.T) { - droneEvent, err := droneutil.GetDroneEvent([]string{}) - require.Error(t, err) - require.Empty(t, droneEvent) - }) -} - -func TestLookup(t *testing.T) { - env := []string{"", "EXAMPLE_KEY=value", "EXAMPLE_KEY"} - t.Run("A valid lookup should return a string and no error", func(t *testing.T) { - val, ok := droneutil.Lookup(env, "EXAMPLE_KEY") - require.True(t, ok) - require.Equal(t, val, "value") - }) - - t.Run("An invalid lookup should return an error", func(t *testing.T) { - _, ok := droneutil.Lookup(env, "EXAMPLE_KEY_DOES_NOT_EXIST") - require.False(t, ok) - }) -} diff --git a/pkg/build/env/fallback.go b/pkg/build/env/fallback.go deleted file mode 100644 index 7f4f31cb09e..00000000000 --- a/pkg/build/env/fallback.go +++ /dev/null @@ -1,40 +0,0 @@ -package env - -import ( - "fmt" - "os" - "strings" - - "github.com/urfave/cli/v2" -) - -// RequireListWithEnvFallback first checks the CLI for a flag with the required -// name. If this is empty, it falls back to taking the environment variable. -// Sadly, we cannot use cli.Flag.EnvVars for this due to it potentially leaking -// environment variables as default values in usage-errors. -func RequireListWithEnvFallback(cctx *cli.Context, name string, envName string) ([]string, error) { - result := cctx.StringSlice(name) - if len(result) == 0 { - for _, v := range strings.Split(os.Getenv(envName), ",") { - value := strings.TrimSpace(v) - if value != "" { - result = append(result, value) - } - } - } - if len(result) == 0 { - return nil, cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1) - } - return result, nil -} - -func RequireStringWithEnvFallback(cctx *cli.Context, name string, envName string) (string, error) { - result := cctx.String(name) - if result == "" { - result = os.Getenv(envName) - } - if result == "" { - return "", cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1) - } - return result, nil -} diff --git a/pkg/build/env/fallback_test.go b/pkg/build/env/fallback_test.go deleted file mode 100644 index 8871bb68a72..00000000000 --- a/pkg/build/env/fallback_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package env - -import ( - "flag" - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" -) - -const ( - flag1 = "flag1" - flag2 = "flag2" -) - -type flagObj struct { - name string - value string -} - -func TestRequireListWithEnvFallback(t *testing.T) { - var app = cli.NewApp() - tests := []struct { - testName string - ctx *cli.Context - name string - envName string - expected []string - expectedErr error - }{ - { - testName: "string slice present in context", - ctx: cli.NewContext(app, applyFlagSet(t, flag1, "a"), nil), - name: flag1, - envName: "", - expected: []string{"a"}, - expectedErr: nil, - }, - { - testName: "string slice present in env", - ctx: cli.NewContext(app, flag.NewFlagSet("test", 0), nil), - name: flag1, - envName: setEnv(t, flag1, "a"), - expected: []string{"a"}, - expectedErr: nil, - }, - { - testName: "string slice absent in both context and env", - ctx: cli.NewContext(app, flag.NewFlagSet("test", 0), nil), - name: flag1, - envName: "", - expected: []string(nil), - expectedErr: cli.Exit("Required flag (flag1) or environment variable () not set", 1), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - flagList, err := RequireListWithEnvFallback(tt.ctx, tt.name, tt.envName) - if tt.expectedErr != nil { - require.Error(t, err) - } - require.Equal(t, tt.expected, flagList) - }) - } -} - -func TestRequireStringWithEnvFallback(t *testing.T) { - var app = cli.NewApp() - tests := []struct { - testName string - ctx *cli.Context - name string - envName string - expected string - expectedErr error - }{ - { - testName: "string present in the context", - ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError), flagObj{name: flag1, value: "a"}), nil), - name: flag1, - envName: "", - expected: "a", - expectedErr: nil, - }, - { - testName: "string present in env", - ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError)), nil), - name: flag1, - envName: setEnv(t, flag1, "a"), - expected: "a", - expectedErr: nil, - }, - { - testName: "string absent from both context and env", - ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("test", flag.ContinueOnError), flagObj{name: flag2, value: "b"}), nil), - name: flag1, - envName: "", - expected: "", - expectedErr: cli.Exit("Required flag (flag1) or environment variable () not set", 1), - }, - } - for _, tt := range tests { - t.Run(tt.testName, func(t *testing.T) { - flagString, err := RequireStringWithEnvFallback(tt.ctx, tt.name, tt.envName) - if tt.expectedErr != nil { - require.Error(t, err) - } - require.Equal(t, tt.expected, flagString) - }) - } -} - -func applyFlagSet(t *testing.T, aFlag, aValue string) *flag.FlagSet { - t.Helper() - var val cli.StringSlice - fl := cli.StringSliceFlag{Name: aFlag, EnvVars: []string{"FLAG"}, Value: &val} - set := flag.NewFlagSet("test", 0) - parseInput := []string{fmt.Sprintf("-%s", aFlag), aValue} - err := fl.Apply(set) - require.NoError(t, err) - err = set.Parse(parseInput) - require.NoError(t, err) - return set -} - -func setFlags(t *testing.T, flagSet *flag.FlagSet, flags ...flagObj) *flag.FlagSet { - t.Helper() - for _, f := range flags { - if f.name != "" { - flagSet.StringVar(&f.name, f.name, f.value, "") - } - } - return flagSet -} - -func setEnv(t *testing.T, key, value string) string { - t.Helper() - os.Clearenv() - - t.Setenv(key, value) - return key -} diff --git a/pkg/build/fsutil/copy_test.go b/pkg/build/fsutil/copy_test.go deleted file mode 100644 index 95cb8e838ae..00000000000 --- a/pkg/build/fsutil/copy_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package fsutil_test - -import ( - "os" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/build/fsutil" -) - -func TestCopyFile(t *testing.T) { - src, err := os.CreateTemp("", "") - require.NoError(t, err) - defer func() { - if err := os.RemoveAll(src.Name()); err != nil { - t.Log(err) - } - }() - - err = os.WriteFile(src.Name(), []byte("Contents"), 0600) - require.NoError(t, err) - - dst, err := os.CreateTemp("", "") - require.NoError(t, err) - defer func() { - if err := os.RemoveAll(dst.Name()); err != nil { - t.Log(err) - } - }() - - err = fsutil.CopyFile(src.Name(), dst.Name()) - require.NoError(t, err) -} - -func TestCopyFile_Permissions(t *testing.T) { - perms := os.FileMode(0700) - if runtime.GOOS == "windows" { - // Windows doesn't have file Unix style file permissions - // It seems you have either 0444 for read-only or 0666 for read-write - perms = os.FileMode(0666) - } - - src, err := os.CreateTemp("", "") - require.NoError(t, err) - - defer func() { - if err := os.RemoveAll(src.Name()); err != nil { - t.Log(err) - } - }() - - err = os.WriteFile(src.Name(), []byte("Contents"), perms) - require.NoError(t, err) - err = os.Chmod(src.Name(), perms) - require.NoError(t, err) - - dst, err := os.CreateTemp("", "") - require.NoError(t, err) - defer func() { - if err := os.RemoveAll(dst.Name()); err != nil { - t.Log(err) - } - }() - - err = fsutil.CopyFile(src.Name(), dst.Name()) - require.NoError(t, err) - - fi, err := os.Stat(dst.Name()) - require.NoError(t, err) - assert.Equal(t, perms, fi.Mode()&os.ModePerm) -} - -// Test case where destination directory doesn't exist. -func TestCopyFile_NonExistentDestDir(t *testing.T) { - src, err := os.CreateTemp("", "") - require.NoError(t, err) - defer func() { - if err := os.RemoveAll(src.Name()); err != nil { - t.Log(err) - } - }() - - err = fsutil.CopyFile(src.Name(), "non-existent/dest") - require.EqualError(t, err, "destination directory doesn't exist: \"non-existent\"") -} diff --git a/pkg/build/fsutil/copyfile.go b/pkg/build/fsutil/copyfile.go deleted file mode 100644 index eb7edbdf263..00000000000 --- a/pkg/build/fsutil/copyfile.go +++ /dev/null @@ -1,107 +0,0 @@ -package fsutil - -import ( - "fmt" - "io" - "log" - "os" - "path/filepath" -) - -// CopyFile copies a file from src to dst. -// -// If src and dst files exist, and are the same, then return success. Otherwise, attempt to create a hard link -// between the two files. If that fails, copy the file contents from src to dst. -func CopyFile(src, dst string) (err error) { - absSrc, err := filepath.Abs(src) - if err != nil { - return fmt.Errorf("failed to get absolute path of source file %q: %w", src, err) - } - sfi, err := os.Stat(src) - if err != nil { - err = fmt.Errorf("couldn't stat source file %q: %w", absSrc, err) - return - } - if !sfi.Mode().IsRegular() { - // Cannot copy non-regular files (e.g., directories, symlinks, devices, etc.) - return fmt.Errorf("non-regular source file %s (%q)", absSrc, sfi.Mode().String()) - } - dpath := filepath.Dir(dst) - exists, err := Exists(dpath) - if err != nil { - return err - } - if !exists { - err = fmt.Errorf("destination directory doesn't exist: %q", dpath) - return - } - - var dfi os.FileInfo - dfi, err = os.Stat(dst) - if err != nil { - if !os.IsNotExist(err) { - return - } - } else { - if !(dfi.Mode().IsRegular()) { - return fmt.Errorf("non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) - } - if os.SameFile(sfi, dfi) { - return copyPermissions(sfi.Name(), dfi.Name()) - } - } - - err = copyFileContents(src, dst) - return err -} - -// copyFileContents copies the contents of the file named src to the file named -// by dst. The file will be created if it does not already exist. If the -// destination file exists, all it's contents will be replaced by the contents -// of the source file. -func copyFileContents(src, dst string) (err error) { - //nolint:gosec - in, err := os.Open(src) - if err != nil { - return - } - defer func() { - if err := in.Close(); err != nil { - log.Println("error closing file", err) - } - }() - - //nolint:gosec - out, err := os.Create(dst) - if err != nil { - return - } - defer func() { - if cerr := out.Close(); cerr != nil && err == nil { - err = cerr - } - }() - - if _, err = io.Copy(out, in); err != nil { - return - } - - if err := out.Sync(); err != nil { - return err - } - - return copyPermissions(src, dst) -} - -func copyPermissions(src, dst string) error { - sfi, err := os.Lstat(src) - if err != nil { - return err - } - - if err := os.Chmod(dst, sfi.Mode()); err != nil { - return err - } - - return nil -} diff --git a/pkg/build/fsutil/exists_test.go b/pkg/build/fsutil/exists_test.go deleted file mode 100644 index f46297a0764..00000000000 --- a/pkg/build/fsutil/exists_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package fsutil_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/build/fsutil" -) - -func TestExists_NonExistent(t *testing.T) { - exists, err := fsutil.Exists("non-existent") - require.NoError(t, err) - - require.False(t, exists) -} diff --git a/pkg/build/fsutil/exsits.go b/pkg/build/fsutil/exsits.go deleted file mode 100644 index 23e09d20133..00000000000 --- a/pkg/build/fsutil/exsits.go +++ /dev/null @@ -1,16 +0,0 @@ -package fsutil - -import "os" - -// Exists determines whether a file/directory exists or not. -func Exists(fpath string) (bool, error) { - _, err := os.Stat(fpath) - if err != nil { - if !os.IsNotExist(err) { - return false, err - } - return false, nil - } - - return true, nil -} diff --git a/pkg/build/gcloud/auth.go b/pkg/build/gcloud/auth.go deleted file mode 100644 index ad68fbc5d17..00000000000 --- a/pkg/build/gcloud/auth.go +++ /dev/null @@ -1,65 +0,0 @@ -package gcloud - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "log" - "os" - "os/exec" - "strings" -) - -func GetDecodedKey() ([]byte, error) { - gcpKey := strings.TrimSpace(os.Getenv("GCP_KEY")) - if gcpKey == "" { - return nil, fmt.Errorf("the environment variable GCP_KEY must be set") - } - - gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKey) - if err != nil { - // key is not always base64 encoded - validKey := []byte(gcpKey) - if json.Valid(validKey) { - return validKey, nil - } - return nil, fmt.Errorf("error decoding the gcp_key, err: %q", err) - } - - return gcpKeyB, nil -} - -func ActivateServiceAccount() error { - byteKey, err := GetDecodedKey() - if err != nil { - return err - } - - f, err := os.CreateTemp("", "*.json") - if err != nil { - return err - } - defer func() { - if err := os.Remove(f.Name()); err != nil { - log.Printf("error removing %s: %s", f.Name(), err) - } - }() - - defer func() { - if err := f.Close(); err != nil { - log.Println("error closing file:", err) - } - }() - - if _, err := f.Write(byteKey); err != nil { - return fmt.Errorf("failed to write GCP key file: %w", err) - } - keyArg := fmt.Sprintf("--key-file=%s", f.Name()) - //nolint:gosec - cmd := exec.Command("gcloud", "auth", "activate-service-account", keyArg) - - if output, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to sign into GCP: %w\n%s", err, output) - } - return nil -} diff --git a/pkg/build/gcloud/storage/gsutil.go b/pkg/build/gcloud/storage/gsutil.go deleted file mode 100644 index 711728f4fb0..00000000000 --- a/pkg/build/gcloud/storage/gsutil.go +++ /dev/null @@ -1,490 +0,0 @@ -package storage - -import ( - "context" - "errors" - "fmt" - "io" - "log" - "mime" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "sync" - "time" - - "cloud.google.com/go/storage" - "google.golang.org/api/iterator" - "google.golang.org/api/option" - - "github.com/grafana/grafana/pkg/build/fsutil" - "github.com/grafana/grafana/pkg/build/gcloud" -) - -var ( - // ErrorNilBucket is returned when a function is called where a bucket argument is expected and the bucket is nil. - ErrorNilBucket = errors.New("a bucket must be provided") -) - -const ( - // maxThreads specify the number of max threads that can run at the same time. - // Set to 1000, since the maximum number of simultaneous open files for the runners is 1024. - maxThreads = 1000 -) - -// Client wraps the gcloud storage Client with convenient helper functions. -// By using an embedded type we can still use the functions provided by storage.Client if we need to. -type Client struct { - storage.Client -} - -// File represents a file in Google Cloud Storage. -type File struct { - FullPath string - PathTrimmed string -} - -// New creates a new Client by checking for the Google Cloud SDK auth key and/or environment variable. -func New() (*Client, error) { - storageClient, err := newClient() - if err != nil { - return nil, err - } - - client := &Client{ - Client: *storageClient, - } - client.SetRetryer() - - return client, nil -} - -// SetRetryer adds a retry strategy for the googleapi client calls that fail. -func (client *Client) SetRetryer() { - client.SetRetry([]storage.RetryOption{ - storage.WithPolicy(storage.RetryAlways), - storage.WithErrorFunc(storage.ShouldRetry), - }...) -} - -// newClient initializes the google-cloud-storage (GCS) client. -// It first checks for the application-default_credentials.json file then the GCP_KEY environment variable. -func newClient() (*storage.Client, error) { - ctx := context.Background() - - byteKey, err := gcloud.GetDecodedKey() - if err != nil { - return nil, fmt.Errorf("failed to get gcp key, err: %w", err) - } - client, err := storage.NewClient(ctx, option.WithCredentialsJSON(byteKey)) - if err != nil { - log.Println("failed to login with GCP_KEY, trying with default application credentials...") - client, err = storage.NewClient(ctx) - if err != nil { - return nil, fmt.Errorf("failed to open Google Cloud Storage client: %w", err) - } - } - - return client, nil -} - -// CopyLocalDir copies a local directory 'dir' to the bucket 'bucket' at the path 'bucketPath'. -func (client *Client) CopyLocalDir(ctx context.Context, dir string, bucket *storage.BucketHandle, bucketPath string, trim bool) error { - if bucket == nil { - return ErrorNilBucket - } - - files, err := ListLocalFiles(dir) - if err != nil { - return err - } - log.Printf("Number or files to be copied over: %d\n", len(files)) - - errs := make([]error, 0, 10) - errsLock := sync.Mutex{} - - for _, chunk := range asChunks(files, maxThreads) { - var wg sync.WaitGroup - for _, f := range chunk { - wg.Add(1) - go func(file File) { - defer wg.Done() - err := client.Copy(ctx, file, bucket, bucketPath, trim) - if err != nil { - log.Printf("failed to copy objects, err: %s\n", err.Error()) - errsLock.Lock() - errs = append(errs, err) - errsLock.Unlock() - } - }(f) - } - wg.Wait() - } - - if len(errs) > 0 { - return fmt.Errorf("copy operation failed: %s", errs) - } - - return nil -} - -// Copy copies a single local file into the bucket at the provided path. -// trim variable should be set to true if the full object path is needed - false otherwise. -func (client *Client) Copy(ctx context.Context, file File, bucket *storage.BucketHandle, remote string, trim bool) (resultErr error) { - if bucket == nil { - return ErrorNilBucket - } - - localFile, err := os.Open(file.FullPath) - if err != nil { - return fmt.Errorf("failed to open file %s, err: %q", file.FullPath, err) - } - defer func() { - if err := localFile.Close(); err != nil { - log.Println("failed to close localfile", "err", err) - } - }() - - extension := strings.ToLower(path.Ext(file.FullPath)) - contentType := mime.TypeByExtension(extension) - - filePath := file.FullPath - if trim { - filePath = file.PathTrimmed - } - - objectPath := path.Join(remote, filePath) - - wc := bucket.Object(objectPath).NewWriter(ctx) - wc.ContentType = contentType - defer func() { - if err := wc.Close(); err != nil { - log.Println("failed to close writer", "err", err) - // Keep the original error intact if there was one: - if resultErr == nil { - resultErr = err - } - } - }() - - if _, err = io.Copy(wc, localFile); err != nil { - resultErr = fmt.Errorf("failed to copy to Cloud Storage: %w", err) - return - } - - return nil -} - -// CopyRemoteDir copies an entire directory 'from' from the bucket 'fromBucket' into the 'toBucket' at the path 'to'. -func (client *Client) CopyRemoteDir(ctx context.Context, fromBucket *storage.BucketHandle, from string, toBucket *storage.BucketHandle, to string) error { - if toBucket == nil || fromBucket == nil { - return ErrorNilBucket - } - - files, err := ListRemoteFiles(ctx, fromBucket, FilesFilter{Prefix: from}) - if err != nil { - return err - } - - var ch = make(chan File, len(files)) - var wg sync.WaitGroup - wg.Add(maxThreads) - - for i := 0; i < maxThreads; i++ { - go func() { - for { - file, ok := <-ch - if !ok { - wg.Done() - return - } - if err := client.RemoteCopy(ctx, file, fromBucket, toBucket, to); err != nil { - log.Printf("failed to copy files between buckets: err: %s\n", err.Error()) - return - } - } - }() - } - - for _, file := range files { - ch <- file - } - - close(ch) - wg.Wait() - - return nil -} - -// RemoteCopy will copy the file 'file' from the 'fromBucket' to the 'toBucket' at the path 'path'. -func (client *Client) RemoteCopy(ctx context.Context, file File, fromBucket, toBucket *storage.BucketHandle, path string) error { - // Should this be path.Join instead of filepath.Join? filepath.Join on Windows will produce `\\` separators instead of `/`. - var ( - src = fromBucket.Object(file.FullPath) - dstObject = filepath.Join(path, file.PathTrimmed) - dst = toBucket.Object(dstObject) - ) - - if _, err := dst.CopierFrom(src).Run(ctx); err != nil { - return fmt.Errorf("failed to copy object %s, to %s, err: %w", file.FullPath, dstObject, err) - } - - return nil -} - -// DeleteDir deletes a directory at 'path' from the bucket. -func (client *Client) DeleteDir(ctx context.Context, bucket *storage.BucketHandle, path string) error { - if bucket == nil { - return ErrorNilBucket - } - - files, err := ListRemoteFiles(ctx, bucket, FilesFilter{Prefix: path}) - if err != nil { - return err - } - - var ch = make(chan string, len(files)) - var wg sync.WaitGroup - wg.Add(maxThreads) - - for i := 0; i < maxThreads; i++ { - go func() { - for { - fullPath, ok := <-ch - if !ok { - wg.Done() - return - } - err := client.Delete(ctx, bucket, fullPath) - if err != nil && !errors.Is(err, storage.ErrObjectNotExist) { - log.Printf("failed to delete objects, err %s\n", err.Error()) - panic(err) - } - } - }() - } - - for _, file := range files { - ch <- file.FullPath - } - - close(ch) - wg.Wait() - - return nil -} - -// Delete deletes single item from the bucket at 'path'. -func (client *Client) Delete(ctx context.Context, bucket *storage.BucketHandle, path string) error { - object := bucket.Object(path) - if err := object.Delete(ctx); err != nil { - return fmt.Errorf("cannot delete %s, err: %w", path, err) - } - return nil -} - -// ListLocalFiles lists files in a local filesystem. -func ListLocalFiles(dir string) ([]File, error) { - var files []File - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() { - files = append(files, File{ - FullPath: path, - // Strip the dir name from the filepath - PathTrimmed: strings.ReplaceAll(path, dir, ""), - }) - } - return nil - }) - - if err != nil { - return nil, fmt.Errorf("error walking path: %v", err) - } - - return files, nil -} - -type FilesFilter struct { - Prefix string - FileExts []string -} - -// ListRemoteFiles lists all the files in the directory (filtering by FilesFilter) and returns a File struct for each one. -func ListRemoteFiles(ctx context.Context, bucket *storage.BucketHandle, filter FilesFilter) ([]File, error) { - if bucket == nil { - return []File{}, ErrorNilBucket - } - - it := bucket.Objects(ctx, &storage.Query{ - Prefix: filter.Prefix, - }) - - var files []File - for { - attrs, err := it.Next() - if err != nil { - if errors.Is(err, iterator.Done) { - break - } - } - if err != nil { - return nil, fmt.Errorf("failed to iterate through bucket, err: %w", err) - } - - extMatch := len(filter.FileExts) == 0 - for _, ext := range filter.FileExts { - if ext == filepath.Ext(attrs.Name) { - extMatch = true - break - } - } - - if extMatch { - files = append(files, File{FullPath: attrs.Name, PathTrimmed: strings.TrimPrefix(attrs.Name, filter.Prefix)}) - } - } - - return files, nil -} - -// DownloadDirectory downloads files from bucket (filtering by FilesFilter) to destPath on disk. -func (client *Client) DownloadDirectory(ctx context.Context, bucket *storage.BucketHandle, destPath string, filter FilesFilter) error { - if bucket == nil { - return ErrorNilBucket - } - - files, err := ListRemoteFiles(ctx, bucket, filter) - if err != nil { - return err - } - - // return err if dir already exists - exists, err := fsutil.Exists(destPath) - if err != nil { - return err - } - if exists { - return fmt.Errorf("destination path %q already exists", destPath) - } - - err = os.MkdirAll(destPath, 0750) - if err != nil && !os.IsExist(err) { - return err - } - - for _, file := range files { - err = client.downloadFile(ctx, bucket, file.FullPath, file.PathTrimmed) - if err != nil { - return err - } - } - return nil -} - -// GetLatestMainBuild gets the latest main build which is successfully uploaded to the gcs bucket. -func GetLatestMainBuild(ctx context.Context, bucket *storage.BucketHandle, path string) (string, error) { - if bucket == nil { - return "", ErrorNilBucket - } - - query := &storage.Query{ - Prefix: path, - } - err := query.SetAttrSelection([]string{"Name", "Generation"}) - if err != nil { - return "", fmt.Errorf("failed to set attribute selector, err: %q", err) - } - it := bucket.Objects(ctx, query) - - var files []string - var oldGeneration int64 - for { - attrs, err := it.Next() - if errors.Is(err, iterator.Done) { - break - } - if err != nil { - return "", fmt.Errorf("failed to iterate through bucket, err: %w", err) - } - if attrs.Generation >= oldGeneration { - files = append([]string{attrs.Name}, files...) - oldGeneration = attrs.Generation - } else { - files = append(files, attrs.Name) - } - } - - var latestVersion string - for i := 0; i < len(files); i++ { - captureVersion := regexp.MustCompile(`(\d+\.\d+\.\d+-\d+)`) - if captureVersion.MatchString(files[i]) { - latestVersion = captureVersion.FindString(files[i]) - break - } - } - - return latestVersion, nil -} - -// downloadFile downloads an object to a file. -func (client *Client) downloadFile(ctx context.Context, bucket *storage.BucketHandle, objectName, destFileName string) error { - if bucket == nil { - return ErrorNilBucket - } - - ctx, cancel := context.WithTimeout(ctx, time.Second*10) - defer cancel() - - // nolint:gosec - f, err := os.Create(destFileName) - if err != nil { - return fmt.Errorf("os.Create: %v", err) - } - - rc, err := bucket.Object(objectName).NewReader(ctx) - if err != nil { - return fmt.Errorf("Object(%q).NewReader: %v", objectName, err) - } - defer func() { - if err := rc.Close(); err != nil { - log.Println("failed to close reader", "err", err) - } - }() - - if _, err := io.Copy(f, rc); err != nil { - return fmt.Errorf("io.Copy: %v", err) - } - - if err = f.Close(); err != nil { - return fmt.Errorf("f.Close: %v", err) - } - - return nil -} - -// asChunks will split the supplied []File into slices with a max size of `chunkSize` -// []string{"a", "b", "c"}, 1 => [][]string{[]string{"a"}, []string{"b"}, []string{"c"}} -// []string{"a", "b", "c"}, 2 => [][]string{[]string{"a", "b"}, []string{"c"}}. -func asChunks(files []File, chunkSize int) [][]File { - var fileChunks [][]File - - if len(files) == 0 { - return [][]File{} - } - - if len(files) > chunkSize && chunkSize > 0 { - for i := 0; i < len(files); i += chunkSize { - end := i + chunkSize - - if end > len(files) { - end = len(files) - } - fileChunks = append(fileChunks, files[i:end]) - } - } else { - fileChunks = [][]File{files} - } - return fileChunks -} diff --git a/pkg/build/gcloud/storage/gsutil_test.go b/pkg/build/gcloud/storage/gsutil_test.go deleted file mode 100644 index 9a8ae5cb6ad..00000000000 --- a/pkg/build/gcloud/storage/gsutil_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package storage - -import ( - "context" - "os" - "path/filepath" - "testing" - - "cloud.google.com/go/storage" - "github.com/stretchr/testify/require" - "google.golang.org/api/option" -) - -func Test_asChunks(t *testing.T) { - type args struct { - files []File - chunkSize int - } - tcs := []struct { - name string - args args - expected [][]File - }{ - { - name: "Happy path #1", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - {FullPath: "/1"}, - {FullPath: "/2"}, - {FullPath: "/3"}, - }, - chunkSize: 5, - }, - expected: [][]File{ - {{FullPath: "/a"}, {FullPath: "/b"}, {FullPath: "/c"}, {FullPath: "/1"}, {FullPath: "/2"}}, - {{FullPath: "/3"}}, - }, - }, - { - name: "Happy path #2", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - {FullPath: "/1"}, - {FullPath: "/2"}, - {FullPath: "/3"}, - }, - chunkSize: 2, - }, - expected: [][]File{ - {{FullPath: "/a"}, {FullPath: "/b"}}, - {{FullPath: "/c"}, {FullPath: "/1"}}, - {{FullPath: "/2"}, {FullPath: "/3"}}, - }, - }, - { - name: "Happy path #3", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - chunkSize: 1, - }, - expected: [][]File{ - {{FullPath: "/a"}}, - {{FullPath: "/b"}}, - {{FullPath: "/c"}}, - }, - }, - { - name: "A chunkSize with 0 value returns the input as a single chunk", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - chunkSize: 0, - }, - expected: [][]File{ - { - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - }, - }, - { - name: "A chunkSize with negative value returns the input as a single chunk", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - chunkSize: -1, - }, - expected: [][]File{ - { - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - }, - }, - { - name: "A chunkSize greater than the size on input returns the input as a single chunk", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - chunkSize: 5, - }, - expected: [][]File{ - { - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - }, - }, - { - name: "A chunkSize equal the size on input returns the input as a single chunk", - args: args{ - files: []File{ - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - chunkSize: 3, - }, - expected: [][]File{ - { - {FullPath: "/a"}, - {FullPath: "/b"}, - {FullPath: "/c"}, - }, - }, - }, - { - name: "An empty input returns empty chunks", - args: args{ - files: []File{}, - chunkSize: 3, - }, - expected: [][]File{}, - }, - } - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - result := asChunks(tc.args.files, tc.args.chunkSize) - require.Equal(t, tc.expected, result) - }) - } -} - -func TestCopyLocalDir(t *testing.T) { - t.Parallel() - - tmpDir := t.TempDir() - testFiles := []string{"file1.txt", "file2.txt"} - for _, testFile := range testFiles { - path := filepath.Join(tmpDir, testFile) - require.NoError(t, os.WriteFile(path, []byte{}, 0600)) - } - - // If an upload fails then the whole copy operation should return an error. - t.Run("failure-should-error", func(t *testing.T) { - t.Parallel() - - // Assemble: - ctx := context.Background() - client := createAnonymousClient(t, ctx) - testBucket := client.Bucket("grafana-testing-repo") - - // Act: - err := client.CopyLocalDir(ctx, tmpDir, testBucket, "test-path", false) - - // Assert: - // This should fail as the client has no access to the bucket to upload to: - require.Error(t, err) - }) -} - -func TestCopyRemoteDir(t *testing.T) { - t.Parallel() - - t.Run("failure-should-error", func(t *testing.T) { - t.Parallel() - - // Assemble: - ctx := context.Background() - client := createAnonymousClient(t, ctx) - testFromBucket := client.Bucket("grafana-testing-repo") - testToBucket := client.Bucket("grafana-testing-repo") - - // Act: - err := client.CopyRemoteDir(ctx, testFromBucket, "test-from", testToBucket, "test-to") - - // Assert: - // This should fail as the client has no access to the bucket to copy from/to. Unfortunately, this does not yet - // cover if a single file fails to get transferred. - require.Error(t, err) - }) -} - -func createAnonymousClient(t *testing.T, ctx context.Context) *Client { - t.Helper() - storageClient, err := storage.NewClient(ctx, option.WithoutAuthentication()) - require.NoError(t, err) - client := &Client{ - Client: *storageClient, - } - require.NoError(t, err) - return client -} diff --git a/pkg/build/gcom/url.go b/pkg/build/gcom/url.go deleted file mode 100644 index e85c1039796..00000000000 --- a/pkg/build/gcom/url.go +++ /dev/null @@ -1,72 +0,0 @@ -package gcom - -import ( - "fmt" - "net/url" - "path" - "strings" - - "github.com/grafana/grafana/pkg/build/versions" -) - -func PackageName(grafana, distro, arch, version, ext string, musl bool, raspberryPi bool) string { - v := versions.ParseSemver(version) - - if raspberryPi { - grafana += "-rpi" - } - - versionString := strings.Join([]string{v.Major, v.Minor, v.Patch}, ".") - fmt.Println("Version string:", versionString) - if distro == "deb" { - if v.BuildMetadata != "" { - versionString += "+" + strings.ReplaceAll(v.BuildMetadata, "-", "~") - } - - if v.Prerelease != "" { - versionString += "~" + v.Prerelease - } - - return strings.Join([]string{grafana, versionString, arch}, "_") + "." + ext - } - - if distro == "rhel" { - if v.BuildMetadata != "" { - versionString += "+" + strings.ReplaceAll(v.BuildMetadata, "-", "~") - } - - if v.Prerelease != "" { - versionString += "~" + v.Prerelease - } - - versionString += "-1" - - // Notable difference between our deb naming and our RPM naming: the file ends with `.arch.ext`, not - // `_arch.ext`. - return strings.Join([]string{grafana, versionString}, "-") + "." + arch + "." + ext - } - - if v.Prerelease != "" { - versionString += "-" + v.Prerelease - } - - if v.BuildMetadata != "" { - versionString += "+" + v.BuildMetadata - } - - if musl { - arch += "-musl" - } - - // grafana-enterprise-1.2.3+example-01.linux-amd64.tar.gz - return fmt.Sprintf("%s-%s.%s-%s.%s", grafana, versionString, distro, arch, ext) -} - -func GetURL(baseURL *url.URL, version, grafana, distro, arch, ext string, musl, raspberryPi bool) *url.URL { - packageName := PackageName(grafana, distro, arch, version, ext, musl, raspberryPi) - return &url.URL{ - Host: baseURL.Host, - Scheme: baseURL.Scheme, - Path: path.Join(baseURL.Path, packageName), - } -} diff --git a/pkg/build/gcom/url_test.go b/pkg/build/gcom/url_test.go deleted file mode 100644 index 680a4326fd1..00000000000 --- a/pkg/build/gcom/url_test.go +++ /dev/null @@ -1,367 +0,0 @@ -package gcom_test - -import ( - "fmt" - "testing" - - "github.com/grafana/grafana/pkg/build/gcom" - "github.com/stretchr/testify/require" -) - -func TestPackageName(t *testing.T) { - type args struct { - Distro string - Arch string - Version string - Ext string - Musl bool - RaspberryPi bool - - Expect string - } - - cases := []args{ - { - RaspberryPi: true, - Distro: "deb", - Arch: "armhf", - Version: "1.2.3", - Ext: "deb", - Expect: "grafana-rpi_1.2.3_armhf.deb", - }, - { - Distro: "deb", - Arch: "arm64", - Version: "1.2.3", - Ext: "deb", - Expect: "grafana_1.2.3_arm64.deb", - }, - { - Distro: "rhel", - Arch: "aarch64", - Version: "1.2.3", - Ext: "rpm", - Expect: "grafana-1.2.3-1.aarch64.rpm", - }, - { - Distro: "rhel", - Arch: "aarch64", - Ext: "rpm.sha256", - Version: "1.2.3", - Expect: "grafana-1.2.3-1.aarch64.rpm.sha256", - }, - { - Distro: "rhel", - Ext: "rpm", - Version: "1.2.3", - Arch: "x86_64", - Expect: "grafana-1.2.3-1.x86_64.rpm", - }, - { - Distro: "rhel", - Ext: "rpm.sha256", - Version: "1.2.3", - Arch: "x86_64", - Expect: "grafana-1.2.3-1.x86_64.rpm.sha256", - }, - { - Distro: "darwin", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.darwin-amd64.tar.gz", - }, - { - Distro: "darwin", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.darwin-amd64.tar.gz.sha256", - }, - { - Distro: "darwin", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.darwin-arm64-musl.tar.gz", - Musl: true, - }, - { - Distro: "darwin", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.darwin-arm64-musl.tar.gz.sha256", - Musl: true, - }, - { - Distro: "darwin", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.darwin-arm64.tar.gz", - }, - { - Distro: "darwin", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.darwin-arm64.tar.gz.sha256", - }, - { - Distro: "linux", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.linux-amd64-musl.tar.gz", - Musl: true, - }, - { - Distro: "linux", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.linux-amd64-musl.tar.gz.sha256", - Musl: true, - }, - { - Distro: "linux", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.linux-amd64.tar.gz", - }, - { - Distro: "linux", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "amd64", - Expect: "grafana-1.2.3.linux-amd64.tar.gz.sha256", - }, - { - Distro: "linux", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.linux-arm64-musl.tar.gz", - Musl: true, - }, - { - Distro: "linux", - Ext: "tar.gz.sha256", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.linux-arm64-musl.tar.gz.sha256", - Musl: true, - }, - { - Distro: "linux", - Ext: "tar.gz", - Version: "1.2.3", - Arch: "arm64", - Expect: "grafana-1.2.3.linux-arm64.tar.gz", - }, - { - Ext: "tar.gz.sha256", - Version: "1.2.3", - Distro: "linux", - Arch: "arm64", - Expect: "grafana-1.2.3.linux-arm64.tar.gz.sha256", - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "linux", - Arch: "armv6", - Expect: "grafana-1.2.3.linux-armv6.tar.gz", - }, - { - Ext: "tar.gz.sha256", - Version: "1.2.3", - Distro: "linux", - Arch: "armv6", - Expect: "grafana-1.2.3.linux-armv6.tar.gz.sha256", - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "linux", - Arch: "armv7", - Expect: "grafana-1.2.3.linux-armv7-musl.tar.gz", - Musl: true, - }, - { - Ext: "tar.gz.sha256", - Version: "1.2.3", - Distro: "linux", - Arch: "armv7", - Expect: "grafana-1.2.3.linux-armv7-musl.tar.gz.sha256", - Musl: true, - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "linux", - Arch: "armv7", - Expect: "grafana-1.2.3.linux-armv7.tar.gz", - }, - { - Ext: "tar.gz.sha256", - Version: "1.2.3", - Distro: "linux", - Arch: "armv7", - Expect: "grafana-1.2.3.linux-armv7.tar.gz.sha256", - }, - { - Version: "1.2.3", - Arch: "amd64", - Ext: "exe", - Distro: "windows", - Expect: "grafana-1.2.3.windows-amd64.exe", - }, - { - Version: "1.2.3", - Arch: "amd64", - Distro: "windows", - Ext: "exe.sha256", - Expect: "grafana-1.2.3.windows-amd64.exe.sha256", - }, - { - Version: "1.2.3", - Arch: "amd64", - Distro: "windows", - Ext: "msi", - Expect: "grafana-1.2.3.windows-amd64.msi", - }, - { - Version: "1.2.3", - Arch: "amd64", - Distro: "windows", - Ext: "msi.sha256", - Expect: "grafana-1.2.3.windows-amd64.msi.sha256", - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "windows", - Expect: "grafana-1.2.3.windows-amd64.tar.gz", - Arch: "amd64", - }, - { - Version: "1.2.3", - Distro: "windows", - Arch: "amd64", - Ext: "tar.gz.sha256", - Expect: "grafana-1.2.3.windows-amd64.tar.gz.sha256", - }, - { - Version: "1.2.3", - Distro: "windows", - Expect: "grafana-1.2.3.windows-amd64.zip", - Ext: "zip", - Arch: "amd64", - }, - { - Version: "1.2.3", - Distro: "windows", - Expect: "grafana-1.2.3.windows-amd64.zip.sha256", - Ext: "zip.sha256", - Arch: "amd64", - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "windows", - Arch: "arm64", - Expect: "grafana-1.2.3.windows-arm64-musl.tar.gz", - Musl: true, - }, - { - Version: "1.2.3", - Ext: "tar.gz.sha256", - Distro: "windows", - Arch: "arm64", - Expect: "grafana-1.2.3.windows-arm64-musl.tar.gz.sha256", - Musl: true, - }, - { - Ext: "tar.gz", - Version: "1.2.3", - Distro: "windows", - Arch: "arm64", - Expect: "grafana-1.2.3.windows-arm64.tar.gz", - }, - { - Version: "1.2.3", - Ext: "tar.gz.sha256", - Distro: "windows", - Arch: "arm64", - Expect: "grafana-1.2.3.windows-arm64.tar.gz.sha256", - }, - { - RaspberryPi: true, - Version: "1.2.3", - Ext: "deb", - Arch: "armhf", - Distro: "deb", - Expect: "grafana-rpi_1.2.3_armhf.deb", - }, - { - RaspberryPi: true, - Version: "1.2.3", - Ext: "deb.sha256", - Distro: "deb", - Arch: "armhf", - Expect: "grafana-rpi_1.2.3_armhf.deb.sha256", - }, - { - Version: "1.2.3", - Ext: "deb", - Distro: "deb", - Expect: "grafana_1.2.3_amd64.deb", - Arch: "amd64", - }, - { - Version: "1.2.3", - Ext: "deb.sha256", - Distro: "deb", - Expect: "grafana_1.2.3_amd64.deb.sha256", - Arch: "amd64", - }, - { - Version: "1.2.3", - Ext: "deb", - Arch: "arm64", - Distro: "deb", - Expect: "grafana_1.2.3_arm64.deb", - }, - { - Version: "1.2.3", - Ext: "deb.sha256", - Arch: "arm64", - Distro: "deb", - Expect: "grafana_1.2.3_arm64.deb.sha256", - }, - { - Version: "1.2.3", - Ext: "deb", - Distro: "deb", - Arch: "armhf", - Expect: "grafana_1.2.3_armhf.deb", - }, - { - Version: "1.2.3", - Ext: "deb.sha256", - Arch: "armhf", - Distro: "deb", - Expect: "grafana_1.2.3_armhf.deb.sha256", - }, - } - - for i, v := range cases { - t.Run(fmt.Sprintf("[%d / %d] %s", i+1, len(cases), v.Expect), func(t *testing.T) { - n := gcom.PackageName("grafana", v.Distro, v.Arch, v.Version, v.Ext, v.Musl, v.RaspberryPi) - require.Equal(t, v.Expect, n) - }) - } -} diff --git a/pkg/build/git/git.go b/pkg/build/git/git.go deleted file mode 100644 index de1ac94559a..00000000000 --- a/pkg/build/git/git.go +++ /dev/null @@ -1,53 +0,0 @@ -package git - -import ( - "context" - "errors" - "fmt" - "regexp" - - "github.com/google/go-github/v70/github" -) - -const ( - MainBranch = "main" - HomeDir = "." - RepoOwner = "grafana" - OSSRepo = "grafana" - EnterpriseRepo = "grafana-enterprise" - EnterpriseCheckName = "Grafana Enterprise" - EnterpriseCheckDescription = "Downstream tests to ensure that your changes are compatible with Grafana Enterprise" -) - -var EnterpriseCheckLabels = []string{"enterprise-ok", "enterprise-failed", "enterprise-override"} - -var ( - ErrorNoDroneBuildLink = errors.New("no drone build link") -) - -type GitService interface { - DeleteRef(ctx context.Context, owner string, repo string, ref string) (*github.Response, error) -} - -type LabelsService interface { - ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error) - RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error) - AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) -} - -type CommentService interface { - CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) -} - -type StatusesService interface { - CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) -} - -func PRCheckRegexp() *regexp.Regexp { - reBranch, err := regexp.Compile(`^prc-([0-9]+)-([A-Za-z0-9]+)\/(.+)$`) - if err != nil { - panic(fmt.Sprintf("Failed to compile regexp: %s", err)) - } - - return reBranch -} diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 3012a2bab49..2c3ea9cebf7 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -8,114 +8,50 @@ go 1.24.6 replace github.com/docker/docker => github.com/moby/moby v27.5.1+incompatible require ( - cloud.google.com/go/storage v1.55.0 // @grafana/grafana-backend-group - github.com/Masterminds/semver/v3 v3.4.0 // @grafana/grafana-developer-enablement-squad - github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources - github.com/docker/docker v28.1.1+incompatible // @grafana/grafana-developer-enablement-squad - github.com/drone/drone-cli v1.8.0 // @grafana/grafana-developer-enablement-squad - github.com/gogo/protobuf v1.3.2 // indirect; @grafana/alerting-backend - github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group github.com/google/uuid v1.6.0 // indirect; @grafana/grafana-backend-group - github.com/googleapis/gax-go/v2 v2.14.2 // indirect; @grafana/grafana-backend-group - github.com/jmespath/go-jmespath v0.4.0 // indirect; @grafana/grafana-backend-group - github.com/stretchr/testify v1.10.0 // @grafana/grafana-backend-group - github.com/urfave/cli v1.22.16 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.6 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect; @grafana/plugins-platform-backend - go.opentelemetry.io/otel v1.37.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.37.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.37.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.37.0 // @grafana/grafana-backend-group - golang.org/x/crypto v0.40.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.42.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/oauth2 v0.30.0 // @grafana/identity-access-team + go.opentelemetry.io/otel/trace v1.37.0 // indirect; @grafana/grafana-backend-group + golang.org/x/net v0.43.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/sync v0.16.0 // @grafana/alerting-backend - golang.org/x/text v0.27.0 // indirect; @grafana/grafana-backend-group - golang.org/x/time v0.11.0 // indirect; @grafana/grafana-backend-group - google.golang.org/api v0.235.0 // @grafana/grafana-backend-group + golang.org/x/text v0.28.0 // indirect; @grafana/grafana-backend-group google.golang.org/grpc v1.74.2 // indirect; @grafana/plugins-platform-backend google.golang.org/protobuf v1.36.6 // indirect; @grafana/plugins-platform-backend - gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend ) require ( - cloud.google.com/go v0.121.1 // indirect - cloud.google.com/go/auth v0.16.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.7.0 // indirect - cloud.google.com/go/iam v1.5.2 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/buildkite/yaml v2.1.0+incompatible // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/drone-runners/drone-runner-docker v1.8.3 // indirect - github.com/drone/drone-go v1.7.1 // indirect - github.com/drone/envsubst v1.0.3 // indirect - github.com/drone/runner-go v1.12.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-logr/logr v1.4.3 // indirect; @grafana/grafana-app-platform-squad github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/sys v0.34.0 // indirect - google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect; @grafana/grafana-backend-group + golang.org/x/sys v0.35.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( dagger.io/dagger v0.18.8 github.com/Masterminds/semver v1.5.0 - github.com/google/go-github/v70 v70.0.0 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/urfave/cli/v3 v3.3.3 ) require ( - cel.dev/expr v0.24.0 // indirect - cloud.google.com/go/monitoring v1.24.2 // indirect github.com/99designs/gqlgen v0.17.73 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/Khan/genqlient v0.8.1 // indirect github.com/adrg/xdg v0.5.3 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect - github.com/go-jose/go-jose/v4 v4.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sosodev/duration v1.3.1 // indirect - github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect - github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect @@ -127,7 +63,6 @@ require ( go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect - gotest.tools/v3 v3.5.1 // indirect ) // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream diff --git a/pkg/build/go.sum b/pkg/build/go.sum index abb46b91775..d2257f2690b 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -1,254 +1,56 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= -cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= -cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= -cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= -cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= -cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= -cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= dagger.io/dagger v0.18.8 h1:k3+DvD93Fy5SKijuPqFGvnQIBdJQJdfZtrGp4rqU1Xg= dagger.io/dagger v0.18.8/go.mod h1:FWhniTblKFaUK6emdtL229v9GUOgC5rqIWIzABIdJIc= -github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs= github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg= github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg= -github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= -github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= -github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8= -github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI= github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/drone-runners/drone-runner-docker v1.8.3 h1:uUnC45C1JMSLW+9uy6RoKG5ugzeXWN89pygs9BMLObY= -github.com/drone-runners/drone-runner-docker v1.8.3/go.mod h1:JR3pZeVZKKpkbTajiq0YtAx9WutkODdVKZGNR83kEwE= -github.com/drone/drone-cli v1.8.0 h1:tpp+GPonS87IKMZCGbIoa+zfDwiuJDL3NIC6S7neNrU= -github.com/drone/drone-cli v1.8.0/go.mod h1:zu6/7OpQjWBw/5VG0M3K4iJc6kSoTrjnY7CRLBrGH84= -github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw= -github.com/drone/drone-go v1.7.1/go.mod h1:fxCf9jAnXDZV1yDr0ckTuWd1intvcQwfJmTRpTZ1mXg= -github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0= -github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= -github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/drone/runner-go v1.12.0 h1:zUjDj9ylsJ4n4Mvy4znddq/Z4EBzcUXzTltpzokKtgs= -github.com/drone/runner-go v1.12.0/go.mod h1:vu4pPPYDoeN6vdYQAY01GGGsAIW4aLganJNaa8Fx8zE= -github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= -github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= -github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= -github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby v27.5.1+incompatible h1:/pN59F/t3U7Q4FPzV88nzqf7Fp0qqCSL2KzhZaiKcKw= -github.com/moby/moby v27.5.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4/go.mod h1:cojhOHk1gbMeklOyDP2oKKLftefXoJreOQGOrXk+Z38= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= -github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= @@ -257,18 +59,8 @@ github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTd github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= -go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -285,8 +77,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWS go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= @@ -303,118 +93,27 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= -google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/build/grafana/variant.go b/pkg/build/grafana/variant.go deleted file mode 100644 index 89654722cf2..00000000000 --- a/pkg/build/grafana/variant.go +++ /dev/null @@ -1 +0,0 @@ -package grafana diff --git a/pkg/build/lerna/lerna.go b/pkg/build/lerna/lerna.go deleted file mode 100644 index 048c0dd374d..00000000000 --- a/pkg/build/lerna/lerna.go +++ /dev/null @@ -1,36 +0,0 @@ -package lerna - -import ( - "context" - "fmt" - "os" - "os/exec" - - "github.com/grafana/grafana/pkg/build/fsutil" -) - -func PackFrontendPackages(ctx context.Context, tag, grafanaDir, artifactsDir string) error { - exists, err := fsutil.Exists(artifactsDir) - if err != nil { - return err - } - if exists { - err = os.RemoveAll(artifactsDir) - if err != nil { - return err - } - } - // nolint:gosec - if err = os.MkdirAll(artifactsDir, 0755); err != nil { - return err - } - - // nolint:gosec - cmd := exec.CommandContext(ctx, "yarn", "lerna", "exec", "--no-private", "--", "yarn", "pack", "--out", fmt.Sprintf("../../npm-artifacts/%%s-%v.tgz", tag)) - cmd.Dir = grafanaDir - if output, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), output, err) - } - - return nil -} diff --git a/pkg/build/metrics/publish.go b/pkg/build/metrics/publish.go deleted file mode 100644 index 4e686eb6dea..00000000000 --- a/pkg/build/metrics/publish.go +++ /dev/null @@ -1,71 +0,0 @@ -package metrics - -import ( - "bytes" - "encoding/json" - "fmt" - "log" - "net/http" - "strconv" - "time" -) - -type payload struct { - Name string `json:"name"` - Value int `json:"value"` - Interval int `json:"interval"` - MType string `json:"mtype"` - Time int64 `json:"time"` -} - -// Publish publishes a set of metrics. -func Publish(metrics map[string]string, apiKey string) error { - log.Println("Publishing metrics") - - t := time.Now().Unix() - data := []payload{} - for k, vS := range metrics { - v, err := strconv.Atoi(vS) - if err != nil { - return fmt.Errorf("key %q has value on invalid format: %q", k, vS) - } - data = append(data, payload{ - Name: k, - Value: v, - Interval: 60, - MType: "gauge", - Time: t, - }) - } - - buf := bytes.Buffer{} - enc := json.NewEncoder(&buf) - if err := enc.Encode(data); err != nil { - return err - } - - log.Printf("Publishing metrics to https://:@graphite-us-central1.grafana.net/metrics, JSON: %s", - buf.String()) - - u := fmt.Sprintf("https://6371:%s@graphite-us-central1.grafana.net/metrics", apiKey) - - //nolint:gosec - resp, err := http.Post(u, "application/json", &buf) - if err != nil { - return fmt.Errorf("metrics publishing failed: %w", err) - } - - defer func() { - if err := resp.Body.Close(); err != nil { - log.Println("Error closing HTTP body", err) - } - }() - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("metrics publishing failed with status code %d", resp.StatusCode) - } - - log.Printf("Metrics successfully published") - - return nil -} diff --git a/pkg/build/npm/npm.go b/pkg/build/npm/npm.go deleted file mode 100644 index 04bcc53b322..00000000000 --- a/pkg/build/npm/npm.go +++ /dev/null @@ -1,239 +0,0 @@ -package npm - -import ( - "context" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/grafana/grafana/pkg/build/gcloud/storage" - "github.com/grafana/grafana/pkg/build/lerna" - "github.com/grafana/grafana/pkg/build/versions" -) - -const GrafanaDir = "." -const NpmArtifactDir = "./npm-artifacts" - -// TODO: could this be replaced by `yarn lerna list -p` ? -var packages = []string{ - "@grafana/ui", - "@grafana/data", - "@grafana/runtime", - "@grafana/e2e-selectors", - "@grafana/schema", - "@grafana/flamegraph", -} - -// PublishNpmPackages will publish local NPM packages to NPM registry. -func PublishNpmPackages(ctx context.Context, tag string) error { - version, err := versions.GetVersion(tag) - if err != nil { - return err - } - - log.Printf("Grafana version: %s", version.Version) - - if err := setNpmCredentials(); err != nil { - return err - } - - npmArtifacts, err := storage.ListLocalFiles(NpmArtifactDir) - if err != nil { - return err - } - for _, packedFile := range npmArtifacts { - // nolint:gosec - cmd := exec.CommandContext(ctx, "npm", "publish", packedFile.FullPath, "--tag", version.Channel) - cmd.Dir = GrafanaDir - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err) - } - } - - return updateTag(ctx, version, tag) -} - -// StoreNpmPackages will store local NPM packages in GCS bucket `bucketName`. -func StoreNpmPackages(ctx context.Context, tag, bucketName string) error { - err := lerna.PackFrontendPackages(ctx, tag, GrafanaDir, NpmArtifactDir) - if err != nil { - return err - } - - gcs, err := storage.New() - if err != nil { - return err - } - - bucket := gcs.Bucket(bucketName) - bucketPath := fmt.Sprintf("artifacts/npm/%s/", tag) - if err = gcs.CopyLocalDir(ctx, NpmArtifactDir, bucket, bucketPath, true); err != nil { - return err - } - - log.Print("Successfully stored npm packages!") - return nil -} - -// FetchNpmPackages will store NPM packages stored in GCS bucket `bucketName` on local disk in `frontend.NpmArtifactDir`. -func FetchNpmPackages(ctx context.Context, tag, bucketName string) error { - gcs, err := storage.New() - if err != nil { - return err - } - - bucketPath := fmt.Sprintf("artifacts/npm/%s/", tag) - bucket := gcs.Bucket(bucketName) - err = gcs.DownloadDirectory(ctx, bucket, NpmArtifactDir, storage.FilesFilter{ - Prefix: bucketPath, - FileExts: []string{".tgz"}, - }) - if err != nil { - return err - } - return nil -} - -// updateTag will move next or latest npm dist-tags, if needed. -// -// Note: This function makes the assumption that npm dist-tags has already -// been updated and hence why move of dist-tags not always happens: -// -// If stable the dist-tag latest was used. -// If beta the dist-tag next was used. -// -// Scenarios: -// -// 1. Releasing a newer stable than the current stable -// Latest and next is 9.1.5. -// 9.1.6 is released, latest and next should point to 9.1.6. -// The next dist-tag is moved to point to 9.1.6. -// -// 2. Releasing during an active beta period: -// Latest and next is 9.1.6. -// 9.2.0-beta1 is released, the latest should stay on 9.1.6, next should point to 9.2.0-beta1 -// No move of dist-tags -// 9.1.7 is released, the latest should point to 9.1.7, next should stay to 9.2.0-beta1 -// No move of dist-tags -// Next week 9.2.0-beta2 is released, the latest should point to 9.1.7, next should point to 9.2.0-beta2 -// No move of dist-tags -// In two weeks 9.2.0 stable is released, the latest and next should point to 9.2.0. -// The next dist-tag is moved to point to 9.2.0. -// -// 3. Releasing an older stable than the current stable -// Latest and next is 9.2.0. -// Next 9.1.8 is released, latest should point to 9.2.0, next should point to 9.2.0 -// The latest dist-tag is moved to point to 9.2.0. -func updateTag(ctx context.Context, version *versions.Version, releaseVersion string) error { - if version.Channel != versions.Latest { - return nil - } - - latestStableVersion, err := getLatestStableVersion() - if err != nil { - return err - } - - betaVersion, err := getLatestBetaVersion() - if err != nil { - return err - } - - isLatest, err := versions.IsGreaterThanOrEqual(releaseVersion, latestStableVersion) - if err != nil { - return err - } - - isNewerThanLatestBeta, err := versions.IsGreaterThanOrEqual(releaseVersion, betaVersion) - if err != nil { - return err - } - - for _, pkg := range packages { - if !isLatest { - err = runMoveLatestNPMTagCommand(ctx, pkg, latestStableVersion) - if err != nil { - return err - } - } - - if isLatest && isNewerThanLatestBeta { - err = runMoveNextNPMTagCommand(ctx, pkg, version.Version) - if err != nil { - return err - } - } - } - - return nil -} - -func getLatestStableVersion() (string, error) { - return versions.GetLatestVersion(versions.LatestStableVersionURL) -} - -func getLatestBetaVersion() (string, error) { - return versions.GetLatestVersion(versions.LatestBetaVersionURL) -} - -func runMoveNextNPMTagCommand(ctx context.Context, pkg string, packageVersion string) error { - // nolint:gosec - cmd := exec.CommandContext(ctx, "npm", "dist-tag", "add", fmt.Sprintf("%s@%s", pkg, packageVersion), "next") - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err) - } - - return nil -} - -func runMoveLatestNPMTagCommand(ctx context.Context, pkg string, latestStableVersion string) error { - // nolint:gosec - cmd := exec.CommandContext(ctx, "npm", "dist-tag", "add", fmt.Sprintf("%s@%s", pkg, latestStableVersion), "latest") - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err) - } - - return nil -} - -// setNpmCredentials Creates a .npmrc file in the users home folder and writes the -// necessary credentials to it for publishing packages to the NPM registry. -func setNpmCredentials() error { - npmToken := strings.TrimSpace(os.Getenv("NPM_TOKEN")) - if npmToken == "" { - return fmt.Errorf("npm token is not set") - } - - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to obtain home directory, err: %q", err) - } - - npmPath := filepath.Join(homeDir, ".npmrc") - registry := []byte(fmt.Sprintf("//registry.npmjs.org/:_authToken=%s", npmToken)) - if _, err = os.Stat(npmPath); os.IsNotExist(err) { - // nolint:gosec - f, err := os.Create(npmPath) - if err != nil { - return fmt.Errorf("couldn't create npmrc file, err: %q", err) - } - _, err = f.Write(registry) - if err != nil { - return fmt.Errorf("failed to write to file, err: %q", err) - } - defer func() { - if err := f.Close(); err != nil { - log.Printf("Failed to close file: %s", err.Error()) - } - }() - } else { - err = os.WriteFile(npmPath, registry, 0644) - if err != nil { - return fmt.Errorf("error writing to file, err: %q", err) - } - } - return nil -} diff --git a/pkg/build/packaging/artifacts.go b/pkg/build/packaging/artifacts.go deleted file mode 100644 index b1740b68c5f..00000000000 --- a/pkg/build/packaging/artifacts.go +++ /dev/null @@ -1,136 +0,0 @@ -package packaging - -import ( - "github.com/grafana/grafana/pkg/build/config" -) - -const ReleaseFolder = "release" -const MainFolder = "main" -const EnterpriseSfx = "-enterprise" -const CacheSettings = "Cache-Control:public, max-age=" - -type BuildArtifact struct { - // Distro can be "windows", "darwin", "deb", "rhel", or "linux" - Distro string - Arch string - // Ext is the file extension without the "." - Ext string - Musl bool - RaspberryPi bool - - // URL can be set optionally by another process - // Note: check other repos before determining this to be dead code - URL string -} - -type PublishConfig struct { - config.Config - - Edition config.Edition - ReleaseMode config.ReleaseMode - GrafanaAPIKey string - WhatsNewURL string - ReleaseNotesURL string - DryRun bool - TTL string - SimulateRelease bool -} - -var LinuxArtifacts = []BuildArtifact{ - { - Distro: "linux", - Arch: "arm64", - Ext: "tar.gz", - }, - { - Distro: "deb", - Arch: "amd64", - Ext: "deb", - }, - { - Distro: "rhel", - Arch: "x86_64", - Ext: "rpm", - }, - { - Distro: "linux", - Arch: "amd64", - Ext: "tar.gz", - }, -} - -var DarwinArtifacts = []BuildArtifact{ - { - Distro: "darwin", - Arch: "amd64", - Ext: "tar.gz", - }, -} - -var WindowsArtifacts = []BuildArtifact{ - { - Distro: "windows", - Arch: "amd64", - Ext: "zip", - }, - { - Distro: "windows", - Arch: "amd64", - Ext: "msi", - }, -} - -var ARMArtifacts = []BuildArtifact{ - { - Distro: "deb", - Arch: "arm64", - Ext: "deb", - }, - { - Distro: "rhel", - Arch: "aarch64", - Ext: "rpm", - }, - { - Distro: "deb", - Arch: "armhf", - Ext: "deb", - RaspberryPi: false, - }, - { - Distro: "deb", - Arch: "armhf", - RaspberryPi: true, - Ext: "deb", - }, - { - Distro: "linux", - Arch: "armv6", - Ext: "tar.gz", - }, - { - Distro: "linux", - Arch: "armv7", - Ext: "tar.gz", - }, - { - Distro: "linux", - Arch: "arm64", - Ext: "tar.gz", - }, - { - Distro: "linux", - Arch: "amd64", - Ext: "tar.gz", - }, -} - -func join(a []BuildArtifact, b ...[]BuildArtifact) []BuildArtifact { - for i := range b { - a = append(a, b[i]...) - } - - return a -} - -var ArtifactConfigs = join(LinuxArtifacts, DarwinArtifacts, WindowsArtifacts, ARMArtifacts) diff --git a/pkg/build/validation/docs.go b/pkg/build/validation/docs.go deleted file mode 100644 index 8cd6793833a..00000000000 --- a/pkg/build/validation/docs.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package validation provides functions and types for validating Grafana releases in both the pre-release and post-release phases. -package validation diff --git a/pkg/build/validation/validation.go b/pkg/build/validation/validation.go deleted file mode 100644 index bc01ed189b7..00000000000 --- a/pkg/build/validation/validation.go +++ /dev/null @@ -1,13 +0,0 @@ -package validation - -type ArtifactType int - -const ( - ArtifactTypeDockerHub ArtifactType = iota - ArtifactTypeGCSObject -) - -type Artifact struct { - Type ArtifactType - URL string -} diff --git a/pkg/build/versions/parse.go b/pkg/build/versions/parse.go deleted file mode 100644 index cf53f6230d4..00000000000 --- a/pkg/build/versions/parse.go +++ /dev/null @@ -1,35 +0,0 @@ -package versions - -import ( - "regexp" - "strings" -) - -var semverRegex = regexp.MustCompile(`^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) - -type Semver struct { - Major string - Minor string - Patch string - Prerelease string - BuildMetadata string -} - -func ParseSemver(version string) Semver { - version = strings.TrimPrefix(version, "v") - matches := semverRegex.FindStringSubmatch(version) - results := make(map[string]string) - for i, name := range semverRegex.SubexpNames() { - if i != 0 && name != "" { - results[name] = matches[i] - } - } - - return Semver{ - Major: results["major"], - Minor: results["minor"], - Patch: results["patch"], - Prerelease: results["prerelease"], - BuildMetadata: results["buildmetadata"], - } -} diff --git a/pkg/build/versions/version.go b/pkg/build/versions/version.go deleted file mode 100644 index 240c750bf68..00000000000 --- a/pkg/build/versions/version.go +++ /dev/null @@ -1,167 +0,0 @@ -package versions - -import ( - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "regexp" - "strconv" - - "github.com/Masterminds/semver/v3" -) - -var ( - reGrafanaTag = regexp.MustCompile(`^v(\d+\.\d+\.\d+$)`) - reGrafanaTagPreview = regexp.MustCompile(`^v(\d+\.\d+\.\d+-preview)`) - reGrafanaTagCustom = regexp.MustCompile(`^v(\d+\.\d+\.\d+-\w+)`) - reGrafanaTagSecurity = regexp.MustCompile(`^v(\d+\.\d+\.\d+\+\w+\-\d+)`) -) - -const ( - Latest = "latest" - Next = "next" - Test = "test" - Security = "security" -) - -type Version struct { - Version string - Channel string -} - -type VersionFromAPI struct { - Version string `json:"version"` -} - -type LatestGcomAPI = string - -const ( - LatestStableVersionURL LatestGcomAPI = "https://grafana.com/api/grafana/versions/stable" - LatestBetaVersionURL LatestGcomAPI = "https://grafana.com/api/grafana/versions/beta" -) - -func GetLatestVersion(url LatestGcomAPI) (string, error) { - // nolint:gosec - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Printf("Failed to close body: %s", err.Error()) - } - }() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("server returned non 200 status code: %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - var apiResponse VersionFromAPI - err = json.Unmarshal(body, &apiResponse) - if err != nil { - return "", err - } - - return apiResponse.Version, nil -} - -// IsGreaterThanOrEqual semantically checks whether newVersion is greater than or equal to stableVersion. -func IsGreaterThanOrEqual(newVersion, stableVersion string) (bool, error) { - v1SemVer, err := semver.NewVersion(newVersion) - if err != nil { - return isGreaterThanOrEqualFourDigit(newVersion, stableVersion) - } - - v2SemVer, err := semver.NewVersion(stableVersion) - if err != nil { - return isGreaterThanOrEqualFourDigit(newVersion, stableVersion) - } - - comp := v1SemVer.Compare(v2SemVer) - switch comp { - case -1: - return false, nil - case 1, 0: - return true, nil - default: - return true, fmt.Errorf("unknown comparison value between scemantic versions, err: %q", err) - } -} - -var fourDigitRe = regexp.MustCompile(`(\d+\.\d+\.\d+)\.(\d+)`) - -func parseFourDigit(version string) (*semver.Version, int, error) { - matches := fourDigitRe.FindStringSubmatch(version) - if len(matches) < 2 { - semVer, err := semver.NewVersion(version) - if err != nil { - return nil, 0, err - } - return semVer, 0, nil - } - semVer, err := semver.NewVersion(matches[1]) - if err != nil { - return nil, 0, err - } - i, err := strconv.Atoi(matches[2]) - if err != nil { - return nil, 0, err - } - - return semVer, i, nil -} - -func isGreaterThanOrEqualFourDigit(newVersion, stableVersion string) (bool, error) { - newVersionSemVer, newVersionSemVerNo, err := parseFourDigit(newVersion) - if err != nil { - return false, err - } - - stableVersionSemVer, stableVersionSemVerNo, err := parseFourDigit(stableVersion) - if err != nil { - return false, err - } - - if stableVersionSemVer.Original() != newVersionSemVer.Original() { - return IsGreaterThanOrEqual(newVersionSemVer.Original(), stableVersionSemVer.Original()) - } - - return newVersionSemVerNo >= stableVersionSemVerNo, nil -} - -func GetVersion(tag string) (*Version, error) { - var version Version - switch { - case reGrafanaTag.MatchString(tag): - version = Version{ - Version: reGrafanaTag.FindStringSubmatch(tag)[1], - Channel: Latest, - } - case reGrafanaTagPreview.MatchString(tag): - version = Version{ - Version: reGrafanaTagPreview.FindStringSubmatch(tag)[1], - Channel: Next, - } - case reGrafanaTagCustom.MatchString(tag): - version = Version{ - Version: reGrafanaTagCustom.FindStringSubmatch(tag)[1], - Channel: Test, - } - case reGrafanaTagSecurity.MatchString(tag): - version = Version{ - Version: reGrafanaTagSecurity.FindStringSubmatch(tag)[1], - Channel: Security, - } - default: - return nil, fmt.Errorf("%s not a supported Grafana version, exitting", tag) - } - - return &version, nil -} diff --git a/pkg/build/versions/version_test.go b/pkg/build/versions/version_test.go deleted file mode 100644 index 2ff950e5008..00000000000 --- a/pkg/build/versions/version_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package versions - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsGreaterThanOrEqual(t *testing.T) { - testCases := []struct { - newVersion string - stableVersion string - expected bool - }{ - {newVersion: "9.0.0", stableVersion: "8.0.0", expected: true}, - {newVersion: "6.0.0", stableVersion: "6.0.0", expected: true}, - {newVersion: "7.0.0", stableVersion: "8.0.0", expected: false}, - {newVersion: "8.5.0-preview1", stableVersion: "8.0.0", expected: true}, - {newVersion: "8.5.0", stableVersion: "8.5.0-preview1", expected: true}, - {newVersion: "9.0.0.1", stableVersion: "9.0.0", expected: true}, - {newVersion: "9.0.0.2", stableVersion: "9.0.0.1", expected: true}, - {newVersion: "9.1.0", stableVersion: "9.0.0.1", expected: true}, - {newVersion: "9.1-0-preview1", stableVersion: "9.0.0.1", expected: true}, - {newVersion: "9.0.0.1", stableVersion: "9.0.1.1", expected: false}, - {newVersion: "9.0.1.1", stableVersion: "9.0.0.1", expected: true}, - {newVersion: "9.0.0.1", stableVersion: "9.0.0.1", expected: true}, - {newVersion: "7.0.0.1", stableVersion: "8.0.0", expected: false}, - {newVersion: "9.1-0-preview1", stableVersion: "9.1-0-preview2", expected: false}, - {newVersion: "9.1-0-preview3", stableVersion: "9.1-0-preview2", expected: true}, - } - - for _, tc := range testCases { - name := fmt.Sprintf("newVersion %s greater than or equal stableVersion %s = %v", tc.newVersion, tc.stableVersion, tc.expected) - t.Run(name, func(t *testing.T) { - result, err := IsGreaterThanOrEqual(tc.newVersion, tc.stableVersion) - require.NoError(t, err) - require.Equal(t, tc.expected, result) - }) - } -} - -func TestGetLatestVersion(t *testing.T) { - t.Run("it returns a version", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - response := VersionFromAPI{ - Version: "8.4.0", - } - jsonRes, err := json.Marshal(&response) - require.NoError(t, err) - _, err = w.Write(jsonRes) - require.NoError(t, err) - })) - version, err := GetLatestVersion(server.URL) - require.NoError(t, err) - require.Equal(t, "8.4.0", version) - }) - - t.Run("it handles non 200 responses", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - })) - _, err := GetLatestVersion(server.URL) - require.Error(t, err) - }) -} diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index a26d501eb78..1dbb83c5b34 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,10 +6,10 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.35.0 + golang.org/x/tools v0.36.0 ) require ( - golang.org/x/mod v0.26.0 // indirect + golang.org/x/mod v0.27.0 // indirect golang.org/x/sync v0.16.0 // indirect ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index fb5c52b3f8c..54bfd049a5f 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index c215182743a..55a4d6ee4da 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -9,7 +9,7 @@ require ( github.com/grafana/cog v0.0.37 github.com/grafana/cuetsy v0.1.11 github.com/matryer/is v1.4.1 - golang.org/x/tools v0.35.0 + golang.org/x/tools v0.36.0 ) require ( @@ -46,10 +46,10 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/text v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 8ee24b96514..9afab1f859e 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -98,16 +98,16 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index 3585c68ebf3..d33ccf561aa 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -27,6 +27,7 @@ import ( _ "github.com/robfig/cron/v3" _ "github.com/russellhaering/goxmldsig" _ "github.com/spf13/cobra" // used by the standalone apiserver cli + _ "github.com/spyzhov/ajson" _ "github.com/stretchr/testify/require" _ "gocloud.dev/secrets/awskms" _ "gocloud.dev/secrets/azurekeyvault" @@ -52,5 +53,4 @@ import ( _ "github.com/grafana/e2e" _ "github.com/grafana/gofpdf" _ "github.com/grafana/gomemcache/memcache" - _ "github.com/spyzhov/ajson" ) diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index 088304f6522..2aa1c140a0b 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -42,11 +42,11 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index 709efab9dd3..cd78ae32b2d 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -92,20 +92,20 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index ac034b92ba2..6e294ad34ae 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -118,12 +118,12 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.35.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.235.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index cfdc045debc..a478d60c720 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -353,21 +353,21 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -386,12 +386,12 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -399,8 +399,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/registry/apis/dashboard/legacy/storage.go b/pkg/registry/apis/dashboard/legacy/storage.go index 5c47a5f6cfc..9b4e06ab746 100644 --- a/pkg/registry/apis/dashboard/legacy/storage.go +++ b/pkg/registry/apis/dashboard/legacy/storage.go @@ -3,7 +3,9 @@ package legacy import ( "context" "encoding/json" + "errors" "fmt" + "iter" "net/http" "time" @@ -234,6 +236,12 @@ func (a *dashboardSqlAccess) ListHistory(ctx context.Context, req *resourcepb.Li return a.ListIterator(ctx, req, cb) } +func (a *dashboardSqlAccess) ListModifiedSince(ctx context.Context, key resource.NamespacedResource, sinceRv int64) (int64, iter.Seq2[*resource.ModifiedResource, error]) { + return 0, func(yield func(*resource.ModifiedResource, error) bool) { + yield(nil, errors.New("not implemented")) + } +} + // List implements StorageBackend. func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) { if req.ResourceVersion != 0 { diff --git a/pkg/registry/apis/iam/noopstorage/storage_backend.go b/pkg/registry/apis/iam/noopstorage/storage_backend.go index 200d0b5c485..eb8133ca0b8 100644 --- a/pkg/registry/apis/iam/noopstorage/storage_backend.go +++ b/pkg/registry/apis/iam/noopstorage/storage_backend.go @@ -3,6 +3,7 @@ package noopstorage import ( "context" "errors" + "iter" "net/http" "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -36,6 +37,12 @@ func (c *StorageBackendImpl) ListIterator(context.Context, *resourcepb.ListReque return 0, errNoopStorage } +func (c *StorageBackendImpl) ListModifiedSince(ctx context.Context, key resource.NamespacedResource, sinceRv int64) (int64, iter.Seq2[*resource.ModifiedResource, error]) { + return 0, func(yield func(*resource.ModifiedResource, error) bool) { + yield(nil, errors.New("not implemented")) + } +} + // ReadResource implements resource.StorageBackend. func (c *StorageBackendImpl) ReadResource(_ context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse { return &resource.BackendReadResponse{ diff --git a/pkg/registry/apis/provisioning/controller/health.go b/pkg/registry/apis/provisioning/controller/health.go new file mode 100644 index 00000000000..d52faece56b --- /dev/null +++ b/pkg/registry/apis/provisioning/controller/health.go @@ -0,0 +1,217 @@ +package controller + +import ( + "context" + "fmt" + "time" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" +) + +// StatusPatcher defines the interface for updating repository status +// +//go:generate mockery --name=StatusPatcher +type StatusPatcher interface { + Patch(ctx context.Context, repo *provisioning.Repository, patchOperations ...map[string]interface{}) error +} + +// HealthChecker provides unified health checking for repositories +type HealthChecker struct { + tester RepositoryTester + statusPatcher StatusPatcher +} + +// RepositoryTester defines the interface for testing repository connectivity +// +//go:generate mockery --name=RepositoryTester +type RepositoryTester interface { + TestRepository(ctx context.Context, repo repository.Repository) (*provisioning.TestResults, error) +} + +// NewHealthChecker creates a new health checker +func NewHealthChecker(tester RepositoryTester, statusPatcher StatusPatcher) *HealthChecker { + return &HealthChecker{ + tester: tester, + statusPatcher: statusPatcher, + } +} + +// ShouldCheckHealth determines if a repository health check should be performed +func (hc *HealthChecker) ShouldCheckHealth(repo *provisioning.Repository) bool { + // If the repository has been updated, run the health check + if repo.Generation != repo.Status.ObservedGeneration { + return true + } + + // If the repository has a hook error, don't run the health check + if repo.Status.Health.Error == provisioning.HealthFailureHook { + return false + } + + // Check general timing for health checks + return !hc.hasRecentHealthCheck(repo.Status.Health) +} + +// hasRecentHealthCheck checks if a health check was performed recently (for timing purposes) +func (hc *HealthChecker) hasRecentHealthCheck(healthStatus provisioning.HealthStatus) bool { + if healthStatus.Checked == 0 { + return false // Never checked + } + + age := time.Since(time.UnixMilli(healthStatus.Checked)) + if healthStatus.Healthy { + return age <= time.Minute*5 // Recent if checked within 5 minutes when healthy + } + return age <= time.Minute // Recent if checked within 1 minute when unhealthy +} + +// HasRecentFailure checks if there's a recent failure of a specific type +func (hc *HealthChecker) HasRecentFailure(healthStatus provisioning.HealthStatus, failureType provisioning.HealthFailureType) bool { + if healthStatus.Checked == 0 || healthStatus.Healthy || healthStatus.Error != failureType { + return false // No failure of this type + } + + age := time.Since(time.UnixMilli(healthStatus.Checked)) + return age <= time.Minute // Recent if within 1 minute +} + +// RecordFailureAndUpdate records a failure and updates the repository status +func (hc *HealthChecker) RecordFailure(ctx context.Context, failureType provisioning.HealthFailureType, err error, repo *provisioning.Repository) error { + // Create the health status with the failure + healthStatus := hc.recordFailure(failureType, err) + + // Create patch operation + patchOp := map[string]interface{}{ + "op": "replace", + "path": "/status/health", + "value": healthStatus, + } + + // Apply the patch + return hc.statusPatcher.Patch(ctx, repo, patchOp) +} + +// recordFailure creates a health status with a specific failure +func (hc *HealthChecker) recordFailure(failureType provisioning.HealthFailureType, err error) provisioning.HealthStatus { + return provisioning.HealthStatus{ + Healthy: false, + Error: failureType, + Checked: time.Now().UnixMilli(), + Message: []string{err.Error()}, + } +} + +// hasHealthStatusChanged checks if the health status has meaningfully changed +func (hc *HealthChecker) hasHealthStatusChanged(old, new provisioning.HealthStatus) bool { + if old.Healthy != new.Healthy { + return true + } + + if len(old.Message) != len(new.Message) { + return true + } + + if old.Checked != new.Checked { + return true + } + + for i, oldMsg := range old.Message { + if i >= len(new.Message) || oldMsg != new.Message[i] { + return true + } + } + + return false +} + +// RefreshHealth performs a health check on an existing repository, +// updates its status if needed, and returns the test results +func (hc *HealthChecker) RefreshHealth(ctx context.Context, repo repository.Repository) (*provisioning.TestResults, provisioning.HealthStatus, error) { + cfg := repo.Config() + + // Use health checker to perform comprehensive health check with existing status + testResults, newHealthStatus, err := hc.refreshHealth(ctx, repo, cfg.Status.Health) + if err != nil { + return nil, provisioning.HealthStatus{}, fmt.Errorf("health check failed: %w", err) + } + + // Only update if health status actually changed + if hc.hasHealthStatusChanged(cfg.Status.Health, newHealthStatus) { + patchOp := map[string]interface{}{ + "op": "replace", + "path": "/status/health", + "value": newHealthStatus, + } + + if err := hc.statusPatcher.Patch(ctx, cfg, patchOp); err != nil { + return testResults, newHealthStatus, fmt.Errorf("update health status: %w", err) + } + } + + return testResults, newHealthStatus, nil +} + +// RefreshTimestamp updates the health status timestamp without changing other fields +func (hc *HealthChecker) RefreshTimestamp(ctx context.Context, repo *provisioning.Repository) error { + // Update the timestamp on the existing health status + healthStatus := repo.Status.Health + healthStatus.Checked = time.Now().UnixMilli() + + // Create patch operation + patchOp := map[string]interface{}{ + "op": "replace", + "path": "/status/health", + "value": healthStatus, + } + + // Apply the patch + return hc.statusPatcher.Patch(ctx, repo, patchOp) +} + +// refreshHealth performs a comprehensive health check +// Returns test results, health status, and any error +func (hc *HealthChecker) refreshHealth(ctx context.Context, repo repository.Repository, existingStatus provisioning.HealthStatus) (*provisioning.TestResults, provisioning.HealthStatus, error) { + res, err := hc.tester.TestRepository(ctx, repo) + if err != nil { + return nil, existingStatus, fmt.Errorf("failed to test repository: %w", err) + } + + if !res.Success { + // Build error messages + var errorMsgs []string + for _, testErr := range res.Errors { + if testErr.Detail != "" { + errorMsgs = append(errorMsgs, testErr.Detail) + } + } + + healthStatus := provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().UnixMilli(), + Message: errorMsgs, + } + + return res, healthStatus, nil + } + + // Health check succeeded + now := time.Now() + healthStatus := provisioning.HealthStatus{ + Healthy: true, + Checked: now.UnixMilli(), + } + + // If the existing status is already healthy with no error messages and + // the last check was recent (within 30 seconds), preserve the existing timestamp + // to avoid unnecessary updates + if existingStatus.Healthy && existingStatus.Error == "" && len(existingStatus.Message) == 0 { + lastCheckedTime := time.UnixMilli(existingStatus.Checked) + if now.Sub(lastCheckedTime) < 30*time.Second { + healthStatus.Checked = existingStatus.Checked + } + } + + return res, healthStatus, nil +} diff --git a/pkg/registry/apis/provisioning/controller/health_test.go b/pkg/registry/apis/provisioning/controller/health_test.go new file mode 100644 index 00000000000..a82cfea9076 --- /dev/null +++ b/pkg/registry/apis/provisioning/controller/health_test.go @@ -0,0 +1,592 @@ +package controller + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/controller/mocks" +) + +func TestNewHealthChecker(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + + hc := NewHealthChecker(mockTester, mockPatcher) + + assert.NotNil(t, hc) + assert.Equal(t, mockTester, hc.tester) + assert.Equal(t, mockPatcher, hc.statusPatcher) +} + +func TestShouldCheckHealth(t *testing.T) { + tests := []struct { + name string + repo *provisioning.Repository + expected bool + }{ + { + name: "should check when generation differs", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 2}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().UnixMilli(), + }, + }, + }, + expected: true, + }, + { + name: "should not check when hook error exists", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHook, + Checked: time.Now().UnixMilli(), + }, + }, + }, + expected: false, + }, + { + name: "should not check when health check is recent and healthy", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Minute * 3).UnixMilli(), // 3 minutes ago + }, + }, + }, + expected: false, + }, + { + name: "should check when health check is old and healthy", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Minute * 6).UnixMilli(), // 6 minutes ago + }, + }, + }, + expected: true, + }, + { + name: "should not check when health error is recent", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().Add(-time.Second * 30).UnixMilli(), // 30 seconds ago + }, + }, + }, + expected: false, + }, + { + name: "should check when health error is old", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().Add(-time.Minute * 2).UnixMilli(), // 2 minutes ago + }, + }, + }, + expected: true, + }, + { + name: "should check when never checked", + repo: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + Status: provisioning.RepositoryStatus{ + ObservedGeneration: 1, + Health: provisioning.HealthStatus{ + Checked: 0, // Never checked + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + hc := NewHealthChecker(mockTester, mockPatcher) + + result := hc.ShouldCheckHealth(tt.repo) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestHasRecentFailure(t *testing.T) { + tests := []struct { + name string + healthStatus provisioning.HealthStatus + failureType provisioning.HealthFailureType + expected bool + }{ + { + name: "no recent failure when never checked", + healthStatus: provisioning.HealthStatus{ + Checked: 0, + }, + failureType: provisioning.HealthFailureHook, + expected: false, + }, + { + name: "recent hook failure", + healthStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHook, + Checked: time.Now().Add(-time.Second * 30).UnixMilli(), + }, + failureType: provisioning.HealthFailureHook, + expected: true, + }, + { + name: "old hook failure", + healthStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHook, + Checked: time.Now().Add(-time.Minute * 2).UnixMilli(), + }, + failureType: provisioning.HealthFailureHook, + expected: false, + }, + { + name: "recent health failure", + healthStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().Add(-time.Second * 30).UnixMilli(), + }, + failureType: provisioning.HealthFailureHealth, + expected: true, + }, + { + name: "old health failure", + healthStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().Add(-time.Minute * 2).UnixMilli(), + }, + failureType: provisioning.HealthFailureHealth, + expected: false, + }, + { + name: "wrong failure type", + healthStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHook, + Checked: time.Now().Add(-time.Second * 30).UnixMilli(), + }, + failureType: provisioning.HealthFailureHealth, + expected: false, + }, + { + name: "healthy status with wrong failure type", + healthStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Second * 30).UnixMilli(), + }, + failureType: provisioning.HealthFailureHook, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + hc := NewHealthChecker(mockTester, mockPatcher) + + result := hc.HasRecentFailure(tt.healthStatus, tt.failureType) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestRecordFailure(t *testing.T) { + tests := []struct { + name string + failureType provisioning.HealthFailureType + err error + patchError error + expectError bool + }{ + { + name: "successful hook failure record", + failureType: provisioning.HealthFailureHook, + err: errors.New("hook failed"), + patchError: nil, + expectError: false, + }, + { + name: "successful health failure record", + failureType: provisioning.HealthFailureHealth, + err: errors.New("health check failed"), + patchError: nil, + expectError: false, + }, + { + name: "patch failure", + failureType: provisioning.HealthFailureHook, + err: errors.New("hook failed"), + patchError: errors.New("patch failed"), + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + hc := NewHealthChecker(mockTester, mockPatcher) + + repo := &provisioning.Repository{ + Status: provisioning.RepositoryStatus{ + Health: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + }, + } + + if tt.patchError != nil { + mockPatcher.On("Patch", mock.Anything, repo, mock.AnythingOfType("map[string]interface {}")). + Return(tt.patchError) + } else { + mockPatcher.On("Patch", mock.Anything, repo, mock.AnythingOfType("map[string]interface {}")). + Return(nil). + Run(func(args mock.Arguments) { + patchOp := args[2].(map[string]interface{}) + assert.Equal(t, "replace", patchOp["op"]) + assert.Equal(t, "/status/health", patchOp["path"]) + + healthStatus := patchOp["value"].(provisioning.HealthStatus) + assert.False(t, healthStatus.Healthy) + assert.Equal(t, tt.failureType, healthStatus.Error) + assert.Contains(t, healthStatus.Message, tt.err.Error()) + assert.Greater(t, healthStatus.Checked, int64(0)) + }) + } + + err := hc.RecordFailure(context.Background(), tt.failureType, tt.err, repo) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + mockPatcher.AssertExpectations(t) + }) + } +} + +func TestRecordFailureFunction(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + hc := NewHealthChecker(mockTester, mockPatcher) + + testErr := errors.New("test error") + result := hc.recordFailure(provisioning.HealthFailureHook, testErr) + + assert.False(t, result.Healthy) + assert.Equal(t, provisioning.HealthFailureHook, result.Error) + assert.Equal(t, []string{"test error"}, result.Message) + assert.Greater(t, result.Checked, int64(0)) +} + +func TestRefreshHealth(t *testing.T) { + tests := []struct { + name string + testResult *provisioning.TestResults + testError error + patchError error + existingStatus provisioning.HealthStatus + expectError bool + expectedHealth bool + expectPatch bool + }{ + { + name: "successful health check", + testResult: &provisioning.TestResults{ + Success: true, + Code: 200, + }, + testError: nil, + existingStatus: provisioning.HealthStatus{ + Healthy: false, + Error: provisioning.HealthFailureHealth, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + expectError: false, + expectedHealth: true, + expectPatch: true, + }, + { + name: "failed health check", + testResult: &provisioning.TestResults{ + Success: false, + Code: 500, + Errors: []provisioning.ErrorDetails{ + {Detail: "connection failed"}, + {Detail: "timeout"}, + }, + }, + testError: nil, + existingStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + expectError: false, + expectedHealth: false, + expectPatch: true, + }, + { + name: "test repository error", + testResult: nil, + testError: errors.New("repository test failed"), + existingStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + expectError: true, + expectedHealth: false, + expectPatch: false, + }, + { + name: "no status change - no patch needed (recent check)", + testResult: &provisioning.TestResults{ + Success: true, + Code: 200, + }, + testError: nil, + existingStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-15 * time.Second).UnixMilli(), + }, + expectError: false, + expectedHealth: true, + expectPatch: false, + }, + { + name: "status unchanged but timestamp needs update (old check)", + testResult: &provisioning.TestResults{ + Success: true, + Code: 200, + }, + testError: nil, + existingStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + expectError: false, + expectedHealth: true, + expectPatch: true, + }, + { + name: "patch error", + testResult: &provisioning.TestResults{ + Success: false, + Code: 500, + Errors: []provisioning.ErrorDetails{ + {Detail: "connection failed"}, + }, + }, + testError: nil, + patchError: errors.New("patch failed"), + existingStatus: provisioning.HealthStatus{ + Healthy: true, + Checked: time.Now().Add(-time.Hour).UnixMilli(), + }, + expectError: true, + expectedHealth: false, + expectPatch: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + mockRepo := &mockRepository{ + config: &provisioning.Repository{ + Status: provisioning.RepositoryStatus{ + Health: tt.existingStatus, + }, + }, + } + + hc := NewHealthChecker(mockTester, mockPatcher) + + if tt.testError != nil { + mockTester.On("TestRepository", mock.Anything, mockRepo).Return(tt.testResult, tt.testError) + } else { + mockTester.On("TestRepository", mock.Anything, mockRepo).Return(tt.testResult, nil) + } + + if tt.expectPatch { + if tt.patchError != nil { + mockPatcher.On("Patch", mock.Anything, mockRepo.config, mock.AnythingOfType("map[string]interface {}")). + Return(tt.patchError) + } else { + mockPatcher.On("Patch", mock.Anything, mockRepo.config, mock.AnythingOfType("map[string]interface {}")). + Return(nil). + Run(func(args mock.Arguments) { + patchOp := args[2].(map[string]interface{}) + assert.Equal(t, "replace", patchOp["op"]) + assert.Equal(t, "/status/health", patchOp["path"]) + + healthStatus := patchOp["value"].(provisioning.HealthStatus) + assert.Equal(t, tt.expectedHealth, healthStatus.Healthy) + }) + } + } + + testResult, healthStatus, err := hc.RefreshHealth(context.Background(), mockRepo) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedHealth, healthStatus.Healthy) + if tt.testResult != nil { + assert.Equal(t, tt.testResult, testResult) + } + } + + mockTester.AssertExpectations(t) + if tt.expectPatch { + mockPatcher.AssertExpectations(t) + } + }) + } +} + +func TestHasHealthStatusChanged(t *testing.T) { + tests := []struct { + name string + old provisioning.HealthStatus + new provisioning.HealthStatus + expected bool + }{ + { + name: "healthy status changed", + old: provisioning.HealthStatus{ + Healthy: true, + Message: []string{}, + }, + new: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error"}, + }, + expected: true, + }, + { + name: "different message count", + old: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error1"}, + }, + new: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error1", "error2"}, + }, + expected: true, + }, + { + name: "different messages", + old: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error1"}, + }, + new: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error2"}, + }, + expected: true, + }, + { + name: "no change", + old: provisioning.HealthStatus{ + Healthy: true, + Message: []string{}, + }, + new: provisioning.HealthStatus{ + Healthy: true, + Message: []string{}, + }, + expected: false, + }, + { + name: "same messages", + old: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error1", "error2"}, + }, + new: provisioning.HealthStatus{ + Healthy: false, + Message: []string{"error1", "error2"}, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTester := mocks.NewRepositoryTester(t) + mockPatcher := mocks.NewStatusPatcher(t) + hc := NewHealthChecker(mockTester, mockPatcher) + + result := hc.hasHealthStatusChanged(tt.old, tt.new) + assert.Equal(t, tt.expected, result) + }) + } +} + +// mockRepository implements repository.Repository interface for testing +type mockRepository struct { + config *provisioning.Repository +} + +func (m *mockRepository) Config() *provisioning.Repository { + return m.config +} + +func (m *mockRepository) Validate() field.ErrorList { + return nil +} + +func (m *mockRepository) Test(ctx context.Context) (*provisioning.TestResults, error) { + return &provisioning.TestResults{Success: true}, nil +} diff --git a/pkg/registry/apis/provisioning/controller/mocks/RepositoryTester.go b/pkg/registry/apis/provisioning/controller/mocks/RepositoryTester.go new file mode 100644 index 00000000000..99b52c7e16f --- /dev/null +++ b/pkg/registry/apis/provisioning/controller/mocks/RepositoryTester.go @@ -0,0 +1,62 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" + + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// RepositoryTester is an autogenerated mock type for the RepositoryTester type +type RepositoryTester struct { + mock.Mock +} + +// TestRepository provides a mock function with given fields: ctx, repo +func (_m *RepositoryTester) TestRepository(ctx context.Context, repo repository.Repository) (*v0alpha1.TestResults, error) { + ret := _m.Called(ctx, repo) + + if len(ret) == 0 { + panic("no return value specified for TestRepository") + } + + var r0 *v0alpha1.TestResults + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, repository.Repository) (*v0alpha1.TestResults, error)); ok { + return rf(ctx, repo) + } + if rf, ok := ret.Get(0).(func(context.Context, repository.Repository) *v0alpha1.TestResults); ok { + r0 = rf(ctx, repo) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.TestResults) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, repository.Repository) error); ok { + r1 = rf(ctx, repo) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRepositoryTester creates a new instance of RepositoryTester. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRepositoryTester(t interface { + mock.TestingT + Cleanup(func()) +}) *RepositoryTester { + mock := &RepositoryTester{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/controller/mocks/StatusPatcher.go b/pkg/registry/apis/provisioning/controller/mocks/StatusPatcher.go new file mode 100644 index 00000000000..7cfe0b737bf --- /dev/null +++ b/pkg/registry/apis/provisioning/controller/mocks/StatusPatcher.go @@ -0,0 +1,55 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// StatusPatcher is an autogenerated mock type for the StatusPatcher type +type StatusPatcher struct { + mock.Mock +} + +// Patch provides a mock function with given fields: ctx, repo, patchOperations +func (_m *StatusPatcher) Patch(ctx context.Context, repo *v0alpha1.Repository, patchOperations ...map[string]interface{}) error { + _va := make([]interface{}, len(patchOperations)) + for _i := range patchOperations { + _va[_i] = patchOperations[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, repo) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Patch") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Repository, ...map[string]interface{}) error); ok { + r0 = rf(ctx, repo, patchOperations...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewStatusPatcher creates a new instance of StatusPatcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStatusPatcher(t interface { + mock.TestingT + Cleanup(func()) +}) *StatusPatcher { + mock := &StatusPatcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/controller/repository.go b/pkg/registry/apis/provisioning/controller/repository.go index 15107e781d7..cf775a08972 100644 --- a/pkg/registry/apis/provisioning/controller/repository.go +++ b/pkg/registry/apis/provisioning/controller/repository.go @@ -2,7 +2,6 @@ package controller import ( "context" - "encoding/json" "errors" "fmt" "time" @@ -35,10 +34,6 @@ type RepoGetter interface { AsRepository(ctx context.Context, cfg *provisioning.Repository) (repository.Repository, error) } -type RepositoryTester interface { - TestRepository(ctx context.Context, repo repository.Repository) (*provisioning.TestResults, error) -} - const loggerName = "provisioning-repository-controller" const ( @@ -61,12 +56,13 @@ type RepositoryController struct { logger logging.Logger dualwrite dualwrite.Service - jobs jobs.Queue - finalizer *finalizer + jobs jobs.Queue + finalizer *finalizer + statusPatcher StatusPatcher // Converts config to instance - repoGetter RepoGetter - tester RepositoryTester + repoGetter RepoGetter + healthChecker *HealthChecker // To allow injection for testing. processFn func(item *queueItem) error enqueueRepository func(obj any) @@ -86,6 +82,8 @@ func NewRepositoryController( tester RepositoryTester, jobs jobs.Queue, dualwrite dualwrite.Service, + healthChecker *HealthChecker, + statusPatcher StatusPatcher, ) (*RepositoryController, error) { rc := &RepositoryController{ client: provisioningClient, @@ -98,13 +96,14 @@ func NewRepositoryController( Name: "provisioningRepositoryController", }, ), - repoGetter: repoGetter, - parsers: parsers, + repoGetter: repoGetter, + healthChecker: healthChecker, + statusPatcher: statusPatcher, + parsers: parsers, finalizer: &finalizer{ lister: resourceLister, clientFactory: clients, }, - tester: tester, jobs: jobs, logger: logging.DefaultLogger.With("logger", loggerName), dualwrite: dualwrite, @@ -247,47 +246,6 @@ func (rc *RepositoryController) handleDelete(ctx context.Context, obj *provision return nil } -func (rc *RepositoryController) shouldCheckHealth(obj *provisioning.Repository) bool { - if obj.Status.Health.Checked == 0 || obj.Generation != obj.Status.ObservedGeneration { - return true - } - - healthAge := time.Since(time.UnixMilli(obj.Status.Health.Checked)) - if obj.Status.Health.Healthy { - return healthAge > time.Minute*5 // when healthy, check every 5 mins - } - - return healthAge > time.Minute // otherwise within a minute -} - -func (rc *RepositoryController) runHealthCheck(ctx context.Context, repo repository.Repository) provisioning.HealthStatus { - logger := logging.FromContext(ctx) - logger.Info("running health check") - res, err := rc.tester.TestRepository(ctx, repo) - if err != nil { - res = &provisioning.TestResults{ - Success: false, - Errors: []provisioning.ErrorDetails{{ - Detail: fmt.Sprintf("error running test repository: %s", err.Error()), - }}, - } - } - - healthStatus := provisioning.HealthStatus{ - Healthy: res.Success, - Checked: time.Now().UnixMilli(), - } - for _, err := range res.Errors { - if err.Detail != "" { - healthStatus.Message = append(healthStatus.Message, err.Detail) - } - } - - logger.Info("health check completed", "status", healthStatus) - - return healthStatus -} - func (rc *RepositoryController) shouldResync(obj *provisioning.Repository) bool { // don't trigger resync if a sync was never started if obj.Status.Sync.Finished == 0 && obj.Status.Sync.State == "" { @@ -309,7 +267,7 @@ func (rc *RepositoryController) shouldResync(obj *provisioning.Repository) bool func (rc *RepositoryController) runHooks(ctx context.Context, repo repository.Repository, obj *provisioning.Repository) ([]map[string]interface{}, error) { logger := logging.FromContext(ctx) hooks, _ := repo.(repository.Hooks) - if hooks == nil || obj.Generation == obj.Status.ObservedGeneration { + if hooks == nil { return nil, nil } @@ -400,26 +358,7 @@ func (rc *RepositoryController) addSyncJob(ctx context.Context, obj *provisionin return nil } -func (rc *RepositoryController) patchStatus(ctx context.Context, obj *provisioning.Repository, patchOperations []map[string]interface{}) error { - if len(patchOperations) == 0 { - return nil - } - - patch, err := json.Marshal(patchOperations) - if err != nil { - return fmt.Errorf("error encoding status patch: %w", err) - } - - _, err = rc.client.Repositories(obj.GetNamespace()). - Patch(ctx, obj.Name, types.JSONPatchType, patch, v1.PatchOptions{}, "status") - if err != nil { - return fmt.Errorf("error applying status patch: %w", err) - } - - return nil -} - -func (rc *RepositoryController) determineSyncStatus(obj *provisioning.Repository, syncOptions *provisioning.SyncJobOptions) *provisioning.SyncStatus { +func (rc *RepositoryController) determineSyncStatus(obj *provisioning.Repository, syncOptions *provisioning.SyncJobOptions, healthStatus provisioning.HealthStatus) *provisioning.SyncStatus { const unhealthyMessage = "Repository is unhealthy" hasUnhealthyMessage := len(obj.Status.Sync.Message) > 0 && obj.Status.Sync.Message[0] == unhealthyMessage @@ -430,13 +369,13 @@ func (rc *RepositoryController) determineSyncStatus(obj *provisioning.Repository LastRef: obj.Status.Sync.LastRef, Started: time.Now().UnixMilli(), } - case obj.Status.Health.Healthy && hasUnhealthyMessage: // if the repository is healthy and the message is set, clear it + case healthStatus.Healthy && hasUnhealthyMessage: // if the repository is healthy and the message is set, clear it // FIXME: is this the clearest way to do this? Should we introduce another status or way of way of handling more // specific errors? return &provisioning.SyncStatus{ LastRef: obj.Status.Sync.LastRef, } - case !obj.Status.Health.Healthy && !hasUnhealthyMessage: // if the repository is unhealthy and the message is not already set, set it + case !healthStatus.Healthy && !hasUnhealthyMessage: // if the repository is unhealthy and the message is not already set, set it return &provisioning.SyncStatus{ State: provisioning.JobStateError, Message: []string{unhealthyMessage}, @@ -450,6 +389,7 @@ func (rc *RepositoryController) determineSyncStatus(obj *provisioning.Repository //nolint:gocyclo func (rc *RepositoryController) process(item *queueItem) error { logger := rc.logger.With("key", item.key) + ctx := logging.Context(context.Background(), logger) namespace, name, err := cache.SplitMetaNamespaceKey(item.key) if err != nil { @@ -464,7 +404,7 @@ func (rc *RepositoryController) process(item *queueItem) error { return err } - ctx, _, err := identity.WithProvisioningIdentity(context.Background(), namespace) + ctx, _, err = identity.WithProvisioningIdentity(ctx, namespace) if err != nil { return err } @@ -476,7 +416,7 @@ func (rc *RepositoryController) process(item *queueItem) error { } shouldResync := rc.shouldResync(obj) - shouldCheckHealth := rc.shouldCheckHealth(obj) + shouldCheckHealth := rc.healthChecker.ShouldCheckHealth(obj) hasSpecChanged := obj.Generation != obj.Status.ObservedGeneration patchOperations := []map[string]interface{}{} @@ -503,28 +443,27 @@ func (rc *RepositoryController) process(item *queueItem) error { return fmt.Errorf("unable to create repository from configuration: %w", err) } - healthStatus := obj.Status.Health - if shouldCheckHealth { - healthStatus = rc.runHealthCheck(ctx, repo) - patchOperations = append(patchOperations, map[string]interface{}{ - "op": "replace", - "path": "/status/health", - "value": healthStatus, - }) + // Handle hooks - may return early if hooks fail + hookOps, shouldContinue, err := rc.processHooks(ctx, repo, obj) + if err != nil { + return fmt.Errorf("process hooks: %w", err) + } + if !shouldContinue { + return nil // Hook handling already updated status and returned early + } + if len(hookOps) > 0 { + patchOperations = append(patchOperations, hookOps...) } - // Run hooks - hookOps, err := rc.runHooks(ctx, repo, obj) - switch { - case err != nil: - return err - case len(hookOps) > 0: - patchOperations = append(patchOperations, hookOps...) + // Handle health checks using the health checker + _, healthStatus, err := rc.healthChecker.RefreshHealth(ctx, repo) + if err != nil { + return fmt.Errorf("update health status: %w", err) } // determine the sync strategy and sync status to apply syncOptions := rc.determineSyncStrategy(ctx, obj, repo, shouldResync, healthStatus) - if syncStatus := rc.determineSyncStatus(obj, syncOptions); syncStatus != nil { + if syncStatus := rc.determineSyncStatus(obj, syncOptions, healthStatus); syncStatus != nil { patchOperations = append(patchOperations, map[string]interface{}{ "op": "replace", "path": "/status/sync", @@ -533,7 +472,7 @@ func (rc *RepositoryController) process(item *queueItem) error { } // Apply all patch operations - if err := rc.patchStatus(ctx, obj, patchOperations); err != nil { + if err := rc.statusPatcher.Patch(ctx, obj, patchOperations...); err != nil { return err } @@ -546,3 +485,29 @@ func (rc *RepositoryController) process(item *queueItem) error { return nil } + +// processHooks handles hook execution with intelligent retry logic +// Returns hook operations, whether processing should continue, and any error +func (rc *RepositoryController) processHooks(ctx context.Context, repo repository.Repository, obj *provisioning.Repository) ([]map[string]interface{}, bool, error) { + shouldRunHooks := obj.Generation != obj.Status.ObservedGeneration + + // Skip hooks if status already indicates recent hook failure to avoid infinite retry + if shouldRunHooks && rc.healthChecker.HasRecentFailure(obj.Status.Health, provisioning.HealthFailureHook) { + shouldRunHooks = false + } + + if !shouldRunHooks { + return nil, true, nil + } + + hookOps, err := rc.runHooks(ctx, repo, obj) + if err != nil { + if err := rc.healthChecker.RecordFailure(ctx, provisioning.HealthFailureHook, err, obj); err != nil { + return nil, false, fmt.Errorf("update status after hook failure: %w", err) + } + + return nil, false, err + } + + return hookOps, true, nil +} diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index 8fe1a933e37..4ae0c3eaf2d 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -58,39 +58,54 @@ func (*filesConnector) NewConnectOptions() (runtime.Object, bool, string) { return nil, true, "" // true adds the {path} component } +// For GET operations, allow even unhealthy repositories +// For write operations (POST, PUT, DELETE), require healthy repository +func (c *filesConnector) getRepo(ctx context.Context, method, name string) (repository.Repository, error) { + if method == http.MethodGet { + return c.getter.GetRepository(ctx, name) + } else { + return c.getter.GetHealthyRepository(ctx, name) + } +} + // TODO: document the synchronous write and delete on the API Spec func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { logger := logging.FromContext(ctx).With("logger", "files-connector", "repository_name", name) ctx = logging.Context(ctx, logger) - repo, err := c.getter.GetHealthyRepository(ctx, name) - if err != nil { - logger.Debug("failed to find repository", "error", err) - return nil, err - } - - readWriter, ok := repo.(repository.ReaderWriter) - if !ok { - return nil, apierrors.NewBadRequest("repository does not support read-writing") - } - - parser, err := c.parsers.GetParser(ctx, readWriter) - if err != nil { - return nil, fmt.Errorf("failed to get parser: %w", err) - } - - clients, err := c.clients.Clients(ctx, repo.Config().Namespace) - if err != nil { - return nil, fmt.Errorf("failed to get clients: %w", err) - } - - folderClient, err := clients.Folder() - if err != nil { - return nil, fmt.Errorf("failed to get folder client: %w", err) - } - folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree()) - dualReadWriter := resources.NewDualReadWriter(readWriter, parser, folders, c.access) return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + repo, err := c.getRepo(ctx, r.Method, name) + if err != nil { + logger.Debug("failed to find repository", "error", err) + responder.Error(err) + return + } + + readWriter, ok := repo.(repository.ReaderWriter) + if !ok { + responder.Error(apierrors.NewBadRequest("repository does not support read-writing")) + return + } + + parser, err := c.parsers.GetParser(ctx, readWriter) + if err != nil { + responder.Error(fmt.Errorf("failed to get parser: %w", err)) + return + } + + clients, err := c.clients.Clients(ctx, repo.Config().Namespace) + if err != nil { + responder.Error(fmt.Errorf("failed to get clients: %w", err)) + return + } + + folderClient, err := clients.Folder() + if err != nil { + responder.Error(fmt.Errorf("failed to get folder client: %w", err)) + return + } + folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree()) + dualReadWriter := resources.NewDualReadWriter(readWriter, parser, folders, c.access) query := r.URL.Query() opts := resources.DualWriteOptions{ Ref: query.Get("ref"), diff --git a/pkg/registry/apis/provisioning/history.go b/pkg/registry/apis/provisioning/history.go index 7770e44fe6d..bf0bb5b35dc 100644 --- a/pkg/registry/apis/provisioning/history.go +++ b/pkg/registry/apis/provisioning/history.go @@ -54,7 +54,7 @@ func (h *historySubresource) NewConnectOptions() (runtime.Object, bool, string) func (h *historySubresource) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { logger := logging.FromContext(ctx).With("logger", "history-subresource") ctx = logging.Context(ctx, logger) - repo, err := h.repoGetter.GetHealthyRepository(ctx, name) + repo, err := h.repoGetter.GetRepository(ctx, name) if err != nil { logger.Debug("failed to find repository", "error", err) return nil, err diff --git a/pkg/registry/apis/provisioning/jobs.go b/pkg/registry/apis/provisioning/jobs.go index e563cc94fa9..8c352bbeee1 100644 --- a/pkg/registry/apis/provisioning/jobs.go +++ b/pkg/registry/apis/provisioning/jobs.go @@ -49,17 +49,17 @@ func (c *jobsConnector) Connect( opts runtime.Object, responder rest.Responder, ) (http.Handler, error) { - repo, err := c.repoGetter.GetHealthyRepository(ctx, name) - if err != nil { - return nil, err - } - cfg := repo.Config() - return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx = r.Context() prefix := fmt.Sprintf("/%s/jobs/", name) idx := strings.Index(r.URL.Path, prefix) if r.Method == http.MethodGet { + // GET operations: allow even for unhealthy repositories + repo, err := c.repoGetter.GetRepository(ctx, name) + if err != nil { + responder.Error(err) + return + } + cfg := repo.Config() if idx > 0 { jobUID := r.URL.Path[idx+len(prefix):] if !ValidUUID(jobUID) { @@ -82,6 +82,15 @@ func (c *jobsConnector) Connect( responder.Object(http.StatusOK, recent) return } + + // POST operations: require healthy repository + repo, err := c.repoGetter.GetHealthyRepository(ctx, name) + if err != nil { + responder.Error(err) + return + } + cfg := repo.Config() + if idx > 0 { responder.Error(apierrors.NewBadRequest("can not post to a job UID")) return diff --git a/pkg/registry/apis/provisioning/refs.go b/pkg/registry/apis/provisioning/refs.go index 8eed640fdd1..1c37e35fd1e 100644 --- a/pkg/registry/apis/provisioning/refs.go +++ b/pkg/registry/apis/provisioning/refs.go @@ -46,7 +46,7 @@ func (*refsConnector) NewConnectOptions() (runtime.Object, bool, string) { func (c *refsConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { logger := logging.FromContext(ctx).With("logger", "refs-connector", "repository_name", name) ctx = logging.Context(ctx, logger) - repo, err := c.getter.GetHealthyRepository(ctx, name) + repo, err := c.getter.GetRepository(ctx, name) if err != nil { logger.Debug("failed to find repository", "error", err) return nil, err diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 79559bb2238..d4bd0d21825 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" "net/url" - "path/filepath" "strings" "time" @@ -95,14 +94,12 @@ type APIBuilder struct { repositoryResources resources.RepositoryResourcesFactory clients resources.ClientFactory ghFactory *github.Factory - clonedir string // where repo clones are managed jobs interface { jobs.Queue jobs.Store } jobHistoryConfig *JobHistoryConfig jobHistory jobs.History - tester *RepositoryTester resourceLister resources.ResourceLister repositoryLister listers.RepositoryLister legacyMigrator legacy.LegacyMigrator @@ -114,6 +111,7 @@ type APIBuilder struct { access authlib.AccessChecker mutators []controller.Mutator statusPatcher *controller.RepositoryStatusPatcher + healthChecker *controller.HealthChecker // Extras provides additional functionality to the API. extras []Extra availableRepositoryTypes map[provisioning.RepositoryType]bool @@ -126,7 +124,6 @@ func NewAPIBuilder( local *local.LocalFolderResolver, features featuremgmt.FeatureToggles, unified resource.ResourceClient, - clonedir string, // where repo clones are managed configProvider apiserver.RestConfigProvider, ghFactory *github.Factory, legacyMigrator legacy.LegacyMigrator, @@ -158,7 +155,6 @@ func NewAPIBuilder( clients: clients, parsers: parsers, repositoryResources: resources.NewRepositoryResourcesFactory(parsers, clients, resourceLister), - clonedir: clonedir, resourceLister: resourceLister, legacyMigrator: legacyMigrator, storageStatus: storageStatus, @@ -251,7 +247,6 @@ func RegisterAPIService( } builder := NewAPIBuilder(folderResolver, features, client, - filepath.Join(cfg.DataPath, "clone"), // where repositories are cloned (temporarialy for now) configProvider, ghFactory, legacyMigrator, storageStatus, usageStats, @@ -286,7 +281,6 @@ func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer { Namespace: a.GetNamespace(), Subresource: a.GetSubresource(), }) - if err != nil { return authorizer.DecisionDeny, "failed to perform authorization", err } @@ -407,6 +401,10 @@ func (b *APIBuilder) GetStatusPatcher() *controller.RepositoryStatusPatcher { return b.statusPatcher } +func (b *APIBuilder) GetHealthChecker() *controller.HealthChecker { + return b.healthChecker +} + func (b *APIBuilder) InstallSchema(scheme *runtime.Scheme) error { err := provisioning.AddToScheme(scheme) if err != nil { @@ -474,10 +472,7 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI storage[provisioning.RepositoryResourceInfo.StoragePath("status")] = repositoryStatusStorage // TODO: Add some logic so that the connectors can registered themselves and we don't have logic all over the place - // TODO: Do not set private fields directly, use factory methods. - storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = &testConnector{ - getter: b, - } + storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = NewTestConnector(b, &repository.Tester{}, b) storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, b.access) storage[provisioning.RepositoryResourceInfo.StoragePath("refs")] = NewRefsConnector(b) storage[provisioning.RepositoryResourceInfo.StoragePath("resources")] = &listConnector{ @@ -622,7 +617,7 @@ func (b *APIBuilder) verifyAgaintsExistingRepositories(cfg *provisioning.Reposit } else { // Folder sync cannot be created if an instance repository exists for _, v := range all { - if v.Spec.Sync.Target == provisioning.SyncTargetTypeInstance { + if v.Spec.Sync.Target == provisioning.SyncTargetTypeInstance && v.Name != cfg.Name { return field.Forbidden(field.NewPath("spec", "sync", "target"), "Cannot create folder repository when instance repository exists: "+v.Name) } @@ -658,14 +653,11 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH jobInformer := sharedInformerFactory.Provisioning().V0alpha1().Jobs() b.client = c.ProvisioningV0alpha1() - - // We do not have a local client until *GetPostStartHooks*, so we can delay init for some - b.tester = &RepositoryTester{ - client: b.GetClient(), - } - b.repositoryLister = repoInformer.Lister() + b.statusPatcher = controller.NewRepositoryStatusPatcher(b.GetClient()) + b.healthChecker = controller.NewHealthChecker(&repository.Tester{}, b.statusPatcher) + // if running solely CRUD, skip the rest of the setup if b.localFileResolver == nil { return nil @@ -692,7 +684,6 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH stageIfPossible, ) - b.statusPatcher = controller.NewRepositoryStatusPatcher(b.GetClient()) syncer := sync.NewSyncer(sync.Compare, sync.FullSync, sync.IncrementalSync) syncWorker := sync.NewSyncWorker( b.clients, @@ -783,6 +774,8 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH &repository.Tester{}, b.jobs, b.storageStatus, + b.GetHealthChecker(), + b.statusPatcher, ) if err != nil { return err @@ -1255,49 +1248,20 @@ func (b *APIBuilder) GetRepository(ctx context.Context, name string) (repository return b.asRepository(ctx, obj) } -func timeSince(when int64) time.Duration { - return time.Duration(time.Now().UnixMilli()-when) * time.Millisecond -} - func (b *APIBuilder) GetHealthyRepository(ctx context.Context, name string) (repository.Repository, error) { repo, err := b.GetRepository(ctx, name) if err != nil { return nil, err } + status := repo.Config().Status.Health if !status.Healthy { - if timeSince(status.Checked) > time.Second*25 { - ctx, _, err = identity.WithProvisioningIdentity(ctx, repo.Config().Namespace) - if err != nil { - return nil, err // The status - } - - // Check health again - s, err := repository.TestRepository(ctx, repo) - if err != nil { - return nil, err // The status - } - - // Write and return the repo with current status - cfg, _ := b.tester.UpdateHealthStatus(ctx, repo.Config(), s) - if cfg != nil { - status = cfg.Status.Health - if cfg.Status.Health.Healthy { - status = cfg.Status.Health - repo, err = b.AsRepository(ctx, cfg) - if err != nil { - return nil, err - } - } - } - } - if !status.Healthy { - return nil, &apierrors.StatusError{ErrStatus: metav1.Status{ - Code: http.StatusFailedDependency, - Message: "The repository configuration is not healthy", - }} - } + return nil, &apierrors.StatusError{ErrStatus: metav1.Status{ + Code: http.StatusFailedDependency, + Message: "The repository configuration is not healthy", + }} } + return repo, err } diff --git a/pkg/registry/apis/provisioning/test.go b/pkg/registry/apis/provisioning/test.go index 9abb1d38a7f..c180d70c7c5 100644 --- a/pkg/registry/apis/provisioning/test.go +++ b/pkg/registry/apis/provisioning/test.go @@ -8,18 +8,37 @@ import ( "reflect" "time" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" - client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/controller" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" ) +type StatusPatcherProvider interface { + GetStatusPatcher() *controller.RepositoryStatusPatcher +} + +type HealthCheckerProvider interface { + GetHealthChecker() *controller.HealthChecker +} + type testConnector struct { - getter RepoGetter + getter RepoGetter + tester controller.RepositoryTester + healthProvider HealthCheckerProvider +} + +func NewTestConnector(getter RepoGetter, tester controller.RepositoryTester, healthProvider HealthCheckerProvider) *testConnector { + return &testConnector{ + getter: getter, + tester: tester, + healthProvider: healthProvider, + } } func (*testConnector) New() runtime.Object { @@ -94,57 +113,71 @@ func (s *testConnector) Connect(ctx context.Context, name string, opts runtime.O } } + var rsp *provisioning.TestResults if repo == nil { + healthChecker := s.healthProvider.GetHealthChecker() + if healthChecker == nil { + // Use precondition failed for when health checker is not ready yet + responder.Error(&errors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusPreconditionFailed, + Reason: metav1.StatusReason("PreconditionFailed"), + Message: "health checker not initialized yet, please try again", + }, + }) + return + } + + // Testing existing repository - get it and update health repo, err = s.getter.GetRepository(ctx, name) if err != nil { responder.Error(err) return } + + // If the last error was not a health check error or empty, return precondition failed + health := repo.Config().Status.Health + if health.Error != provisioning.HealthFailureHealth && health.Error != "" { + rsp = &provisioning.TestResults{ + Success: false, + Code: http.StatusPreconditionFailed, + Errors: func() []provisioning.ErrorDetails { + var errs []provisioning.ErrorDetails + for _, msg := range health.Message { + errs = append(errs, provisioning.ErrorDetails{Detail: msg}) + } + return errs + }(), + } + + if err := healthChecker.RefreshTimestamp(ctx, repo.Config()); err != nil { + responder.Error(err) + return + } + + responder.Object(rsp.Code, rsp) + return + } + + rsp, _, err = healthChecker.RefreshHealth(ctx, repo) + if err != nil { + responder.Error(err) + return + } + } else { + // Testing temporary repository - just run test without status update + rsp, err = s.tester.TestRepository(ctx, repo) + if err != nil { + responder.Error(err) + return + } } - // Only call test if field validation passes - rsp, err := repository.TestRepository(ctx, repo) - if err != nil { - responder.Error(err) - return - } responder.Object(rsp.Code, rsp) }), 30*time.Second), nil } -// TODO: Move tester to a more suitable location out of the connector. -type RepositoryTester struct { - // Repository+Jobs - client client.ProvisioningV0alpha1Interface -} - -// This function will check if the repository is configured and functioning as expected -func (t *RepositoryTester) UpdateHealthStatus(ctx context.Context, cfg *provisioning.Repository, res *provisioning.TestResults) (*provisioning.Repository, error) { - if res == nil { - res = &provisioning.TestResults{ - Success: false, - Errors: []provisioning.ErrorDetails{{ - Detail: "missing health status", - }}, - } - } - - repo := cfg.DeepCopy() - repo.Status.Health = provisioning.HealthStatus{ - Healthy: res.Success, - Checked: time.Now().UnixMilli(), - } - for _, err := range res.Errors { - if err.Detail != "" { - repo.Status.Health.Message = append(repo.Status.Health.Message, err.Detail) - } - } - - _, err := t.client.Repositories(repo.GetNamespace()). - UpdateStatus(ctx, repo, metav1.UpdateOptions{}) - return repo, err -} - var ( _ rest.Storage = (*testConnector)(nil) _ rest.Connecter = (*testConnector)(nil) diff --git a/pkg/registry/apis/provisioning/webhooks/register.go b/pkg/registry/apis/provisioning/webhooks/register.go index 7de867fed9a..7fa6550f15e 100644 --- a/pkg/registry/apis/provisioning/webhooks/register.go +++ b/pkg/registry/apis/provisioning/webhooks/register.go @@ -3,9 +3,12 @@ package webhooks import ( "context" "fmt" - "path/filepath" "strings" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kube-openapi/pkg/spec3" + "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning" @@ -22,9 +25,6 @@ import ( "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/kube-openapi/pkg/spec3" ) // WebhookExtraBuilder is a function that returns an ExtraBuilder. @@ -34,6 +34,16 @@ type WebhookExtraBuilder struct { provisioningapis.ExtraBuilder } +// HACK: assume that the URL is public if it starts with "https://" and does not contain any local IP ranges +func isPublicURL(url string) bool { + return strings.HasPrefix(url, "https://") && + !strings.Contains(url, "localhost") && + !strings.HasPrefix(url, "https://127.") && + !strings.HasPrefix(url, "https://192.") && + !strings.HasPrefix(url, "https://10.") && + !strings.HasPrefix(url, "https://172.16.") +} + func ProvideWebhooks( cfg *setting.Cfg, features featuremgmt.FeatureToggles, @@ -48,8 +58,8 @@ func ProvideWebhooks( urlProvider := func(_ string) string { return cfg.AppURL } - // HACK: Assume is only public if it is HTTPS - isPublic := strings.HasPrefix(urlProvider(""), "https://") + + isPublic := isPublicURL(urlProvider("")) clients := resources.NewClientFactory(configProvider) parsers := resources.NewParserFactory(clients) @@ -71,9 +81,9 @@ func ProvideWebhooks( urlProvider, repositorySecrets, ghFactory, - filepath.Join(cfg.DataPath, "clone"), parsers, []jobs.Worker{pullRequestWorker}, + isPublic, // Pass the public URL flag ) }, } @@ -87,9 +97,9 @@ type WebhookExtra struct { urlProvider func(namespace string) string secrets secrets.RepositorySecrets ghFactory *github.Factory - clonedir string parsers resources.ParserFactory workers []jobs.Worker + isPublic bool // Flag to determine if webhook-enhanced repositories should be created } func NewWebhookExtra( @@ -98,9 +108,9 @@ func NewWebhookExtra( urlProvider func(namespace string) string, secrets secrets.RepositorySecrets, ghFactory *github.Factory, - clonedir string, parsers resources.ParserFactory, workers []jobs.Worker, + isPublic bool, ) *WebhookExtra { return &WebhookExtra{ render: render, @@ -108,9 +118,9 @@ func NewWebhookExtra( urlProvider: urlProvider, secrets: secrets, ghFactory: ghFactory, - clonedir: clonedir, parsers: parsers, workers: workers, + isPublic: isPublic, } } @@ -155,7 +165,8 @@ func (e *WebhookExtra) GetJobWorkers() []jobs.Worker { // AsRepository delegates repository creation to the webhook connector func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Repository) (repository.Repository, error) { - if r.Spec.Type == provisioning.GitHubRepositoryType { + // Only handle GitHub repositories with webhooks if URL is public + if r.Spec.Type == provisioning.GitHubRepositoryType && e.isPublic { gvr := provisioning.RepositoryResourceInfo.GroupVersionResource() webhookURL := fmt.Sprintf( "%sapis/%s/%s/namespaces/%s/%s/%s/webhook", @@ -209,7 +220,11 @@ func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Reposit } func (e *WebhookExtra) RepositoryTypes() []provisioning.RepositoryType { - return []provisioning.RepositoryType{ - provisioning.GitHubRepositoryType, + // Only claim to handle GitHub repositories if URL is public + if e.isPublic { + return []provisioning.RepositoryType{ + provisioning.GitHubRepositoryType, + } } + return []provisioning.RepositoryType{} } diff --git a/pkg/registry/schemas/core_kind.go b/pkg/registry/schemas/core_kind.go index 9e59b1eb9a2..c60b38d6161 100644 --- a/pkg/registry/schemas/core_kind.go +++ b/pkg/registry/schemas/core_kind.go @@ -48,15 +48,6 @@ func GetCoreKinds() ([]CoreKind, error) { CueFile: librarypanelCue, }) - publicdashboardCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/publicdashboard/public_dashboard_kind.cue")) - if err != nil { - return nil, err - } - kinds = append(kinds, CoreKind{ - Name: "publicdashboard", - CueFile: publicdashboardCue, - }) - return kinds, nil } diff --git a/pkg/server/test_env.go b/pkg/server/test_env.go index ff151cf7b60..e1659ebb460 100644 --- a/pkg/server/test_env.go +++ b/pkg/server/test_env.go @@ -1,11 +1,14 @@ package server import ( + "github.com/stretchr/testify/mock" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/grpcserver" @@ -14,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/web" - "github.com/stretchr/testify/mock" ) func ProvideTestEnv( @@ -34,7 +36,8 @@ func ProvideTestEnv( resourceClient resource.ResourceClient, idService auth.IDService, githubFactory *github.Factory, - repositorySecrets secrets.RepositorySecrets, + decryptService secret.DecryptService, + repositorySecrets secrets.RepositorySecrets, // TODO... remove ) (*TestEnv, error) { return &TestEnv{ TestingT: testingT, @@ -50,7 +53,8 @@ func ProvideTestEnv( ResourceClient: resourceClient, IDService: idService, GitHubFactory: githubFactory, - RepositorySecrets: repositorySecrets, + DecryptService: decryptService, + RepositorySecrets: repositorySecrets, // TODO, remove }, nil } @@ -72,5 +76,6 @@ type TestEnv struct { ResourceClient resource.ResourceClient IDService auth.IDService GitHubFactory *github.Factory - RepositorySecrets secrets.RepositorySecrets + DecryptService secret.DecryptService + RepositorySecrets secrets.RepositorySecrets // NOTE, this will be removed soon } diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 45a5f618e92..7b3c7db6b68 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -1430,7 +1430,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - testEnv, err := ProvideTestEnv(testingT, server, sqlStore, cfg, notificationServiceMock, grpcserverProvider, inMemory, httpclientProvider, oauthtokentestService, featureToggles, resourceClient, idimplService, factory, repositorySecrets) + testEnv, err := ProvideTestEnv(testingT, server, sqlStore, cfg, notificationServiceMock, grpcserverProvider, inMemory, httpclientProvider, oauthtokentestService, featureToggles, resourceClient, idimplService, factory, v3, repositorySecrets) if err != nil { return nil, err } diff --git a/pkg/services/apiserver/builder/openapi.go b/pkg/services/apiserver/builder/openapi.go index cde3548a136..5326ea071e2 100644 --- a/pkg/services/apiserver/builder/openapi.go +++ b/pkg/services/apiserver/builder/openapi.go @@ -34,7 +34,9 @@ func GetOpenAPIDefinitions(builders []APIGroupBuilder, additionalGetters ...open return bytes.Equal(aa, bb) }, ) - logging.DefaultLogger.Error("error initializing DataQuery apiequality", "err", err) + if err != nil { + logging.DefaultLogger.Error("error initializing DataQuery apiequality", "err", err) + } }) return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition { diff --git a/pkg/services/apiserver/options/extra.go b/pkg/services/apiserver/options/extra.go index 5f74f570c47..129b414b3ba 100644 --- a/pkg/services/apiserver/options/extra.go +++ b/pkg/services/apiserver/options/extra.go @@ -45,6 +45,7 @@ func (o *ExtraOptions) ApplyTo(c *genericapiserver.RecommendedConfig) error { logger := slog.New(handler) if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{ string(genericfeatures.APIServerTracing): false, + string(genericfeatures.WatchList): true, }); err != nil { return err } diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index 97dacad25ae..276cb4494b4 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -3,7 +3,7 @@ [[ if and .CSPEnabled .IsDevelopmentEnv ]] - + [[ end ]] @@ -14,7 +14,9 @@ - + + + [[range $asset := .Assets.CSSFiles]] diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 7c1e1dd4060..96ef6e36929 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -381,11 +381,13 @@ func (am *Alertmanager) GetRemoteState(ctx context.Context) (notifier.ExternalSt // Mimir state has two parts: // - "sil:": silences // - "nfl:": notification log entries + // The tenant ID can be different in the remote AM, so we consider only the part before the ':'. for _, p := range protoState.Parts { - switch p.Key { - case "sil:" + am.tenantID: + k := strings.Split(p.Key, ":") + switch k[0] { + case "sil": rs.Silences = p.Data - case "nfl:" + am.tenantID: + case "nfl": rs.Nflog = p.Data default: return rs, fmt.Errorf("unknown part key %q", p.Key) diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index 6ce6abb8994..701ba6df5c1 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -182,8 +182,8 @@ func TestGetRemoteState(t *testing.T) { state := alertingClusterPB.FullState{ Parts: []alertingClusterPB.Part{ - {Key: "nfl:test", Data: []byte("test-nflog")}, - {Key: "sil:test", Data: []byte("test-silences")}, + {Key: "nfl:otherID", Data: []byte("test-nflog")}, + {Key: "sil:otherID", Data: []byte("test-silences")}, }, } rawState, err := state.Marshal() diff --git a/pkg/services/ngalert/writer/datasourcewriter.go b/pkg/services/ngalert/writer/datasourcewriter.go index fc9a3087c7b..19af0db0a5b 100644 --- a/pkg/services/ngalert/writer/datasourcewriter.go +++ b/pkg/services/ngalert/writer/datasourcewriter.go @@ -32,6 +32,13 @@ const ( cacheCleanupInterval = 10 * time.Minute ) +type backendType string + +const ( + grafanaCloudPromType backendType = "grafanacloud-prom" + prometheusType backendType = "prometheus" +) + type DatasourceWriterConfig struct { // Timeout is the maximum time to wait for a remote write to succeed. Timeout time.Duration @@ -203,6 +210,13 @@ func (w *DatasourceWriter) makeWriter(ctx context.Context, orgID int64, dsUID st headers.Add(k, v) } + var backend backendType + if dsUID == string(grafanaCloudPromType) { + backend = grafanaCloudPromType + } else { + backend = prometheusType + } + cfg := PrometheusWriterConfig{ URL: u.String(), HTTPOptions: httpclient.Options{ @@ -212,7 +226,8 @@ func (w *DatasourceWriter) makeWriter(ctx context.Context, orgID int64, dsUID st Header: headers, ProxyOptions: ho.ProxyOptions, }, - Timeout: w.cfg.Timeout, + Timeout: w.cfg.Timeout, + BackendType: backend, } if err != nil { return nil, err diff --git a/pkg/services/ngalert/writer/datasourcewriter_test.go b/pkg/services/ngalert/writer/datasourcewriter_test.go index 09fc1ac6455..f06d2d95de4 100644 --- a/pkg/services/ngalert/writer/datasourcewriter_test.go +++ b/pkg/services/ngalert/writer/datasourcewriter_test.go @@ -2,7 +2,9 @@ package writer import ( "context" + "fmt" "net/http" + "strings" "testing" "time" @@ -10,6 +12,7 @@ import ( sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -127,7 +130,7 @@ func TestDatasourceWriter(t *testing.T) { series := []map[string]string{{"foo": "1"}, {"foo": "2"}, {"foo": "3"}, {"foo": "4"}} frames := frameGenFromLabels(t, data.FrameTypeNumericWide, series) - datasources := setupDataSources(t) + testDS := setupDataSources(t) cfg := DatasourceWriterConfig{ Timeout: time.Second * 5, @@ -136,26 +139,26 @@ func TestDatasourceWriter(t *testing.T) { met := metrics.NewRemoteWriterMetrics(prometheus.NewRegistry()) pluginContextProvider := &mockPluginContextProvider{} - writer := NewDatasourceWriter(cfg, datasources, httpclient.NewProvider(), pluginContextProvider, clock.New(), log.New("test"), met) + writer := NewDatasourceWriter(cfg, testDS, httpclient.NewProvider(), pluginContextProvider, clock.New(), log.New("test"), met) t.Run("when writing a prometheus datasource then the request is made to the expected endpoint", func(t *testing.T) { - datasources.Reset() + testDS.Reset() err := writer.WriteDatasource(context.Background(), "prom-1", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) - assert.Equal(t, 1, datasources.prom1.RequestsCount) - assert.Equal(t, 0, datasources.prom2.RequestsCount) + assert.Equal(t, 1, testDS.prom1.RequestsCount) + assert.Equal(t, 0, testDS.prom2.RequestsCount) err = writer.WriteDatasource(context.Background(), "prom-2", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) - assert.Equal(t, 1, datasources.prom1.RequestsCount) - assert.Equal(t, 1, datasources.prom2.RequestsCount) + assert.Equal(t, 1, testDS.prom1.RequestsCount) + assert.Equal(t, 1, testDS.prom2.RequestsCount) }) t.Run("when writing an unknown datasource then an error is returned", func(t *testing.T) { - datasources.Reset() + testDS.Reset() err := writer.WriteDatasource(context.Background(), "prom-unknown", "metric", time.Now(), frames, 1, map[string]string{}) require.Error(t, err) @@ -163,7 +166,7 @@ func TestDatasourceWriter(t *testing.T) { }) t.Run("when writing a non-prometheus datasource then an error is returned", func(t *testing.T) { - datasources.Reset() + testDS.Reset() err := writer.WriteDatasource(context.Background(), "loki-1", "metric", time.Now(), frames, 1, map[string]string{}) require.Error(t, err) @@ -171,14 +174,14 @@ func TestDatasourceWriter(t *testing.T) { }) t.Run("when writing with an empty datasource uid then the default is written", func(t *testing.T) { - datasources.Reset() + testDS.Reset() err := writer.WriteDatasource(context.Background(), "", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) }) t.Run("when custom headers are configured, they are passed to the request", func(t *testing.T) { - datasources.Reset() + testDS.Reset() header1 := "X-Custom-Header" header2 := "X-Another-Header" @@ -192,17 +195,17 @@ func TestDatasourceWriter(t *testing.T) { DefaultDatasourceUID: "prom-2", CustomHeaders: headers, } - writer = NewDatasourceWriter(cfg, datasources, httpclient.NewProvider(), pluginContextProvider, clock.New(), log.New("test"), met) + writer = NewDatasourceWriter(cfg, testDS, httpclient.NewProvider(), pluginContextProvider, clock.New(), log.New("test"), met) err := writer.WriteDatasource(context.Background(), "prom-1", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) - assert.Equal(t, headers[header1], datasources.prom1.LastHeaders.Get(header1)) - assert.Equal(t, headers[header2], datasources.prom1.LastHeaders.Get(header2)) + assert.Equal(t, headers[header1], testDS.prom1.LastHeaders.Get(header1)) + assert.Equal(t, headers[header2], testDS.prom1.LastHeaders.Get(header2)) }) t.Run("when PDC is enabled proxy options are passed to HTTP client provider", func(t *testing.T) { - datasources.Reset() + testDS.Reset() mockProvider := newMockHTTPClientProvider() @@ -212,7 +215,7 @@ func TestDatasourceWriter(t *testing.T) { } met := metrics.NewRemoteWriterMetrics(prometheus.NewRegistry()) - writer := NewDatasourceWriter(cfg, datasources, mockProvider, &mockPluginContextProvider{}, clock.New(), log.New("test"), met) + writer := NewDatasourceWriter(cfg, testDS, mockProvider, &mockPluginContextProvider{}, clock.New(), log.New("test"), met) err := writer.WriteDatasource(context.Background(), "prom-3", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) @@ -228,7 +231,7 @@ func TestDatasourceWriter(t *testing.T) { }) t.Run("when PDC is disabled proxy options are not set", func(t *testing.T) { - datasources.Reset() + testDS.Reset() mockProvider := newMockHTTPClientProvider() @@ -238,7 +241,7 @@ func TestDatasourceWriter(t *testing.T) { } met := metrics.NewRemoteWriterMetrics(prometheus.NewRegistry()) - writer := NewDatasourceWriter(cfg, datasources, mockProvider, &mockPluginContextProvider{}, clock.New(), log.New("test"), met) + writer := NewDatasourceWriter(cfg, testDS, mockProvider, &mockPluginContextProvider{}, clock.New(), log.New("test"), met) err := writer.WriteDatasource(context.Background(), "prom-1", "metric", time.Now(), frames, 1, map[string]string{}) require.NoError(t, err) @@ -247,6 +250,62 @@ func TestDatasourceWriter(t *testing.T) { require.NotNil(t, mockProvider.lastOptions) require.Nil(t, mockProvider.lastOptions.ProxyOptions) }) + + t.Run("datasource uses correct backend type in metrics", func(t *testing.T) { + testCases := []struct { + name string + datasourceUID string + expectedBackendType string + }{ + { + name: "grafanacloud-prom uses special backend type", + datasourceUID: string(grafanaCloudPromType), + expectedBackendType: string(grafanaCloudPromType), + }, + { + name: "prometheus uses default backend type", + datasourceUID: "prom-1", + expectedBackendType: "prometheus", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testDS.Reset() + + if tc.datasourceUID == string(grafanaCloudPromType) { + gcProm, _ := testDS.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ + Name: string(grafanaCloudPromType), + UID: string(grafanaCloudPromType), + Type: datasources.DS_PROMETHEUS, + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Prometheus"}`)), + }) + gcProm.URL = testDS.prom1.srv.URL + } + + cfg := DatasourceWriterConfig{ + Timeout: time.Second * 5, + DefaultDatasourceUID: "prom-2", + } + + reg := prometheus.NewRegistry() + met := metrics.NewRemoteWriterMetrics(reg) + writer := NewDatasourceWriter(cfg, testDS, httpclient.NewProvider(), pluginContextProvider, clock.New(), log.New("test"), met) + + err := writer.WriteDatasource(context.Background(), tc.datasourceUID, "metric", time.Now(), frames, 1, map[string]string{}) + require.NoError(t, err) + + expectedMetric := fmt.Sprintf(` + # HELP grafana_alerting_remote_writer_writes_total The total number of remote writes attempted. + # TYPE grafana_alerting_remote_writer_writes_total counter + grafana_alerting_remote_writer_writes_total{backend="%s",org="1",status_code="200"} 1 + `, tc.expectedBackendType) + require.NoError(t, testutil.CollectAndCompare(met.WritesTotal, + strings.NewReader(expectedMetric), + "grafana_alerting_remote_writer_writes_total")) + }) + } + }) } func TestDatasourceWriterGetRemoteWriteURL(t *testing.T) { diff --git a/pkg/services/ngalert/writer/prom.go b/pkg/services/ngalert/writer/prom.go index 64fb525ff44..7ade2fc1e09 100644 --- a/pkg/services/ngalert/writer/prom.go +++ b/pkg/services/ngalert/writer/prom.go @@ -20,8 +20,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" ) -const backendType = "prometheus" - const ( // Network error strings networkErrDialTCP = "dial tcp" @@ -236,16 +234,18 @@ type HttpClientProvider interface { } type PrometheusWriter struct { - client promremote.Client - clock clock.Clock - logger log.Logger - metrics *metrics.RemoteWriter + client promremote.Client + clock clock.Clock + logger log.Logger + metrics *metrics.RemoteWriter + backendType backendType } type PrometheusWriterConfig struct { URL string HTTPOptions httpclient.Options Timeout time.Duration + BackendType backendType } func NewPrometheusWriter( @@ -272,11 +272,19 @@ func NewPrometheusWriter( return nil, err } + var backend backendType + if cfg.BackendType != "" { + backend = cfg.BackendType + } else { + backend = prometheusType + } + return &PrometheusWriter{ - client: client, - clock: clock, - logger: l, - metrics: metrics, + client: client, + clock: clock, + logger: l, + metrics: metrics, + backendType: backend, }, nil } @@ -295,7 +303,7 @@ func (w PrometheusWriter) WriteDatasource(ctx context.Context, dsUID string, nam // Write writes the given frames to the Prometheus remote write endpoint. func (w PrometheusWriter) Write(ctx context.Context, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error { l := w.logger.FromContext(ctx) - lvs := []string{fmt.Sprint(orgID), backendType} + lvs := []string{fmt.Sprint(orgID), string(w.backendType)} points, err := PointsFromFrames(name, t, frames, extraLabels) if err != nil { diff --git a/pkg/services/store/testdata/public_testdata.golden.jsonc b/pkg/services/store/testdata/public_testdata.golden.jsonc index d18191f2d39..a37239596c2 100644 --- a/pkg/services/store/testdata/public_testdata.golden.jsonc +++ b/pkg/services/store/testdata/public_testdata.golden.jsonc @@ -1,5 +1,5 @@ // 🌟 This was machine generated. Do not edit. 🌟 -// +// // Frame[0] { // "type": "directory-listing", // "typeVersion": [ @@ -10,7 +10,7 @@ // "HasMore": false // } // } -// Name: +// Name: // Dimensions: 3 Fields by 3 Rows // +----------------------------+----------------------+---------------+ // | Name: name | Name: mediaType | Name: size | @@ -21,8 +21,8 @@ // | example-with-style.geojson | application/geo+json | 3332 | // | usa-states.geojson | application/geo+json | 89263 | // +----------------------------+----------------------+---------------+ -// -// +// +// // 🌟 This was machine generated. Do not edit. 🌟 { "status": 200, @@ -87,4 +87,4 @@ } } ] -} +} \ No newline at end of file diff --git a/pkg/storage/unified/resource/cdk_backend.go b/pkg/storage/unified/resource/cdk_backend.go index c47ec01d378..fdabaca6759 100644 --- a/pkg/storage/unified/resource/cdk_backend.go +++ b/pkg/storage/unified/resource/cdk_backend.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "iter" "net/http" "sort" "strconv" @@ -74,6 +75,12 @@ type cdkBackend struct { stream chan<- *WrittenEvent } +func (s *cdkBackend) ListModifiedSince(ctx context.Context, key NamespacedResource, sinceRv int64) (int64, iter.Seq2[*ModifiedResource, error]) { + return 0, func(yield func(*ModifiedResource, error) bool) { + yield(nil, errors.New("not implemented")) + } +} + func (s *cdkBackend) getPath(key *resourcepb.ResourceKey, rv int64) string { var buffer bytes.Buffer buffer.WriteString(s.root) diff --git a/pkg/storage/unified/resource/search_test.go b/pkg/storage/unified/resource/search_test.go index 07a95639ed6..f1e005ade86 100644 --- a/pkg/storage/unified/resource/search_test.go +++ b/pkg/storage/unified/resource/search_test.go @@ -2,7 +2,9 @@ package resource import ( "context" + "errors" "fmt" + "iter" "log/slog" "sync" "testing" @@ -98,6 +100,12 @@ func (m *mockStorageBackend) ListHistory(ctx context.Context, req *resourcepb.Li return 0, nil } +func (m *mockStorageBackend) ListModifiedSince(ctx context.Context, key NamespacedResource, sinceRv int64) (int64, iter.Seq2[*ModifiedResource, error]) { + return 0, func(yield func(*ModifiedResource, error) bool) { + yield(nil, errors.New("not implemented")) + } +} + // mockSearchBackend implements SearchBackend for testing with tracking capabilities type mockSearchBackend struct { mu sync.Mutex diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 1b52245157d..f5dc2b7f2a5 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "iter" "log/slog" "net/http" "sync" @@ -114,6 +115,10 @@ type StorageBackend interface { // ListHistory is like ListIterator, but it returns the history of a resource ListHistory(context.Context, *resourcepb.ListRequest, func(ListIterator) error) (int64, error) + // ListModifiedSince will return all resources that have changed since the given resource version. + // If a resource has changes, only the latest change will be returned. + ListModifiedSince(ctx context.Context, key NamespacedResource, sinceRv int64) (int64, iter.Seq2[*ModifiedResource, error]) + // Get all events from the store // For HA setups, this will be more events than the local WriteEvent above! WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) @@ -122,6 +127,13 @@ type StorageBackend interface { GetResourceStats(ctx context.Context, namespace string, minCount int) ([]ResourceStats, error) } +type ModifiedResource struct { + Action resourcepb.WatchEvent_Type + Key resourcepb.ResourceKey + Value []byte + ResourceVersion int64 +} + type ResourceStats struct { NamespacedResource diff --git a/pkg/storage/unified/resource/storage_backend.go b/pkg/storage/unified/resource/storage_backend.go index dff33f63b36..eafb9b40f98 100644 --- a/pkg/storage/unified/resource/storage_backend.go +++ b/pkg/storage/unified/resource/storage_backend.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "iter" "math/rand/v2" "net/http" "sort" @@ -450,6 +451,12 @@ func applyPagination(keys []DataKey, lastSeenRV int64, sortAscending bool) []Dat return pagedKeys } +func (k *kvStorageBackend) ListModifiedSince(ctx context.Context, key NamespacedResource, sinceRv int64) (int64, iter.Seq2[*ModifiedResource, error]) { + return 0, func(yield func(*ModifiedResource, error) bool) { + yield(nil, errors.New("not implemented")) + } +} + // ListHistory is like ListIterator, but it returns the history of a resource. func (k *kvStorageBackend) ListHistory(ctx context.Context, req *resourcepb.ListRequest, fn func(ListIterator) error) (int64, error) { if err := validateListHistoryRequest(req); err != nil { diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index ca3e941eb92..a4df140d541 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "iter" "math" "sync" "time" @@ -631,6 +632,99 @@ func (b *backend) listLatest(ctx context.Context, req *resourcepb.ListRequest, c return iter.listRV, err } +// ListModifiedSince will return all resources that have changed since the given resource version. +// If a resource has changes, only the latest change will be returned. +func (b *backend) ListModifiedSince(ctx context.Context, key resource.NamespacedResource, sinceRv int64) (int64, iter.Seq2[*resource.ModifiedResource, error]) { + tx, err := b.db.BeginTx(ctx, RepeatableRead) + if err != nil { + return 0, func(yield func(*resource.ModifiedResource, error) bool) { + yield(nil, err) + } + } + + // Fetch latest RV within the transaction + latestRv, err := b.fetchLatestRV(ctx, tx, b.dialect, key.Group, key.Resource) + if err != nil { + terr := tx.Rollback() + if terr != nil { + b.log.Warn("Error rolling back transaction in ListModifiedSince", "error", terr) + } + return 0, func(yield func(*resource.ModifiedResource, error) bool) { + yield(nil, err) + } + } + + // since results are sorted by name ASC and rv DESC, we can get away with tracking the last seen + lastSeen := "" + + // rollback transaction if iterator not called within 30 seconds + rollbackTimer := time.AfterFunc(30*time.Second, func() { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { + b.log.Warn("rollback timer error", "err", err) + } + }) + + seq := func(yield func(*resource.ModifiedResource, error) bool) { + rollbackTimer.Stop() + + defer func() { + // Always rollback the read-only transaction when iterator is done + if rollbackErr := tx.Rollback(); rollbackErr != nil { + b.log.Warn("Error rolling back transaction in ListModifiedSince", "error", rollbackErr) + } + }() + + query := sqlResourceListModifiedSinceRequest{ + SQLTemplate: sqltemplate.New(b.dialect), + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + SinceRv: sinceRv, + } + + rows, err := dbutil.QueryRows(ctx, tx, sqlResourceHistoryListModifiedSince, query) + if err != nil { + yield(nil, err) + return + } + if rows != nil { + defer func() { + if cerr := rows.Close(); cerr != nil { + b.log.Warn("listSinceModified error closing rows", "error", cerr) + } + }() + } + + for rows.Next() { + mr := &resource.ModifiedResource{} + if err := rows.Scan(&mr.Key.Namespace, &mr.Key.Group, &mr.Key.Resource, &mr.Key.Name, &mr.ResourceVersion, &mr.Action, &mr.Value); err != nil { + if !yield(nil, err) { + return + } + continue + } + + // Deduplicate by name (namespace, group, and resource are always the same in the result set) + if mr.Key.Name == lastSeen { + continue + } + + if mr.Key.Name <= lastSeen { + // resource names should be sorted alphabetically. So if not, the query is not correct. + yield(nil, fmt.Errorf("listModifiedSince: resources are not sorted by name ASC, lastSeen: %q, current: %q", lastSeen, mr.Key.Name)) + } + + lastSeen = mr.Key.Name + + if !yield(mr, nil) { + return + } + } + } + + return latestRv, seq +} + // listAtRevision fetches the resources from the resource_history table at a specific revision. func (b *backend) listAtRevision(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) { ctx, span := b.tracer.Start(ctx, tracePrefix+"listAtRevision") diff --git a/pkg/storage/unified/sql/data/resource_history_list_since_modified.sql b/pkg/storage/unified/sql/data/resource_history_list_since_modified.sql new file mode 100644 index 00000000000..5c4b05eb997 --- /dev/null +++ b/pkg/storage/unified/sql/data/resource_history_list_since_modified.sql @@ -0,0 +1,14 @@ +SELECT + {{.Ident "namespace"}}, + {{.Ident "group"}}, + {{.Ident "resource"}}, + {{.Ident "name"}}, + {{.Ident "resource_version"}}, + {{.Ident "action"}}, + {{.Ident "value"}} +FROM resource_history +WHERE {{.Ident "namespace" }} = {{.Arg .Namespace }} + AND {{.Ident "group" }} = {{.Arg .Group }} + AND {{.Ident "resource" }} = {{.Arg .Resource }} + AND {{.Ident "resource_version" }} > {{.Arg .SinceRv }} -- needs to be exclusive of the sinceRv +ORDER BY {{.Ident "name" }} ASC, {{.Ident "resource_version" }} DESC diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 43ae7f3a45a..3253f589ec7 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -30,24 +30,25 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlResourceDelete = mustTemplate("resource_delete.sql") - sqlResourceInsert = mustTemplate("resource_insert.sql") - sqlResourceUpdate = mustTemplate("resource_update.sql") - sqlResourceRead = mustTemplate("resource_read.sql") - sqlResourceStats = mustTemplate("resource_stats.sql") - sqlResourceList = mustTemplate("resource_list.sql") - sqlResourceHistoryList = mustTemplate("resource_history_list.sql") - sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") - sqlResourceHistoryRead = mustTemplate("resource_history_read.sql") - sqlResourceHistoryReadLatestRV = mustTemplate("resource_history_read_latest_rv.sql") - sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") - sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql") - sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql") - sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") - sqlResourceHistoryDelete = mustTemplate("resource_history_delete.sql") - sqlResourceHistoryPrune = mustTemplate("resource_history_prune.sql") - sqlResourceTrash = mustTemplate("resource_trash.sql") - sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql") + sqlResourceDelete = mustTemplate("resource_delete.sql") + sqlResourceInsert = mustTemplate("resource_insert.sql") + sqlResourceUpdate = mustTemplate("resource_update.sql") + sqlResourceRead = mustTemplate("resource_read.sql") + sqlResourceStats = mustTemplate("resource_stats.sql") + sqlResourceList = mustTemplate("resource_list.sql") + sqlResourceHistoryList = mustTemplate("resource_history_list.sql") + sqlResourceHistoryListModifiedSince = mustTemplate("resource_history_list_since_modified.sql") + sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") + sqlResourceHistoryRead = mustTemplate("resource_history_read.sql") + sqlResourceHistoryReadLatestRV = mustTemplate("resource_history_read_latest_rv.sql") + sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") + sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql") + sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql") + sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") + sqlResourceHistoryDelete = mustTemplate("resource_history_delete.sql") + sqlResourceHistoryPrune = mustTemplate("resource_history_prune.sql") + sqlResourceTrash = mustTemplate("resource_trash.sql") + sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql") // sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql") sqlResourceVersionGet = mustTemplate("resource_version_get.sql") @@ -425,3 +426,27 @@ func (r *sqlResourceVersionListRequest) Results() (*groupResourceVersion, error) x := *r.groupResourceVersion return &x, nil } + +type sqlResourceListModifiedSinceRequest struct { + sqltemplate.SQLTemplate + Namespace string + Group string + Resource string + SinceRv int64 +} + +func (r sqlResourceListModifiedSinceRequest) Validate() error { + if r.Namespace == "" { + return fmt.Errorf("missing namespace") + } + if r.Group == "" { + return fmt.Errorf("missing group") + } + if r.Resource == "" { + return fmt.Errorf("missing resource") + } + if r.SinceRv < 0 { + return fmt.Errorf("since resource version must be greater than or equal to zero") + } + return nil +} diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index dcdf64016d5..b5fbd407ade 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -120,6 +120,18 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, }, + sqlResourceHistoryListModifiedSince: { + { + Name: "single path", + Data: &sqlResourceListModifiedSinceRequest{ + SQLTemplate: mocks.NewTestingSQLTemplate(), + Namespace: "ns", + Group: "group", + Resource: "res", + SinceRv: 10000, + }, + }, + }, sqlResourceHistoryPoll: { { Name: "single path", diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_history_list_since_modified-single path.sql b/pkg/storage/unified/sql/testdata/mysql--resource_history_list_since_modified-single path.sql new file mode 100755 index 00000000000..9e2d8bff37c --- /dev/null +++ b/pkg/storage/unified/sql/testdata/mysql--resource_history_list_since_modified-single path.sql @@ -0,0 +1,14 @@ +SELECT + `namespace`, + `group`, + `resource`, + `name`, + `resource_version`, + `action`, + `value` +FROM resource_history +WHERE `namespace` = 'ns' + AND `group` = 'group' + AND `resource` = 'res' + AND `resource_version` > 10000 -- needs to be exclusive of the sinceRv +ORDER BY `name` ASC, `resource_version` DESC diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_history_list_since_modified-single path.sql b/pkg/storage/unified/sql/testdata/postgres--resource_history_list_since_modified-single path.sql new file mode 100755 index 00000000000..28089ffaa66 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/postgres--resource_history_list_since_modified-single path.sql @@ -0,0 +1,14 @@ +SELECT + "namespace", + "group", + "resource", + "name", + "resource_version", + "action", + "value" +FROM resource_history +WHERE "namespace" = 'ns' + AND "group" = 'group' + AND "resource" = 'res' + AND "resource_version" > 10000 -- needs to be exclusive of the sinceRv +ORDER BY "name" ASC, "resource_version" DESC diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_history_list_since_modified-single path.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_history_list_since_modified-single path.sql new file mode 100755 index 00000000000..28089ffaa66 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_history_list_since_modified-single path.sql @@ -0,0 +1,14 @@ +SELECT + "namespace", + "group", + "resource", + "name", + "resource_version", + "action", + "value" +FROM resource_history +WHERE "namespace" = 'ns' + AND "group" = 'group' + AND "resource" = 'res' + AND "resource_version" > 10000 -- needs to be exclusive of the sinceRv +ORDER BY "name" ASC, "resource_version" DESC diff --git a/pkg/storage/unified/testing/storage_backend.go b/pkg/storage/unified/testing/storage_backend.go index e3314376715..5488b44a144 100644 --- a/pkg/storage/unified/testing/storage_backend.go +++ b/pkg/storage/unified/testing/storage_backend.go @@ -35,6 +35,7 @@ const ( TestGetResourceStats = "get resource stats" TestListHistory = "list history" TestListHistoryErrorReporting = "list history error reporting" + TestListModifiedSince = "list events since rv" TestListTrash = "list trash" TestCreateNewResource = "create new resource" ) @@ -78,6 +79,7 @@ func RunStorageBackendTest(t *testing.T, newBackend NewBackendFunc, opts *TestOp {TestListHistoryErrorReporting, runTestIntegrationBackendListHistoryErrorReporting}, {TestListTrash, runTestIntegrationBackendTrash}, {TestCreateNewResource, runTestIntegrationBackendCreateNewResource}, + {TestListModifiedSince, runTestIntegrationBackendListModifiedSince}, } for _, tc := range cases { @@ -482,6 +484,77 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend }) } +func runTestIntegrationBackendListModifiedSince(t *testing.T, backend resource.StorageBackend, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + ns := nsPrefix + "-history-ns" + rvCreated, _ := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) + require.Greater(t, rvCreated, int64(0)) + rvUpdated, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_MODIFIED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rvUpdated, rvCreated) + rvDeleted, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rvDeleted, rvUpdated) + + t.Run("will list latest modified event when resource has multiple events", func(t *testing.T) { + key := resource.NamespacedResource{ + Namespace: ns, + Group: "group", + Resource: "resource", + } + latestRv, seq := backend.ListModifiedSince(ctx, key, rvCreated) + require.Greater(t, latestRv, rvCreated) + + counter := 0 + for res, err := range seq { + require.NoError(t, err) + require.Equal(t, rvDeleted, res.ResourceVersion) + counter++ + } + require.Equal(t, 1, counter) // only one event should be returned + }) + + t.Run("no events if none after the given resource version", func(t *testing.T) { + key := resource.NamespacedResource{ + Namespace: ns, + Group: "group", + Resource: "resource", + } + latestRv, seq := backend.ListModifiedSince(ctx, key, rvDeleted) + require.GreaterOrEqual(t, latestRv, rvDeleted) + + counter := 0 + for _, _ = range seq { + counter++ + } + require.Equal(t, 0, counter) // no events should be returned + }) + + t.Run("will only return modified events for the given key", func(t *testing.T) { + key := resource.NamespacedResource{ + Namespace: "other-ns", + Group: "group", + Resource: "resource", + } + + // Write an event for another tenant for the same resource + rvCreatedOtherTenant, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace("other-ns")) + require.NoError(t, err) + + latestRv, seq := backend.ListModifiedSince(ctx, key, rvCreated) + require.Greater(t, latestRv, rvCreated) + + counter := 0 + for res, err := range seq { + require.NoError(t, err) + require.Equal(t, rvCreatedOtherTenant, res.ResourceVersion) + require.Equal(t, key.Namespace, res.Key.Namespace) + counter++ + } + require.Equal(t, 1, counter) // only one event should be returned + }) +} + func runTestIntegrationBackendListHistory(t *testing.T, backend resource.StorageBackend, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) server := newServer(t, backend) diff --git a/pkg/storage/unified/testing/storage_backend_test.go b/pkg/storage/unified/testing/storage_backend_test.go index a1da3b82e72..ced0b6869a8 100644 --- a/pkg/storage/unified/testing/storage_backend_test.go +++ b/pkg/storage/unified/testing/storage_backend_test.go @@ -23,7 +23,8 @@ func TestBadgerKVStorageBackend(t *testing.T) { NSPrefix: "kvstorage-test", SkipTests: map[string]bool{ // TODO: fix these tests and remove this skip - TestBlobSupport: true, + TestBlobSupport: true, + TestListModifiedSince: true, }, }) } diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index 2352079084e..39bf07a014c 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -2375,6 +2375,9 @@ "metadata": { "default": {} }, + "secure": { + "default": {} + }, "spec": { "default": {} }, @@ -2859,6 +2862,14 @@ "type": "integer", "format": "int64" }, + "error": { + "description": "The type of the error\n\nPossible enum values:\n - `\"health\"`\n - `\"hook\"`", + "type": "string", + "enum": [ + "health", + "hook" + ] + }, "healthy": { "description": "When not healthy, requests will not be executed", "type": "boolean", @@ -3464,6 +3475,14 @@ } ] }, + "secure": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SecureValues" + } + ] + }, "spec": { "default": {}, "allOf": [ @@ -4193,6 +4212,30 @@ } ] }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SecureValues": { + "description": "NOT YET USED FOR REAL -- testing secure value workflow", + "type": "object", + "properties": { + "token": { + "description": "Token used to connect the configured repository", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue" + } + ] + }, + "webhookSecret": { + "description": "Some webhooks (github) require a secret key value", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue" + } + ] + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncJobOptions": { "type": "object", "required": [ @@ -4406,6 +4449,45 @@ } } }, + "com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue": { + "description": "Allow access to a secure value inside", + "oneOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "create" + ] + }, + { + "required": [ + "remove" + ] + } + ], + "properties": { + "create": { + "description": "Create a secure value -- this is only used for POST/PUT", + "type": "string", + "maxLength": 24576, + "minLength": 1 + }, + "name": { + "description": "Name in the secret service (reference)", + "type": "string", + "maxLength": 253, + "minLength": 1 + }, + "remove": { + "description": "Remove this value from the secure value map Values owned by this resource will be deleted if necessary", + "type": "boolean" + } + }, + "additionalProperties": false + }, "com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured": { "type": "object", "additionalProperties": true, diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index 9e5b754c4d9..10abcea4c66 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -1,15 +1,13 @@ package apis import ( - "context" - "encoding/json" "fmt" + "runtime" "testing" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/version" - apimachineryversion "k8s.io/apimachinery/pkg/version" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tests/testinfra" @@ -38,18 +36,9 @@ func TestIntegrationOpenAPIs(t *testing.T) { t.Run("check valid version response", func(t *testing.T) { disco := h.NewDiscoveryClient() - req := disco.RESTClient().Get(). - Prefix("version"). - SetHeader("Accept", "application/json") - - result := req.Do(context.Background()) - require.NoError(t, result.Error()) - - raw, err := result.Raw() - require.NoError(t, err) - info := apimachineryversion.Info{} - err = json.Unmarshal(raw, &info) + info, err := disco.ServerVersion() require.NoError(t, err) + require.Equal(t, runtime.Version(), info.GoVersion) // Make sure the gitVersion is parsable v, err := version.Parse(info.GitVersion) @@ -57,10 +46,15 @@ func TestIntegrationOpenAPIs(t *testing.T) { require.Equal(t, info.Major, fmt.Sprintf("%d", v.Major())) require.Equal(t, info.Minor, fmt.Sprintf("%d", v.Minor())) - // Check that OpenAPI v2 (used by kubectl) returns properly - v2, err := disco.OpenAPISchema() - require.NoError(t, err, "requesting OpenAPI v2") - require.Equal(t, "Grafana API Server", v2.Info.Title) + // Check the v3 path resolves properly + // NOTE: fetching the v2 schema sometimes returns a 503 in our test infrastructure + // Removing the explicit `OneOf` properties from InlineSecureValue in: + // https://github.com/grafana/grafana/blob/main/pkg/apimachinery/apis/common/v0alpha1/secure_values.go#L78 + // will consistently support V2, however kubectl and everything else continues to work + paths, err := disco.OpenAPIV3().Paths() + + require.NoError(t, err, "requesting OpenAPI v3") + require.NotEmpty(t, paths, "has registered paths") }) dir := "openapi_snapshots" diff --git a/pkg/tests/apis/provisioning/files_test.go b/pkg/tests/apis/provisioning/files_test.go index afe7bfd62b9..7fa121e00f5 100644 --- a/pkg/tests/apis/provisioning/files_test.go +++ b/pkg/tests/apis/provisioning/files_test.go @@ -25,23 +25,19 @@ func TestIntegrationProvisioning_DeleteResources(t *testing.T) { ctx := context.Background() const repo = "delete-test-repo" - localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": repo, - "SyncEnabled": true, - "SyncTarget": "instance", + helper.CreateRepo(t, TestRepo{ + Name: repo, + Path: helper.ProvisioningPath, + Target: "instance", + Copies: map[string]string{ + "testdata/all-panels.json": "dashboard1.json", + "testdata/text-options.json": "folder/dashboard2.json", + "testdata/timeline-demo.json": "folder/nested/dashboard3.json", + "testdata/.keep": "folder/nested/.keep", + }, + ExpectedDashboards: 3, + ExpectedFolders: 2, }) - _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) - require.NoError(t, err) - - // Copy the dashboards to the repository path - helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "dashboard1.json") - helper.CopyToProvisioningPath(t, "testdata/text-options.json", "folder/dashboard2.json") - helper.CopyToProvisioningPath(t, "testdata/timeline-demo.json", "folder/nested/dashboard3.json") - // make sure we don't fail when there is a .keep file in a folder - helper.CopyToProvisioningPath(t, "testdata/.keep", "folder/nested/.keep") - - // Trigger and wait for a sync job to finish - helper.SyncAndWait(t, repo, nil) dashboards, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err) @@ -118,21 +114,17 @@ func TestIntegrationProvisioning_MoveResources(t *testing.T) { helper := runGrafana(t) ctx := context.Background() - const repo = "move-test-repo" - localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": repo, - "SyncEnabled": true, - "SyncTarget": "instance", + repo := "move-test-repo" + helper.CreateRepo(t, TestRepo{ + Name: repo, + Path: helper.ProvisioningPath, + Target: "instance", + Copies: map[string]string{ + "testdata/all-panels.json": "all-panels.json", + }, + ExpectedDashboards: 1, + ExpectedFolders: 0, }) - _, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) - require.NoError(t, err) - - // Copy test dashboards to the repository path for initial setup - const originalDashboard = "all-panels.json" - helper.CopyToProvisioningPath(t, "testdata/all-panels.json", originalDashboard) - - // Wait for sync to ensure the dashboard is created in Grafana - helper.SyncAndWait(t, repo, nil) // Verify the original dashboard exists in Grafana (using the UID from all-panels.json) const allPanelsUID = "n1jR8vnnz" // This is the UID from the all-panels.json file @@ -146,7 +138,7 @@ func TestIntegrationProvisioning_MoveResources(t *testing.T) { // Perform the move operation using helper function resp := helper.postFilesRequest(t, repo, filesPostOptions{ targetPath: targetPath, - originalPath: originalDashboard, + originalPath: "all-panels.json", message: "move file without content change", }) // nolint:errcheck @@ -167,7 +159,7 @@ func TestIntegrationProvisioning_MoveResources(t *testing.T) { require.Equal(t, "Panel tests - All panels", title, "content should be preserved") // Verify original file no longer exists - _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", originalDashboard) + _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json") require.Error(t, err, "original file should no longer exist") // Verify dashboard still exists in Grafana with same content but may have updated path references diff --git a/pkg/tests/apis/provisioning/health_test.go b/pkg/tests/apis/provisioning/health_test.go new file mode 100644 index 00000000000..17f145047dc --- /dev/null +++ b/pkg/tests/apis/provisioning/health_test.go @@ -0,0 +1,204 @@ +package provisioning + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +func TestIntegrationHealth(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + helper := runGrafana(t) + ctx := context.Background() + repo := "test-repo-health" + helper.CreateRepo(t, TestRepo{ + Name: repo, + }) + + // Verify the health status before calling the endpoint + repoObj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}) + require.NoError(t, err) + originalRepo := unstructuredToRepository(t, repoObj) + require.True(t, originalRepo.Status.Health.Healthy, "repository should be marked healthy") + require.Empty(t, originalRepo.Status.Health.Error, "should be empty") + require.Empty(t, originalRepo.Status.Health.Message, "should not have messages") + + t.Run("test endpoint with new repository configuration works", func(t *testing.T) { + newRepoConfig := map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "spec": map[string]any{ + "title": "Test New Configuration", + "type": "local", + "local": map[string]any{ + "path": helper.ProvisioningPath, + }, + "workflows": []string{"write"}, + "sync": map[string]any{ + "enabled": true, + "target": "folder", + "intervalSeconds": 10, + }, + }, + } + + configBytes, err := json.Marshal(newRepoConfig) + require.NoError(t, err) + + // Test the new configuration - this should work + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name("test-new-config"). + SubResource("test"). + Body(configBytes). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + require.NoError(t, result.Error(), "test endpoint should work for new repository configurations") + + obj, err := result.Get() + require.NoError(t, err) + + testResults := parseTestResults(t, obj) + require.True(t, testResults.Success, "test should succeed for valid repository configuration") + require.Equal(t, 200, testResults.Code, "should return 200 for successful test") + + // Verify the repository was not actually created (this was just a test) + _, err = helper.Repositories.Resource.Get(ctx, "test-new-config", metav1.GetOptions{}) + require.True(t, err != nil, "repository should not be created during test") + }) + + t.Run("test endpoint with existing repository", func(t *testing.T) { + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("test"). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + require.NoError(t, result.Error(), "test endpoint should return NOT an error for existing repository") + obj, err := result.Get() + require.NoError(t, err) + testResults := parseTestResults(t, obj) + t.Logf("SUCCESS: Test endpoint worked for existing repository: Success=%v, Code=%d", + testResults.Success, testResults.Code) + require.True(t, testResults.Success, "test should succeed for existing repository") + require.Equal(t, 200, testResults.Code, "should return 200 for successful test") + + // Verify repository health status after update + repoObj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}) + require.NoError(t, err) + afterTest := unstructuredToRepository(t, repoObj) + require.True(t, afterTest.Status.Health.Healthy, "repository should be marked healthy") + require.Empty(t, afterTest.Status.Health.Error, "should be empty") + require.Empty(t, afterTest.Status.Health.Message, "should not have messages") + // For healthy repositories, timestamp may not change immediately as it can take up to 30 seconds to update + }) + + t.Run("test endpoint with unhealthy repository", func(t *testing.T) { + // Remove the repository folder to make it unhealthy + repoPath := helper.ProvisioningPath + err := os.RemoveAll(repoPath) + require.NoError(t, err, "should be able to remove repository directory") + + // Wait a bit for the system to detect the unhealthy state + // (In a real scenario, this would be detected during the next health check cycle) + + // Get the repository status before the test + repoObj, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}) + require.NoError(t, err) + beforeTest := unstructuredToRepository(t, repoObj) + t.Logf("Before test - Healthy: %v, Checked: %d", beforeTest.Status.Health.Healthy, beforeTest.Status.Health.Checked) + + // Call the test endpoint + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("test"). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + // The test endpoint may return an error for unhealthy repositories + obj, err := result.Get() + if result.Error() != nil { + t.Logf("Test endpoint returned error for unhealthy repository (expected): %v", result.Error()) + } else { + require.NoError(t, err) + testResults := parseTestResults(t, obj) + t.Logf("Test endpoint result for unhealthy repository: Success=%v, Code=%d", + testResults.Success, testResults.Code) + } + + // Verify repository health status after test - timestamp should change + repoObj, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}) + require.NoError(t, err) + afterTest := unstructuredToRepository(t, repoObj) + t.Logf("After test - Healthy: %v, Checked: %d", afterTest.Status.Health.Healthy, afterTest.Status.Health.Checked) + + // For unhealthy repositories, the timestamp should change as the health check will be triggered + require.NotEqual(t, beforeTest.Status.Health.Checked, afterTest.Status.Health.Checked, "should change the timestamp for unhealthy repository check") + + // Recreate the repository directory to restore healthy state + err = os.MkdirAll(repoPath, 0o750) + require.NoError(t, err, "should be able to recreate repository directory") + + // Call the test endpoint again to trigger health check after recreating directory + result = helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("test"). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + // Should succeed now that the directory is recreated + require.NoError(t, result.Error(), "test endpoint should work after recreating directory") + obj, err = result.Get() + require.NoError(t, err) + testResults := parseTestResults(t, obj) + require.True(t, testResults.Success, "test should succeed after recreating directory") + require.Equal(t, 200, testResults.Code, "should return 200 after recreating directory") + + // Verify repository health status is now healthy again + repoObj, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}) + require.NoError(t, err) + finalRepo := unstructuredToRepository(t, repoObj) + t.Logf("After recreating directory - Healthy: %v, Checked: %d", finalRepo.Status.Health.Healthy, finalRepo.Status.Health.Checked) + require.True(t, finalRepo.Status.Health.Healthy, "repository should be healthy again after recreating directory") + require.Empty(t, finalRepo.Status.Health.Error, "should have no error after recreating directory") + + // Timestamp should have changed again due to the health check + require.NotEqual(t, afterTest.Status.Health.Checked, finalRepo.Status.Health.Checked, "timestamp should change when repository becomes healthy again") + }) +} + +// parseTestResults extracts TestResults from the API response +func parseTestResults(t *testing.T, obj runtime.Object) *provisioning.TestResults { + t.Helper() + + unstructuredObj, ok := obj.(*unstructured.Unstructured) + require.True(t, ok, "expected unstructured object") + + data, err := json.Marshal(unstructuredObj.Object) + require.NoError(t, err) + + var testResults provisioning.TestResults + err = json.Unmarshal(data, &testResults) + require.NoError(t, err) + + return &testResults +} diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index 554ad36bff8..0fb50148e8b 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -316,11 +316,11 @@ func (h *provisioningTestHelper) RenderObject(t *testing.T, filePath string, val func (h *provisioningTestHelper) CopyToProvisioningPath(t *testing.T, from, to string) { fullPath := path.Join(h.ProvisioningPath, to) t.Logf("Copying file from '%s' to provisioning path '%s'", from, fullPath) - err := os.MkdirAll(path.Dir(fullPath), 0750) + err := os.MkdirAll(path.Dir(fullPath), 0o750) require.NoError(t, err, "failed to create directories for provisioning path") file := h.LoadFile(from) - err = os.WriteFile(fullPath, file, 0600) + err = os.WriteFile(fullPath, file, 0o600) require.NoError(t, err, "failed to write file to provisioning path") } @@ -461,13 +461,16 @@ func (h *provisioningTestHelper) logRepositoryObject(t *testing.T, obj map[strin } type TestRepo struct { - Name string - Target string - Path string - Values map[string]any - Copies map[string]string - ExpectedDashboards int - ExpectedFolders int + Name string + Target string + Path string + Values map[string]any + Copies map[string]string + ExpectedDashboards int + ExpectedFolders int + SkipSync bool + SkipResourceAssertions bool + Template string } func (h *provisioningTestHelper) CreateRepo(t *testing.T, repo TestRepo) { @@ -480,52 +483,81 @@ func (h *provisioningTestHelper) CreateRepo(t *testing.T, repo TestRepo) { if repo.Path != "" { repoPath = repo.Path // Ensure the directory exists - err := os.MkdirAll(repoPath, 0750) + err := os.MkdirAll(repoPath, 0o750) require.NoError(t, err, "should be able to create repository path") } templateVars := map[string]any{ "Name": repo.Name, - "SyncEnabled": true, + "SyncEnabled": !repo.SkipSync, "SyncTarget": repo.Target, } if repo.Path != "" { templateVars["Path"] = repoPath } + // Add custom values from TestRepo + for key, value := range repo.Values { + templateVars[key] = value + } - localTmp := h.RenderObject(t, "testdata/local-write.json.tmpl", templateVars) + tmpl := "testdata/local-write.json.tmpl" + if repo.Template != "" { + tmpl = repo.Template + } + localTmp := h.RenderObject(t, tmpl, templateVars) _, err := h.Repositories.Resource.Create(t.Context(), localTmp, metav1.CreateOptions{}) require.NoError(t, err) + h.WaitForHealthyRepository(t, repo.Name) for from, to := range repo.Copies { if repo.Path != "" { // Copy to custom path fullPath := path.Join(repoPath, to) - err := os.MkdirAll(path.Dir(fullPath), 0750) + err := os.MkdirAll(path.Dir(fullPath), 0o750) require.NoError(t, err, "failed to create directories for custom path") file := h.LoadFile(from) - err = os.WriteFile(fullPath, file, 0600) + err = os.WriteFile(fullPath, file, 0o600) require.NoError(t, err, "failed to write file to custom path") } else { h.CopyToProvisioningPath(t, from, to) } } - // Trigger and wait for initial sync to populate resources - h.SyncAndWait(t, repo.Name, nil) - - // Debug state after initial sync - h.DebugState(t, repo.Name, "AFTER INITIAL SYNC") + if !repo.SkipSync { + // Trigger and wait for initial sync to populate resources + h.SyncAndWait(t, repo.Name, nil) + h.DebugState(t, repo.Name, "AFTER INITIAL SYNC") + } else { + h.DebugState(t, repo.Name, "AFTER REPO CREATION") + } // Verify initial state - dashboards, err := h.DashboardsV1.Resource.List(t.Context(), metav1.ListOptions{}) - require.NoError(t, err) - require.Equal(t, repo.ExpectedDashboards, len(dashboards.Items), "should the expected dashboards after sync") + if !repo.SkipResourceAssertions { + dashboards, err := h.DashboardsV1.Resource.List(t.Context(), metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, repo.ExpectedDashboards, len(dashboards.Items), "should the expected dashboards after sync") + folders, err := h.Folders.Resource.List(t.Context(), metav1.ListOptions{}) + require.NoError(t, err) + require.Equal(t, repo.ExpectedFolders, len(folders.Items), "should have the expected folders after sync") + } +} - folders, err := h.Folders.Resource.List(t.Context(), metav1.ListOptions{}) - require.NoError(t, err) - require.Equal(t, repo.ExpectedFolders, len(folders.Items), "should have the expected folders after sync") +// WaitForHealthyRepository waits for a repository to become healthy. +func (h *provisioningTestHelper) WaitForHealthyRepository(t *testing.T, name string) { + require.EventuallyWithT(t, func(collect *assert.CollectT) { + repoStatus, err := h.Repositories.Resource.Get(t.Context(), name, metav1.GetOptions{}) + if !assert.NoError(collect, err, "failed to get repository status") { + return + } + errType := mustNestedString(repoStatus.Object, "status", "health", "error") + assert.Empty(collect, errType, "repository %s has health error: %s", name, errType) + msgs := mustNestedStringSlice(repoStatus.Object, "status", "health", "message") + assert.Empty(collect, msgs, "repository %s has health messages: %v", name, msgs) + status, found := mustNestedBool(repoStatus.Object, "status", "health", "healthy") + assert.True(collect, found, "repository %s does not have health status", name) + assert.True(collect, status, "repository %s is not healthy yet", name) + }, time.Second*10, time.Millisecond*50, "repository %s should become healthy", name) } type grafanaOption func(opts *testinfra.GrafanaOpts) @@ -655,6 +687,15 @@ func mustNestedString(obj map[string]interface{}, fields ...string) string { return v } +func mustNestedBool(obj map[string]interface{}, fields ...string) (bool, bool) { + v, found, err := unstructured.NestedBool(obj, fields...) + if err != nil { + panic(err) + } + + return v, found +} + func mustNestedStringSlice(obj map[string]interface{}, fields ...string) []string { v, _, err := unstructured.NestedStringSlice(obj, fields...) if err != nil { diff --git a/pkg/tests/apis/provisioning/movejob_test.go b/pkg/tests/apis/provisioning/movejob_test.go index 770f0f9bce9..166d6653a7a 100644 --- a/pkg/tests/apis/provisioning/movejob_test.go +++ b/pkg/tests/apis/provisioning/movejob_test.go @@ -197,16 +197,6 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) { assert.True(collect, apierrors.IsNotFound(err), "repository should be deleted") }, time.Second*5, time.Millisecond*50, "repository should be deleted before creating new one") - // Create a unique repository for resource reference testing to avoid contamination - const refRepo = "move-ref-test-repo" - localRefTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": refRepo, - "SyncEnabled": true, - "SyncTarget": "instance", - }) - _, err = helper.Repositories.Resource.Create(ctx, localRefTmp, metav1.CreateOptions{}) - require.NoError(t, err) - // Create modified test files with unique UIDs for ResourceRef testing allPanelsContent := helper.LoadFile("testdata/all-panels.json") textOptionsContent := helper.LoadFile("testdata/text-options.json") @@ -232,8 +222,12 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) { helper.CopyToProvisioningPath(t, tmpFile2, "move-source-2.json") helper.CopyToProvisioningPath(t, tmpFile3, "move-source-3.json") - // Sync to populate resources in Grafana - helper.SyncAndWait(t, refRepo, nil) + // Create a unique repository for resource reference testing to avoid contamination + const refRepo = "move-ref-test-repo" + helper.CreateRepo(t, TestRepo{ + Name: refRepo, + SkipResourceAssertions: true, // HACK: I am not sure why sometimes it's 6 or 3 dashbaords. + }) t.Run("move single dashboard by resource reference", func(t *testing.T) { spec := provisioning.JobSpec{ diff --git a/pkg/tests/apis/provisioning/repository_test.go b/pkg/tests/apis/provisioning/repository_test.go index e3213517869..ad6a254059e 100644 --- a/pkg/tests/apis/provisioning/repository_test.go +++ b/pkg/tests/apis/provisioning/repository_test.go @@ -40,11 +40,11 @@ func TestIntegrationProvisioning_CreatingAndGetting(t *testing.T) { for _, inputFilePath := range inputFiles { t.Run(inputFilePath, func(t *testing.T) { input := helper.RenderObject(t, inputFilePath, nil) + name := mustNestedString(input.Object, "metadata", "name") _, err := helper.Repositories.Resource.Create(ctx, input, createOptions) require.NoError(t, err, "failed to create resource") - name := mustNestedString(input.Object, "metadata", "name") output, err := helper.Repositories.Resource.Get(ctx, name, metav1.GetOptions{}) require.NoError(t, err, "failed to read back resource") @@ -257,18 +257,15 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { // ) const repo = "github-create-test" - _, err := helper.Repositories.Resource.Create(ctx, - helper.RenderObject(t, "testdata/github-readonly.json.tmpl", map[string]any{ - "Name": repo, - "SyncEnabled": true, - "SyncTarget": "instance", - "Path": "grafana/", - }), - metav1.CreateOptions{}, - ) - require.NoError(t, err) + testRepo := TestRepo{ + Name: repo, + Template: "testdata/github-readonly.json.tmpl", + Target: "folder", + ExpectedDashboards: 3, + ExpectedFolders: 3, // Folder sync creates an additional folder for the repository itself + } - helper.SyncAndWait(t, repo, nil) + helper.CreateRepo(t, testRepo) // By now, we should have synced, meaning we have data to read in the local Grafana instance! @@ -324,10 +321,12 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + // Create repository directly without health checks since we're only testing URL cleanup input := helper.RenderObject(t, "testdata/github-readonly.json.tmpl", map[string]any{ - "Name": test.name, - "URL": test.input, - "SyncTarget": "instance", + "Name": test.name, + "URL": test.input, + "SyncTarget": "folder", + "SyncEnabled": false, // Disable sync since we're just testing URL cleanup }) _, err := helper.Repositories.Resource.Create(ctx, input, metav1.CreateOptions{}) @@ -353,7 +352,7 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { }) } -func TestIntegrationProvisioning_InstanceSyncValidation(t *testing.T) { +func TestIntegrationProvisioning_RepositoryLimits(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } @@ -361,112 +360,20 @@ func TestIntegrationProvisioning_InstanceSyncValidation(t *testing.T) { helper := runGrafana(t) ctx := context.Background() - t.Run("single instance sync is allowed", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) - - repoName := "instance-repo-single" - testRepo := TestRepo{ - Name: repoName, - Target: "instance", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 0, - } - - // Create instance sync repository - should succeed - helper.CreateRepo(t, testRepo) - - // Clean up at end of test - helper.CleanupAllRepos(t) - }) - - t.Run("instance sync rejected when any other repository exists", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) - - existingFolderName := "existing-folder-repo" - instanceRepoName := "instance-repo-blocked" - - // Create a folder sync repository first - folderTestRepo := TestRepo{ - Name: existingFolderName, - Target: "folder", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 1, // One folder expected after sync - } - helper.CreateRepo(t, folderTestRepo) - - // Try to create an instance sync repository - should fail because any other repository exists - instanceRepo := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": instanceRepoName, - "SyncEnabled": true, - "SyncTarget": "instance", - }) - - _, err := helper.Repositories.Resource.Create(ctx, instanceRepo, metav1.CreateOptions{FieldValidation: "Strict"}) - require.Error(t, err, "instance sync repository should be rejected when any other repository exists") - - // Verify the error message mentions that instance can only be created when no other repositories exist - statusError := helper.RequireApiErrorStatus(err, metav1.StatusReasonInvalid, http.StatusUnprocessableEntity) - require.Contains(t, statusError.Message, "Instance repository can only be created when no other repositories exist. Found: "+existingFolderName) - - // Clean up at end of test - helper.CleanupAllRepos(t) - }) - - t.Run("multiple folder syncs are allowed", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) - - firstFolderName := "folder-repo-multi-1" - secondFolderName := "folder-repo-multi-2" - - // Create first folder sync repository - folderTestRepo1 := TestRepo{ - Name: firstFolderName, - Target: "folder", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 1, // One folder expected after sync - } - helper.CreateRepo(t, folderTestRepo1) - - // Create second folder sync repository - should succeed - folderTestRepo2 := TestRepo{ - Name: secondFolderName, - Target: "folder", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 2, // Two folders expected after sync (1 + 1) - } - helper.CreateRepo(t, folderTestRepo2) - - // Clean up at end of test - helper.CleanupAllRepos(t) - }) + originalName := "original-repo" + // Create instance sync repository first + originalRepo := TestRepo{ + Name: originalName, + Target: "instance", + Copies: map[string]string{}, // No files needed for this test + ExpectedDashboards: 0, + ExpectedFolders: 0, + } + helper.CreateRepo(t, originalRepo) t.Run("folder sync is rejected when instance sync exists", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) - - instanceRepoName := "instance-blocking-folder" - folderRepoName := "folder-blocked-by-instance" - - // Create instance sync repository first - instanceTestRepo := TestRepo{ - Name: instanceRepoName, - Target: "instance", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 0, - } - helper.CreateRepo(t, instanceTestRepo) - - // Try to create folder sync repository - should fail folderRepo := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": folderRepoName, + "Name": "folder-blocked-by-instance", "SyncEnabled": true, "SyncTarget": "folder", }) @@ -476,55 +383,56 @@ func TestIntegrationProvisioning_InstanceSyncValidation(t *testing.T) { // Verify the error message mentions the existing instance repository statusError := helper.RequireApiErrorStatus(err, metav1.StatusReasonInvalid, http.StatusUnprocessableEntity) - require.Contains(t, statusError.Message, "Cannot create folder repository when instance repository exists: "+instanceRepoName) - - // Clean up at end of test - helper.CleanupAllRepos(t) + require.Contains(t, statusError.Message, "Cannot create folder repository when instance repository exists: "+originalName) }) - t.Run("instance sync can only be created when no repositories exist", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) + t.Run("change between folder and instance sync for the same repository if no previous sync happened", func(t *testing.T) { + repo, err := helper.Repositories.Resource.Get(ctx, originalName, metav1.GetOptions{}) + require.NoError(t, err, "failed to get repository") + err = unstructured.SetNestedField(repo.Object, "folder", "spec", "sync", "target") + require.NoError(t, err, "failed to set syncTarget to folder") + _, err = helper.Repositories.Resource.Update(ctx, repo, metav1.UpdateOptions{FieldValidation: "Strict"}) + require.NoError(t, err, "failed to update repository to folder sync") - // This test verifies that instance sync can ONLY be created when there are no other repositories - instanceRepoName := "instance-only-when-empty" + // Verify that the repository is now a folder sync + // We verify with the listing APIs because it may take some time for the update to propagate + require.Eventually(t, func() bool { + repos, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{}) + if err != nil { + return false + } - // First, create instance sync repository when no other repositories exist - should succeed - instanceTestRepo := TestRepo{ - Name: instanceRepoName, - Target: "instance", - Copies: map[string]string{}, // No files needed for this test - ExpectedDashboards: 0, - ExpectedFolders: 0, - } - helper.CreateRepo(t, instanceTestRepo) + for _, repo := range repos.Items { + if repo.GetName() == originalName { + syncTarget, found, err := unstructured.NestedString(repo.Object, "spec", "sync", "target") + if err != nil || !found { + return false + } - // Now try to create any other repository - should fail - otherRepoName := "other-repo-blocked" - otherRepo := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": otherRepoName, + return syncTarget == "folder" + } + } + + return false + }, time.Second*10, time.Millisecond*100, "failed to verify that sync target is folder") + }) + + t.Run("instance sync rejected when any other repository exists", func(t *testing.T) { + instanceRepo := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": "instance-repo-blocked", "SyncEnabled": true, - "SyncTarget": "folder", + "SyncTarget": "instance", }) - _, err := helper.Repositories.Resource.Create(ctx, otherRepo, metav1.CreateOptions{FieldValidation: "Strict"}) - require.Error(t, err, "folder sync repository should be rejected when instance sync exists") + _, err := helper.Repositories.Resource.Create(ctx, instanceRepo, metav1.CreateOptions{FieldValidation: "Strict"}) + require.Error(t, err, "instance sync repository should be rejected when any other repository exists") statusError := helper.RequireApiErrorStatus(err, metav1.StatusReasonInvalid, http.StatusUnprocessableEntity) - require.Contains(t, statusError.Message, "Cannot create folder repository when instance repository exists: "+instanceRepoName) - - // Clean up at end of test - helper.CleanupAllRepos(t) + require.Contains(t, statusError.Message, "Instance repository can only be created when no other repositories exist. Found: "+originalName) }) - t.Run("repository limit validation", func(t *testing.T) { - // Ensure clean state - helper.CleanupAllRepos(t) - - // This test verifies the 10 repository limit validation by actually creating 10 repositories - - // Create 10 repositories - should all succeed - for i := 1; i <= 10; i++ { + t.Run("repository limit validation of 10 for folder syncs repositories", func(t *testing.T) { + for i := 2; i <= 10; i++ { repoName := fmt.Sprintf("limit-test-repo-%d", i) limitTestRepo := TestRepo{ Name: repoName, @@ -547,12 +455,8 @@ func TestIntegrationProvisioning_InstanceSyncValidation(t *testing.T) { _, err := helper.Repositories.Resource.Create(ctx, eleventhRepo, metav1.CreateOptions{FieldValidation: "Strict"}) require.Error(t, err, "11th repository should be rejected due to limit") - // Verify the error message mentions the repository limit statusError := helper.RequireApiErrorStatus(err, metav1.StatusReasonInvalid, http.StatusUnprocessableEntity) require.Contains(t, statusError.Message, "Maximum number of 10 repositories reached") - - // Clean up at end of test - helper.CleanupAllRepos(t) }) } @@ -569,11 +473,7 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) { const targetPath = "all-panels.json" // Set up the repository. - localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{"Name": repo}) - obj, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) - require.NoError(t, err) - name, _, _ := unstructured.NestedString(obj.Object, "metadata", "name") - require.Equal(t, repo, name, "wrote the expected name") + helper.CreateRepo(t, TestRepo{Name: repo}) // Write a file -- this will create it *both* in the local file system, and in grafana t.Run("write all panels", func(t *testing.T) { @@ -678,7 +578,7 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) { require.Error(t, result.Error(), "invalid path should return error") // Read a file with a bad path - _, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "../../all-panels.json") + _, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "../../all-panels.json") require.Error(t, err, "invalid path should error") }) @@ -718,7 +618,7 @@ spec: err = json.Unmarshal(raw, obj) require.NoError(t, err) - name, _, _ = unstructured.NestedString(obj.Object, "resource", "upsert", "metadata", "name") + name, _, _ := unstructured.NestedString(obj.Object, "resource", "upsert", "metadata", "name") require.True(t, strings.HasPrefix(name, "prefix-"), "should generate name") }) } @@ -739,15 +639,15 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T const repo = "local-tmp" // Set up the repository and the file to import. - helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "all-panels.json") - localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ - "Name": repo, - "SyncEnabled": true, - }) - + testRepo := TestRepo{ + Name: repo, + Target: "instance", + Copies: map[string]string{"testdata/all-panels.json": "all-panels.json"}, + ExpectedDashboards: 1, + ExpectedFolders: 0, + } // We create the repository - _, err = helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{}) - require.NoError(t, err) + helper.CreateRepo(t, testRepo) // Now, we import it, such that it may exist // The sync may not be necessary as the sync may have happened automatically at this point diff --git a/pkg/tests/apis/provisioning/secrets_test.go b/pkg/tests/apis/provisioning/secrets_test.go index 331d4bc3067..ba571fe25bb 100644 --- a/pkg/tests/apis/provisioning/secrets_test.go +++ b/pkg/tests/apis/provisioning/secrets_test.go @@ -9,14 +9,105 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) +func TestIntegrationProvisioning_InlineSecrets(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + helper := runGrafana(t, useAppPlatformSecrets) + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + ctx := context.Background() + + decryptService := helper.GetEnv().DecryptService + require.NotNil(t, decryptService, "decrypt service wired properly") + + type expectedField struct { + Path []string + DecryptedValue string // only try decrypting if not empty + } + + tests := []struct { + name string + values map[string]any + inputFile string + expectedFields []expectedField + }{ + { + name: "inline github token encrypted", + values: map[string]any{ + "SecureTokenCreate": "some-token", + "SecureWebhookSecretCreate": "some-secret", + }, + inputFile: "testdata/github-with-inline-secrets.json.tmpl", + expectedFields: []expectedField{ + { + Path: []string{"secure", "token", "name"}, + DecryptedValue: "some-token", + }, + { + Path: []string{"secure", "webhookSecret", "name"}, + DecryptedValue: "some-secret", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + input := helper.RenderObject(t, test.inputFile, test.values) + obj, err := helper.Repositories.Resource.Create(ctx, input, createOptions) + require.NoError(t, err, "failed to create resource") + require.True(t, strings.HasPrefix(obj.GetName(), "test-"), "created a unique name") + var created []string + + // Move encrypted token mutation + for _, expectedField := range test.expectedFields { + name, found, err := unstructured.NestedString(obj.Object, expectedField.Path...) + require.NoError(t, err, "error getting expected path") + require.True(t, found, expectedField.Path) + require.NotEmpty(t, name, expectedField.Path) + created = append(created, name) + + if expectedField.DecryptedValue != "" { + decrypted, err := decryptService.Decrypt(ctx, "provisioning.grafana.app", obj.GetNamespace(), name) + require.NoError(t, err, "decryption error") + require.Len(t, decrypted, 1) + + val := decrypted[name].Value() + require.NotNil(t, val) + require.Equal(t, expectedField.DecryptedValue, val.DangerouslyExposeAndConsumeValue()) + } + } + + err = helper.Repositories.Resource.Delete(ctx, obj.GetName(), metav1.DeleteOptions{}) + require.NoError(t, err, "failed to delete repository") + + // Finalizers will be running async... so we need to wait until it is actually removed + require.Eventually(t, func() bool { + _, err := helper.Repositories.Resource.Get(ctx, obj.GetName(), metav1.GetOptions{}) + return apierrors.IsNotFound(err) + }, time.Second*15, time.Millisecond*300, "should be removed") + + // now check that we can no longer decrypt the requested values + results, err := decryptService.Decrypt(ctx, "provisioning.grafana.app", obj.GetNamespace(), created...) + require.NoError(t, err, "failed to execute decrypt with removed secrets") + for k, v := range results { + require.ErrorContains(t, v.Error(), "not found", "expecting not found error for all secrets: %s", k) + } + }) + } +} + func TestIntegrationProvisioning_LegacySecrets(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") diff --git a/pkg/tests/apis/provisioning/testdata/github-with-inline-secrets.json.tmpl b/pkg/tests/apis/provisioning/testdata/github-with-inline-secrets.json.tmpl new file mode 100644 index 00000000000..c867b6472d0 --- /dev/null +++ b/pkg/tests/apis/provisioning/testdata/github-with-inline-secrets.json.tmpl @@ -0,0 +1,23 @@ +{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "metadata": { + "generateName": "test-" + }, + "spec": { + "title": "title", + "description": "something", + "type": "github", + "github": { + "url": "{{ or .URL "https://github.com/grafana/grafana-git-sync-demo" }}", + "branch": "{{ or .Branch "integration-test" }}", + "generateDashboardPreviews": {{ if .GenerateDashboardPreviews }} true {{ else }} false {{ end }}, + "token": "{{ or .Token "" }}", + "path": "{{ or .Path "grafana/" }}" + } + }, + "secure": { + "token": { "create": "{{ or .SecureTokenCreate "" }}" }, + "webhookSecret": { "create": "{{ or .SecureWebhookSecretCreate "" }}" } + } +} diff --git a/playwright.config.ts b/playwright.config.ts index e1a6b654e73..279ceb8a3f4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices, PlaywrightTestConfig, Project } from '@playwright/test'; import path, { dirname } from 'path'; import { PluginOptions } from '@grafana/plugin-e2e'; @@ -7,6 +7,19 @@ const testDirRoot = 'e2e-playwright'; const pluginDirRoot = path.join(testDirRoot, 'plugin-e2e'); const DEFAULT_URL = 'http://localhost:3001'; +function withAuth(project: Project): Project { + project.dependencies ??= []; + project.use ??= {}; + + project.dependencies = project.dependencies.concat('authenticate'); + project.use = { + ...project.use, + storageState: `playwright/.auth/${process.env.GRAFANA_ADMIN_USER || 'admin'}.json`, + }; + + return project; +} + export default defineConfig({ fullyParallel: true, /* Retry on CI only */ @@ -20,6 +33,7 @@ export default defineConfig({ timeout: 10_000, }, use: { + ...devices['Desktop Chrome'], baseURL: process.env.GRAFANA_URL ?? DEFAULT_URL, trace: 'retain-on-failure', httpCredentials: { @@ -59,15 +73,10 @@ export default defineConfig({ }, }, // Run all tests in parallel using user with admin role - { + withAuth({ name: 'admin', testDir: path.join(pluginDirRoot, '/plugin-e2e-api-tests/as-admin-user'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), // Run all tests in parallel using user with viewer role { name: 'viewer', @@ -78,210 +87,97 @@ export default defineConfig({ }, dependencies: ['createUserAndAuthenticate'], }, - { + withAuth({ name: 'elasticsearch', testDir: path.join(pluginDirRoot, '/elasticsearch'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'mysql', testDir: path.join(pluginDirRoot, '/mysql'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'mssql', testDir: path.join(pluginDirRoot, '/mssql'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'extensions-test-app', testDir: path.join(testDirRoot, '/test-plugins/grafana-extensionstest-app'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'grafana-e2etest-datasource', testDir: path.join(testDirRoot, '/test-plugins/grafana-test-datasource'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloudwatch', testDir: path.join(pluginDirRoot, '/cloudwatch'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'azuremonitor', testDir: path.join(pluginDirRoot, '/azuremonitor'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloudmonitoring', testDir: path.join(pluginDirRoot, '/cloudmonitoring'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'graphite', testDir: path.join(pluginDirRoot, '/graphite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'influxdb', testDir: path.join(pluginDirRoot, '/influxdb'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'opentsdb', testDir: path.join(pluginDirRoot, '/opentsdb'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'jaeger', testDir: path.join(pluginDirRoot, '/jaeger'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'grafana-postgresql-datasource', testDir: path.join(pluginDirRoot, '/grafana-postgresql-datasource'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'canvas', testDir: path.join(testDirRoot, '/canvas'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'zipkin', testDir: path.join(pluginDirRoot, '/zipkin'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), { name: 'unauthenticated', testDir: path.join(testDirRoot, '/unauthenticated'), - use: { - ...devices['Desktop Chrome'], - }, }, - { + withAuth({ name: 'various', testDir: path.join(testDirRoot, '/various-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'panels', testDir: path.join(testDirRoot, '/panels-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'smoke', testDir: path.join(testDirRoot, '/smoke-tests-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'dashboards', testDir: path.join(testDirRoot, '/dashboards-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'loki', testDir: path.join(testDirRoot, '/loki'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'cloud-plugins', testDir: path.join(testDirRoot, '/cloud-plugins-suite'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, - { + }), + withAuth({ name: 'dashboard-new-layouts', testDir: path.join(testDirRoot, '/dashboard-new-layouts'), - use: { - ...devices['Desktop Chrome'], - storageState: 'playwright/.auth/admin.json', - }, - dependencies: ['authenticate'], - }, + }), ], }); diff --git a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts index 33ee7549eb7..b20424ce1ad 100644 --- a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts +++ b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts @@ -658,6 +658,7 @@ export type CreateRepositoryTestApiArg = { /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ kind?: string; metadata?: any; + secure?: any; spec?: any; status?: any; }; @@ -909,6 +910,37 @@ export type JobList = { kind?: string; metadata?: ListMeta; }; +export type InlineSecureValue = + | { + /** Create a secure value -- this is only used for POST/PUT */ + create?: string; + /** Name in the secret service (reference) */ + name: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove?: boolean; + } + | { + /** Create a secure value -- this is only used for POST/PUT */ + create: string; + /** Name in the secret service (reference) */ + name?: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove?: boolean; + } + | { + /** Create a secure value -- this is only used for POST/PUT */ + create?: string; + /** Name in the secret service (reference) */ + name?: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove: boolean; + }; +export type SecureValues = { + /** Token used to connect the configured repository */ + token?: InlineSecureValue; + /** Some webhooks (github) require a secret key value */ + webhookSecret?: InlineSecureValue; +}; export type BitbucketRepositoryConfig = { /** The branch to use in the repository. */ branch: string; @@ -1018,6 +1050,12 @@ export type RepositorySpec = { export type HealthStatus = { /** When the health was checked last time */ checked?: number; + /** The type of the error + + Possible enum values: + - `"health"` + - `"hook"` */ + error?: 'health' | 'hook'; /** When not healthy, requests will not be executed */ healthy: boolean; /** Summary messages (can be shown to users) Will only be populated when not healthy */ @@ -1079,6 +1117,7 @@ export type Repository = { /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ kind?: string; metadata?: ObjectMeta; + secure?: SecureValues; spec?: RepositorySpec; status?: RepositoryStatus; }; diff --git a/public/app/api/clients/provisioning/v0alpha1/index.ts b/public/app/api/clients/provisioning/v0alpha1/index.ts index d99727c062c..5b59f304aa2 100644 --- a/public/app/api/clients/provisioning/v0alpha1/index.ts +++ b/public/app/api/clients/provisioning/v0alpha1/index.ts @@ -1,5 +1,7 @@ import { t } from '@grafana/i18n'; import { isFetchError } from '@grafana/runtime'; +import { clearFolders } from 'app/features/browse-dashboards/state/slice'; +import { getState } from 'app/store/store'; import { notifyApp } from '../../../../core/actions'; import { createSuccessNotification, createErrorNotification } from '../../../../core/copy/appNotification'; @@ -209,6 +211,24 @@ export const provisioningAPIv0alpha1 = generatedAPI.enhanceEndpoints({ dispatch(refetchChildren({ parentUID: undefined, pageSize: PAGE_SIZE })); }, }, + getRepositoryJobsWithPath: { + onQueryStarted: async (_, { queryFulfilled, dispatch }) => { + try { + const result = await queryFulfilled; + const job = result.data; + + // Clear folder cache after successful move/delete jobs + // We use clearFolders here to clear cached data and closes folders (immediate visual feedback) + // Force a refetch of subfolders if user has opened them, so user see latest data + if (job.status?.state === 'success' && (job.spec?.action === 'delete' || job.spec?.action === 'move')) { + const state = getState().browseDashboards; + dispatch(clearFolders(Object.keys(state.childrenByParentUID))); + } + } catch (e) { + console.error('Error in getRepositoryJobsWithPath:', e); + } + }, + }, }, }); diff --git a/public/app/app.ts b/public/app/app.ts index 01fc1068130..05f6a2e0ab0 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -136,11 +136,11 @@ export class GrafanaApp { window.parent.postMessage('GrafanaAppInit', '*'); const regionalFormat = config.featureToggles.localeFormatPreference ? config.regionalFormat - : config.bootData.user.language; + : contextSrv.user.language; const initI18nPromise = initializeI18n( { - language: config.bootData.user.language, + language: contextSrv.user.language, ns: NAMESPACES, module: loadTranslations, }, @@ -163,7 +163,7 @@ export class GrafanaApp { startMeasure('frontend_app_init'); setLocale(config.regionalFormat); - setWeekStart(config.bootData.user.weekStart); + setWeekStart(contextSrv.user.weekStart); setPanelRenderer(PanelRenderer); setPluginPage(PluginPage); setFolderPicker(LazyFolderPicker); @@ -171,7 +171,7 @@ export class GrafanaApp { setLocationSrv(locationService); setCorrelationsService(new CorrelationsService()); setEmbeddedDashboard(EmbeddedDashboardLazy); - setTimeZoneResolver(() => config.bootData.user.timezone); + setTimeZoneResolver(() => contextSrv.user.timezone); initGrafanaLive(); setCurrentUser(contextSrv.user); @@ -372,8 +372,8 @@ async function initEchoSrv() { }, buildInfo: config.buildInfo, user: { - id: String(config.bootData.user?.id), - email: config.bootData.user?.email, + id: String(contextSrv.user?.id), + email: contextSrv.user?.email, }, ignoreUrls: rudderstackUrls, }) @@ -405,7 +405,7 @@ async function initEchoSrv() { new RudderstackBackend({ writeKey: config.rudderstackWriteKey, dataPlaneUrl: config.rudderstackDataPlaneUrl, - user: config.bootData.user, + user: contextSrv.user, sdkUrl: config.rudderstackSdkUrl, configUrl: config.rudderstackConfigUrl, integrationsUrl: config.rudderstackIntegrationsUrl, diff --git a/public/app/core/components/AppChrome/MegaMenu/hooks.ts b/public/app/core/components/AppChrome/MegaMenu/hooks.ts index 35c1f4be482..db8bb2807f9 100644 --- a/public/app/core/components/AppChrome/MegaMenu/hooks.ts +++ b/public/app/core/components/AppChrome/MegaMenu/hooks.ts @@ -3,8 +3,10 @@ import { useMemo } from 'react'; import { config } from '@grafana/runtime'; import { useGetUserPreferencesQuery } from 'app/features/preferences/api'; +import { contextSrv } from '../../../services/context_srv'; + export const usePinnedItems = () => { - const preferences = useGetUserPreferencesQuery(undefined, { skip: !config.bootData.user.isSignedIn }); + const preferences = useGetUserPreferencesQuery(undefined, { skip: !contextSrv.user.isSignedIn }); const pinnedItems = useMemo(() => preferences.data?.navbar?.bookmarkUrls || [], [preferences]); if (config.featureToggles.pinNavItems) { diff --git a/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx b/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx index 4effc8dc94b..c6903685aef 100644 --- a/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx +++ b/public/app/core/components/AppChrome/TopBar/SignInLink.test.tsx @@ -8,6 +8,7 @@ import { SignInLink } from './SignInLink'; jest.mock('app/core/services/context_srv', () => ({ contextSrv: { + ...jest.requireActual('app/core/services/context_srv').contextSrv, setRedirectToUrl: jest.fn(), }, })); diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx index 4d8d94dbcef..3f36302a7f5 100644 --- a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx @@ -40,6 +40,7 @@ export const TopSearchBarCommandPaletteTrigger = React.memo(() => { return ; }); +TopSearchBarCommandPaletteTrigger.displayName = 'TopSearchBarCommandPaletteTrigger'; interface PretendTextInputProps { onClick: () => void; diff --git a/public/app/core/components/NestedFolderPicker/FolderRepo.tsx b/public/app/core/components/NestedFolderPicker/FolderRepo.tsx index be50e30b5be..b0f750d991d 100644 --- a/public/app/core/components/NestedFolderPicker/FolderRepo.tsx +++ b/public/app/core/components/NestedFolderPicker/FolderRepo.tsx @@ -1,6 +1,7 @@ import { t } from '@grafana/i18n'; import { Badge, Stack } from '@grafana/ui'; import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView'; +import { useIsProvisionedInstance } from 'app/features/provisioning/hooks/useIsProvisionedInstance'; import { getReadOnlyTooltipText } from 'app/features/provisioning/utils/repository'; import { NestedFolderDTO } from 'app/features/search/service/types'; import { FolderDTO, FolderListItemDTO } from 'app/types/folders'; @@ -14,7 +15,10 @@ export function FolderRepo({ folder }: Props) { // folder is not present // folder have parentUID // folder is not managed - const skipRender = !folder || ('parentUID' in folder && folder.parentUID) || !folder.managedBy; + // if whole instance is provisioned + const isProvisionedInstance = useIsProvisionedInstance(); + const skipRender = + !folder || ('parentUID' in folder && folder.parentUID) || !folder.managedBy || isProvisionedInstance; const { isReadOnlyRepo, repoType } = useGetResourceRepositoryView({ folderName: skipRender ? undefined : folder?.uid, diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index bfb965a73bd..4975bf8884d 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -104,3 +104,4 @@ export const DashboardPicker = forwardRef( ); } ); +DashboardPicker.displayName = 'DashboardPicker'; diff --git a/public/app/core/navigation/patch/interceptLinkClicks.ts b/public/app/core/navigation/patch/interceptLinkClicks.ts index 7a83a6f5302..0421f599f16 100644 --- a/public/app/core/navigation/patch/interceptLinkClicks.ts +++ b/public/app/core/navigation/patch/interceptLinkClicks.ts @@ -1,6 +1,7 @@ import { locationUtil, urlUtil } from '@grafana/data'; import { locationService, navigationLogger } from '@grafana/runtime'; -import { config } from 'app/core/config'; + +import { contextSrv } from '../../services/context_srv'; export function interceptLinkClicks(e: MouseEvent) { const anchor = e.target instanceof Element && getParentAnchor(e.target); @@ -16,7 +17,7 @@ export function interceptLinkClicks(e: MouseEvent) { if (href && !target) { const params = urlUtil.parseKeyValue(href.split('?')[1]); - const orgIdChange = params.orgId && Number(params.orgId) !== config.bootData.user.orgId; + const orgIdChange = params.orgId && Number(params.orgId) !== contextSrv.user.orgId; navigationLogger('utils', false, 'intercepting link click', e); e.preventDefault(); diff --git a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts index 5b3c6c97a03..03ea4baef00 100644 --- a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts +++ b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts @@ -1,4 +1,4 @@ -import { BuildInfo, CurrentUserDTO } from '@grafana/data'; +import { BuildInfo } from '@grafana/data'; import { EchoBackend, EchoEventType, @@ -8,6 +8,7 @@ import { PageviewEchoEvent, } from '@grafana/runtime'; +import { User } from '../../../context_srv'; import { loadScript } from '../../utils'; type Properties = Record; @@ -37,7 +38,7 @@ export interface RudderstackBackendOptions { writeKey: string; dataPlaneUrl: string; buildInfo: BuildInfo; - user?: CurrentUserDTO; + user?: User; sdkUrl?: string; configUrl?: string; integrationsUrl?: string; diff --git a/public/app/core/services/impression_srv.ts b/public/app/core/services/impression_srv.ts index 3dcd3bdd39c..2801049a10e 100644 --- a/public/app/core/services/impression_srv.ts +++ b/public/app/core/services/impression_srv.ts @@ -1,9 +1,10 @@ import { filter, isArray, isNumber, isString } from 'lodash'; import { getBackendSrv } from '@grafana/runtime'; -import config from 'app/core/config'; import store from 'app/core/store'; +import { contextSrv } from './context_srv'; + export class ImpressionSrv { constructor() {} @@ -58,7 +59,7 @@ export class ImpressionSrv { } impressionKey() { - return 'dashboard_impressions-' + config.bootData.user.orgId; + return 'dashboard_impressions-' + contextSrv.user.orgId; } } diff --git a/public/app/core/specs/impression_srv.test.ts b/public/app/core/specs/impression_srv.test.ts index 32a479c36c3..692ce9435f8 100644 --- a/public/app/core/specs/impression_srv.test.ts +++ b/public/app/core/specs/impression_srv.test.ts @@ -7,14 +7,16 @@ jest.mock('@grafana/runtime', () => { return { ...originalRuntime, getBackendSrv: mockBackendSrv, - config: { - ...originalRuntime.config, - bootData: { - ...originalRuntime.config.bootData, - user: { - ...originalRuntime.config.bootData.user, - orgId: 'testOrgId', - }, + }; +}); + +jest.mock('app/core/services/context_srv', () => { + return { + contextSrv: { + ...jest.requireActual('app/core/services/context_srv').contextSrv, + user: { + ...jest.requireActual('app/core/services/context_srv').contextSrv.user, + orgId: 'testOrgId', }, }, }; diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index b6bfc95b006..53ebf12af8b 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -99,7 +99,7 @@ export const getShareUrlParams = ( const urlParamsUpdate: UrlQueryMap = {}; if (panel) { - urlParamsUpdate.viewPanel = panel.state.key; + urlParamsUpdate.viewPanel = panel.getPathId(); } if (opts.useAbsoluteTimeRange) { diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 7f1deda8289..4ff786b2bc4 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -4,7 +4,7 @@ import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testin import { dateTime } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config, locationService } from '@grafana/runtime'; +import { locationService } from '@grafana/runtime'; import { mockAlertRuleApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import { waitForServerRequest } from 'app/features/alerting/unified/mocks/server/events'; import { @@ -17,6 +17,8 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons import { MatcherOperator, SilenceState } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types/accessControl'; +import { contextSrv } from '../../../core/services/context_srv'; + import NewSilencePage from './NewSilencePage'; import ExistingSilenceEditorPage from './components/silences/SilencesEditor'; import SilencesTablePage from './components/silences/SilencesTable'; @@ -89,8 +91,8 @@ const ui = { }; const setUserLogged = (isLogged: boolean) => { - config.bootData.user.isSignedIn = isLogged; - config.bootData.user.name = isLogged ? 'admin' : ''; + contextSrv.user.isSignedIn = isLogged; + contextSrv.user.name = isLogged ? 'admin' : ''; }; const enterSilenceLabel = async (index: number, name: string, matcher: MatcherOperator, value: string) => { diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx index f41b228ffea..c3397893af5 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx @@ -12,6 +12,7 @@ import { getFiltersFromUrlParams } from '../../utils/misc'; import { AlertStateFilter } from './AlertStateFilter'; import { GroupBy } from './GroupBy'; import { MatcherFilter } from './MatcherFilter'; +import { ReceiverFilter } from './ReceiverFilter'; interface Props { groups: AlertmanagerGroup[]; @@ -20,7 +21,7 @@ interface Props { export const AlertGroupFilter = ({ groups }: Props) => { const [filterKey, setFilterKey] = useState(Math.floor(Math.random() * 100)); const [queryParams, setQueryParams] = useQueryParams(); - const { groupBy = [], queryString, alertState } = getFiltersFromUrlParams(queryParams); + const { groupBy = [], queryString, alertState, receivers = [] } = getFiltersFromUrlParams(queryParams); const matcherFilterKey = `matcher-${filterKey}`; const styles = useStyles2(getStyles); @@ -31,11 +32,12 @@ export const AlertGroupFilter = ({ groups }: Props) => { queryString: null, alertState: null, contactPoint: null, + receivers: null, }); setTimeout(() => setFilterKey(filterKey + 1), 100); }; - const showClearButton = !!(groupBy.length > 0 || queryString || alertState); + const showClearButton = !!(groupBy.length > 0 || queryString || alertState || receivers.length > 0); return (
@@ -50,6 +52,13 @@ export const AlertGroupFilter = ({ groups }: Props) => { groupBy={groupBy} onGroupingChange={(keys) => setQueryParams({ groupBy: keys.length ? keys.join(',') : null })} /> + + setQueryParams({ receivers: receivers.length ? receivers.join(',') : null }) + } + /> setQueryParams({ alertState: value ? value : null })} diff --git a/public/app/features/alerting/unified/components/alert-groups/ReceiverFilter.tsx b/public/app/features/alerting/unified/components/alert-groups/ReceiverFilter.tsx new file mode 100644 index 00000000000..42636da1a15 --- /dev/null +++ b/public/app/features/alerting/unified/components/alert-groups/ReceiverFilter.tsx @@ -0,0 +1,53 @@ +import { uniq } from 'lodash'; + +import { SelectableValue } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Icon, Label, MultiSelect, Tooltip } from '@grafana/ui'; +import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; + +const collator = new Intl.Collator('en', { sensitivity: 'accent' }); + +interface Props { + groups: AlertmanagerGroup[]; + receivers: string[]; + onReceiversChange: (receivers: string[]) => void; +} + +export const ReceiverFilter = ({ groups, receivers, onReceiversChange }: Props) => { + const receiverOptions = uniq(groups.map((group) => group.receiver.name)) + .map>((receiverName) => ({ + label: receiverName === 'NONE' ? t('alerting.receiver-filter.no-grouping', 'No grouping') : receiverName, + value: receiverName, + })) + .sort((a, b) => collator.compare(a.label || '', b.label || '')); + + return ( +
+ + + aria-label={t('alerting.receiver-filter.aria-label-contact-points', 'Filter by contact points')} + value={receivers} + placeholder={t('alerting.receiver-filter.placeholder-contact-point', 'Filter by contact point')} + prefix={} + onChange={(items) => { + onReceiversChange(items.map(({ value }) => value).filter((v): v is string => v !== undefined)); + }} + options={receiverOptions} + width={34} + /> +
+ ); +}; diff --git a/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx b/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx index 50dcafd10e6..ff4acf483d4 100644 --- a/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx +++ b/public/app/features/alerting/unified/components/folder-actions/DeleteModal.tsx @@ -54,3 +54,4 @@ export const DeleteModal = React.memo(({ onConfirm, onDismiss, isOpen, folderNam /> ); }); +DeleteModal.displayName = 'DeleteModal'; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 43579acbcc7..aa5e9afa9f1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -37,7 +37,7 @@ import { isExpressionQueryInAlert, } from '../../../rule-editor/formProcessing'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; -import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { GRAFANA_RULES_SOURCE_NAME, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; import { PromOrLokiQuery, isPromOrLokiQuery } from '../../../utils/rule-form'; import { isCloudAlertingRuleByType, @@ -422,7 +422,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod const typeInForm = getValues('type'); if (typeInForm === RuleFormType.cloudAlerting) { setValue('type', RuleFormType.grafana); - setValue('dataSourceName', null); // set data source name back to "null" + setValue('dataSourceName', GRAFANA_RULES_SOURCE_NAME); prevExpressions.length > 0 && restoreExpressionsInQueries(); prevCondition && setValue('condition', prevCondition); diff --git a/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx b/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx index 9cceef11d22..ca347fc9a05 100644 --- a/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx +++ b/public/app/features/alerting/unified/components/silences/SilenceGrafanaRuleDrawer.tsx @@ -37,5 +37,6 @@ const SilenceGrafanaRuleDrawer = React.memo( ), (prevProps, nextProps) => prevProps.ruleUid === nextProps.ruleUid ); +SilenceGrafanaRuleDrawer.displayName = 'SilenceGrafanaRuleDrawer'; export default SilenceGrafanaRuleDrawer; diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index 30154485057..021132493cb 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -14,7 +14,7 @@ import { parseDuration, } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { config, isFetchError, locationService } from '@grafana/runtime'; +import { isFetchError, locationService } from '@grafana/runtime'; import { Alert, Button, @@ -32,6 +32,7 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource'; import { MatcherOperator, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; +import { contextSrv } from '../../../../../core/services/context_srv'; import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities'; import { useAlertmanager } from '../../state/AlertmanagerContext'; import { SilenceFormFields } from '../../types/silence-form'; @@ -216,7 +217,7 @@ export const SilencesEditor = ({ [clearErrors, duration, endsAt, prevDuration, setValue, startsAt] ); - const userLogged = Boolean(config.bootData.user.isSignedIn && config.bootData.user.name); + const userLogged = Boolean(contextSrv.user.isSignedIn && contextSrv.user.name); return ( diff --git a/public/app/features/alerting/unified/components/silences/utils.ts b/public/app/features/alerting/unified/components/silences/utils.ts index 8c8088b9db8..a0909f7e647 100644 --- a/public/app/features/alerting/unified/components/silences/utils.ts +++ b/public/app/features/alerting/unified/components/silences/utils.ts @@ -1,11 +1,12 @@ import { DefaultTimeZone, addDurationToDate, dateTime, intervalToAbbreviatedDurationString } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { SilenceFormFields } from 'app/features/alerting/unified/types/silence-form'; import { matcherToMatcherField } from 'app/features/alerting/unified/utils/alertmanager'; import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/constants'; import { parseQueryParamMatchers } from 'app/features/alerting/unified/utils/matchers'; import { MatcherOperator, Silence } from 'app/plugins/datasource/alertmanager/types'; +import { contextSrv } from '../../../../../core/services/context_srv'; + /** * Parse query params and return default silence form values */ @@ -68,7 +69,7 @@ export const getDefaultSilenceFormValues = (partial?: Partial startsAt: now.toISOString(), endsAt: endsAt.toISOString(), comment: `created ${dateTime().format('YYYY-MM-DD HH:mm')}`, - createdBy: config.bootData.user.name, + createdBy: contextSrv.user.name, duration: '2h', isRegex: false, matcherName: '', diff --git a/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx b/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx index 7928a57ed25..48c0301ecc5 100644 --- a/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx +++ b/public/app/features/alerting/unified/group-details/components/DraggableRulesTable.tsx @@ -152,6 +152,7 @@ const ListItem = forwardRef( ); } ); +ListItem.displayName = 'ListItem'; const getStyles = (theme: GrafanaTheme2) => ({ listItem: css({ diff --git a/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts b/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts index 3d88eafd006..53f0578d504 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts @@ -9,12 +9,19 @@ import { getFiltersFromUrlParams } from '../utils/misc'; export const useFilteredAmGroups = (groups: AlertmanagerGroup[]) => { const [queryParams] = useQueryParams(); - const { queryString, alertState } = getFiltersFromUrlParams(queryParams); + const { queryString, alertState, receivers } = getFiltersFromUrlParams(queryParams); return useMemo(() => { const matchers = queryString ? parsePromQLStyleMatcherLooseSafe(queryString) : []; return groups.reduce((filteredGroup: AlertmanagerGroup[], group) => { + // Filter by receiver if specified + const receiverMatches = receivers && receivers.length > 0 ? receivers.includes(group.receiver.name) : true; + + if (!receiverMatches) { + return filteredGroup; + } + const alerts = group.alerts.filter(({ labels, status }) => { const labelsMatch = labelsMatchMatchers(labels, matchers); const filtersMatch = alertState ? status.state === alertState : true; @@ -30,5 +37,5 @@ export const useFilteredAmGroups = (groups: AlertmanagerGroup[]) => { } return filteredGroup; }, []); - }, [queryString, groups, alertState]); + }, [queryString, groups, alertState, receivers]); }; diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 6378aa33ddc..ff0a8f11d2e 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -9,7 +9,6 @@ import { PluginExtensionTypes, ReducerID, } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { DataQuery, defaultDashboard } from '@grafana/schema'; import { contextSrv } from 'app/core/services/context_srv'; import { MOCK_GRAFANA_ALERT_RULE_TITLE } from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler'; @@ -338,7 +337,7 @@ export const mockSilence = (partial: Partial = {}): Silence => { startsAt: new Date().toISOString(), endsAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(), updatedAt: new Date().toISOString(), - createdBy: config.bootData.user.name || 'admin', + createdBy: contextSrv.user.name || 'admin', comment: 'Silence noisy alerts', status: { state: SilenceState.Active, diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx index 2694bfc580d..f6819349f9e 100644 --- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx @@ -470,6 +470,7 @@ const DataSourceLogo = forwardRef(({ data /> ); }); +DataSourceLogo.displayName = 'DataSourceLogo'; const dataSourceLogoStyles = (theme: GrafanaTheme2) => ({ logo: css({ diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 4501be47b69..e7db8710c32 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -134,7 +134,8 @@ export const getFiltersFromUrlParams = (queryParams: UrlQueryMap): FilterState = const dataSource = queryParams.dataSource === undefined ? undefined : String(queryParams.dataSource); const ruleType = queryParams.ruleType === undefined ? undefined : String(queryParams.ruleType); const groupBy = queryParams.groupBy === undefined ? undefined : String(queryParams.groupBy).split(','); - return { queryString, alertState, dataSource, groupBy, ruleType }; + const receivers = queryParams.receivers === undefined ? undefined : String(queryParams.receivers).split(','); + return { queryString, alertState, dataSource, groupBy, ruleType, receivers }; }; export const getNotificationPoliciesFilters = (searchParams: URLSearchParams) => { diff --git a/public/app/features/apiserver/client.ts b/public/app/features/apiserver/client.ts index b4401ba0244..9aeda6c53ea 100644 --- a/public/app/features/apiserver/client.ts +++ b/public/app/features/apiserver/client.ts @@ -58,7 +58,7 @@ export class ScopedResourceClient implements .getStream>({ scope: LiveChannelScope.Watch, namespace: this.gvr.group, - path: `${this.gvr.version}/${this.gvr.resource}${query}/${config.bootData.user.uid}`, + path: `${this.gvr.version}/${this.gvr.resource}${query}/${contextSrv.user.uid}`, }) .pipe( filter((event) => isLiveChannelMessageEvent(event)), diff --git a/public/app/features/browse-dashboards/components/BrowseActions/useSelectionRepoValidation.ts b/public/app/features/browse-dashboards/components/BrowseActions/useSelectionRepoValidation.ts index cde7e9d56e5..b91cf70d01c 100644 --- a/public/app/features/browse-dashboards/components/BrowseActions/useSelectionRepoValidation.ts +++ b/public/app/features/browse-dashboards/components/BrowseActions/useSelectionRepoValidation.ts @@ -2,6 +2,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { config } from '@grafana/runtime'; import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1'; +import { useIsProvisionedInstance } from 'app/features/provisioning/hooks/useIsProvisionedInstance'; import { getIsReadOnlyRepo } from 'app/features/provisioning/utils/repository'; import { useSelector } from 'app/types/store'; @@ -15,6 +16,7 @@ export function useSelectionRepoValidation(selectedItems: Omit 0 ? repoUIDs[0] : undefined; const isCrossRepo = new Set(repoUIDs).size > 1; - const isInLockedRepo = (uid: string) => !selectedItemsRepoUID || getRepoUid(uid) === selectedItemsRepoUID; + const isInLockedRepo = (uid: string) => { + // if whole instance is provisioned, all items are considered in the locked (same) repo + if (isProvisionedInstance) { + return true; + } + return !selectedItemsRepoUID || getRepoUid(uid) === selectedItemsRepoUID; + }; const isUidInReadOnlyRepo = (uid: string) => { const repo = getRepositoryByUid(getRepoUid(uid)); return repo ? getIsReadOnlyRepo(repo) : false; diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index 5cd79f3341f..b5895c5e376 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -1,9 +1,13 @@ -import { useCallback } from 'react'; +import { skipToken } from '@reduxjs/toolkit/query'; +import { useCallback, useMemo } from 'react'; import { Trans, t } from '@grafana/i18n'; +import { config } from '@grafana/runtime'; import { CallToActionCard, EmptyState, LinkButton, TextLink } from '@grafana/ui'; +import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1'; +import { useIsProvisionedInstance } from 'app/features/provisioning/hooks/useIsProvisionedInstance'; import { DashboardViewItem } from 'app/features/search/types'; -import { useDispatch } from 'app/types/store'; +import { useDispatch, useSelector } from 'app/types/store'; import { PAGE_SIZE } from '../api/services'; import { fetchNextChildrenPage } from '../state/actions'; @@ -13,6 +17,7 @@ import { useChildrenByParentUIDState, useBrowseLoadingStatus, useLoadNextChildrenPage, + rootItemsSelector, } from '../state/hooks'; import { setFolderOpenState, setItemSelectionState, setAllSelection } from '../state/slice'; import { BrowseDashboardsState, DashboardTreeSelection, SelectionState, BrowseDashboardsPermissions } from '../types'; @@ -34,6 +39,27 @@ export function BrowseView({ folderUID, width, height, permissions }: BrowseView const selectedItems = useCheckboxSelectionState(); const childrenByParentUID = useChildrenByParentUIDState(); const canSelect = canSelectItems(permissions); + const isProvisionedInstance = useIsProvisionedInstance(); + const provisioningEnabled = config.featureToggles.provisioning; + const { data: settingsData } = useGetFrontendSettingsQuery(!provisioningEnabled ? skipToken : undefined); + const rootItems = useSelector(rootItemsSelector); + + const excludeUIDs = useMemo(() => { + if (isProvisionedInstance || !provisioningEnabled) { + return []; + } + if (provisioningEnabled) { + // if only one repo folder and no local folders, then don't exclude it from selection + if (rootItems?.items.length === 1 && settingsData?.items.length === 1) { + return []; + } + // loop through settingsData to find all available repo name, and exclude them from select all action + // repo root folder is not actionable on browse dashboards page + return settingsData?.items.map((repo) => repo.name); + } + + return []; + }, [isProvisionedInstance, settingsData, provisioningEnabled, rootItems]); const handleFolderClick = useCallback( (clickedFolderUID: string, isOpen: boolean) => { @@ -164,7 +190,7 @@ export function BrowseView({ folderUID, width, height, permissions }: BrowseView height={height} isSelected={isSelected} onFolderClick={handleFolderClick} - onAllSelectionChange={(newState) => dispatch(setAllSelection({ isSelected: newState, folderUID }))} + onAllSelectionChange={(newState) => dispatch(setAllSelection({ isSelected: newState, folderUID, excludeUIDs }))} onItemSelectionChange={handleItemSelectionChange} isItemLoaded={isItemLoaded} requestLoadMore={handleLoadMore} diff --git a/public/app/features/browse-dashboards/components/BulkActions/BulkMoveProvisionedResource.tsx b/public/app/features/browse-dashboards/components/BulkActions/BulkMoveProvisionedResource.tsx index f20d41d650d..9133fb2f72f 100644 --- a/public/app/features/browse-dashboards/components/BulkActions/BulkMoveProvisionedResource.tsx +++ b/public/app/features/browse-dashboards/components/BulkActions/BulkMoveProvisionedResource.tsx @@ -31,7 +31,7 @@ interface FormProps extends BulkActionProvisionResourceProps { folderPath?: string; } -function FormContent({ initialValues, selectedItems, repository, workflowOptions, folderPath, onDismiss }: FormProps) { +function FormContent({ initialValues, selectedItems, repository, workflowOptions, onDismiss }: FormProps) { // States const [job, setJob] = useState(); const [targetFolderUID, setTargetFolderUID] = useState(undefined); @@ -40,14 +40,24 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions // Hooks const { createBulkJob, isLoading: isCreatingJob } = useBulkActionJob(); const methods = useForm({ defaultValues: initialValues }); - const { handleSubmit, watch } = methods; + const { + handleSubmit, + watch, + setError, + clearErrors, + formState: { errors }, + } = methods; const workflow = watch('workflow'); // Get target folder data const { data: targetFolder } = useGetFolderQuery(targetFolderUID ? { name: targetFolderUID } : skipToken); const setupMoveOperation = () => { - const targetFolderPathInRepo = getTargetFolderPathInRepo({ targetFolder }); + const targetFolderPathInRepo = getTargetFolderPathInRepo({ + targetFolderUID, + targetFolder, + repoName: repository.name, + }); const resources = collectSelectedItems(selectedItems); return { targetFolderPathInRepo, resources }; @@ -60,12 +70,15 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions const { targetFolderPathInRepo, resources } = setupMoveOperation(); if (!targetFolderPathInRepo) { - throw new Error( - t( + setError('targetFolderUID', { + type: 'manual', + message: t( 'browse-dashboards.bulk-move-resources-form.error-no-target-folder-path', - 'Target folder path in repository is invalid, please select another folder.' - ) - ); + 'Target folder path is invalid or empty, please select again.' + ), + }); + setHasSubmitted(false); + return; } // Create the move job spec @@ -73,7 +86,7 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions action: 'move', move: { ref: data.workflow === 'write' ? undefined : data.ref, - targetPath: `${targetFolderPathInRepo}/`, + targetPath: targetFolderPathInRepo, resources, }, }; @@ -90,7 +103,7 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions result.error, ], }); - setHasSubmitted(false); // Reset submit state so user can try again + setHasSubmitted(false); } }; @@ -106,12 +119,23 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions {hasSubmitted && job ? ( - + ) : ( <> {/* Target folder selection */} - - + + { + setTargetFolderUID(uid || ''); + clearErrors('targetFolderUID'); + }} + />
; +} + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css({ + border: `2px solid ${theme.colors.primary.main}`, + borderTopLeftRadius: 'unset', + borderTopRightRadius: 'unset', + borderBottomLeftRadius: theme.shape.radius.default, + borderBottomRightRadius: theme.shape.radius.default, + overflow: 'hidden', + }), +}); diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx index 4de16f29479..c1f768b525c 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx @@ -10,6 +10,7 @@ import { ServiceAccountPageUnconnected, Props } from './ServiceAccountPage'; jest.mock('app/core/core', () => ({ contextSrv: { + ...jest.requireActual('app/core/core').contextSrv, licensedAccessControlEnabled: () => false, hasPermission: () => true, hasPermissionInMetadata: () => false, diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx index d4b870c2921..1be6a388016 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx @@ -9,6 +9,7 @@ import { Props, ServiceAccountsListPageUnconnected } from './ServiceAccountsList jest.mock('app/core/core', () => ({ contextSrv: { + ...jest.requireActual('app/core/core').contextSrv, licensedAccessControlEnabled: () => false, hasPermission: () => true, hasPermissionInMetadata: () => true, diff --git a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx index cebdf95c955..47a8a0e01eb 100644 --- a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx @@ -285,7 +285,12 @@ const OrganizeFieldsTransformerEditor = ({ options, input, onChange }: OrganizeF <>
{uiOrderByItems.map((item, idx) => ( - + ))}
{provided.placeholder} diff --git a/public/app/features/transformers/partitionByValues/partitionByValues.test.ts b/public/app/features/transformers/partitionByValues/partitionByValues.test.ts index 337efd10215..2431f5a2f5e 100644 --- a/public/app/features/transformers/partitionByValues/partitionByValues.test.ts +++ b/public/app/features/transformers/partitionByValues/partitionByValues.test.ts @@ -35,6 +35,7 @@ describe('Partition by values transformer', () => { expect(partitioned[0].length).toEqual(3); expect(partitioned[0].name).toEqual('Europe'); + expect(partitioned[0].refId).toEqual('A-Europe'); expect(partitioned[0].fields[0].name).toEqual('model'); expect(partitioned[0].fields[1].name).toEqual('region'); expect(partitioned[0].fields[0].values).toEqual(['E1', 'E2', 'E3']); @@ -42,6 +43,7 @@ describe('Partition by values transformer', () => { expect(partitioned[1].length).toEqual(3); expect(partitioned[1].name).toEqual('China'); + expect(partitioned[1].refId).toEqual('A-China'); expect(partitioned[1].fields[0].name).toEqual('model'); expect(partitioned[1].fields[1].name).toEqual('region'); expect(partitioned[1].fields[0].values).toEqual(['C1', 'C2', 'C3']); @@ -75,6 +77,7 @@ describe('Partition by values transformer', () => { expect(partitioned[0].length).toEqual(1); expect(partitioned[0].name).toEqual('Europe OK'); + expect(partitioned[0].refId).toEqual('A-Europe OK'); expect(partitioned[0].fields[0].name).toEqual('model'); expect(partitioned[0].fields[1].name).toEqual('region'); expect(partitioned[0].fields[2].name).toEqual('status'); @@ -84,6 +87,7 @@ describe('Partition by values transformer', () => { expect(partitioned[1].length).toEqual(2); expect(partitioned[1].name).toEqual('Europe FAIL'); + expect(partitioned[1].refId).toEqual('A-Europe FAIL'); expect(partitioned[1].fields[0].name).toEqual('model'); expect(partitioned[1].fields[1].name).toEqual('region'); expect(partitioned[1].fields[2].name).toEqual('status'); @@ -93,6 +97,7 @@ describe('Partition by values transformer', () => { expect(partitioned[2].length).toEqual(2); expect(partitioned[2].name).toEqual('China OK'); + expect(partitioned[2].refId).toEqual('A-China OK'); expect(partitioned[2].fields[0].name).toEqual('model'); expect(partitioned[2].fields[1].name).toEqual('region'); expect(partitioned[2].fields[2].name).toEqual('status'); @@ -102,6 +107,7 @@ describe('Partition by values transformer', () => { expect(partitioned[3].length).toEqual(1); expect(partitioned[3].name).toEqual('China FAIL'); + expect(partitioned[3].refId).toEqual('A-China FAIL'); expect(partitioned[3].fields[0].name).toEqual('model'); expect(partitioned[3].fields[1].name).toEqual('region'); expect(partitioned[3].fields[2].name).toEqual('status'); @@ -135,9 +141,16 @@ describe('Partition by values transformer', () => { let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source); expect(partitioned[0].name).toEqual('region=Europe status=OK'); + expect(partitioned[0].refId).toEqual('A-region=Europe status=OK'); + expect(partitioned[1].name).toEqual('region=Europe status=FAIL'); + expect(partitioned[1].refId).toEqual('A-region=Europe status=FAIL'); + expect(partitioned[2].name).toEqual('region=China status=OK'); + expect(partitioned[2].refId).toEqual('A-region=China status=OK'); + expect(partitioned[3].name).toEqual('region=China status=FAIL'); + expect(partitioned[3].refId).toEqual('A-region=China status=FAIL'); }); it('should partition by multiple fields with custom frame naming {append: true}', () => { @@ -165,9 +178,16 @@ describe('Partition by values transformer', () => { let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source); expect(partitioned[0].name).toEqual('XYZ Europe OK'); + expect(partitioned[0].refId).toEqual('A-XYZ Europe OK'); + expect(partitioned[1].name).toEqual('XYZ Europe FAIL'); + expect(partitioned[1].refId).toEqual('A-XYZ Europe FAIL'); + expect(partitioned[2].name).toEqual('XYZ China OK'); + expect(partitioned[2].refId).toEqual('A-XYZ China OK'); + expect(partitioned[3].name).toEqual('XYZ China FAIL'); + expect(partitioned[3].refId).toEqual('A-XYZ China FAIL'); }); it('should partition by multiple fields with custom frame naming {withNames: true, append: true}', () => { @@ -196,9 +216,16 @@ describe('Partition by values transformer', () => { let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source); expect(partitioned[0].name).toEqual('XYZ region=Europe status=OK'); + expect(partitioned[0].refId).toEqual('A-XYZ region=Europe status=OK'); + expect(partitioned[1].name).toEqual('XYZ region=Europe status=FAIL'); + expect(partitioned[1].refId).toEqual('A-XYZ region=Europe status=FAIL'); + expect(partitioned[2].name).toEqual('XYZ region=China status=OK'); + expect(partitioned[2].refId).toEqual('A-XYZ region=China status=OK'); + expect(partitioned[3].name).toEqual('XYZ region=China status=FAIL'); + expect(partitioned[3].refId).toEqual('A-XYZ region=China status=FAIL'); }); it('should partition by multiple fields naming: {asLabels: true}', () => { @@ -230,6 +257,12 @@ describe('Partition by values transformer', () => { expect(partitioned[2].name).toEqual('XYZ'); expect(partitioned[3].name).toEqual('XYZ'); + // all frame refIds are same + expect(partitioned[0].refId).toEqual('A-XYZ'); + expect(partitioned[1].refId).toEqual('A-XYZ'); + expect(partitioned[2].refId).toEqual('A-XYZ'); + expect(partitioned[3].refId).toEqual('A-XYZ'); + // all frames contain all fields expect(partitioned[0].fields[0].name).toEqual('model'); expect(partitioned[0].fields[1].name).toEqual('region'); diff --git a/public/app/features/transformers/partitionByValues/partitionByValues.ts b/public/app/features/transformers/partitionByValues/partitionByValues.ts index e0da0630cc0..4cef11b0673 100644 --- a/public/app/features/transformers/partitionByValues/partitionByValues.ts +++ b/public/app/features/transformers/partitionByValues/partitionByValues.ts @@ -149,6 +149,7 @@ export function partitionByValues( return { name: frameName, + refId: `${frame.refId}-${frameName}`, meta: frame.meta, length: idxs.length, fields: filteredFields.map((f) => { diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx index f8c06b9390a..66a7357ff53 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/GroupBySection.tsx @@ -131,6 +131,7 @@ const makeRenderGroupBy = ( onDeleteGroupBy: (propertyName: string) => void, templateVariableOptions: SelectableValue ) => { + // eslint-disable-next-line react/display-name return ( item: BuilderQueryEditorGroupByExpression, onChangeItem: (updatedItem: BuilderQueryEditorGroupByExpression) => void, diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts index d6a40c4b89c..b38fd288633 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts @@ -76,6 +76,17 @@ describe('CloudWatchLogsQueryRunner', () => { }); }); + it('does not append -logs to the requestId if requestId is not provided', async () => { + const { runner, queryMock } = setupMockedLogsQueryRunner(); + + const request = { + ...LogsRequestMock, + }; + await expect(runner.handleLogQueries(LogsRequestMock.targets, request, queryMock)).toEmitValuesWith(() => { + expect(queryMock.mock.calls[0][0].requestId).toEqual(''); + }); + }); + it('should request to start each query and then request to get the query results', async () => { const { runner } = setupMockedLogsQueryRunner(); diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts index 97ba156b287..0ccc22029c1 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts @@ -415,12 +415,14 @@ export class CloudWatchLogsQueryRunner extends CloudWatchRequest { options?: DataQueryRequest ): Observable { const range = options?.range || getDefaultTimeRange(); + // append -logs to prevent requestId from matching metric queries from the same panel + const requestId = options?.requestId ? `${options?.requestId}-logs` : ''; const requestParams: DataQueryRequest = { ...options, range, skipQueryCache: true, - requestId: options?.requestId + '-logs' || '', // adding -logs to prevent requestId from matching metric queries from the same panel + requestId, interval: options?.interval || '', // dummy intervalMs: options?.intervalMs || 1, // dummy scopedVars: options?.scopedVars || {}, // dummy diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index 524b044fed0..e525a2bb69c 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -24,7 +24,7 @@ import { config } from '@grafana/runtime'; import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes'; import { activateSceneObjectAndParentTree, - findOriginalVizPanelByKey, + findVizPanelByKey, getVizPanelKeyForPanelId, } from 'app/features/dashboard-scene/utils/utils'; @@ -63,7 +63,7 @@ export class DashboardDatasource extends DataSourceApi { return of({ data: [] }); } - let sourcePanel = this.findSourcePanel(scene, panelId); + let sourcePanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId)); if (!sourcePanel) { return of({ data: [], error: { message: 'Could not find source panel' } }); @@ -295,11 +295,6 @@ export class DashboardDatasource extends DataSourceApi { }; } - private findSourcePanel(scene: SceneObject, panelId: number) { - // We're trying to find the original panel, not a cloned one, since `panelId` alone cannot resolve clones - return findOriginalVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId)); - } - private emitFirstLoadedDataIfMixedDS( requestId: string ): (source: Observable) => Observable { diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.test.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.test.ts index e8c17b0fac1..af17dae040f 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.test.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.test.ts @@ -1,4 +1,4 @@ -import { createContext, ItemDataType } from '@grafana/assistant'; +import { createAssistantContextItem } from '@grafana/assistant'; import { DataFrame, DataQueryResponse, @@ -14,14 +14,10 @@ import { enrichDataFrameWithAssistantContentMapper } from './utils'; // Mock the createContext function jest.mock('@grafana/assistant', () => ({ - createContext: jest.fn(), - ItemDataType: { - Datasource: 'datasource', - Structured: 'structured', - }, + createAssistantContextItem: jest.fn(), })); -const mockCreateContext = createContext as jest.MockedFunction; +const mockCreateContext = createAssistantContextItem as jest.MockedFunction; describe('enrichDataFrameWithAssistantContentMapper', () => { beforeEach(() => { @@ -113,14 +109,12 @@ describe('enrichDataFrameWithAssistantContentMapper', () => { expect(mockCreateContext).toHaveBeenCalledTimes(2); // Verify datasource context - expect(mockCreateContext).toHaveBeenCalledWith(ItemDataType.Datasource, { - datasourceName: 'PyroscopeDatasource', + expect(mockCreateContext).toHaveBeenCalledWith('datasource', { datasourceUid: 'test-uid', - datasourceType: 'grafana-pyroscope-datasource', }); // Verify structured context - expect(mockCreateContext).toHaveBeenCalledWith(ItemDataType.Structured, { + expect(mockCreateContext).toHaveBeenCalledWith('structured', { title: 'Analyze Flame Graph', data: { start: request.range.from.valueOf(), @@ -327,7 +321,7 @@ describe('enrichDataFrameWithAssistantContentMapper', () => { const mapper = enrichDataFrameWithAssistantContentMapper(request, 'TestDatasource'); mapper(response); - expect(mockCreateContext).toHaveBeenCalledWith(ItemDataType.Structured, { + expect(mockCreateContext).toHaveBeenCalledWith('structured', { title: 'Analyze Flame Graph', data: { start: fromTime.valueOf(), diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts index 3440d29d584..6d95e974456 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/utils.ts @@ -1,7 +1,7 @@ import { invert } from 'lodash'; import Prism, { Grammar, Token } from 'prismjs'; -import { createContext, ItemDataType } from '@grafana/assistant'; +import { createAssistantContextItem } from '@grafana/assistant'; import { AbstractLabelMatcher, AbstractLabelOperator, @@ -158,12 +158,10 @@ export function enrichDataFrameWithAssistantContentMapper( } const context = [ - createContext(ItemDataType.Datasource, { - datasourceName: datasourceName, + createAssistantContextItem('datasource', { datasourceUid: query.datasource.uid, - datasourceType: query.datasource.type, }), - createContext(ItemDataType.Structured, { + createAssistantContextItem('structured', { title: 'Analyze Flame Graph', data: { start: request.range.from.valueOf(), diff --git a/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx b/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx index 213e8b86c7a..b4b46a8a26b 100644 --- a/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx @@ -155,7 +155,7 @@ export const TimeRegionEditor = ({ value, onChange }: Props) => { description={ <> {t('dashboard-settings.time-regions.advanced-description-use', 'Use ')} - + {t('dashboard-settings.time-regions.advanced-description-cron', 'Cron syntax')} {t( diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx index 32e953d6087..5d4a0334ae4 100644 --- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx +++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx @@ -57,7 +57,7 @@ export const CanvasTooltip = ({ scene }: Props) => { const shouldDisplayTimeContentItem = timeField && lastTimeValue && element.data.field && getFieldDisplayName(timeField) !== element.data.field; - const headerItem: VizTooltipItem | null = { + const headerItem: VizTooltipItem = { label: element.getName(), value: '', }; diff --git a/public/app/plugins/panel/histogram/HistogramTooltip.tsx b/public/app/plugins/panel/histogram/HistogramTooltip.tsx index e87f55228b8..563f76e7089 100644 --- a/public/app/plugins/panel/histogram/HistogramTooltip.tsx +++ b/public/app/plugins/panel/histogram/HistogramTooltip.tsx @@ -50,12 +50,10 @@ export const HistogramTooltip = ({ const xMinVal = formattedValueToString(xMinDisp!(xMinField.values[dataIdxs[0]!])); const xMaxVal = formattedValueToString(xMaxDisp!(xMaxField.values[dataIdxs[1]!])); - const headerItem: VizTooltipItem | null = xMinField.config.custom?.hideFrom?.tooltip - ? null - : { - label: 'Bucket', - value: `${xMinVal} - ${xMaxVal}`, - }; + const headerItem: VizTooltipItem = { + label: 'Bucket', + value: `${xMinVal} - ${xMaxVal}`, + }; const contentItems = useMemo( () => getContentItems(xMinOnlyFrame.fields, xMinField, dataIdxs, seriesIdx, mode, sortOrder), diff --git a/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx b/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx index 8c0bae437e9..1e5a908249c 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx @@ -90,12 +90,10 @@ export const TimeSeriesTooltip = ({ } } - const headerItem: VizTooltipItem | null = xField.config.custom?.hideFrom?.tooltip - ? null - : { - label: xField.type === FieldType.time ? '' : (xField.state?.displayName ?? xField.name), - value: xVal, - }; + const headerItem: VizTooltipItem = { + label: xField.type === FieldType.time ? '' : (xField.state?.displayName ?? xField.name), + value: xVal, + }; return ( diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index 84516174db5..a2a4b6e06ba 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -272,6 +272,7 @@ export interface FilterState { alertState?: string; groupBy?: string[]; ruleType?: string; + receivers?: string[]; } export interface SilenceFilterState { diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index e64da24ddad..998a965e6d7 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -3569,7 +3569,6 @@ "button-cancel": "Zrušit", "button-move": "Přesunout", "button-moving": "Probíhá přesouvání…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11958,7 +11957,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Sbalit řádek dotazu", "datasource-help": "Zobrazit nápovědu pro zdroj dat", "drag-and-drop": "Přetažením změníte pořadí", @@ -11969,11 +11967,7 @@ "replace-query-from-library": "Nahradit dotazem z knihovny", "show-response": "Zobrazit odpověď" }, - "query-editor-not-exported": "Doplněk zdroje dat neexportuje žádnou komponentu editoru dotazů", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Doplněk zdroje dat neexportuje žádnou komponentu editoru dotazů" }, "recently-deleted": { "buttons": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index d0e17a078c2..ecff9dd453d 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Abbrechen", "button-move": "Verschieben", "button-moving": "Wird verschoben ...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Abfragezeile einklappen", "datasource-help": "Datenquellen-Hilfe anzeigen", "drag-and-drop": "Ziehen und Ablegen zum Neuordnen", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Mit Abfrage von Bibliothek ersetzen", "show-response": "Antwort anzeigen " }, - "query-editor-not-exported": "Datenquellen-Plugin exportiert keine Komponente des Abfrageeditors", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Datenquellen-Plugin exportiert keine Komponente des Abfrageeditors" }, "recently-deleted": { "buttons": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 7423a3f2437..37a908c0714 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2221,6 +2221,13 @@ "preview": "Preview", "previewCondition": "Preview alert rule condition" }, + "receiver-filter": { + "aria-label-contact-points": "Filter by contact points", + "contact-point": "Contact point", + "no-grouping": "No grouping", + "placeholder-contact-point": "Filter by contact point", + "tooltip-contact-point": "Filter notifications by the contact point they are being delivered to." + }, "receiver-form": { "add-contact-point-integration": "Add contact point integration", "body-attention": "Because there is no default policy configured yet, this contact point will automatically be set as default.", @@ -3543,7 +3550,6 @@ "button-cancel": "Cancel", "button-move": "Move", "button-moving": "Moving...", - "button-tooltip": "Please select a target folder", "error": { "read-only-message": "If you have direct access to the target, please make modifications directly in the target repository.", "read-only-saving-message": "Repository is read-only and provisioned in git. {{readOnlyMessage}}", @@ -3552,7 +3558,7 @@ "repository-not-found-title": "Repository not found" }, "error-moving-resources": "Error moving resources", - "error-no-target-folder-path": "Target folder path in repository is invalid, please select another folder.", + "error-no-target-folder-path": "Target folder path is invalid or empty, please select again.", "move-warning": "This will move selected folders and their descendants. In total, this will affect:", "target-folder": "Target Folder" }, @@ -7131,7 +7137,7 @@ "rich-history-card": { "add-comment-form": "Add comment form", "add-comment-tooltip": "Add comment", - "add-to-library": "Add to library", + "add-to-library": "Save query", "cancel": "Cancel", "confirm-delete": "Delete", "copy-query-tooltip": "Copy query to clipboard", @@ -7243,7 +7249,7 @@ } }, "secondary-actions": { - "add-from-query-library": "Add query from library", + "add-from-query-library": "Add from saved queries", "query-add-button": "Add query", "query-add-button-aria-label": "Add query", "query-history-button": "Query history", @@ -11886,7 +11892,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "Discard changes", "collapse-row": "Collapse query row", "datasource-help": "Show data source help", "drag-and-drop": "Drag and drop to reorder", @@ -11894,14 +11899,10 @@ "expand-row": "Expand query row", "hide-response": "Hide response", "remove-query": "Remove query", - "replace-query-from-library": "Replace with query from library", + "replace-query-from-library": "Replace with saved query", "show-response": "Show response" }, - "query-editor-not-exported": "Data source plugin does not export any Query Editor component", - "query-library": { - "editing-tooltip": "Updating query from library\nUID: {{queryLibraryRef}}", - "from-library": "Updating query from library" - } + "query-editor-not-exported": "Data source plugin does not export any Query Editor component" }, "recently-deleted": { "buttons": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 05912a5e5f9..711b696b1d5 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Cancelar", "button-move": "Mover", "button-moving": "Moviendo...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Contraer la fila de la consulta", "datasource-help": "Mostrar la ayuda de la fuente de datos", "drag-and-drop": "Arrastrar y soltar para reordenar", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Reemplazar con consulta de la librería", "show-response": "Mostrar respuesta" }, - "query-editor-not-exported": "El complemento de la fuente de datos no exporta ningún componente del editor de consultas", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "El complemento de la fuente de datos no exporta ningún componente del editor de consultas" }, "recently-deleted": { "buttons": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 58b51d7b92a..2c49be2d284 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Annuler", "button-move": "Déplacer", "button-moving": "Déplacement en cours…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Réduire la ligne de requête", "datasource-help": "Afficher l'aide de la source de données", "drag-and-drop": "Glisser et déposer pour réorganiser", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Remplacer par une requête à partir de la bibliothèque", "show-response": "Afficher la réponse" }, - "query-editor-not-exported": "Le plugin source de données n'exporte aucun composant de l'éditeur de requête", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Le plugin source de données n'exporte aucun composant de l'éditeur de requête" }, "recently-deleted": { "buttons": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 7d58b837fab..e78ea292dee 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Mégse", "button-move": "Áthelyezés", "button-moving": "Áthelyezés...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Lekérdezési sor összecsukása", "datasource-help": "Adatforrás súgójának megjelenítése", "drag-and-drop": "Húzza át az átrendezéshez", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Lecserélés könyvtárból származó lekérdezésre", "show-response": "Válasz megjelenítése" }, - "query-editor-not-exported": "Az adatforrás-bővítmény nem exportál egyetlen lekérdezésszerkesztő-komponenst sem", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Az adatforrás-bővítmény nem exportál egyetlen lekérdezésszerkesztő-komponenst sem" }, "recently-deleted": { "buttons": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index c38637f7ba6..f1eb0635700 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -3530,7 +3530,6 @@ "button-cancel": "Batal", "button-move": "Pindahkan", "button-moving": "Memindahkan...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11850,7 +11849,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Ciutkan baris kueri", "datasource-help": "Tampilkan bantuan sumber data", "drag-and-drop": "Seret dan lepas untuk menyusun ulang", @@ -11861,11 +11859,7 @@ "replace-query-from-library": "Ganti dengan kueri dari pustaka", "show-response": "Tampilkan respons" }, - "query-editor-not-exported": "Plugin sumber data tidak mengekspor komponen Editor Kueri apa pun", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Plugin sumber data tidak mengekspor komponen Editor Kueri apa pun" }, "recently-deleted": { "buttons": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 31c16a2cb1f..2d4d7195176 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Annulla", "button-move": "Sposta", "button-moving": "Spostamento in corso...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Riduci riga della query", "datasource-help": "Mostra la guida dell'origine dati", "drag-and-drop": "Trascina e rilascia per riordinare", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Sostituisci con query dalla libreria", "show-response": "Mostra la risposta" }, - "query-editor-not-exported": "Il plug-in dell'origine dati non esporta alcun componente dell'editor di query", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Il plug-in dell'origine dati non esporta alcun componente dell'editor di query" }, "recently-deleted": { "buttons": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index ca2986e06b0..1464d0d3c0b 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -3530,7 +3530,6 @@ "button-cancel": "キャンセル", "button-move": "移動", "button-moving": "移動中…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11850,7 +11849,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "クエリ行を折りたたむ", "datasource-help": "データソースヘルプを表示", "drag-and-drop": "ドラッグアンドドロップで並べ替え", @@ -11861,11 +11859,7 @@ "replace-query-from-library": "ライブラリからのクエリに置き換え", "show-response": "応答の表示" }, - "query-editor-not-exported": "データソースプラグインは、クエリエディタコンポーネントをエクスポートしません", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "データソースプラグインは、クエリエディタコンポーネントをエクスポートしません" }, "recently-deleted": { "buttons": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index b3196f8878a..eb4a68a3ae4 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -3530,7 +3530,6 @@ "button-cancel": "취소", "button-move": "이동", "button-moving": "이동 중...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11850,7 +11849,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "쿼리 행 접기", "datasource-help": "데이터 소스 도움말 표시", "drag-and-drop": "드래그 앤 드롭으로 순서 변경", @@ -11861,11 +11859,7 @@ "replace-query-from-library": "라이브러리의 쿼리로 교체", "show-response": "응답 표시" }, - "query-editor-not-exported": "데이터 소스 플러그인은 쿼리 편집기 구성 요소를 내보내지 않습니다.", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "데이터 소스 플러그인은 쿼리 편집기 구성 요소를 내보내지 않습니다." }, "recently-deleted": { "buttons": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 796c4213e22..98d1d944642 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Annuleren", "button-move": "Verplaatsen", "button-moving": "Bezig met verplaatsen...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Queryrij samenvouwen", "datasource-help": "Hulp bij gegevensbron weergeven", "drag-and-drop": "Versleep om opnieuw te rangschikken", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Vervangen door query uit bibliotheek", "show-response": "Antwoord weergeven" }, - "query-editor-not-exported": "Gegevensbronplug-in exporteert geen Query Editor-component", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Gegevensbronplug-in exporteert geen Query Editor-component" }, "recently-deleted": { "buttons": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 420e5e613be..4aafd278a0f 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -3569,7 +3569,6 @@ "button-cancel": "Anuluj", "button-move": "Przenieś", "button-moving": "Przenoszenie…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11958,7 +11957,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Zwiń wiersz zapytania", "datasource-help": "Pokaż pomoc dotyczącą źródła danych", "drag-and-drop": "Przeciągnij i upuść, aby zmienić kolejność", @@ -11969,11 +11967,7 @@ "replace-query-from-library": "Zastąp zapytanie z biblioteki", "show-response": "Pokaż odpowiedź" }, - "query-editor-not-exported": "Wtyczka źródła danych nie eksportuje żadnego komponentu Edytora zapytań", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Wtyczka źródła danych nie eksportuje żadnego komponentu Edytora zapytań" }, "recently-deleted": { "buttons": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 0b1e15c8a82..9a914901e92 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Cancelar", "button-move": "Mover", "button-moving": "Movendo…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Recolher linha de consulta", "datasource-help": "Mostrar ajuda da fonte de dados", "drag-and-drop": "Arraste e solte para reordenar", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Substituir por consulta da biblioteca", "show-response": "Mostrar resposta" }, - "query-editor-not-exported": "O plug-in de origem de dados não exporta nenhum componente de editor de consulta", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "O plug-in de origem de dados não exporta nenhum componente de editor de consulta" }, "recently-deleted": { "buttons": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index d00ab726ab9..21e93eebb00 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Cancelar", "button-move": "Mover", "button-moving": "A mover...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Recolher linha de consulta", "datasource-help": "Mostrar ajuda da origem de dados", "drag-and-drop": "Arrastar e largar para reordenar", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Substituir por consulta da biblioteca", "show-response": "Mostrar resposta" }, - "query-editor-not-exported": "O plugin de origem de dados não exporta nenhum componente do Editor de Consultas", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "O plugin de origem de dados não exporta nenhum componente do Editor de Consultas" }, "recently-deleted": { "buttons": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index bfd88914620..c732a87732b 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -3569,7 +3569,6 @@ "button-cancel": "Отмена", "button-move": "Переместить", "button-moving": "Перемещение...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11958,7 +11957,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Свернуть строку запроса", "datasource-help": "Показать справку по источнику данных", "drag-and-drop": "Перетащить для изменения порядка", @@ -11969,11 +11967,7 @@ "replace-query-from-library": "Замена на запрос из библиотеки", "show-response": "Показать ответ" }, - "query-editor-not-exported": "Плагин источника данных не экспортирует компоненты редактора запросов", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Плагин источника данных не экспортирует компоненты редактора запросов" }, "recently-deleted": { "buttons": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index d7b44f1827d..24f3cfbbf2c 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "Avbryt", "button-move": "Flytta", "button-moving": "Flyttar …", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Dölj frågeraden", "datasource-help": "Visa hjälp med datakälla", "drag-and-drop": "Ändra ordning genom dra och släpp", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Ersätt med fråga från biblioteket", "show-response": "Visa svar" }, - "query-editor-not-exported": "Tilläggsprogram för datakälla exporterar inte någon frågeredigerarkomponent", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Tilläggsprogram för datakälla exporterar inte någon frågeredigerarkomponent" }, "recently-deleted": { "buttons": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 71411fabcdd..b855f9b23da 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -3543,7 +3543,6 @@ "button-cancel": "İptal et", "button-move": "Taşı", "button-moving": "Taşınıyor...", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11886,7 +11885,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "Sorgu satırını daralt", "datasource-help": "Veri kaynağı yardımını göster", "drag-and-drop": "Yeniden düzenlemek için sürükleyip bırakın", @@ -11897,11 +11895,7 @@ "replace-query-from-library": "Kütüphaneden sorguyla değiştir", "show-response": "Yanıtı göster" }, - "query-editor-not-exported": "Veri kaynağı eklentisi herhangi bir Sorgu Düzenleyici bileşeni sunmuyor/içermiyor.", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "Veri kaynağı eklentisi herhangi bir Sorgu Düzenleyici bileşeni sunmuyor/içermiyor." }, "recently-deleted": { "buttons": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 542150c3144..bfae2b0d552 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -3530,7 +3530,6 @@ "button-cancel": "取消", "button-move": "移动", "button-moving": "正在移动…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11850,7 +11849,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "折叠查询行", "datasource-help": "显示数据源帮助", "drag-and-drop": "拖放以重新排序", @@ -11861,11 +11859,7 @@ "replace-query-from-library": "替换为来自库的查询", "show-response": "显示回复" }, - "query-editor-not-exported": "数据源插件不导出任何查询编辑器组件", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "数据源插件不导出任何查询编辑器组件" }, "recently-deleted": { "buttons": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index a03d8f5cc20..fb224080379 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -3530,7 +3530,6 @@ "button-cancel": "取消", "button-move": "移動", "button-moving": "正在移動…", - "button-tooltip": "", "error": { "read-only-message": "", "read-only-saving-message": "", @@ -11850,7 +11849,6 @@ }, "query-operation": { "header": { - "cancel-query-library-edit": "", "collapse-row": "收闔查詢列", "datasource-help": "顯示資料來源說明", "drag-and-drop": "拖放以重新排序", @@ -11861,11 +11859,7 @@ "replace-query-from-library": "替換為來自資料庫的查詢", "show-response": "顯示回應" }, - "query-editor-not-exported": "資料來源外掛程式不匯出任何查詢編輯器元件", - "query-library": { - "editing-tooltip": "", - "from-library": "" - } + "query-editor-not-exported": "資料來源外掛程式不匯出任何查詢編輯器元件" }, "recently-deleted": { "buttons": { diff --git a/scripts/drone/dagger.star b/scripts/drone/dagger.star deleted file mode 100644 index 3f02f7282bb..00000000000 --- a/scripts/drone/dagger.star +++ /dev/null @@ -1,9 +0,0 @@ -""" -Utilities / functions for working with dagger pipelines -""" - -def with_dagger_install(commands = [], dagger_version = ""): - return [ - "wget -qO- https://github.com/dagger/dagger/releases/download/{}/dagger_{}_linux_amd64.tar.gz | tar zx -C /bin".format(dagger_version, dagger_version), - "apk add docker", - ] + commands diff --git a/scripts/drone/env-var-check.sh b/scripts/drone/env-var-check.sh deleted file mode 100755 index adfbe88f4ce..00000000000 --- a/scripts/drone/env-var-check.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# ensure DRONE_SERVER and DRONE_TOKEN env variables are set -if [ -z "$DRONE_SERVER" ]; then - echo "DRONE_SERVER environment variable is not set." - exit 1 -fi - -if [ -z "$DRONE_TOKEN" ]; then - echo "DRONE_TOKEN environment variable is not set." - exit 1 -fi \ No newline at end of file diff --git a/scripts/drone/events/cron.star b/scripts/drone/events/cron.star deleted file mode 100644 index 6f9e524139b..00000000000 --- a/scripts/drone/events/cron.star +++ /dev/null @@ -1,179 +0,0 @@ -""" -This module provides functions for cronjob pipelines and steps used within. -""" - -load( - "scripts/drone/utils/images.star", - "images", -) -load("scripts/drone/vault.star", "from_secret") - -aquasec_trivy_image = "aquasec/trivy:0.21.0" - -def cronjobs(): - return [ - scan_docker_image_pipeline("latest"), - scan_docker_image_pipeline("main"), - scan_docker_image_pipeline("latest-ubuntu"), - scan_docker_image_pipeline("main-ubuntu"), - scan_build_test_publish_docker_image_pipeline(), - ] - -def authenticate_gcr_step(): - return { - "name": "authenticate-gcr", - "image": "docker:dind", - "commands": ["echo $${GCR_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io"], - "environment": { - "GCR_CREDENTIALS": from_secret("gcr_credentials"), - }, - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}, {"name": "config", "path": "/root/.docker/"}], - } - -def cron_job_pipeline(cronName, name, steps): - return { - "kind": "pipeline", - "type": "docker", - "platform": { - "os": "linux", - "arch": "amd64", - }, - "name": name, - "trigger": { - "event": "cron", - "cron": cronName, - }, - "clone": { - "retries": 3, - }, - "steps": steps, - "volumes": [ - { - "name": "docker", - "host": { - "path": "/var/run/docker.sock", - }, - }, - { - "name": "config", - "temp": {}, - }, - ], - } - -def scan_docker_image_pipeline(tag): - """Generates a cronjob pipeline for nightly scans of grafana Docker images. - - Args: - tag: determines which image tag is scanned. - - Returns: - Drone cronjob pipeline. - """ - docker_image = "grafana/grafana:{}".format(tag) - - return cron_job_pipeline( - cronName = "nightly", - name = "scan-" + docker_image + "-image", - steps = [ - authenticate_gcr_step(), - scan_docker_image_unknown_low_medium_vulnerabilities_step(docker_image), - scan_docker_image_high_critical_vulnerabilities_step(docker_image), - slack_job_failed_step("grafana-backend-ops", docker_image), - ], - ) - -def scan_build_test_publish_docker_image_pipeline(): - """Generates a cronjob pipeline for nightly scans of grafana Docker images. - - Returns: - Drone cronjob pipeline. - """ - - return cron_job_pipeline( - cronName = "nightly", - name = "scan-build-test-and-publish-docker-images", - steps = [ - authenticate_gcr_step(), - scan_docker_image_unknown_low_medium_vulnerabilities_step("all"), - scan_docker_image_high_critical_vulnerabilities_step("all"), - slack_job_failed_step("grafana-backend-ops", "build-images"), - ], - ) - -def scan_docker_image_unknown_low_medium_vulnerabilities_step(docker_image): - """Generates a step for scans of Grafana Docker images. - - Args: - docker_image: determines which image is scanned. - - Returns: - Drone cronjob step . - """ - - cmds = [] - if docker_image == "all": - for key in images: - cmds = cmds + ["trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM " + images[key]] - else: - cmds = ["trivy image --exit-code 0 --severity UNKNOWN,LOW,MEDIUM " + docker_image] - return { - "name": "scan-unknown-low-medium-vulnerabilities", - "image": aquasec_trivy_image, - "commands": cmds, - "depends_on": ["authenticate-gcr"], - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}, {"name": "config", "path": "/root/.docker/"}], - } - -def scan_docker_image_high_critical_vulnerabilities_step(docker_image): - """Generates a step for scans of Grafana Docker images. - - Args: - docker_image: determines which image is scanned. - - Returns: - Drone cronjob step . - """ - - cmds = [] - if docker_image == "all": - for key in images: - cmds = cmds + ["trivy --exit-code 1 --severity HIGH,CRITICAL " + images[key]] - else: - cmds = ["trivy image --exit-code 1 --severity HIGH,CRITICAL " + docker_image] - return { - "name": "scan-high-critical-vulnerabilities", - "image": aquasec_trivy_image, - "commands": cmds, - "depends_on": ["authenticate-gcr"], - "environment": { - "GOOGLE_APPLICATION_CREDENTIALS": from_secret("gcr_credentials_json"), - }, - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}, {"name": "config", "path": "/root/.docker/"}], - } - -def slack_job_failed_step(channel, image): - return { - "name": "slack-notify-failure", - "image": images["plugins_slack"], - "settings": { - "webhook": from_secret("slack_webhook_backend"), - "channel": channel, - "template": "Nightly docker image scan job for " + - image + - " failed: {{build.link}}", - }, - "when": {"status": "failure"}, - } - -def post_to_grafana_com_step(): - return { - "name": "post-to-grafana-com", - "image": images["publish"], - "environment": { - "GRAFANA_COM_API_KEY": from_secret("grafana_api_key"), - "GCP_KEY": from_secret("gcp_key"), - }, - "depends_on": ["compile-build-cmd"], - "commands": ["./bin/build publish grafana-com --edition oss"], - } diff --git a/scripts/drone/events/main.star b/scripts/drone/events/main.star deleted file mode 100644 index 35f60141760..00000000000 --- a/scripts/drone/events/main.star +++ /dev/null @@ -1,61 +0,0 @@ -""" -This module returns all the pipelines used in the event of pushes to the main branch. -""" - -load( - "scripts/drone/pipelines/build.star", - "build_e2e", -) -load( - "scripts/drone/pipelines/docs.star", - "docs_pipelines", - "trigger_docs_main", -) -load( - "scripts/drone/pipelines/trigger_downstream.star", - "enterprise_downstream_pipeline", -) -load( - "scripts/drone/utils/utils.star", - "failure_template", - "notify_pipeline", -) - -ver_mode = "main" -trigger = { - "event": [ - "push", - ], - "branch": "main", - "paths": { - "exclude": [ - "*.md", - "docs/**", - "latest.json", - ], - }, - "repo": [ - "grafana/grafana", - ], -} - -def main_pipelines(): - # This is how we should define any new pipelines. At some point we should update existing ones. - # Let's make an effort to reduce the amount of string constants in "depends_on" lists. - pipelines = [ - docs_pipelines(ver_mode, trigger_docs_main()), - build_e2e(trigger, ver_mode), - enterprise_downstream_pipeline(), - notify_pipeline( - name = "main-notify", - slack_channel = "grafana-ci-notifications", - trigger = dict(trigger, status = ["failure"]), - depends_on = [ - "main-build-e2e-publish", - ], - template = failure_template, - secret = "slack_webhook", - ), - ] - - return pipelines diff --git a/scripts/drone/events/pr.star b/scripts/drone/events/pr.star deleted file mode 100644 index 29a456329cb..00000000000 --- a/scripts/drone/events/pr.star +++ /dev/null @@ -1,87 +0,0 @@ -""" -This module returns all pipelines used in the event of a pull request. -It also includes a function generating a PR trigger from a list of included and excluded paths. -""" - -load( - "scripts/drone/pipelines/build.star", - "build_e2e", -) -load( - "scripts/drone/pipelines/docs.star", - "docs_pipelines", - "trigger_docs_pr", -) -load( - "scripts/drone/pipelines/verify_drone.star", - "verify_drone", -) -load( - "scripts/drone/pipelines/verify_starlark.star", - "verify_starlark", -) - -ver_mode = "pr" -trigger = { - "event": [ - "pull_request", - ], - "paths": { - "exclude": [ - "*.md", - "docs/**", - "latest.json", - ], - }, -} - -def pr_pipelines(): - return [ - verify_drone( - get_pr_trigger( - include_paths = ["scripts/drone/**", ".drone.yml", ".drone.star"], - ), - ver_mode, - ), - verify_starlark( - get_pr_trigger( - include_paths = ["scripts/drone/**", ".drone.star"], - ), - ver_mode, - ), - build_e2e(trigger, ver_mode), - docs_pipelines(ver_mode, trigger_docs_pr()), - ] - -def get_pr_trigger(include_paths = None, exclude_paths = None): - """Generates a trigger filter from the lists of included and excluded path patterns. - - This function is primarily intended to generate a trigger for code changes - as the patterns 'docs/**' and '*.md' are always excluded. - - Args: - include_paths: a list of path patterns using the same syntax as gitignore. - Changes affecting files matching these path patterns trigger the pipeline. - exclude_paths: a list of path patterns using the same syntax as gitignore. - Changes affecting files matching these path patterns do not trigger the pipeline. - - Returns: - Drone trigger. - """ - paths_ex = ["docs/**", "*.md"] - paths_in = [] - if include_paths: - for path in include_paths: - paths_in.extend([path]) - if exclude_paths: - for path in exclude_paths: - paths_ex.extend([path]) - return { - "event": [ - "pull_request", - ], - "paths": { - "exclude": paths_ex, - "include": paths_in, - }, - } diff --git a/scripts/drone/events/release.star b/scripts/drone/events/release.star deleted file mode 100644 index d71f01f3581..00000000000 --- a/scripts/drone/events/release.star +++ /dev/null @@ -1,279 +0,0 @@ -""" -This module returns all the pipelines used in the event of a release along with supporting functions. -""" - -load( - "scripts/drone/steps/github.star", - "github_app_generate_token_step", - "github_app_pipeline_volumes", - "github_app_step_volumes", -) -load( - "scripts/drone/steps/lib.star", - "compile_build_cmd", - "publish_grafanacom_step", - "publish_linux_packages_step", - "verify_grafanacom_step", - "yarn_install_step", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) -load( - "scripts/drone/vault.star", - "from_secret", - "gcp_grafanauploads_base64", - "npm_token", - "prerelease_bucket", - "rgm_gcp_key_base64", -) - -ver_mode = "release" -semver_regex = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" - -def retrieve_npm_packages_step(): - return { - "name": "retrieve-npm-packages", - "image": images["publish"], - "depends_on": [ - "compile-build-cmd", - "yarn-install", - ], - "failure": "ignore", - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads_base64), - "PRERELEASE_BUCKET": from_secret(prerelease_bucket), - }, - "commands": ["./bin/build artifacts npm retrieve --tag ${DRONE_TAG}"], - } - -def release_pr_step(depends_on = []): - return { - "name": "create-release-pr", - "image": images["curl"], - "depends_on": depends_on, - "environment": { - "GH_CLI_URL": "https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz", - }, - "commands": [ - "export GITHUB_TOKEN=$(cat /github-app/token)", - "apk add perl", - "v_target=`echo $${{TAG}} | perl -pe 's/{}/v\\1.\\2.x/'`".format(semver_regex), - # Install gh CLI - "curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr", - # Run the release-pr workflow - "gh workflow run " + - "-f dry_run=$${DRY_RUN} " + - "-f version=$${TAG} " + - # If the submitter has set a target branch, then use that, otherwise use the default - "-f target=$${v_target} " + - "-f latest=$${LATEST} " + - "--repo=grafana/grafana release-pr.yml", - ], - "volumes": github_app_step_volumes(), - } - -def release_npm_packages_step(): - return { - "name": "release-npm-packages", - "image": images["node"], - "depends_on": [ - "compile-build-cmd", - "retrieve-npm-packages", - ], - "failure": "ignore", - "environment": { - "NPM_TOKEN": from_secret(npm_token), - }, - "commands": ["./bin/build artifacts npm release --tag ${DRONE_TAG}"], - } - -def publish_artifacts_step(): - return { - "name": "publish-artifacts", - "image": images["publish"], - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads_base64), - "PRERELEASE_BUCKET": from_secret("prerelease_bucket"), - }, - "commands": [ - "./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET}", - ], - "depends_on": ["compile-build-cmd"], - } - -def publish_storybook_step(): - return { - "name": "publish-storybook", - "image": images["publish"], - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads_base64), - "PRERELEASE_BUCKET": from_secret("prerelease_bucket"), - }, - "commands": [ - "./bin/build artifacts storybook --tag ${DRONE_TAG}", - ], - "depends_on": ["compile-build-cmd"], - } - -def publish_artifacts_pipelines(mode): - """Published artifacts after they've been stored and tested in prerelease buckets. - - Args: - mode: public or security. - Defaults to ''. - - Returns: - List of Drone pipelines. - """ - trigger = { - "event": ["promote"], - "target": [mode], - } - steps = [ - compile_build_cmd(), - publish_artifacts_step(), - publish_storybook_step(), - github_app_generate_token_step(), - release_pr_step(depends_on = ["publish-artifacts", github_app_generate_token_step()["name"]]), - ] - - return [ - pipeline( - name = "create-release-pr", - trigger = { - "event": ["promote"], - "target": "release-pr", - }, - steps = [ - release_pr_step(), - ], - volumes = github_app_pipeline_volumes(), - ), - pipeline( - name = "publish-artifacts-{}".format(mode), - trigger = trigger, - steps = steps, - environment = {"EDITION": "oss"}, - volumes = github_app_pipeline_volumes(), - ), - ] - -def publish_packages_pipeline(): - """Generates pipelines used for publishing packages for OSS. - - Returns: - List of Drone pipelines. One for each of OSS and enterprise packages. - """ - - trigger = { - "event": ["promote"], - "target": ["public"], - } - oss_steps = [ - compile_build_cmd(), - publish_linux_packages_step(package_manager = "deb"), - publish_linux_packages_step(package_manager = "rpm"), - publish_grafanacom_step(ver_mode = "release"), - verify_grafanacom_step(), - ] - - deps = [ - "publish-artifacts-public", - "publish-docker-public", - ] - - return [ - pipeline( - name = "verify-grafanacom-artifacts", - trigger = { - "event": ["promote"], - "target": "verify-grafanacom-artifacts", - }, - steps = [ - verify_grafanacom_step(depends_on = []), - ], - ), - pipeline( - name = "publish-packages", - trigger = trigger, - steps = oss_steps, - depends_on = deps, - environment = {"EDITION": "oss"}, - ), - pipeline( - name = "publish-grafanacom", - trigger = { - "event": ["promote"], - "target": "publish-grafanacom", - }, - steps = [ - compile_build_cmd(), - publish_grafanacom_step(ver_mode = "release", depends_on = ["compile-build-cmd"]), - ], - ), - ] - -def publish_npm_pipelines(): - trigger = { - "event": ["promote"], - "target": ["public"], - } - steps = [ - compile_build_cmd(), - yarn_install_step(), - retrieve_npm_packages_step(), - release_npm_packages_step(), - ] - - return [ - pipeline( - name = "publish-npm-packages-public", - trigger = trigger, - steps = steps, - environment = {"EDITION": "oss"}, - ), - ] - -def verify_release_pipeline( - name = "verify-prerelease-assets", - bucket = from_secret(prerelease_bucket), - gcp_key = from_secret(rgm_gcp_key_base64), - version = "${DRONE_TAG}", - trigger = {}, - depends_on = [ - "release-build-e2e-publish", - ]): - """ - Runs a script that 'gsutil stat's every artifact that should have been produced by the pre-release process. - - Returns: - A single Drone pipeline that runs the script. - """ - step = { - "name": "gsutil-stat", - "depends_on": ["clone"], - "image": images["cloudsdk"], - "environment": { - "BUCKET": bucket, - "GCP_KEY": gcp_key, - }, - "commands": [ - "apt-get update && apt-get install -yq gettext", - "printenv GCP_KEY | base64 -d > /tmp/key.json", - "gcloud auth activate-service-account --key-file=/tmp/key.json", - "./scripts/list-release-artifacts.sh {} | xargs -n1 gsutil stat >> /tmp/stat.log".format(version), - "! cat /tmp/stat.log | grep \"No URLs matched\"", - ], - } - return pipeline( - depends_on = depends_on, - name = name, - trigger = trigger, - steps = [step], - ) diff --git a/scripts/drone/events/rrc-patch.star b/scripts/drone/events/rrc-patch.star deleted file mode 100644 index 5b888e5b21c..00000000000 --- a/scripts/drone/events/rrc-patch.star +++ /dev/null @@ -1,47 +0,0 @@ -""" -This module returns all the pipelines used in the event of pushes to an RRC branch. -""" - -load( - "scripts/drone/steps/lib.star", - "enterprise_downstream_step", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -ver_mode = "rrc" -trigger = { - "ref": { - "include": [ - "refs/tags/rrc*", - ], - }, - "branch": [ - "instant", - "fast", - "steady", - "slow", - ], -} - -def rrc_patch_pipelines(): - pipelines = [ - rrc_enterprise_downstream_pipeline(trigger = trigger), - ] - - return pipelines - -def rrc_enterprise_downstream_pipeline(trigger): - # Triggers a downstream pipeline in the grafana-enterprise repository for the rrc branch - environment = {"EDITION": "oss"} - steps = [ - enterprise_downstream_step(ver_mode = ver_mode), - ] - return pipeline( - name = "rrc-trigger-downstream", - trigger = trigger, - steps = steps, - environment = environment, - ) diff --git a/scripts/drone/pipelines/aws_marketplace.star b/scripts/drone/pipelines/aws_marketplace.star deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/scripts/drone/pipelines/build.star b/scripts/drone/pipelines/build.star deleted file mode 100644 index e08891078ca..00000000000 --- a/scripts/drone/pipelines/build.star +++ /dev/null @@ -1,170 +0,0 @@ -"""This module contains the comprehensive build pipeline.""" - -load( - "scripts/drone/steps/github.star", - "github_app_generate_token_step", - "github_app_pipeline_volumes", -) -load( - "scripts/drone/steps/lib.star", - "build_frontend_package_step", - "build_storybook_step", - "build_test_plugins_step", - "cloud_plugins_e2e_tests_step", - "compile_build_cmd", - "download_grabpl_step", - "e2e_tests_artifacts", - "enterprise_downstream_step", - "frontend_metrics_step", - "grafana_server_step", - "identify_runner_step", - "playwright_e2e_report_post_link", - "playwright_e2e_report_upload", - "playwright_e2e_tests_step", - "publish_images_step", - "release_canary_npm_packages_step", - "store_storybook_step", - "test_a11y_frontend_step", - "trigger_oss", - "update_package_json_version", - "upload_cdn_step", - "upload_packages_step", - "verify_gen_cue_step", - "verify_gen_jsonnet_step", - "yarn_install_step", -) -load( - "scripts/drone/steps/rgm.star", - "rgm_artifacts_step", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -# This function isn't actually unused but I don't know why the linter thinks it is... -# @unused -def build_e2e(trigger, ver_mode): - """Perform e2e building, testing, and publishing. - - Args: - trigger: controls which events can trigger the pipeline execution. - ver_mode: used in the naming of the pipeline. Either 'pr' or 'main'. - - Returns: - Drone pipeline. - """ - - environment = {"EDITION": "oss"} - init_steps = [ - github_app_generate_token_step(), - identify_runner_step(), - download_grabpl_step(), - compile_build_cmd(), - verify_gen_cue_step(), - verify_gen_jsonnet_step(), - yarn_install_step(), - ] - - build_steps = [] - - create_packages = rgm_artifacts_step( - artifacts = [ - "targz:grafana:linux/amd64", - "targz:grafana:linux/arm64", - "targz:grafana:linux/arm/v7", - "docker:grafana:linux/amd64", - "docker:grafana:linux/amd64:ubuntu", - "docker:grafana:linux/arm64", - "docker:grafana:linux/arm64:ubuntu", - "docker:grafana:linux/arm/v7", - "docker:grafana:linux/arm/v7:ubuntu", - ], - file = "packages.txt", - tag_format = "{{ .version_base }}-{{ .buildID }}-{{ .arch }}", - ubuntu_tag_format = "{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}", - ) - - publish_docker = publish_images_step( - depends_on = [create_packages["name"]], - docker_repo = "grafana", - trigger = trigger_oss, - ver_mode = ver_mode, - ) - - if ver_mode == "pr": - build_steps.extend( - [ - build_frontend_package_step(), - enterprise_downstream_step(ver_mode = ver_mode), - ], - ) - else: - # The only other event or "ver_mode" where this is used is 'main' - update_package_json = update_package_json_version() - create_packages["depends_on"] = [update_package_json["name"]] - - build_steps.extend([ - update_package_json, - build_frontend_package_step(depends_on = ["update-package-json-version"]), - ]) - - build_steps.extend( - [ - create_packages, - publish_docker, - build_test_plugins_step(), - grafana_server_step(), - # Note: Main E2E test suites (dashboards, panels, smoke-tests, various) have been migrated to GitHub Actions - # Only keeping tests that are not yet covered by GitHub Actions - cloud_plugins_e2e_tests_step( - "cloud-plugins-suite", - cloud = "azure", - trigger = trigger_oss, - ), - playwright_e2e_tests_step(), - playwright_e2e_report_upload(), - playwright_e2e_report_post_link(), - e2e_tests_artifacts(), # Collects artifacts from remaining E2E tests - build_storybook_step(ver_mode = ver_mode), - test_a11y_frontend_step(ver_mode = ver_mode), - ], - ) - - if ver_mode == "main": - build_steps.extend( - [ - store_storybook_step(trigger = trigger_oss, ver_mode = ver_mode), - frontend_metrics_step(trigger = trigger_oss), - publish_images_step( - depends_on = [create_packages["name"]], - docker_repo = "grafana-oss", - trigger = trigger_oss, - ver_mode = ver_mode, - ), - release_canary_npm_packages_step(trigger = trigger_oss), - upload_packages_step( - depends_on = [create_packages["name"]], - trigger = trigger_oss, - ver_mode = ver_mode, - ), - upload_cdn_step( - depends_on = [create_packages["name"]], - trigger = trigger_oss, - ver_mode = ver_mode, - ), - ], - ) - - publish_suffix = "" - if ver_mode == "main": - publish_suffix = "-publish" - - return pipeline( - name = "{}-build-e2e{}".format(ver_mode, publish_suffix), - environment = environment, - services = [], - steps = init_steps + build_steps, - trigger = trigger, - volumes = github_app_pipeline_volumes(), - ) diff --git a/scripts/drone/pipelines/docs.star b/scripts/drone/pipelines/docs.star deleted file mode 100644 index 7b1f63642bc..00000000000 --- a/scripts/drone/pipelines/docs.star +++ /dev/null @@ -1,84 +0,0 @@ -""" -This module returns all the pipelines used in the event of documentation changes along with supporting functions. -""" - -load( - "scripts/drone/steps/lib.star", - "build_docs_website_step", - "identify_runner_step", - "verify_gen_cue_step", - "yarn_install_step", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -docs_paths = { - "include": [ - "*.md", - "docs/**", - "packages/**/*.md", - "latest.json", - ], -} - -def docs_pipelines(ver_mode, trigger): - environment = {"EDITION": "oss"} - steps = [ - identify_runner_step(), - yarn_install_step(), - lint_docs(), - build_docs_website_step(), - verify_gen_cue_step(), - ] - - return pipeline( - name = "{}-docs".format(ver_mode), - trigger = trigger, - services = [], - steps = steps, - environment = environment, - ) - -def lint_docs(): - return { - "name": "lint-docs", - "image": images["node"], - "depends_on": [ - "yarn-install", - ], - "environment": { - "NODE_OPTIONS": "--max_old_space_size=8192", - }, - "commands": [ - "yarn run prettier:checkDocs", - ], - } - -def trigger_docs_main(): - return { - "branch": "main", - "event": [ - "push", - ], - "repo": [ - "grafana/grafana", - ], - "paths": docs_paths, - } - -def trigger_docs_pr(): - return { - "event": [ - "pull_request", - ], - "repo": [ - "grafana/grafana", - ], - "paths": docs_paths, - } diff --git a/scripts/drone/pipelines/github.star b/scripts/drone/pipelines/github.star deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/scripts/drone/pipelines/publish_images.star b/scripts/drone/pipelines/publish_images.star deleted file mode 100644 index a947d05d0b4..00000000000 --- a/scripts/drone/pipelines/publish_images.star +++ /dev/null @@ -1,130 +0,0 @@ -""" -This module returns the pipeline used for publishing Docker images and its steps. -""" - -load( - "scripts/drone/steps/lib.star", - "compile_build_cmd", - "download_grabpl_step", - "fetch_images_step", - "identify_runner_step", - "publish_images_step", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) -load( - "scripts/drone/vault.star", - "from_secret", -) - -def publish_image_public_step(): - """Returns a step which publishes images - - Returns: - A drone step which publishes Docker images for a public release. - """ - command = """ - bash -c ' - IMAGE_TAG=$(echo "$${TAG}" | sed -e "s/+/-/g") - debug= - if [[ -n $${DRY_RUN} ]]; then debug=echo; fi - docker login -u $${DOCKER_USER} -p $${DOCKER_PASSWORD} - - # Push the grafana-image-tags images - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 - $$debug docker push grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Create the grafana manifests - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG} \ - grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - - $$debug docker manifest create grafana/grafana:$${IMAGE_TAG}-ubuntu \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - # Push the grafana manifests - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG} - $$debug docker manifest push grafana/grafana:$${IMAGE_TAG}-ubuntu - - # if LATEST is set, then also create & push latest - if [[ -n $${LATEST} ]]; then - $$debug docker manifest create grafana/grafana:latest \ - grafana/grafana-image-tags:$${IMAGE_TAG}-amd64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-arm64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-armv7 - $$debug docker manifest create grafana/grafana:latest-ubuntu \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-amd64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-arm64 \ - grafana/grafana-image-tags:$${IMAGE_TAG}-ubuntu-armv7 - - $$debug docker manifest push grafana/grafana:latest - $$debug docker manifest push grafana/grafana:latest-ubuntu - - fi'""" - return { - "environment": { - "DOCKER_USER": from_secret("docker_username"), - "DOCKER_PASSWORD": from_secret("docker_password"), - }, - "name": "publish-images-grafana", - "image": images["docker"], - "depends_on": ["fetch-images"], - "commands": [ - "apk add bash", - command, - ], - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}], - } - -def publish_image_pipelines_public(): - """Generates the pipeline used for publising public Docker images. - - Returns: - Drone pipeline - """ - return [ - pipeline( - name = "publish-docker-public", - trigger = { - "event": ["promote"], - "target": ["public"], - }, - steps = [ - identify_runner_step(), - download_grabpl_step(), - compile_build_cmd(), - fetch_images_step(), - publish_image_public_step(), - publish_images_step("release", "grafana-oss"), - ], - environment = {"EDITION": "oss"}, - ), - pipeline( - name = "manually-publish-docker-public", - trigger = { - "event": ["promote"], - "target": ["publish-docker-public"], - }, - steps = [ - identify_runner_step(), - download_grabpl_step(), - compile_build_cmd(), - fetch_images_step(), - publish_image_public_step(), - ], - environment = {"EDITION": "oss"}, - ), - ] diff --git a/scripts/drone/pipelines/trigger_downstream.star b/scripts/drone/pipelines/trigger_downstream.star deleted file mode 100644 index 698e3af027d..00000000000 --- a/scripts/drone/pipelines/trigger_downstream.star +++ /dev/null @@ -1,46 +0,0 @@ -""" -This module returns the pipeline used for triggering a downstream pipeline for Grafana Enterprise. -""" - -load( - "scripts/drone/steps/lib.star", - "enterprise_downstream_step", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -trigger = { - "event": [ - "push", - ], - "branch": "main", - "paths": { - "exclude": [ - "*.md", - "docs/**", - "latest.json", - ], - }, - "repo": [ - "grafana/grafana", - ], -} - -def enterprise_downstream_pipeline(): - environment = {"EDITION": "oss"} - steps = [ - enterprise_downstream_step(ver_mode = "main"), - ] - deps = [ - "main-build-e2e-publish", - ] - return pipeline( - name = "main-trigger-downstream", - trigger = trigger, - services = [], - steps = steps, - depends_on = deps, - environment = environment, - ) diff --git a/scripts/drone/pipelines/verify_drone.star b/scripts/drone/pipelines/verify_drone.star deleted file mode 100644 index 366b702bd08..00000000000 --- a/scripts/drone/pipelines/verify_drone.star +++ /dev/null @@ -1,29 +0,0 @@ -""" -This module returns the pipeline used for verifying Drone configuration. -""" - -load( - "scripts/drone/steps/lib.star", - "compile_build_cmd", - "identify_runner_step", - "lint_drone_step", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -def verify_drone(trigger, ver_mode): - environment = {"EDITION": "oss"} - steps = [ - identify_runner_step(), - compile_build_cmd(), - lint_drone_step(), - ] - return pipeline( - name = "{}-verify-drone".format(ver_mode), - trigger = trigger, - services = [], - steps = steps, - environment = environment, - ) diff --git a/scripts/drone/pipelines/verify_starlark.star b/scripts/drone/pipelines/verify_starlark.star deleted file mode 100644 index e939cd598ef..00000000000 --- a/scripts/drone/pipelines/verify_starlark.star +++ /dev/null @@ -1,27 +0,0 @@ -""" -This module returns a Drone pipeline that verifies all Starlark files are linted. -""" - -load( - "scripts/drone/steps/lib.star", - "identify_runner_step", - "lint_starlark_step", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", -) - -def verify_starlark(trigger, ver_mode): - environment = {"EDITION": "oss"} - steps = [ - identify_runner_step(), - lint_starlark_step(), - ] - return pipeline( - name = "{}-verify-starlark".format(ver_mode), - trigger = trigger, - services = [], - steps = steps, - environment = environment, - ) diff --git a/scripts/drone/rgm.star b/scripts/drone/rgm.star deleted file mode 100644 index 604df62766e..00000000000 --- a/scripts/drone/rgm.star +++ /dev/null @@ -1,382 +0,0 @@ -""" -'rgm' pipelines are pipelines that use dagger (located in 'pkg/build/daggerbuild') -""" - -load( - "scripts/drone/dagger.star", - "with_dagger_install", -) -load( - "scripts/drone/events/release.star", - "verify_release_pipeline", -) -load( - "scripts/drone/steps/github.star", - "github_app_generate_token_step", - "github_app_pipeline_volumes", - "github_app_step_volumes", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/utils/utils.star", - "pipeline", - "with_deps", -) -load( - "scripts/drone/variables.star", - "dagger_version", -) -load( - "scripts/drone/vault.star", - "from_secret", - "npm_token", - "rgm_cdn_destination", - "rgm_dagger_token", - "rgm_destination", - "rgm_downloads_destination", - "rgm_gcp_key_base64", - "rgm_storybook_destination", -) - -docs_paths = { - "exclude": [ - "*.md", - "docs/**", - "packages/**/*.md", - "latest.json", - ], -} - -tag_trigger = { - "event": { - "exclude": [ - "promote", - ], - }, - "ref": { - "include": [ - "refs/tags/v*", - ], - "exclude": [ - "refs/tags/*-cloud*", - ], - }, -} - -main_trigger = { - "event": [ - "push", - ], - "branch": "main", - "paths": docs_paths, - "repo": [ - "grafana/grafana", - ], -} - -nightly_trigger = { - "event": { - "include": [ - "cron", - ], - }, - "cron": { - "include": [ - "nightly-release", - ], - }, -} - -version_branch_trigger = {"ref": ["refs/heads/v[0-9]*"]} - -def rgm_env_secrets(env): - """Adds the rgm secret ENV variables to the given env arg - - Args: - env: A map of environment varables. This function will adds the necessary secrets to it (and potentially overwrite them). - Returns: - Drone step. - """ - env["DESTINATION"] = from_secret(rgm_destination) - env["STORYBOOK_DESTINATION"] = from_secret(rgm_storybook_destination) - env["CDN_DESTINATION"] = from_secret(rgm_cdn_destination) - env["DOWNLOADS_DESTINATION"] = from_secret(rgm_downloads_destination) - - env["GCP_KEY_BASE64"] = from_secret(rgm_gcp_key_base64) - env["_EXPERIMENTAL_DAGGER_CLOUD_TOKEN"] = from_secret(rgm_dagger_token) - env["GPG_PRIVATE_KEY"] = from_secret("packages_gpg_private_key") - env["GPG_PUBLIC_KEY"] = from_secret("packages_gpg_public_key") - env["GPG_PASSPHRASE"] = from_secret("packages_gpg_passphrase") - env["DOCKER_USERNAME"] = from_secret("docker_username") - env["DOCKER_PASSWORD"] = from_secret("docker_password") - env["NPM_TOKEN"] = from_secret(npm_token) - env["GCOM_API_KEY"] = from_secret("grafana_api_key") - return env - -def rgm_run(name, script): - """Returns a pipeline that does a full build & package of Grafana. - - Args: - name: The name of the pipeline step. - script: The script in the container to run. - Returns: - Drone step. - """ - env = { - "ALPINE_BASE": images["alpine"], - "UBUNTU_BASE": images["ubuntu"], - } - rgm_run_step = { - "name": name, - "image": images["go"], - "pull": "always", - "commands": with_dagger_install([ - "export GRAFANA_DIR=$$(pwd)", - "export GITHUB_TOKEN=$(cat /github-app/token)", - "./pkg/build/daggerbuild/scripts/{}".format(script), - ], dagger_version), - "environment": rgm_env_secrets(env), - # The docker socket is a requirement for running dagger programs - # In the future we should find a way to use dagger without mounting the docker socket. - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}] + github_app_step_volumes(), - } - - return [ - rgm_run_step, - ] - -def rgm_copy(src, dst): - """Copies file from/to GCS. - - Args: - src: source of the files. - dst: destination of the files. - - Returns: - Drone step. - """ - commands = [ - "printenv GCP_KEY_BASE64 | base64 -d > /tmp/key.json", - "gcloud auth activate-service-account --key-file=/tmp/key.json", - "gcloud storage cp -r {} {}".format(src, dst), - ] - - return { - "name": "rgm-copy", - "image": "google/cloud-sdk:alpine", - "commands": commands, - "environment": rgm_env_secrets({}), - } - -def rgm_publish_packages(bucket = "grafana-packages"): - """Publish deb and rpm packages. - - Args: - bucket: target bucket to publish the packages. - - Returns: - Drone steps. - """ - steps = [] - for package_manager in ["deb", "rpm"]: - steps.append({ - "name": "publish-{}".format(package_manager), - # See https://github.com/grafana/deployment_tools/blob/master/docker/package-publish/README.md for docs on that image - "image": images["package_publish"], - "privileged": True, - "settings": { - "access_key_id": from_secret("packages_access_key_id"), - "secret_access_key": from_secret("packages_secret_access_key"), - "service_account_json": from_secret("packages_service_account"), - "target_bucket": bucket, - "gpg_passphrase": from_secret("packages_gpg_passphrase"), - "gpg_public_key": from_secret("packages_gpg_public_key"), - "gpg_private_key": from_secret("packages_gpg_private_key"), - "package_path": "file:///drone/src/dist/*.{}".format(package_manager), - }, - }) - - return steps - -def rgm_main(): - # Runs a package / build process (with some distros) when commits are merged to main - return pipeline( - name = "rgm-main-prerelease", - trigger = main_trigger, - steps = rgm_run("rgm-build", "drone_build_main.sh"), - ) - -def rgm_tag(): - # Runs a package / build process (with all distros) when a tag is made - return pipeline( - name = "rgm-tag-prerelease", - trigger = tag_trigger, - steps = rgm_run("rgm-build", "drone_build_tag_grafana.sh"), - ) - -def rgm_version_branch(): - # Runs a package / build proces (with all distros) when a commit lands on a version branch - return pipeline( - name = "rgm-version-branch-prerelease", - trigger = version_branch_trigger, - steps = rgm_run("rgm-build", "drone_build_tag_grafana.sh"), - ) - -def rgm_nightly_build(): - """Nightly build pipeline. - - Returns: - Drone pipeline. - """ - src = "$${DRONE_WORKSPACE}/dist/*" - dst = "$${DESTINATION}/$${DRONE_BUILD_EVENT}" - copy_step = rgm_copy(src, dst) - if not dst.startswith("gs://"): - copy_step["commands"].insert(0, "mkdir -p {}".format(dst)) - - copy_steps = with_deps([copy_step], ["rgm-build"]) - - return pipeline( - name = "rgm-nightly-build", - trigger = nightly_trigger, - steps = rgm_run("rgm-build", "drone_build_nightly_grafana.sh") + copy_steps, - ) - -def rgm_nightly_publish(): - """Nightly publish pipeline. - - Returns: - Drone pipeline. - """ - src = "$${DESTINATION}/$${DRONE_BUILD_EVENT}/*_$${DRONE_BUILD_NUMBER}_*" - dst = "$${DRONE_WORKSPACE}/dist" - - publish_steps = with_deps(rgm_run("rgm-publish", "drone_publish_nightly_grafana.sh"), ["rgm-copy"]) - package_steps = with_deps(rgm_publish_packages(), ["rgm-publish"]) - copy_step = rgm_copy(src, dst) - if not dst.startswith("gs://"): - copy_step["commands"].insert(0, "mkdir -p {}".format(dst)) - return pipeline( - name = "rgm-nightly-publish", - trigger = nightly_trigger, - steps = [copy_step] + publish_steps + package_steps, - depends_on = ["rgm-nightly-build"], - ) - -def rgm_nightly_pipeline(): - return [ - rgm_nightly_build(), - rgm_nightly_publish(), - ] - -def rgm_tag_pipeline(): - build = rgm_tag() - - return [ - build, - verify_release_pipeline( - trigger = tag_trigger, - name = "rgm-tag-verify-prerelease-assets", - bucket = "grafana-prerelease", - depends_on = [ - build["name"], - ], - ), - ] - -def rgm_version_branch_pipeline(): - return [ - rgm_version_branch(), - verify_release_pipeline( - trigger = version_branch_trigger, - name = "rgm-prerelease-verify-prerelease-assets", - bucket = "grafana-prerelease", - depends_on = [ - "rgm-version-branch-prerelease", - ], - ), - ] - -def rgm_main_pipeline(): - return [ - rgm_main(), - ] - -def rgm_promotion_pipeline(): - """Promotion build pipeline. - - Returns: - Drone pipeline. - """ - promotion_trigger = { - "event": ["promote"], - "target": "upload-packages", - } - - env = { - "ALPINE_BASE": images["alpine"], - "UBUNTU_BASE": images["ubuntu"], - } - - # Expected promotion args: - # * GRAFANA_REF = commit hash, branch name, or tag name - # * ENTERPRISE_REF = commit hash, branch name, or tag name. If not building an enterprise artifact, then this can be - # left empty. - # * ARTIFACTS = comma delimited list of artifacts (ex: "targz:grafana:linux/amd64,rpm:grafana:linux/amd64") - # * VERSION = version string of Grafana that is being built (ex: v10.0.0) - # * UPLOAD_TO = Google Cloud Storage URL to upload the built artifacts to. (ex: gs://some-bucket/path) - build_step = { - "name": "rgm-build", - "image": images["go"], - "pull": "always", - "commands": with_dagger_install([ - "export GITHUB_TOKEN=$(cat /github-app/token)", - "dagger run --silent go run ./pkg/build/cmd artifacts " + - "-a $${ARTIFACTS} " + - "--grafana-ref=$${GRAFANA_REF} " + - "--enterprise-ref=$${ENTERPRISE_REF} " + - "--grafana-repo=$${GRAFANA_REPO} " + - "--build-id=$${DRONE_BUILD_NUMBER} " + - "--version=$${VERSION}", - ], dagger_version), - "environment": rgm_env_secrets(env), - # The docker socket is a requirement for running dagger programs - # In the future we should find a way to use dagger without mounting the docker socket. - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}] + github_app_step_volumes(), - } - - generate_token_step = github_app_generate_token_step() - publish_step = rgm_copy("dist/*", "$${UPLOAD_TO}") - build_step["depends_on"] = [ - generate_token_step["name"], - ] - - publish_step["depends_on"] = [ - build_step["name"], - ] - - steps = [ - generate_token_step, - build_step, - publish_step, - ] - - return [ - pipeline( - name = "rgm-promotion", - trigger = promotion_trigger, - steps = steps, - volumes = github_app_step_volumes() + github_app_pipeline_volumes(), - ), - ] - -def rgm(): - return ( - rgm_main_pipeline() + - rgm_tag_pipeline() + - rgm_version_branch_pipeline() + - rgm_promotion_pipeline() - ) diff --git a/scripts/drone/steps/github.star b/scripts/drone/steps/github.star deleted file mode 100644 index 2325ba83a57..00000000000 --- a/scripts/drone/steps/github.star +++ /dev/null @@ -1,42 +0,0 @@ -""" -This module is used to interface with the GitHub App to extract temporary installation tokens. -""" - -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/vault.star", - "from_secret", - "github_app_app_id", - "github_app_app_installation_id", - "github_app_private_key", -) - -def github_app_step_volumes(): - return [ - {"name": "github-app", "path": "/github-app"}, - ] - -def github_app_pipeline_volumes(): - return [ - {"name": "github-app", "temp": {}}, - ] - -def github_app_generate_token_step(): - return { - "name": "github-app-generate-token", - "image": images["github_app_secret_writer"], - "environment": { - "GITHUB_APP_ID": from_secret(github_app_app_id), - "GITHUB_APP_INSTALLATION_ID": from_secret(github_app_app_installation_id), - "GITHUB_APP_PRIVATE_KEY": from_secret(github_app_private_key), - }, - "commands": [ - "echo $(/usr/bin/github-app-external-token) > /github-app/token", - ], - "volumes": github_app_step_volumes(), - # forks or those without access would cause it to fail, but we can safely ignore it since there'll be no token. - "failure": "ignore", - } diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star deleted file mode 100644 index 746c183767e..00000000000 --- a/scripts/drone/steps/lib.star +++ /dev/null @@ -1,1189 +0,0 @@ -""" -This module is a library of Drone steps and other pipeline components. -""" - -load( - "scripts/drone/steps/github.star", - "github_app_generate_token_step", - "github_app_step_volumes", -) -load( - "scripts/drone/steps/rgm.star", - "rgm_build_backend_step", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/variables.star", - "grabpl_version", -) -load( - "scripts/drone/vault.star", - "from_secret", - "gcp_grafanauploads", - "gcp_grafanauploads_base64", - "gcp_upload_artifacts_key", - "npm_token", - "prerelease_bucket", -) - -trigger_oss = { - "repo": [ - "grafana/grafana", - ], -} - -def yarn_install_step(): - return { - "name": "yarn-install", - "image": images["node"], - "commands": [ - "yarn install --immutable || yarn install --immutable", - ], - "depends_on": [], - } - -def wire_install_step(): - return { - "name": "wire-install", - "image": images["go"], - "commands": [ - "apk add --update make", - "make gen-go", - ], - "depends_on": [ - "verify-gen-cue", - ], - } - -def identify_runner_step(): - return { - "name": "identify-runner", - "image": images["alpine"], - "commands": [ - "echo $DRONE_RUNNER_NAME", - ], - } - -def enterprise_setup_step(source = "${DRONE_SOURCE_BRANCH}", canFail = True, isPromote = False): - """Setup the enterprise source into the ./grafana-enterprise directory. - - Args: - source: controls which revision of grafana-enterprise is checked out, if it exists. The name 'source' derives from the 'source branch' of a pull request. - canFail: controls whether the step can fail. This is useful for pull requests where the enterprise source may not exist. - isPromote: controls whether or not this step is being used in a promote pipeline. If it is, then the clone enterprise step will not check if the pull request is a fork. - Returns: - Drone step. - """ - step = clone_enterprise_step_pr(source = source, target = "${DRONE_TARGET_BRANCH}", canFail = canFail, location = "../grafana-enterprise", isPromote = isPromote) - step["commands"] += [ - "cd ../", - "ln -s src grafana", - "cd ./grafana-enterprise", - "./build.sh", - ] - - return step - -def clone_enterprise_step_pr(source = "${DRONE_COMMIT}", target = "main", canFail = False, location = "grafana-enterprise", isPromote = False): - """Clone the enterprise source into the ./grafana-enterprise directory. - - Args: - source: controls which revision of grafana-enterprise is checked out, if it exists. The name 'source' derives from the 'source branch' of a pull request. - target: controls which revision of grafana-enterprise is checked out, if it 'source' does not exist. The name 'target' derives from the 'target branch' of a pull request. If this does not exist, then 'main' will be checked out. - canFail: controls whether or not this step is allowed to fail. If it fails and this is true, then the pipeline will continue. canFail is used in pull request pipelines where enterprise may be cloned but may not clone in forks. - location: the path where grafana-enterprise is cloned. - isPromote: controls whether or not this step is being used in a promote pipeline. If it is, then the step will not check if the pull request is a fork. - Returns: - Drone step. - """ - - if isPromote: - check = [] - else: - check = [ - 'is_fork=$(curl --retry 5 "https://$${GITHUB_TOKEN}@api.github.com/repos/grafana/grafana/pulls/$DRONE_PULL_REQUEST" | jq .head.repo.fork)', - 'if [ "$is_fork" != false ]; then return 1; fi', # Only clone if we're confident that 'fork' is 'false'. Fail if it's also empty. - ] - - step = { - "name": "clone-enterprise", - "image": images["git"], - "commands": [ - "apk add --update curl jq bash", - "GITHUB_TOKEN=$(cat /github-app/token)", - ] + check + [ - 'git clone "https://x-access-token:$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" ' + location, - "cd {}".format(location), - 'if git checkout {0}; then echo "checked out {0}"; elif git checkout {1}; then echo "git checkout {1}"; else git checkout main; fi'.format(source, target), - ], - "depends_on": [ - github_app_generate_token_step()["name"], - ], - "volumes": github_app_step_volumes(), - } - - if canFail: - step["failure"] = "ignore" - - return step - -def download_grabpl_step(): - return { - "name": "grabpl", - "image": images["curl"], - "commands": [ - "mkdir -p bin", - "curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/{}/grabpl".format( - grabpl_version, - ), - "chmod +x bin/grabpl", - ], - } - -def lint_drone_step(): - return { - "name": "lint-drone", - "image": images["curl"], - "commands": [ - "./bin/build verify-drone", - ], - "depends_on": [ - "compile-build-cmd", - ], - } - -def lint_starlark_step(): - return { - "name": "lint-starlark", - "image": images["go"], - "commands": [ - "go install github.com/bazelbuild/buildtools/buildifier@latest", - "buildifier --lint=warn -mode=check -r .", - ], - "depends_on": [], - } - -def enterprise_downstream_step(ver_mode): - """Triggers a downstream pipeline in the grafana-enterprise repository. - - Args: - ver_mode: indirectly controls the revision used for downstream pipelines. - It also used to allow the step to fail for pull requests without blocking merging. - - Returns: - Drone step. - """ - repo = "grafana/grafana-enterprise@" - if ver_mode == "pr" or ver_mode == "rrc": - repo += "${DRONE_SOURCE_BRANCH}" - else: - repo += "main" - - step = { - "name": "trigger-enterprise-downstream", - "image": images["drone_downstream"], - "settings": { - "server": "https://drone.grafana.net", - "token": from_secret("drone_token"), - "repositories": [ - repo, - ], - "params": [ - "SOURCE_BUILD_NUMBER=${DRONE_COMMIT}", - "SOURCE_COMMIT=${DRONE_COMMIT}", - ], - }, - } - - if ver_mode == "pr": - step.update({"failure": "ignore"}) - step["settings"]["params"].append("OSS_PULL_REQUEST=${DRONE_PULL_REQUEST}") - - if ver_mode == "rrc": - step["settings"]["params"].append("SOURCE_TAG=${DRONE_TAG}") - - return step - -def validate_modfile_step(): - return { - "name": "validate-modfile", - "image": images["go"], - "commands": [ - "go run scripts/modowners/modowners.go check go.mod", - ], - } - -def validate_openapi_spec_step(): - return { - "name": "validate-openapi-spec", - "image": images["go"], - "commands": [ - "apk add --update make", - "make swagger-validate", - ], - } - -def dockerize_step(name, hostname, port, canFail = False): - step = { - "name": name, - "image": images["dockerize"], - "commands": [ - "dockerize -wait tcp://{}:{} -timeout 120s".format(hostname, port), - ], - } - - if canFail: - step["failure"] = "ignore" - - return step - -def build_storybook_step(ver_mode): - return { - "name": "build-storybook", - "image": images["node"], - "depends_on": [ - # Best to ensure that this step doesn't mess with what's getting built and packaged - "rgm-package", - "build-frontend-packages", - ], - "environment": { - "NODE_OPTIONS": "--max_old_space_size=4096", - }, - "commands": [ - "yarn storybook:build", - "./bin/build verify-storybook", - ], - "when": get_trigger_storybook(ver_mode), - } - -def store_storybook_step(ver_mode, trigger = None): - """Publishes the Grafana UI components storybook. - - Args: - ver_mode: controls whether a release or canary version is published. - trigger: a Drone trigger for the step. - Defaults to None. - - Returns: - Drone step. - """ - commands = [] - if ver_mode == "release": - commands.extend( - [ - "./bin/build store-storybook --deployment latest", - "./bin/build store-storybook --deployment ${DRONE_TAG}", - ], - ) - - else: - # main pipelines should deploy storybook to grafana-storybook/canary public bucket - commands = [ - "./bin/build store-storybook --deployment canary", - ] - - step = { - "name": "store-storybook", - "image": images["publish"], - "depends_on": [ - "build-storybook", - ] + - end_to_end_tests_deps(), - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads), - "PRERELEASE_BUCKET": from_secret(prerelease_bucket), - }, - "commands": commands, - "when": get_trigger_storybook(ver_mode), - } - if trigger and ver_mode in ("release-branch", "main"): - # no dict merge operation available, https://github.com/harness/drone-cli/pull/220 - when_cond = { - "repo": [ - "grafana/grafana", - ], - "paths": { - "include": [ - "packages/grafana-ui/**", - ], - }, - } - step = dict(step, when = when_cond) - return step - -def e2e_tests_artifacts(): - # Note: This function is kept for backward compatibility but now only handles - # artifacts from the remaining E2E tests that haven't been migrated to GitHub Actions - return { - "name": "e2e-tests-artifacts-upload", - "image": images["cloudsdk"], - "depends_on": [ - # Note: Main E2E tests have been migrated to GitHub Actions - # Only depend on remaining Drone E2E tests - "end-to-end-tests-cloud-plugins-suite-azure", - "playwright-plugin-e2e", - github_app_generate_token_step()["name"], - ], - "failure": "ignore", - "when": { - "status": [ - "success", - "failure", - ], - }, - "environment": { - "GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY": from_secret(gcp_upload_artifacts_key), - "E2E_TEST_ARTIFACTS_BUCKET": "releng-pipeline-artifacts-dev", - }, - "commands": [ - "export GITHUB_TOKEN=$(cat /github-app/token)", - # if no videos found do nothing (may be fewer videos now that main tests are in GitHub Actions) - "if [ -z `find ./e2e -type f -name *spec.ts.mp4` ]; then echo 'no e2e videos found from remaining tests'; exit 0; fi", - "apt-get update", - "apt-get install -yq zip", - "printenv GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY > /tmp/gcpkey_upload_artifacts.json", - "gcloud auth activate-service-account --key-file=/tmp/gcpkey_upload_artifacts.json", - # we want to only include files in e2e folder that end with .spec.ts.mp4 - 'find ./e2e -type f -name "*spec.ts.mp4" | zip e2e/videos.zip -@', - "gsutil cp e2e/videos.zip gs://$${E2E_TEST_ARTIFACTS_BUCKET}/${DRONE_BUILD_NUMBER}/artifacts/videos/videos.zip", - "export E2E_ARTIFACTS_VIDEO_ZIP=https://storage.googleapis.com/$${E2E_TEST_ARTIFACTS_BUCKET}/${DRONE_BUILD_NUMBER}/artifacts/videos/videos.zip", - 'echo "E2E Test artifacts uploaded to: $${E2E_ARTIFACTS_VIDEO_ZIP}"', - 'curl -X POST https://api.github.com/repos/${DRONE_REPO}/statuses/${DRONE_COMMIT_SHA} -H "Authorization: token $${GITHUB_TOKEN}" -d ' + - '"{\\"state\\":\\"success\\",\\"target_url\\":\\"$${E2E_ARTIFACTS_VIDEO_ZIP}\\", \\"description\\": \\"Click on the details to download e2e recording videos\\", \\"context\\": \\"e2e_artifacts\\"}"', - ], - "volumes": github_app_step_volumes(), - } - -def playwright_e2e_report_upload(): - return { - "name": "playwright-e2e-report-upload", - "image": images["cloudsdk"], - "depends_on": [ - "playwright-plugin-e2e", - ], - "failure": "ignore", - "when": { - "status": [ - "success", - "failure", - ], - }, - "environment": { - "GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY": from_secret(gcp_upload_artifacts_key), - }, - "commands": [ - "apt-get update", - "apt-get install -yq zip", - "printenv GCP_GRAFANA_UPLOAD_ARTIFACTS_KEY > /tmp/gcpkey_upload_artifacts.json", - "gcloud auth activate-service-account --key-file=/tmp/gcpkey_upload_artifacts.json", - "gsutil cp -r ./playwright-report/. gs://releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report", - "export E2E_PLAYWRIGHT_REPORT_URL=https://storage.googleapis.com/releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report/index.html", - 'echo "E2E Playwright report uploaded to: \n $${E2E_PLAYWRIGHT_REPORT_URL}"', - ], - } - -def playwright_e2e_report_post_link(): - return { - "name": "playwright-e2e-report-post-link", - "image": images["curl"], - "depends_on": [ - "playwright-e2e-report-upload", - github_app_generate_token_step()["name"], - ], - "failure": "ignore", - "when": { - "status": [ - "success", - "failure", - ], - }, - "commands": [ - "GITHUB_TOKEN=$(cat /github-app/token)", - # if the trace doesn't folder exists, it means that there are no failed tests. - "if [ ! -d ./playwright-report/trace ]; then echo 'all tests passed'; exit 0; fi", - # if it exists, we will post a comment on the PR with the link to the report - "export E2E_PLAYWRIGHT_REPORT_URL=https://storage.googleapis.com/releng-pipeline-artifacts-dev/${DRONE_BUILD_NUMBER}/playwright-report/index.html", - "curl -L " + - "-X POST https://api.github.com/repos/grafana/grafana/issues/${DRONE_PULL_REQUEST}/comments " + - '-H "Accept: application/vnd.github+json" ' + - '-H "Authorization: Bearer $${GITHUB_TOKEN}" ' + - '-H "X-GitHub-Api-Version: 2022-11-28" -d ' + - '"{\\"body\\":\\"❌ Failed to run Playwright plugin e2e tests.

Click [here]($${E2E_PLAYWRIGHT_REPORT_URL}) to browse the Playwright report and trace viewer.
For information on how to run Playwright tests locally, refer to the [Developer guide](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#to-run-the-playwright-tests). \\"}"', - ], - "volumes": github_app_step_volumes(), - } - -def upload_cdn_step(ver_mode, trigger = None, depends_on = ["grafana-server"]): - """Uploads CDN assets using the Grafana build tool. - - Args: - ver_mode: only uses the step trigger when ver_mode == 'release-branch' or 'main' - trigger: a Drone trigger for the step. - Defaults to None. - depends_on: drone steps that this step depends on - - Returns: - Drone step. - """ - - step = { - "name": "upload-cdn-assets", - "image": images["publish"], - "depends_on": depends_on, - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads), - "PRERELEASE_BUCKET": from_secret(prerelease_bucket), - }, - "commands": [ - "./bin/build upload-cdn --edition oss", - ], - } - if trigger and ver_mode in ("release-branch", "main"): - step = dict(step, when = trigger) - return step - -def build_backend_step(distros = "linux/amd64,linux/arm64"): - """Build the backend code using the Grafana build tool. - - Args: - distros: a list of distributes to be built. For a full list, see `go tool dist list`. - - Returns: - Drone step. - """ - - return rgm_build_backend_step(distros) - -def build_frontend_step(): - """Build the frontend code to ensure it's compilable - - Returns: - Drone step. - """ - return { - "name": "build-frontend", - "image": images["node"], - "environment": { - "NODE_OPTIONS": "--max_old_space_size=8192", - }, - "depends_on": [ - "compile-build-cmd", - "yarn-install", - ], - "commands": [ - "yarn build", - ], - } - -def build_test_plugins_step(): - """Build the test plugins used in e2e tests - - Returns: - Drone step. - """ - return { - "name": "build-test-plugins", - "image": images["node"], - "environment": { - "NODE_OPTIONS": "--max_old_space_size=8192", - }, - "depends_on": [ - "yarn-install", - ], - "commands": [ - "yarn e2e:plugin:build", - ], - } - -def update_package_json_version(): - """Updates the packages/ to use a version that has the build ID in it: 10.0.0pre -> 10.0.0-5432pre - - Returns: - Drone step that updates the 'version' key in package.json - """ - - return { - "name": "update-package-json-version", - "image": images["node"], - "depends_on": [ - "yarn-install", - ], - "commands": [ - "apk add --update jq", - "new_version=$(cat package.json | jq -r .version | sed s/pre/${DRONE_BUILD_NUMBER}/g)", - "echo \"New version: $new_version\"", - "yarn run lerna version $new_version --exact --no-git-tag-version --no-push --force-publish -y", - "yarn install --mode=update-lockfile", - ], - } - -def build_frontend_package_step(depends_on = []): - """Build the frontend packages using the Grafana build tool. - - Args: - depends_on: a list of step names (strings) that must complete before this step runs. - - Returns: - Drone step. - """ - - cmds = [ - "apk add --update jq bash", # bash is needed for the validate-npm-packages.sh script since it has a 'bash' - # shebang. - "yarn packages:build", - "yarn packages:pack", - "./scripts/validate-npm-packages.sh", - ] - - return { - "name": "build-frontend-packages", - "image": images["node"], - "environment": { - "NODE_OPTIONS": "--max_old_space_size=8192", - }, - "depends_on": [ - "yarn-install", - ] + depends_on, - "commands": cmds, - } - -def build_plugins_step(ver_mode): - if ver_mode != "pr": - env = { - "GRAFANA_API_KEY": from_secret("grafana_api_key"), - } - else: - env = None - return { - "name": "build-plugins", - "image": images["node"], - "environment": env, - "depends_on": [ - "yarn-install", - ], - "commands": [ - "apk add --update findutils", # Replaces the busybox 'find' with the GNU one. - "yarn plugins:build", - ], - } - -def betterer_frontend_step(): - """Run betterer on frontend code. - - Returns: - Drone step. - """ - - return { - "name": "betterer-frontend", - "image": images["node"], - "depends_on": [ - "yarn-install", - ], - "commands": [ - "apk add --update git bash", - "yarn betterer:ci", - ], - } - -def verify_i18n_step(): - extract_error_message = "\nExtraction failed. Make sure that you have no dynamic translation phrases, such as 't(\\`preferences.theme.\\$${themeID}\\`, themeName)' and that no translation key is used twice. Search the output for '[warning]' to find the offending file." - uncommited_error_message = "\nTranslation extraction has not been committed. Please run 'make i18n-extract', commit the changes and push again." - return { - "name": "verify-i18n", - "image": images["node_deb"], - "depends_on": [ - "yarn-install", - ], - "commands": [ - "make i18n-extract || (echo \"{}\" && false)".format(extract_error_message), - # Verify that translation extraction has been committed - ''' - file_diff=$(git diff --dirstat public/locales) - if [ -n "$file_diff" ]; then - echo $file_diff - echo "{}" - exit 1 - fi - '''.format(uncommited_error_message), - ], - } - -def verify_api_clients_step(): - uncommited_error_message = "\nAPI client generation has not been committed. Please run 'yarn generate-apis', commit the changes and push again." - return { - "name": "verify-api-clients", - "image": images["node_deb"], - "depends_on": [ - "yarn-install", - ], - "commands": [ - "yarn generate-apis", - # Verify that client generation has been run and committed - ''' - file_diff=$(git diff ':!conf') - if [ -n "$file_diff" ]; then - echo $file_diff - echo "{}" - exit 1 - fi - '''.format(uncommited_error_message), - ], - } - -def test_a11y_frontend_step(ver_mode, port = 3001): - """Runs automated accessiblity tests against the frontend. - - Args: - ver_mode: controls whether the step is blocking or just reporting. - If ver_mode == 'pr', the step causes the pipeline to fail. - port: which port to grafana-server is expected to be listening on. - Defaults to 3001. - - Returns: - Drone step. - """ - commands = [ - # Note - this runs in a container running node 14, which does not support the -y option to npx - "npx wait-on@7.0.1 http://$HOST:$PORT", - "pa11y-ci --config e2e/pa11yci.conf.js", - ] - failure = "ignore" - no_thresholds = "true" - if ver_mode == "pr": - failure = "always" - no_thresholds = "false" - - return { - "name": "test-a11y-frontend", - # TODO which image should be used? - "image": images["docker_puppeteer"], - "depends_on": [ - "grafana-server", - ], - "environment": { - "GRAFANA_MISC_STATS_API_KEY": from_secret("grafana_misc_stats_api_key"), - "HOST": "grafana-server", - "PORT": port, - "NO_THRESHOLDS": no_thresholds, - }, - "failure": failure, - "commands": commands, - } - -def frontend_metrics_step(trigger = None): - """Reports frontend metrics to Grafana Cloud. - - Args: - trigger: a Drone trigger for the step. - Defaults to None. - - Returns: - Drone step. - """ - - step = { - "name": "publish-frontend-metrics", - "image": images["node"], - "depends_on": [ - "test-a11y-frontend", - ], - "environment": { - "GRAFANA_MISC_STATS_API_KEY": from_secret("grafana_misc_stats_api_key"), - }, - "failure": "ignore", - "commands": [ - "apk add --update bash grep git", - "./scripts/ci-frontend-metrics.sh ./grafana/public/build | ./bin/build publish-metrics $$GRAFANA_MISC_STATS_API_KEY", - ], - } - if trigger: - step = dict(step, when = trigger) - return step - -def grafana_server_step(): - """Runs the grafana-server binary as a service. - - Returns: - Drone step. - """ - environment = { - "GF_SERVER_HTTP_PORT": "3001", - "GF_SERVER_ROUTER_LOGGING": "1", - "GF_APP_MODE": "development", - } - - return { - "name": "grafana-server", - "image": images["alpine"], - "detach": True, - "depends_on": [ - "rgm-package", - ], - "environment": environment, - "commands": [ - "apk add --update tar bash", - "mkdir grafana", - "tar --strip-components=1 -xvf ./dist/*amd64.tar.gz -C grafana", - "cp -r devenv scripts tools grafana && cd grafana && ./scripts/grafana-server/start-server", - ], - } - -def e2e_tests_step(suite, port = 3001, tries = None): - cmd = "./bin/build e2e-tests --port {} --suite {}".format(port, suite) - if tries: - cmd += " --tries {}".format(tries) - return { - "name": "end-to-end-tests-{}".format(suite), - "image": images["cypress"], - "depends_on": [ - "grafana-server", - "build-test-plugins", - ], - "environment": { - "HOST": "grafana-server", - }, - "commands": [ - cmd, - ], - } - -def start_storybook_step(): - return { - "name": "start-storybook", - "image": images["node"], - "depends_on": [ - "yarn-install", - ], - "commands": [ - "yarn storybook --quiet", - ], - "detach": True, - } - -def cloud_plugins_e2e_tests_step(suite, cloud, trigger = None): - """Run cloud plugins end-to-end tests. - - Args: - suite: affects the pipeline name. - TODO: check if this actually affects step behavior. - cloud: used to determine cloud provider specific tests. - trigger: a Drone trigger for the step. - Defaults to None. - - Returns: - Drone step. - """ - environment = {} - when = {} - if trigger: - when = trigger - if cloud == "azure": - environment = { - "CYPRESS_CI": "true", - "HOST": "grafana-server", - "AZURE_SP_APP_ID": from_secret("azure_sp_app_id"), - "AZURE_SP_PASSWORD": from_secret("azure_sp_app_pw"), - "AZURE_TENANT": from_secret("azure_tenant"), - } - when = dict( - when, - paths = { - "include": [ - "pkg/tsdb/azuremonitor/**", - "public/app/plugins/datasource/azuremonitor/**", - "e2e/cloud-plugins-suite/azure-monitor.spec.ts", - ], - }, - ) - branch = "${DRONE_SOURCE_BRANCH}".replace("/", "-") - step = { - "name": "end-to-end-tests-{}-{}".format(suite, cloud), - "image": "us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-14.3.2:1.0.0", - "depends_on": [ - "grafana-server", - github_app_generate_token_step()["name"], - ], - "environment": environment, - "commands": [ - "GITHUB_TOKEN=$(cat /github-app/token)", - "cd /", - "./cpp-e2e/scripts/ci-run.sh {} {}".format(cloud, branch), - ], - "volumes": github_app_step_volumes(), - } - step = dict(step, when = when) - return step - -def playwright_e2e_tests_step(): - return { - "environment": { - "PORT": "3001", - "HOST": "grafana-server", - "PROV_DIR": "/grafana/scripts/grafana-server/tmp/conf/provisioning", - }, - "name": "playwright-plugin-e2e", - "image": images["node_deb"], - "depends_on": [ - "grafana-server", - "build-test-plugins", - ], - "commands": [ - "npx wait-on@7.0.1 http://$HOST:$PORT", - "yarn playwright install --with-deps chromium", - "GRAFANA_URL=http://$HOST:$PORT yarn e2e:playwright --grep @plugins", - ], - } - -def build_docs_website_step(): - return { - "name": "build-docs-website", - # Use latest revision here, since we want to catch if it breaks - "image": images["docs"], - "pull": "always", - "commands": [ - "mkdir -p /hugo/content/docs/grafana/latest", - "echo -e '---\\nredirectURL: /docs/grafana/latest/\\ntype: redirect\\nversioned: true\\n---\\n' > /hugo/content/docs/grafana/_index.md", - "cp -r docs/sources/* /hugo/content/docs/grafana/latest/", - "cd /hugo && make prod", - ], - } - -def fetch_images_step(): - return { - "name": "fetch-images", - "image": images["cloudsdk"], - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads), - "DOCKER_USER": from_secret("docker_username"), - "DOCKER_PASSWORD": from_secret("docker_password"), - }, - "commands": ["./bin/build artifacts docker fetch --edition oss"], - "depends_on": ["compile-build-cmd"], - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}], - } - -def publish_images_step(ver_mode, docker_repo, trigger = None, depends_on = ["rgm-build-docker"]): - """Generates a step for publishing public Docker images with grabpl. - - Args: - ver_mode: controls whether the image needs to be built or retrieved from a previous build. - If ver_mode == 'release', the previously built image is fetched instead of being built again. - docker_repo: the Docker image name. - It is combined with the 'grafana/' library prefix. - trigger: a Drone trigger for the pipeline. - Defaults to None. - depends_on: drone steps that this step depends on - - Returns: - Drone step. - """ - name = docker_repo - docker_repo = "grafana/{}".format(docker_repo) - - environment = { - "GCP_KEY": from_secret(gcp_grafanauploads), - "DOCKER_USER": from_secret("docker_username"), - "DOCKER_PASSWORD": from_secret("docker_password"), - "GITHUB_APP_ID": "329617", - "GITHUB_APP_INSTALLATION_ID": "37346161", - "GITHUB_APP_PRIVATE_KEY": from_secret("delivery-bot-app-private-key"), - } - - cmd = "./bin/grabpl artifacts docker publish --dockerhub-repo {}".format( - docker_repo, - ) - - deps = depends_on - if ver_mode == "release": - deps = ["fetch-images"] - cmd += " --version-tag ${DRONE_TAG}" - - if ver_mode == "pr": - environment = { - "DOCKER_USER": from_secret("docker_username"), - "DOCKER_PASSWORD": from_secret("docker_password"), - "GITHUB_APP_ID": "329617", - "GITHUB_APP_INSTALLATION_ID": "37346161", - "GITHUB_APP_PRIVATE_KEY": from_secret("delivery-bot-app-private-key"), - } - - step = { - "name": "publish-images-{}".format(name), - "image": images["cloudsdk"], - "environment": environment, - "commands": [cmd], - "depends_on": deps, - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}], - } - if trigger and ver_mode in ("release-branch", "main"): - step = dict(step, when = trigger) - if ver_mode == "pr": - step = dict(step, failure = "ignore") - - return step - -def release_canary_npm_packages_step(trigger = None): - """Releases canary NPM packages. - - Args: - trigger: a Drone trigger for the step. - Defaults to None. - - Returns: - Drone step. - """ - step = { - "name": "release-canary-npm-packages", - "image": images["node"], - "depends_on": end_to_end_tests_deps() + ["build-frontend-packages"], - "environment": { - "NPM_TOKEN": from_secret(npm_token), - }, - "commands": [ - "apk add --update bash git", - "./scripts/publish-npm-packages.sh --dist-tag 'canary' --registry 'https://registry.npmjs.org'", - ], - } - - if trigger: - step = dict( - step, - when = dict( - trigger, - paths = { - "include": [ - "packages/**", - ], - }, - ), - ) - - return step - -def upload_packages_step( - ver_mode, - trigger = None, - depends_on = [ - # Note: Main E2E tests have been migrated to GitHub Actions - # Updated dependencies to only include remaining Drone E2E tests - "end-to-end-tests-cloud-plugins-suite-azure", - "playwright-plugin-e2e", - ]): - """Upload packages to object storage. - - Args: - ver_mode: when ver_mode == 'main', inhibit upload of enterprise - edition packages when executed. - trigger: a Drone trigger for the step. - Defaults to None. - depends_on: drone steps that this step depends on - - Returns: - Drone step. - """ - step = { - "name": "upload-packages", - "image": images["publish"], - "depends_on": depends_on, - "environment": { - "GCP_KEY": from_secret(gcp_grafanauploads_base64), - "PRERELEASE_BUCKET": from_secret("prerelease_bucket"), - }, - "commands": [ - "./bin/build upload-packages --edition oss", - ], - } - if trigger and ver_mode in ("release-branch", "main"): - step = dict(step, when = trigger) - return step - -def publish_grafanacom_step(ver_mode, depends_on = ["publish-linux-packages-deb", "publish-linux-packages-rpm"]): - """Publishes Grafana packages to grafana.com. - - Args: - ver_mode: if ver_mode == 'main', pass the DRONE_BUILD_NUMBER environment - variable as the value for the --build-id option. - TODO: is this actually used by the grafanacom subcommand? I think it might - just use the environment variable directly. - depends_on: what other steps this one depends on (strings) - - Returns: - Drone step. - """ - if ver_mode == "release": - cmd = "./bin/build publish grafana-com --edition oss ${DRONE_TAG}" - elif ver_mode == "main": - build_no = "${DRONE_BUILD_NUMBER}" - cmd = "./bin/build publish grafana-com --edition oss --build-id {}".format( - build_no, - ) - else: - fail("Unexpected version mode {}".format(ver_mode)) - - return { - "name": "publish-grafanacom", - "image": images["publish"], - "depends_on": depends_on, - "environment": { - "GRAFANA_COM_API_KEY": from_secret("grafana_api_key"), - "GCP_KEY": from_secret(gcp_grafanauploads_base64), - }, - "commands": [ - cmd, - ], - } - -def verify_grafanacom_step(depends_on = ["publish-grafanacom"]): - return { - "name": "verify-grafanacom", - "image": images["node"], - "commands": [ - # Download and install `curl` and `bash` - both of which aren't available inside of the `node:{version}-alpine` docker image. - "apk add curl bash", - - # There may be a slight lag between when artifacts are uploaded to Google Storage, - # and when they become available on the website. This `for` loop sould account for that discrepancy. - # We attempt the verification up to 5 times. If successful, exit the loop with a success (0) status. - # If any attempt fails, but it's not the final attempt, wait 60 seconds before the next attempt. - # If the 5th (final) attempt fails, exit with error (1) status. - """ - for i in {1..5}; do - if ./scripts/drone/verify-grafanacom.sh; then - exit 0 - elif [ $i -eq 5 ]; then - exit 1 - else - sleep 60 - fi - done - """, - ], - "depends_on": depends_on, - } - -def publish_linux_packages_step(package_manager = "deb"): - return { - "name": "publish-linux-packages-{}".format(package_manager), - # See https://github.com/grafana/deployment_tools/blob/master/docker/package-publish/README.md for docs on that image - "image": images["package_publish"], - "depends_on": ["compile-build-cmd"], - "privileged": True, - "settings": { - "access_key_id": from_secret("packages_access_key_id"), - "secret_access_key": from_secret("packages_secret_access_key"), - "service_account_json": from_secret("packages_service_account"), - "target_bucket": "grafana-packages", - "deb_distribution": "auto", - "gpg_passphrase": from_secret("packages_gpg_passphrase"), - "gpg_public_key": from_secret("packages_gpg_public_key"), - "gpg_private_key": from_secret("packages_gpg_private_key"), - "package_path": "gs://grafana-prerelease/artifacts/downloads/*${{DRONE_TAG}}/oss/**.{}".format( - package_manager, - ), - }, - } - -# This retry will currently continue for 30 minutes until fail, unless successful. -def retry_command(command, attempts = 60, delay = 30): - return [ - "for i in $(seq 1 %d); do" % attempts, - " if %s; then" % command, - ' echo "Command succeeded on attempt $i"', - " break", - " else", - ' echo "Attempt $i failed"', - " if [ $i -eq %d ]; then" % attempts, - " echo 'All attempts failed'", - " exit 1", - " fi", - ' echo "Waiting %d seconds before next attempt..."' % delay, - " sleep %d" % delay, - " fi", - "done", - ] - -def verify_gen_cue_step(): - return { - "name": "verify-gen-cue", - "image": images["go"], - "depends_on": [], - "commands": [ - "# It is required that code generated from Thema/CUE be committed and in sync with its inputs.", - "# The following command will fail if running code generators produces any diff in output.", - "apk add --update make", - "CODEGEN_VERIFY=1 make gen-cue", - ], - } - -def verify_gen_jsonnet_step(): - return { - "name": "verify-gen-jsonnet", - "image": images["go"], - "depends_on": [], - "commands": [ - "# It is required that generated jsonnet is committed and in sync with its inputs.", - "# The following command will fail if running code generators produces any diff in output.", - "apk add --update make", - "CODEGEN_VERIFY=1 make gen-jsonnet", - ], - } - -def end_to_end_tests_deps(): - # Note: Main E2E tests have been migrated to GitHub Actions - # Only return dependencies for E2E tests that still run in Drone - return [ - "end-to-end-tests-cloud-plugins-suite-azure", - "playwright-plugin-e2e", - ] - -def compile_build_cmd(): - dependencies = [] - - return { - "name": "compile-build-cmd", - "image": images["go"], - "commands": [ - "go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd", - ], - "depends_on": dependencies, - "environment": { - "CGO_ENABLED": 0, - }, - } - -def get_trigger_storybook(ver_mode): - """Generate a Drone trigger for UI changes that affect the Grafana UI storybook. - - Args: - ver_mode: affects whether the trigger is event tags or changed files. - - Returns: - Drone trigger. - """ - trigger_storybook = "" - if ver_mode == "release": - trigger_storybook = {"event": ["tag"]} - else: - trigger_storybook = { - "paths": { - "include": [ - "packages/grafana-ui/**", - ], - }, - } - return trigger_storybook - -def slack_step(channel, template, secret): - return { - "name": "slack", - "image": images["plugins_slack"], - "settings": { - "webhook": from_secret(secret), - "channel": channel, - "template": template, - }, - } diff --git a/scripts/drone/steps/rgm.star b/scripts/drone/steps/rgm.star deleted file mode 100644 index 8114dceca39..00000000000 --- a/scripts/drone/steps/rgm.star +++ /dev/null @@ -1,103 +0,0 @@ -""" -Individual steps that use 'grafana-build' to replace existing individual steps. -These aren't used in releases. -""" - -load( - "scripts/drone/dagger.star", - "with_dagger_install", -) -load( - "scripts/drone/utils/images.star", - "images", -) -load( - "scripts/drone/variables.star", - "dagger_version", -) -load( - "scripts/drone/vault.star", - "from_secret", - "rgm_dagger_token", -) - -def artifacts_cmd(artifacts = []): - cmd = "go run ./pkg/build/cmd artifacts " - - for artifact in artifacts: - cmd += "-a {} ".format(artifact) - - return cmd - -# rgm_artifacts_step will create artifacts using the '/src/build artifacts' command. -def rgm_artifacts_step( - name = "rgm-package", - artifacts = ["targz:grafana:linux/amd64", "targz:grafana:linux/arm64"], - file = "packages.txt", - depends_on = ["yarn-install"], - tag_format = "{{ .version }}-{{ .arch }}", - ubuntu_tag_format = "{{ .version }}-ubuntu-{{ .arch }}", - verify = "false"): - cmd = artifacts_cmd(artifacts = artifacts) - - return { - "name": name, - "image": images["go"], - "pull": "always", - "depends_on": depends_on, - "environment": { - "_EXPERIMENTAL_DAGGER_CLOUD_TOKEN": from_secret(rgm_dagger_token), - }, - "commands": with_dagger_install([ - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --version", - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'", - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all", - cmd + - "--yarn-cache=$$YARN_CACHE_FOLDER " + - "--build-id=$$DRONE_BUILD_NUMBER " + - "--ubuntu-base=ubuntu-base " + - "--alpine-base=alpine-base " + - "--tag-format='{}' ".format(tag_format) + - "--ubuntu-tag-format='{}' ".format(ubuntu_tag_format) + - "--verify='{}' ".format(verify) + - "--grafana-dir=$$PWD > {}".format(file), - "find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i", - ], dagger_version), - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}], - } - -# rgm_build_backend will create compile the grafana backend for various platforms. -def rgm_build_backend_step(artifacts = ["backend:grafana:linux/amd64", "backend:grafana:linux/arm64"]): - return rgm_artifacts_step(name = "rgm-build-backend", artifacts = artifacts, depends_on = []) - -def rgm_build_docker_step(depends_on = ["yarn-install"], file = "docker.txt", tag_format = "{{ .version }}-{{ .arch }}", ubuntu_tag_format = "{{ .version }}-ubuntu-{{ .arch }}"): - return { - "name": "rgm-build-docker", - "image": images["go"], - "pull": "always", - "environment": { - "_EXPERIMENTAL_DAGGER_CLOUD_TOKEN": from_secret(rgm_dagger_token), - }, - "commands": with_dagger_install([ - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --version", - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'", - "docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all", - "go run ./pkg/build/cmd artifacts " + - "-a docker:grafana:linux/amd64 " + - "-a docker:grafana:linux/amd64:ubuntu " + - "-a docker:grafana:linux/arm64 " + - "-a docker:grafana:linux/arm64:ubuntu " + - "-a docker:grafana:linux/arm/v7 " + - "-a docker:grafana:linux/arm/v7:ubuntu " + - "--yarn-cache=$$YARN_CACHE_FOLDER " + - "--build-id=$$DRONE_BUILD_NUMBER " + - "--ubuntu-base=ubuntu-base " + - "--alpine-base=alpine-base " + - "--tag-format='{}' ".format(tag_format) + - "--grafana-dir=$$PWD " + - "--ubuntu-tag-format='{}' > {}".format(ubuntu_tag_format, file), - "find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i", - ], dagger_version), - "volumes": [{"name": "docker", "path": "/var/run/docker.sock"}], - "depends_on": depends_on, - } diff --git a/scripts/drone/utils/images.star b/scripts/drone/utils/images.star deleted file mode 100644 index 02b03169ed6..00000000000 --- a/scripts/drone/utils/images.star +++ /dev/null @@ -1,30 +0,0 @@ -""" -This module contains all the docker images that are used to build test and publish Grafana. -""" - -load( - "scripts/drone/variables.star", - "golang_version", - "nodejs_version", -) - -images = { - "docker": "docker:27-cli", - "git": "alpine/git:2.40.1", - "go": "golang:{}-alpine".format(golang_version), - "node": "node:{}-alpine".format(nodejs_version), - "node_deb": "node:{}-bookworm".format(nodejs_version[:2]), - "cloudsdk": "google/cloud-sdk:431.0.0", - "publish": "grafana/grafana-ci-deploy:1.3.3", - "alpine": "alpine:3.21.3", - "ubuntu": "ubuntu:22.04", - "curl": "byrnedo/alpine-curl:0.1.8", - "plugins_slack": "plugins/slack", - "package_publish": "us.gcr.io/kubernetes-dev/package-publish:latest", - "drone_downstream": "grafana/drone-downstream", - "docker_puppeteer": "grafana/docker-puppeteer:1.1.0", - "docs": "grafana/docs-base:latest", - "cypress": "cypress/included:14.3.2", - "dockerize": "jwilder/dockerize:0.6.1", - "github_app_secret_writer": "us-docker.pkg.dev/grafanalabs-global/docker-deployment-tools-prod/github-app-secret-writer:2024-11-05-v11688112090.1-83920c59", -} diff --git a/scripts/drone/utils/utils.star b/scripts/drone/utils/utils.star deleted file mode 100644 index 4cc7aac6e49..00000000000 --- a/scripts/drone/utils/utils.star +++ /dev/null @@ -1,140 +0,0 @@ -""" -This module contains utility functions for generating Drone pipelines. -""" - -load( - "scripts/drone/steps/lib.star", - "slack_step", -) -load( - "scripts/drone/vault.star", - "gar_pull_secret", - "gcr_pull_secret", -) - -failure_template = "Build {{build.number}} failed for commit: : {{build.link}}\nBranch: \nAuthor: {{build.author}}" - -def pipeline( - name, - trigger, - steps, - services = [], - platform = "linux", - depends_on = [], - environment = None, - volumes = []): - """Generate a Drone Docker pipeline with commonly used values. - - In addition to the parameters provided, it configures: - - the use of an image pull secret - - a retry count for cloning - - a volume 'docker' that can be used to access the Docker socket - - Args: - name: controls the pipeline name. - trigger: a Drone trigger for the pipeline. - steps: the Drone steps for the pipeline. - services: auxiliary services used during the pipeline. - Defaults to []. - platform: abstracts platform specific configuration primarily for different Drone behavior on Windows. - Defaults to 'linux'. - depends_on: list of pipelines that must have succeeded before this pipeline can start. - Defaults to []. - environment: environment variables passed through to pipeline steps. - Defaults to None. - volumes: additional volumes available to be mounted by pipeline steps. - Defaults to []. - - Returns: - Drone pipeline - """ - if platform != "windows": - platform_conf = { - "platform": {"os": "linux", "arch": "amd64"}, - # A shared cache is used on the host - # To avoid issues with parallel builds, we run this repo on single build agents - "node": {"type": "no-parallel"}, - } - else: - platform_conf = { - "platform": { - "os": "windows", - "arch": "amd64", - "version": "1809", - }, - } - - docker_mount_path = "/var/run/docker.sock" - if platform == "windows": - docker_mount_path = "//./pipe/docker_engine/" - - pipeline = { - "kind": "pipeline", - "type": "docker", - "name": name, - "trigger": trigger, - "services": services, - "steps": steps, - "clone": { - "retries": 3, - }, - "volumes": [ - { - "name": "docker", - "host": { - "path": docker_mount_path, - }, - }, - ], - "depends_on": depends_on, - "image_pull_secrets": [gcr_pull_secret, gar_pull_secret], - } - if environment: - pipeline.update( - { - "environment": environment, - }, - ) - - pipeline["volumes"].extend(volumes) - pipeline.update(platform_conf) - - return pipeline - -def notify_pipeline( - name, - slack_channel, - trigger, - depends_on = [], - template = None, - secret = None): - trigger = dict(trigger) - return { - "kind": "pipeline", - "type": "docker", - "platform": { - "os": "linux", - "arch": "amd64", - }, - "name": name, - "trigger": trigger, - "steps": [ - slack_step(slack_channel, template, secret), - ], - "clone": { - "retries": 3, - }, - "depends_on": depends_on, - } - -# TODO: this overrides any existing dependencies because we're following the existing logic -# it should append to any existing dependencies -def with_deps(steps, deps = []): - for step in steps: - step["depends_on"] = deps - return steps - -def ignore_failure(steps): - for step in steps: - step["failure"] = "ignore" - return steps diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star deleted file mode 100644 index 7c45f17b75f..00000000000 --- a/scripts/drone/variables.star +++ /dev/null @@ -1,10 +0,0 @@ -""" -global variables -""" - -grabpl_version = "v3.1.2" -golang_version = "1.24.6" - -# nodejs_version should match what's in ".nvmrc", but without the v prefix. -nodejs_version = "22.16.0" -dagger_version = "v0.18.8" diff --git a/scripts/drone/vault.star b/scripts/drone/vault.star deleted file mode 100644 index 05f3617d01f..00000000000 --- a/scripts/drone/vault.star +++ /dev/null @@ -1,166 +0,0 @@ -""" -This module returns functions for generating Drone secrets fetched from Vault. -""" -gcr_pull_secret = "gcr" -gar_pull_secret = "gar" -drone_token = "drone_token" -prerelease_bucket = "prerelease_bucket" -gcp_upload_artifacts_key = "gcp_upload_artifacts_key" -gcp_grafanauploads = "gcp_grafanauploads" -gcp_grafanauploads_base64 = "gcp_grafanauploads_base64" -gcp_download_build_container_assets_key = "gcp_download_build_container_assets_key" - -azure_sp_app_id = "azure_sp_app_id" -azure_sp_app_pw = "azure_sp_app_pw" -azure_tenant = "azure_tenant" - -github_app_app_id = "github-app-app-id" -github_app_app_installation_id = "github-app-installation-id" -github_app_private_key = "github-app-private-key" - -rgm_gcp_key_base64 = "gcp_key_base64" -rgm_destination = "destination" -rgm_storybook_destination = "rgm_storybook_destination" -rgm_cdn_destination = "rgm_cdn_destination" -rgm_downloads_destination = "rgm_downloads_destination" -rgm_dagger_token = "dagger_token" - -docker_username = "docker_username" -docker_password = "docker_password" - -npm_token = "npm_token" - -def from_secret(secret): - return {"from_secret": secret} - -def vault_secret(name, path, key): - return { - "kind": "secret", - "name": name, - "get": { - "path": path, - "name": key, - }, - } - -def secrets(): - return [ - vault_secret(github_app_app_id, "ci/data/repo/grafana/grafana/github-app", "app-id"), - vault_secret(github_app_app_installation_id, "ci/data/repo/grafana/grafana/github-app", "app-installation-id"), - vault_secret(github_app_private_key, "ci/data/repo/grafana/grafana/github-app", "private-key"), - vault_secret(gcp_grafanauploads, "infra/data/ci/grafana-release-eng/grafanauploads", "credentials.json"), - vault_secret(gcp_grafanauploads_base64, "infra/data/ci/grafana-release-eng/grafanauploads", "credentials_base64"), - vault_secret("grafana_api_key", "infra/data/ci/grafana-release-eng/grafanacom", "api_key"), - vault_secret(gcr_pull_secret, "secret/data/common/gcr", ".dockerconfigjson"), - vault_secret(gar_pull_secret, "secret/data/common/gar", ".dockerconfigjson"), - vault_secret(drone_token, "infra/data/ci/drone", "machine-user-token"), - vault_secret(prerelease_bucket, "infra/data/ci/grafana/prerelease", "bucket"), - vault_secret(docker_username, "ci/data/common/dockerhub", "username"), - vault_secret(docker_password, "ci/data/common/dockerhub", "password"), - vault_secret( - gcp_upload_artifacts_key, - "infra/data/ci/grafana/releng/artifacts-uploader-service-account", - "credentials.json", - ), - vault_secret( - gcp_download_build_container_assets_key, - "infra/data/ci/grafana/assets-downloader-build-container-service-account", - "credentials.json", - ), - vault_secret( - azure_sp_app_id, - "infra/data/ci/datasources/cpp-azure-resourcemanager-credentials", - "application_id", - ), - vault_secret( - azure_sp_app_pw, - "infra/data/ci/datasources/cpp-azure-resourcemanager-credentials", - "application_secret", - ), - vault_secret( - azure_tenant, - "infra/data/ci/datasources/cpp-azure-resourcemanager-credentials", - "tenant_id", - ), - vault_secret( - npm_token, - "infra/data/ci/grafana-release-eng/npm", - "token", - ), - # Package publishing - vault_secret( - "packages_gpg_public_key", - "infra/data/ci/packages-publish/gpg", - "public-key-b64", - ), - vault_secret( - "packages_gpg_private_key", - "infra/data/ci/packages-publish/gpg", - "private-key-b64", - ), - vault_secret( - "packages_gpg_passphrase", - "infra/data/ci/packages-publish/gpg", - "passphrase", - ), - vault_secret( - "packages_service_account", - "infra/data/ci/packages-publish/service-account", - "credentials.json", - ), - vault_secret( - "packages_access_key_id", - "infra/data/ci/packages-publish/bucket-credentials", - "AccessID", - ), - vault_secret( - "packages_secret_access_key", - "infra/data/ci/packages-publish/bucket-credentials", - "Secret", - ), - vault_secret( - "static_asset_editions", - "infra/data/ci/grafana-release-eng/artifact-publishing", - "static_asset_editions", - ), - vault_secret( - rgm_gcp_key_base64, - "infra/data/ci/grafana-release-eng/rgm", - "gcp_service_account_prod_base64", - ), - vault_secret( - rgm_destination, - "infra/data/ci/grafana-release-eng/rgm", - "destination_prod", - ), - vault_secret( - rgm_storybook_destination, - "infra/data/ci/grafana-release-eng/rgm", - "storybook_destination", - ), - vault_secret( - rgm_cdn_destination, - "infra/data/ci/grafana-release-eng/rgm", - "cdn_destination", - ), - vault_secret( - rgm_downloads_destination, - "infra/data/ci/grafana-release-eng/rgm", - "downloads_destination", - ), - vault_secret( - rgm_dagger_token, - "infra/data/ci/grafana-release-eng/rgm", - "dagger_token", - ), - vault_secret( - "delivery-bot-app-private-key", - "ci/data/repo/grafana/grafana/delivery-bot-app", - "PRIVATE_KEY", - ), - vault_secret( - "gcr_credentials", - "secret/data/common/gcr", - "service-account", - ), - ] diff --git a/scripts/drone/verify-grafanacom.sh b/scripts/drone/verify-grafanacom.sh deleted file mode 100755 index b2950f8529c..00000000000 --- a/scripts/drone/verify-grafanacom.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -version=${1:-$TAG} - -# Construct the URL based on the provided version and edition -if [ "$EDITION" = "enterprise" ]; then - url="https://grafana.com/api/downloads/grafana-enterprise/versions/$version" -else - url="https://grafana.com/api/downloads/grafana/versions/$version" -fi - -# Make a request to the GCOM API to retrieve the artifacts for the specified version. Exit if the request fails. -if ! artifacts=$(curl "$url"); then - echo "Failed to retrieve artifact URLs from Grafana.com API. Please check the API key, authentication, edition, and version." - exit 1 -fi - -# Use Node.js to parse the JSON response and extract the download URLs -url_string=$(node -e " - const artifacts = JSON.parse(JSON.stringify($artifacts)); - const downloadUrls = artifacts.packages.map((package) => package.links.find((link) => link.rel === 'download').href); - console.log(downloadUrls.join(' ')); -") - -# Convert the url_string to a Bash array -read -r -a urls <<< "$url_string" - -# If empty, no artifact URLs were found for the specified version. Exit with an error. -if [ ${#urls[@]} -eq 0 ]; then - echo "No artifact URLs found for version $version. Please check the provided version." - exit 1 -fi - -# Iterate over the URLs and check the status code of each. If any URL does not return a 200 status code, add it to the failed_urls string. -failed_urls="" -for url in "${urls[@]}"; do - status_code=$(curl -L -s -o /dev/null -w "%{http_code}" "$url") - if [ "$status_code" -ne 200 ]; then - failed_urls+="$url\n" - fi -done - -# If any URLs failed, print them and exit with an error. -if [ -n "$failed_urls" ]; then - echo "The following URLs did not return a 200 status code:" - echo "$failed_urls" - exit 1 -else - echo "All URLs returned a 200 status code. Download links are valid for version $version." - exit 0 -fi diff --git a/scripts/grafana-server/custom.ini b/scripts/grafana-server/custom.ini index d8e92d8782e..b1f396a57d2 100644 --- a/scripts/grafana-server/custom.ini +++ b/scripts/grafana-server/custom.ini @@ -18,6 +18,9 @@ queryService=true [environment] stack_id = 12345 +[panels] +enable_alpha = true + [plugins] allow_loading_unsigned_plugins=grafana-extensionstest-app,grafana-extensionexample1-app,grafana-extensionexample2-app,grafana-extensionexample3-app,grafana-e2etest-datasource diff --git a/yarn.lock b/yarn.lock index a27155c3ac9..794ab15ef98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3056,9 +3056,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/assistant@npm:0.0.14": - version: 0.0.14 - resolution: "@grafana/assistant@npm:0.0.14" +"@grafana/assistant@npm:0.0.16": + version: 0.0.16 + resolution: "@grafana/assistant@npm:0.0.16" peerDependencies: "@grafana/data": ">=12.1.0" "@grafana/runtime": ">=12.1.0" @@ -3066,7 +3066,7 @@ __metadata: "@grafana/ui": ">=12.1.0" react: ">=18.0.0" rxjs: ">=7.0.0" - checksum: 10/3e85effd69494b023d395940f5f926da1d2868850db613581d7b2f398e8f00fe07339fdb6fa12b3029603cd394d012104f187f2fd2fcb85196dac72cd6e072df + checksum: 10/8482e09d3e86a865a8cf90908ae1438317b994798b5bf5d1a2369154b786e1ba20f30e208d48bed249c8427f11f87307b338c5307a8cb5143cffb2ef02050847 languageName: node linkType: hard @@ -3171,9 +3171,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/eslint-config@npm:8.0.0": - version: 8.0.0 - resolution: "@grafana/eslint-config@npm:8.0.0" +"@grafana/eslint-config@npm:8.1.0": + version: 8.1.0 + resolution: "@grafana/eslint-config@npm:8.1.0" peerDependencies: "@stylistic/eslint-plugin-ts": ">=2.9.0" "@typescript-eslint/eslint-plugin": ">=6.18.0" @@ -3184,7 +3184,7 @@ __metadata: eslint-plugin-react: ">=7.33.0" eslint-plugin-react-hooks: ">=4.6.0" typescript: ">=5.2.0" - checksum: 10/b9a875d9e157eff1212cbd2648f18a1b04ebdd2bbea7d13ecb63d5b441fb3f550aa452eccdbf3ccadedd25996e6fb33cba615e3dfdd21ce9e00b505287a51dc4 + checksum: 10/d82f169de300dbb2ca62f74a4cb80461ab43e1adb5885e59a02c6080d9cef93f9aaac584b58b91f360014edf52b3dd1e81aad97e67011edc99de54a49deba7f2 languageName: node linkType: hard @@ -3586,11 +3586,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.29.7": - version: 6.29.7 - resolution: "@grafana/scenes-react@npm:6.29.7" +"@grafana/scenes-react@npm:^6.30.0": + version: 6.30.1 + resolution: "@grafana/scenes-react@npm:6.30.1" dependencies: - "@grafana/scenes": "npm:6.29.7" + "@grafana/scenes": "npm:6.30.1" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3602,13 +3602,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/d3db42b8face33a871cc2b983b00890b4a7b9cf685b06dd02df730036f9c13f1cdbc0dcb578453a0ce29381bdceb26d2a07ae4be77a8f753677dde1560eb491f + checksum: 10/695c61665f38f09f6b49552a32caf1fbe6a3209d9df7039fd9110dcbda9e2031e41d53b7e471021e779bee78f8f27a3978fed96f9e70a07f2157e25f96a81937 languageName: node linkType: hard -"@grafana/scenes@npm:6.29.7": - version: 6.29.7 - resolution: "@grafana/scenes@npm:6.29.7" +"@grafana/scenes@npm:6.30.1, @grafana/scenes@npm:^6.30.0": + version: 6.30.1 + resolution: "@grafana/scenes@npm:6.30.1" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3628,7 +3628,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/a2bbda33c78cd568d6333e033e1b587917ee4ca00d8f69e3707197d6712beba1d1b9244f05259bc4cce7416b9ec67d24eddaa1e6073695e2b4a7af1a37253174 + checksum: 10/6c50a31330ecb674de6664d6e119e54dee7ecee50925a71870c0a08562e4b3d5614e56e34008d6710e65ef03a8220ba2e9d811804b8a001392de46ef745a2e75 languageName: node linkType: hard @@ -5851,7 +5851,7 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.9.0": +"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 @@ -12201,6 +12201,13 @@ __metadata: languageName: node linkType: hard +"bintrees@npm:1.0.2": + version: 1.0.2 + resolution: "bintrees@npm:1.0.2" + checksum: 10/071896cea5ea5413316c8436e95799444c208630d5c539edd8a7089fc272fc5d3634aa4a2e4847b28350dda1796162e14a34a0eda53108cc5b3c2ff6a036c1fa + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -18292,12 +18299,12 @@ __metadata: "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/alerting": "workspace:*" - "@grafana/assistant": "npm:0.0.14" + "@grafana/assistant": "npm:0.0.16" "@grafana/aws-sdk": "npm:0.7.1" "@grafana/azure-sdk": "npm:0.0.7" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/eslint-config": "npm:8.0.0" + "@grafana/eslint-config": "npm:8.1.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" "@grafana/faro-core": "npm:^1.19.0" "@grafana/faro-web-sdk": "npm:^1.19.0" @@ -18313,8 +18320,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.9" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:6.29.7" - "@grafana/scenes-react": "npm:6.29.7" + "@grafana/scenes": "npm:^6.30.0" + "@grafana/scenes-react": "npm:^6.30.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" @@ -18542,6 +18549,7 @@ __metadata: postcss-scss: "npm:4.0.9" prettier: "npm:3.6.2" prismjs: "npm:1.30.0" + prom-client: "npm:^15.1.3" publint: "npm:^0.3.12" rc-slider: "npm:11.1.8" rc-tree: "npm:5.13.1" @@ -26297,6 +26305,16 @@ __metadata: languageName: node linkType: hard +"prom-client@npm:^15.1.3": + version: 15.1.3 + resolution: "prom-client@npm:15.1.3" + dependencies: + "@opentelemetry/api": "npm:^1.4.0" + tdigest: "npm:^0.1.1" + checksum: 10/eba75e15ab896845d39359e3a4d6f7913ea05339b3122d8dde8c8c374669ad1a1d1ab2694ab2101c420bd98086a564e4f2a18aa29018fc14a4732e57c1c19aec + languageName: node + linkType: hard + "promise-all-reject-late@npm:^1.0.0": version: 1.0.1 resolution: "promise-all-reject-late@npm:1.0.1" @@ -30724,6 +30742,15 @@ __metadata: languageName: node linkType: hard +"tdigest@npm:^0.1.1": + version: 0.1.2 + resolution: "tdigest@npm:0.1.2" + dependencies: + bintrees: "npm:1.0.2" + checksum: 10/45be99fa52dab74b8edafe150e473cdc45aa1352c75ed516a39905f350a08c3175f6555598111042c3677ba042d7e3cae6b5ce4c663fe609bc634f326aabc9d6 + languageName: node + linkType: hard + "teex@npm:^1.0.1": version: 1.0.1 resolution: "teex@npm:1.0.1"