CI Migration: Backport pkg/build (daggerbuild) and GHAs to release-12.0.3 (#107997)

* CI: move grafana-build into pkg/build (#105640)

* move grafana-build into pkg/build

* Update go.mod/go.sum files after cherry-pick build

- Updates from workspace sync after cherry-picking daggerbuild package
- Resolves dependency conflicts from pkg/build integration
- Required for successful build after CI migration backport

* CI Migration: Replace .github with main branch version

- Complete replacement of .github directory from main branch
- Includes updated workflows, actions, and configuration files
- Ensures all CI improvements from main are included
- Next step: fix branch-specific issues in separate commits

* Fix branch triggers: Update workflows to target release branches

- Update 8 workflows to trigger on release-* instead of main
- Ensures workflows run on pull requests to release branches
- Workflows updated: documentation-ci, pr-codeql-analysis-*, pr-dependabot-update-go-workspace, pr-go-workspace-check, pr-k8s-codegen-check, verify-kinds, codeowners-validator

* Fix branch triggers: Update workflows to target both main and release branches

- CORRECTED: Include both main and release-* branch patterns
- Ensures workflows run on both main branch (original functionality) and release branches (needed for backport)
- Previous commit incorrectly removed main branch support
- Workflows updated: documentation-ci, pr-codeql-analysis-*, pr-dependabot-update-go-workspace, pr-go-workspace-check, pr-k8s-codegen-check, verify-kinds, codeowners-validator

* CI Migration: Update .gitignore to allow OSS wire file to be committed

- Remove **/wire_gen.go (allow pkg/server/wire_gen.go to be committed)
- Add /pkg/server/enterprise_wire_gen.go (keep enterprise wire file ignored)
- This enables the new committed wire files architecture for CI

* CI Migration: Add enhanced wire tool with automatic build constraints

- Backport enhanced wire tool from main branch with -gen_tags flag
- This enables automatic regeneration with proper constraints to prevent conflicts
- Wire file contains all required functions: Initialize, InitializeDocumentBuilders, etc.
- Solves the missing wire functions issue causing CI failures
- Enhanced tool ensures build constraints persist on regeneration

* CI Migration: Update Makefile to use enhanced wire tool with build constraints

- Update gen-go target to use -gen_tags flag for automatic build constraints
- Ensures future engineers get proper build constraints when running 'make gen-go'
- Matches main branch approach for consistent wire file generation
- Removes dependency on WIRE_TAGS variable and GO_RACE_FLAG for simplicity

* Add gen-enterprise-go target to Makefile for release-12.0.3

- Backports gen-enterprise-go target from main branch
- Enables enterprise wire file generation in release branches
- Part of CI migration backport wire infrastructure fixes
- Resolves CI issues where enterprise wire functions were missing

* Fix wire tool golden file format for Drone CI compatibility

- Update all 43 golden test files to match main branch format
- Change go:generate command from github.com/google/wire/cmd/wire to ./pkg/build/wire/cmd/wire/main.go
- Remove legacy '// +build !wireinject' constraints
- Fixes Drone CI test failures that were blocking OSS PR merge
- All wire tests now pass successfully

This aligns the backport branch with the golden file format updates
that were made in main branch when the wire tool was enhanced.

* Update swagger specs to include enterprise APIs

- Regenerated api-enterprise-spec.json with enterprise API definitions
- Updated api-merged.json with merged OSS and enterprise specs
- Regenerated openapi3.json with complete API specifications
- Fixes enterprise CI swagger generation validation failures
- Enterprise APIs now properly detected and documented

* Fix OSS integration tests missing enterprise build tags in Drone CI

- Add -tags=enterprise to all integration test steps in lib.star
- Fixes test-backend-integration, postgres-integration-tests, and mysql-integration-tests
- Regenerate .drone.yml with proper enterprise build tags
- Resolves 'server could not find the requested resource' errors for enterprise APIs
- Ensures enterprise APIs (querylibrary, reporting, banners, scim) are registered during tests
- Aligns OSS Drone CI with GitHub Actions behavior and enterprise repository

* Fix CODEOWNERS for release-12.0.3 compatibility

- Remove 21 entries that reference files/directories not present in release branch
- Resolves File Exist Checker failures in codeowners-validator workflow
- Maintains team ownership assignments from main branch for existing files
- Lines removed: .air.toml, apps/secret/, apps/iam/, e2e-playwright/, packages/grafana-alerting/, etc.

* baldm0mma/ update releasefinder

* Add i18n-extract script for release branch compatibility

- Add 'i18n-extract': 'make i18n-extract' to package.json scripts
- Resolves i18n-verify workflow failure that expects this script
- The make target already exists and handles OSS + enterprise i18n extraction
- Maintains i18n verification functionality from main branch workflows
- Simple safe addition that calls existing make infrastructure

* Phase 4: Fix GitHub Actions workflow branch triggers

- Add release-*.*.* pattern to 8 workflows missing it
- Fix duplicate release branch patterns in 3 workflows
- Ensure consistent branch trigger format across all workflows
- Critical workflows now properly trigger on release branches

Fixes workflows: actionlint, backend-code-checks, go-lint, reject-gh-secrets,
run-schema-v2-e2e, shellcheck, swagger-gen, trivy-scan

* Fix: Backport E2E runner infrastructure for Enterprise CI

- Add e2e/main.go and e2e/internal/ directory from main branch
- Add urfave/cli/v3 dependency required by E2E runner
- Fixes Enterprise CI failure: 'Build E2E test runner' and 'Build & package Grafana for e2e tests'
- Root cause: E2E runner infrastructure was added to main after release-12.0.3 branch creation
- Solution: Backport missing E2E runner files to enable Enterprise CI completion

Resolves: no Go files in /opt/actions-runner/_work/grafana-enterprise/grafana-enterprise/grafana/e2e

* Fix: Update go.mod dependency classification for urfave/cli/v3

- Change urfave/cli/v3 from indirect to direct dependency
- Fixes Go Workspace Check failure in CI
- Required after adding E2E runner infrastructure that directly imports urfave/cli/v3
- Resolves: make update-workspace corrects dependency classification

* Fix: Add team ownership for urfave/cli/v3 dependency

- Assign @grafana/grafana-backend-group as owner for github.com/urfave/cli/v3@v3.3.8
- Follows existing pattern: urfave/cli v1 and v2 also owned by grafana-backend-group
- Resolves Backend Code Checks / Validate Backend Configs CI failure
- Required for E2E runner infrastructure dependency ownership compliance

Fixes: modowners check requiring team assignment for newly added dependencies

* Fix: Revert experimental E2E playwright infrastructure to stable version

- Revert pr-e2e-tests.yml to stable Cypress-based E2E testing
- Remove experimental storybook-verification-playwright.yml workflow
- Revert run-dashboard-search-e2e.yml and release-pr.yml to use e2e/test-plugins/
- Keep stable E2E runner infrastructure (e2e/main.go + e2e/internal/)
- Remove experimental playwright features per team recommendation

Team feedback: Playwright tests are experimental and shouldn't be backported to stable release branches

* Fix: Complete cleanup of experimental playwright dependencies

- Revert package.json to stable version (remove e2e-playwright scripts and path references)
- Revert playwright.config.ts to stable plugin-e2e configuration
- Remove all experimental playwright infrastructure dependencies
- Ensure clean stable E2E testing environment

All experimental features removed per team recommendation for stable release branches

* Fix: Restore working package.json configuration

- Revert package.json to version 12.0.3 (working release branch version)
- Fix workspace dependency resolution issues caused by incorrect revert to main branch version
- FE tests, betterer, and linting should now work correctly

Issue was caused by reverting package.json to main branch (12.1.0-pre) instead of keeping
the working release branch configuration (12.0.3)

* CI: mirror some CI dependencies (#106148)

* mirror some CI dependencies
* remove -v from go build

* Fix: Backport missing e2e/run-suite script for daggerbuild E2E system

The daggerbuild E2E system (used by OSS workflows) expects ./e2e/run-suite
to exist, but we only backported the new E2E runner infrastructure.

This script is needed for:
- OSS workflow: 'go run ./pkg/build/e2e --suite=dashboards-suite'
- Legacy Cypress configuration with video support
- Integration between daggerbuild and existing Cypress test suites

Resolves E2E test failures in OSS workflows where videos directory
cannot be found because run-suite script was missing.

* Fix: Resolve daggerbuild E2E path doubling issue

- Extract just suite name from full path using filepath.Base()
- Prevents doubled paths like './e2e/e2e/dashboards-suite/videos'
- Resolves 'no spec files found' and 'no such file or directory' errors
- GitHub Actions passes full paths like 'e2e/dashboards-suite' but run-suite script expects just 'dashboards-suite'

* Infrastructure: Wholesale copy pkg/build/ from main

COMPLETE DAGGERBUILD SYSTEM UPDATE:
- Modern E2E system with CLI framework and --flags support
- New accessibility testing system (a11y/)
- New Playwright E2E testing system (e2e-playwright/)
- External infrastructure improvements (musl.cc → dl.grafana.com/ci)
- Updated daggerbuild core components with latest fixes
- Updated Go dependencies and wire modules

ARCHITECTURAL COMPATIBILITY VERIFIED:
- Modern pkg/build calls: ./e2e-runner cypress --start-grafana=false --cypress-video
- Our e2e runner supports: All required parameters including --suite, --env flags
- GitHub Actions workflows: Pass compatible arguments
- Binary builds: Successfully creates working e2e-runner

RESOLVES ISSUES:
- Fixes --flags parameter compatibility with GitHub Actions
- Includes all follow-up infrastructure improvements
- Provides complete, tested system with modern CLI framework
- Eliminates external dependency failures (musl.cc timeouts)
- Removes need for path doubling workarounds (modern system handles full paths correctly)

Replaces incremental cherry-picking with complete, tested system from main.

* Dependencies: Update Go modules after pkg/build wholesale copy

DEPENDENCY UPDATES:
- Updated go.work.sum with new dependencies from modern pkg/build system
- Updated all workspace module dependencies (go.mod/go.sum files)
- Removed pkg/build/cmd/enterprise.go (enterprise-only file, gets copied during enterprise development)

ENTERPRISE INTEGRATION FIX:
- enterprise.go file doesn't belong in OSS repository
- Gets copied from grafana-enterprise during development mode
- Main branch doesn't have this file, explaining module compatibility

All Go modules now properly resolved and compatible with modern pkg/build architecture.

* Dependencies: Update workspace Go module checksums after pkg/build wholesale copy

- Synchronizes all go.sum files across workspace modules
- Adds missing .mod.h1 entries that were required after infrastructure update
- Resolves Go Workspace Check CI failures
- Updates 22 modules: .citools/, apps/, pkg/ subdirectories plus go.work.sum

Addresses CI error: 'Please run make update-workspace and commit the changes'

* Infrastructure: Revert to pre-Playwright E2E system for release branch compatibility

Strategic combination of modern daggerbuild with stable E2E infrastructure:

**Modern Daggerbuild (from main):**
- Latest CLI framework (github.com/urfave/cli/v3)
- External infrastructure fixes (musl.cc → dl.grafana.com/ci)
- Complete pkg/build/ system with all enhancements
- Updated dependencies and architecture improvements

**Stable E2E Infrastructure (pre-Playwright):**
- pkg/build/e2e/service.go: Reverted to version before commit b6580ccb10
- pkg/build/a11y/: Reverted to stable version compatible with existing infrastructure
- Removed experimental pkg/build/e2e-playwright/ system
- Removed experimental e2e-playwright/ test infrastructure

**Why This Approach:**
- GitHub Actions workflows call 'go run ./pkg/build/e2e' (modern system)
- Modern system now expects only standard e2e/ directory (compatible)
- Release branches have standard e2e/ infrastructure (dashboards-suite, various-suite, etc.)
- Eliminates dependency on experimental Playwright infrastructure

**Key Dependencies Met:**
- e2e/pa11yci.conf.js: Added for a11y test compatibility
- /src/e2e directory: Standard directory structure (no e2e-playwright needed)
- Existing test plugins: Uses stable e2e/test-plugins/

**Result:**
Best of both worlds - modern, reliable daggerbuild infrastructure with
proven E2E testing that works across all release branches without
requiring experimental dependencies.

* Fix: Remove unused embed.go file causing golangci-lint failure

- Removes pkg/build/daggerbuild/msi/embed.go with unused embed.FS variable
- MSI build process uses directory mounting instead of embedded filesystem
- Resolves 'var resources is unused (unused)' linting error

* Fix: Update Node.js version to match working E2E configuration

- Changes from v22.11.0 to v22.16.0 to match commit 17952d45e4
- Working commit had all E2E tests passing with same daggerbuild infrastructure
- Node.js version differences can affect frontend builds and UI rendering
- Ensures consistent environment between working reference and current branch

* Fix: Use conditional container base for E2E compatibility

- Alpine for OSS tests (better old arch dashboardScene=false compatibility)
- Ubuntu for Enterprise tests (when image renderer needed)
- Restores original release-12.0.3 Alpine behavior for OSS
- Should resolve old arch E2E test failures

* Fix: Go fmt formatting for conditional container logic

* Revert: Remove unnecessary Alpine/Ubuntu conditional logic

- Real issue was wrong environment variable name (dashboardScene vs DISABLE_SCENES)
- Keep Ubuntu base (original main branch behavior) for simplicity
- Container base was not the root cause of old arch failures

* Experiment: Copy scripts/ from pre-Playwright commit (ea0ddb3fc9)

- Copied scripts directory from commit ea0ddb3fc9 (just before Playwright migration)
- This overwrites some CI migration fixes but testing if it's actually needed
- Can revert back to b9b4602dbd if CI breaks

Test commit to see real impact on GitHub Actions workflows.

* trigger ci 01

* Fix: Copy .drone.star from main to match scripts structure

- Resolves Starlark evaluation error for missing integration_test_pipelines
- Aligns .drone.star with main branch scripts structure
- Same fix applied to both OSS and Enterprise repositories

* make drone

* Fix ARM/v7 Docker build failures by using dagger run for cross-compilation

- Change artifacts_cmd() in scripts/drone/steps/rgm.star to use 'dagger run go run' instead of 'go run'
- Add --max-execution-steps 100000 to make drone target in Makefile to properly regenerate .drone.yml
- This fixes CGO cross-compilation issues with SQLite for ARM/v7 Docker builds
- Matches the approach already used successfully in Enterprise builds

* Fix Docker base image references in rgm build steps

- Change from symbolic names (alpine-base, ubuntu-base) to actual Docker images
- alpine-base → alpine:3.21.3
- ubuntu-base → ubuntu:22.04
- Add ubuntu and alpine parameters to rgm_artifacts_step and rgm_build_docker_step functions
- Fixes Docker pull errors: 'repository does not exist or may require authorization'
- Aligns OSS configuration with Enterprise approach

---------

Co-authored-by: Kevin Minehart <5140827+kminehart@users.noreply.github.com>
This commit is contained in:
Jev Forsberg
2025-07-18 09:14:14 -06:00
committed by GitHub
co-authored by Kevin Minehart
parent aee6b20c7f
commit dc6bd2a20a
432 changed files with 24660 additions and 16787 deletions
+19 -19
View File
@@ -9,7 +9,7 @@ require (
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel v1.36.0
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/apiserver v0.32.3
@@ -29,7 +29,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.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-20240810084448-b931b754e476 // indirect
@@ -126,33 +126,33 @@ require (
go.opentelemetry.io/auto/sdk v1.1.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.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
+40 -40
View File
@@ -25,8 +25,8 @@ 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/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/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/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -358,30 +358,30 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
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.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 h1:UIrZgRBHUrYRlJ4V419lVb4rs2ar0wFzKNAebaP05XU=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
go.opentelemetry.io/contrib/samplers/jaegerremote v0.29.0 h1:VpYbyLrB5BS3blBCJMqHRIrbU4RlPnyFovR3La+1j4Q=
go.opentelemetry.io/contrib/samplers/jaegerremote v0.29.0/go.mod h1:XAJmM2MWhiIoTO4LCLBVeE8w009TmsYk6hq1UNdXs5A=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -397,8 +397,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@@ -408,8 +408,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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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=
@@ -419,18 +419,18 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -451,8 +451,8 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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=
@@ -466,8 +466,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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@@ -481,20 +481,20 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+9 -9
View File
@@ -33,16 +33,16 @@ require (
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.1 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+22 -22
View File
@@ -74,23 +74,23 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
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.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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@@ -103,15 +103,15 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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/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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.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=
@@ -135,8 +135,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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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=
@@ -147,10 +147,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+16 -16
View File
@@ -5,7 +5,7 @@ go 1.24.4
require (
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250314071911-14e2784e6979
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.72.1
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.32.3
@@ -16,7 +16,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.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/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
@@ -69,26 +69,26 @@ require (
go.etcd.io/etcd/client/v3 v3.5.16 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.32.3 // indirect
+36 -36
View File
@@ -6,8 +6,8 @@ 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -194,22 +194,22 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
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.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -225,8 +225,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@@ -243,11 +243,11 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@@ -265,8 +265,8 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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=
@@ -279,8 +279,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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@@ -290,20 +290,20 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+16 -16
View File
@@ -10,8 +10,8 @@ require (
github.com/prometheus/client_golang v1.22.0
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
k8s.io/apimachinery v0.32.3
k8s.io/apiserver v0.32.3
k8s.io/component-base v0.32.3
@@ -23,7 +23,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.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/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
@@ -75,25 +75,25 @@ require (
go.etcd.io/etcd/client/v3 v3.5.16 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
google.golang.org/grpc v1.72.1 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.73.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
+36 -36
View File
@@ -6,8 +6,8 @@ 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -200,24 +200,24 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
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.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 h1:UIrZgRBHUrYRlJ4V419lVb4rs2ar0wFzKNAebaP05XU=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -233,8 +233,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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=
@@ -251,11 +251,11 @@ 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@@ -273,8 +273,8 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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=
@@ -287,8 +287,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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@@ -298,20 +298,20 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE=
google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+22
View File
@@ -0,0 +1,22 @@
# Pa11y accessability tests
We use pa11y to run some automated simple accessability tests. They're ran with dagger to help orchestrate starting server + tests in a reproducable manner.
To run the tests locally:
1. Install dagger locally https://docs.dagger.io/install/
2. Grab the grafana.tar.gz artifact by either
1. Downloading it from the Github Action artifact from your PR
1. Build it locally with:
```sh
dagger run go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir="$PWD" > dist/files.txt
cat dist/files.txt # Will output the path to the grafana.tar.gz
```
3. Run the dagger pipeline with:
```sh
dagger -v run go run ./pkg/build/a11y --package=(full path to .tar.gz) --results=./pa11y-ci-results.json
```
The JSON results file will be saved to the file from the `--results` arg
4. If they fail and you want to see the full output
1. Run the dagger command with `dagger -vE [...]`
2. At the end, arrow up to the exec pa11y-ci segment and hit Enter
+179
View File
@@ -0,0 +1,179 @@
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"dagger.io/dagger"
"github.com/urfave/cli/v3"
)
var (
grafanaHost = "grafana"
grafanaPort = 3001
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := NewApp().Run(ctx, os.Args); err != nil {
cancel()
fmt.Println(err)
os.Exit(1)
}
}
func NewApp() *cli.Command {
return &cli.Command{
Name: "a11y",
Usage: "Run Grafana accessibility tests",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "grafana-dir",
Usage: "Path to the grafana/grafana clone directory",
Value: ".",
Validator: mustBeDir("grafana-dir"),
TakesFile: true,
},
&cli.StringFlag{
Name: "package",
Usage: "Path to the grafana tar.gz package",
Value: "grafana.tar.gz",
Validator: mustBeFile("package", false),
TakesFile: true,
},
&cli.StringFlag{
Name: "license",
Usage: "Path to the Grafana Enterprise license file (optional)",
Validator: mustBeFile("license", true),
TakesFile: true,
},
&cli.StringFlag{
Name: "config",
Usage: "Path to the pa11y config file to use",
Value: "e2e/pa11yci.conf.js",
Validator: mustBeFile("config", true),
TakesFile: true,
},
&cli.StringFlag{
Name: "results",
Usage: "Path to the pa11y results file to export",
TakesFile: true,
},
&cli.BoolFlag{
Name: "no-threshold-fail",
Usage: "Don't fail the task if any of the tests fail. Use this in combination with --results to list all violations even if they're within thresholds",
Value: false,
},
},
Action: run,
}
}
func run(ctx context.Context, cmd *cli.Command) error {
grafanaDir := cmd.String("grafana-dir")
targzPath := cmd.String("package")
licensePath := cmd.String("license")
pa11yConfigPath := cmd.String("config")
pa11yResultsPath := cmd.String("results")
noThresholdFail := cmd.Bool("no-threshold-fail")
d, err := dagger.Connect(ctx)
if err != nil {
return fmt.Errorf("failed to connect to Dagger: %w", err)
}
// Explicitly only the files used by the grafana-server service
hostSrc := d.Host().Directory(grafanaDir, dagger.HostDirectoryOpts{
Include: []string{
"./devenv",
"./e2e/test-plugins", // Directory is included so provisioning works, but they're not actually build
"./scripts/grafana-server/custom.ini",
"./scripts/grafana-server/start-server",
"./scripts/grafana-server/kill-server",
"./scripts/grafana-server/variables",
},
})
targz := d.Host().File(targzPath)
pa11yConfig := d.Host().File(pa11yConfigPath)
var license *dagger.File
if licensePath != "" {
license = d.Host().File(licensePath)
}
svc, err := GrafanaService(ctx, d, GrafanaServiceOpts{
HostSrc: hostSrc,
GrafanaTarGz: targz,
License: license,
})
if err != nil {
return fmt.Errorf("failed to create Grafana service: %w", err)
}
c, runErr := RunTest(ctx, d, svc, pa11yConfig, noThresholdFail, pa11yResultsPath)
if runErr != nil {
return fmt.Errorf("failed to run a11y test suite: %w", runErr)
}
c, syncErr := c.Sync(ctx)
if syncErr != nil {
return fmt.Errorf("failed to sync a11y test suite: %w", syncErr)
}
code, codeErr := c.ExitCode(ctx)
if codeErr != nil {
return fmt.Errorf("failed to get exit code of a11y test suite: %w", codeErr)
}
if code == 0 {
log.Printf("a11y tests passed with exit code %d", code)
} else if noThresholdFail {
log.Printf("a11y tests failed with exit code %d, but noFail is true", code)
} else {
return fmt.Errorf("a11y tests failed with exit code %d", code)
}
log.Println("a11y tests completed successfully")
return nil
}
func mustBeFile(arg string, emptyOk bool) func(string) error {
return func(s string) error {
if s == "" {
if emptyOk {
return nil
}
return cli.Exit(arg+" cannot be empty", 1)
}
stat, err := os.Stat(s)
if err != nil {
return cli.Exit(arg+" does not exist or cannot be read: "+s, 1)
}
if stat.IsDir() {
return cli.Exit(arg+" must be a file, not a directory: "+s, 1)
}
return nil
}
}
func mustBeDir(arg string) func(string) error {
return func(s string) error {
if s == "" {
return cli.Exit(arg+" cannot be empty", 1)
}
stat, err := os.Stat(s)
if err != nil {
return cli.Exit(arg+" does not exist or cannot be read: "+s, 1)
}
if !stat.IsDir() {
return cli.Exit(arg+" must be a directory: "+s, 1)
}
return nil
}
}
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"context"
"fmt"
"dagger.io/dagger"
)
func RunTest(
ctx context.Context,
d *dagger.Client,
grafanaService *dagger.Service,
pa11yConfig *dagger.File,
noThresholdFail bool,
pa11yResultsPath string,
) (*dagger.Container, error) {
// docker-puppeteer container already has Chrome and Pa11y installed in it
pa11yContainer := d.Container().From("grafana/docker-puppeteer:1.1.0").
WithWorkdir("/src").
WithExec([]string{"mkdir", "-p", "./screenshots"}). // not yet exported
WithEnvVariable("HOST", grafanaHost).
WithEnvVariable("PORT", fmt.Sprint(grafanaPort))
if noThresholdFail {
// This logic is non-intuitive - --no-threshold-fail will make pa11y fail (by removing thresholds from the config)
// so it can write all violations to the results file. This failure is then ignored by the caller in main.go.
// Otherwise, pa11y ignores violations if they're within the thresholds and doesn't include them in the results file
pa11yContainer = pa11yContainer.
WithEnvVariable("NO_THRESHOLDS", "true")
}
pa11yContainer = pa11yContainer.
WithServiceBinding(grafanaHost, grafanaService).
WithMountedFile("pa11yci-config.js", pa11yConfig).
WithExec([]string{"pa11y-ci", "--config", "pa11yci-config.js"}, dagger.ContainerWithExecOpts{
Expect: dagger.ReturnTypeAny, // allow this to fail here so we can handle non-zero exit codes at the caller
})
if pa11yResultsPath != "" {
_, err := pa11yContainer.File("/src/pa11y-ci-results.json").Export(ctx, pa11yResultsPath)
if err != nil {
return nil, fmt.Errorf("failed to get pa11y results: %w", err)
}
}
return pa11yContainer, nil
}
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"context"
"fmt"
"os"
"strings"
"dagger.io/dagger"
)
type GrafanaServiceOpts struct {
HostSrc *dagger.Directory
GrafanaTarGz *dagger.File
License *dagger.File
}
func GrafanaService(ctx context.Context, d *dagger.Client, opts GrafanaServiceOpts) (*dagger.Service, error) {
container := d.Container().From("alpine:3").
WithExec([]string{"apk", "add", "--no-cache", "bash", "tar", "netcat-openbsd"}).
WithMountedFile("/src/grafana.tar.gz", opts.GrafanaTarGz).
WithExec([]string{"mkdir", "-p", "/src/grafana"}).
WithExec([]string{"tar", "--strip-components=1", "-xzf", "/src/grafana.tar.gz", "-C", "/src/grafana"}).
WithDirectory("/src/grafana/devenv", opts.HostSrc.Directory("./devenv")).
WithDirectory("/src/grafana/e2e/test-plugins", opts.HostSrc.Directory("./e2e/test-plugins")).
WithDirectory("/src/grafana/scripts", opts.HostSrc.Directory("./scripts")).
WithWorkdir("/src/grafana").
WithEnvVariable("GF_APP_MODE", "development").
WithEnvVariable("GF_SERVER_HTTP_PORT", fmt.Sprint(grafanaPort)).
WithEnvVariable("GF_SERVER_ROUTER_LOGGING", "1").
WithExposedPort(grafanaPort)
var licenseArg string
if opts.License != nil {
licenseArg = "/src/license.jwt"
container = container.WithMountedFile(licenseArg, opts.License)
}
// We add all GF_ environment variables to allow for overriding Grafana configuration.
// It is unlikely the runner has any such otherwise.
for _, env := range os.Environ() {
if strings.HasPrefix(env, "GF_") {
parts := strings.SplitN(env, "=", 2)
container = container.WithEnvVariable(parts[0], parts[1])
}
}
svc := container.AsService(dagger.ContainerAsServiceOpts{Args: []string{"bash", "-x", "scripts/grafana-server/start-server", licenseArg}})
return svc, nil
}
+4 -1
View File
@@ -12,9 +12,12 @@ runs:
go-version-file: go.mod
- name: Bump versions
uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e
env:
GO_MOD_DIR: ${{ inputs.go-mod-dir }}
VERSION: ${{ inputs.version }}
with:
verb: run
args: go run ./pkg/build/actions/bump-version -version=${{ inputs.version }}
args: go run ./pkg/build/actions/bump-version -version=${VERSION}
- name: make gen-cue
shell: bash
run: make gen-cue
+20 -1
View File
@@ -2,10 +2,12 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
@@ -26,6 +28,23 @@ import (
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")
@@ -330,7 +349,7 @@ func postRequest(cfg packaging.PublishConfig, pth string, body any, descr string
return nil
}
resp, err := http.DefaultClient.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err)
}
+131 -135
View File
@@ -4,8 +4,8 @@ import (
"log"
"os"
"github.com/grafana/grafana/pkg/build"
"github.com/grafana/grafana/pkg/build/cmd/util"
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd"
"github.com/urfave/cli/v2"
)
@@ -17,15 +17,131 @@ func registerAppCommand(c *cli.Command) {
}
func main() {
app := cli.NewApp()
app.Commands = cli.Commands{
// TODO change the registerer if the user is running using a JSON file etc
for k, v := range cmd.Artifacts {
if err := cmd.GlobalCLI.Register(k, v); err != nil {
panic(err)
}
}
app := cmd.GlobalCLI.App()
artifactsCommand := cmd.GlobalCLI.ArtifactsCommand()
artifactsCommand.Subcommands = cli.Commands{
{
Name: "build",
Action: build.RunCmdCLI,
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: "Run Grafana e2e tests",
Usage: "[ARCHIVED] Run Grafana e2e tests",
Action: EndToEndTests,
Flags: []cli.Flag{
&triesFlag,
@@ -50,14 +166,9 @@ func main() {
},
},
},
{
Name: "whatsnew-checker",
Usage: "Checks whatsNewUrl in package.json for differences between the tag and the docs version",
Action: WhatsNewChecker,
},
{
Name: "upload-cdn",
Usage: "Upload public/* to a cdn bucket",
Usage: "[ARCHIVED] Upload public/* to a cdn bucket",
Action: UploadCDN,
Flags: []cli.Flag{
&editionFlag,
@@ -65,18 +176,18 @@ func main() {
},
{
Name: "publish-metrics",
Usage: "Publish a set of metrics from stdin",
Usage: "[ARCHIVED] Publish a set of metrics from stdin",
ArgsUsage: "<api-key>",
Action: util.MaxArgCountWrapper(1, PublishMetrics),
},
{
Name: "verify-drone",
Usage: "Verify Drone configuration",
Usage: "[ARCHIVED] Verify Drone configuration",
Action: VerifyDrone,
},
{
Name: "store-storybook",
Usage: "Stores storybook to GCS buckets",
Usage: "[ARCHIVED] Stores storybook to GCS buckets",
Action: StoreStorybook,
Flags: []cli.Flag{
&cli.StringFlag{
@@ -87,12 +198,12 @@ func main() {
},
{
Name: "verify-storybook",
Usage: "Integrity check for storybook build",
Usage: "[ARCHIVED] Integrity check for storybook build",
Action: VerifyStorybook,
},
{
Name: "upload-packages",
Usage: "Upload Grafana packages",
Usage: "[ARCHIVED] Upload Grafana packages",
Action: UploadPackages,
Flags: []cli.Flag{
&jobsFlag,
@@ -103,125 +214,10 @@ func main() {
},
},
},
{
Name: "artifacts",
Usage: "Handle Grafana artifacts",
Subcommands: cli.Commands{
{
Name: "storybook",
Usage: "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: "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: "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: "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: "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,
},
},
},
},
},
},
artifactsCommand,
{
Name: "publish",
Usage: "Publish packages to Grafana com and repositories",
Usage: "[ARCHIVED] Publish packages to Grafana com and repositories",
Subcommands: cli.Commands{
{
Name: "grafana-com",
@@ -292,7 +288,7 @@ func main() {
},
},
},
}
}...)
app.Commands = append(app.Commands, additionalCommands...)
-71
View File
@@ -1,71 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/grafana/grafana/pkg/build/config"
"github.com/urfave/cli/v2"
"golang.org/x/mod/semver"
)
const GrafanaDir = "."
var whatsNewRegex = regexp.MustCompile(`^.*whats-new-in-(v\d*-[\d+]*)`)
type PackageJSON struct {
Grafana Grafana `json:"grafana"`
Version string `json:"version"`
}
type Grafana struct {
WhatsNewUrl string `json:"whatsNewUrl"`
}
func WhatsNewChecker(c *cli.Context) error {
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
if metadata.ReleaseMode.IsTest {
fmt.Println("test mode, skipping check")
return nil
}
if metadata.ReleaseMode.Mode != config.TagMode {
return fmt.Errorf("non-tag pipeline, exiting")
}
tag := fmt.Sprintf("v%s", metadata.GrafanaVersion)
if !semver.IsValid(tag) {
return fmt.Errorf("non-semver compatible version %s, exiting", tag)
}
majorMinorDigits := strings.Replace(semver.MajorMinor(tag), ".", "-", 1)
pkgJSONPath := filepath.Join(GrafanaDir, "package.json")
//nolint:gosec
pkgJSONB, err := os.ReadFile(pkgJSONPath)
if err != nil {
return fmt.Errorf("failed to read %q: %w", pkgJSONPath, err)
}
var pkgObj PackageJSON
if err := json.Unmarshal(pkgJSONB, &pkgObj); err != nil {
return fmt.Errorf("failed decoding %q: %w", pkgJSONPath, err)
}
whatsNewSplit := whatsNewRegex.FindStringSubmatch(pkgObj.Grafana.WhatsNewUrl)
whatsNewVersion := whatsNewSplit[1]
if whatsNewVersion != majorMinorDigits {
return fmt.Errorf("whatsNewUrl in package.json needs to be updated to %s/", strings.Replace(whatsNewSplit[0], whatsNewVersion, majorMinorDigits, 1))
}
return nil
}
-83
View File
@@ -1,83 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
const (
DroneBuildEvent = "DRONE_BUILD_EVENT"
DroneTag = "DRONE_TAG"
DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE"
)
const whatsNewUrl = "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-"
func TestWhatsNewChecker(t *testing.T) {
tests := []struct {
envMap map[string]string
packageJsonVersion string
name string
wantErr bool
errMsg string
}{
{envMap: map[string]string{DroneBuildEvent: config.PullRequest}, packageJsonVersion: "", name: "non-tag event", wantErr: true, errMsg: "non-tag pipeline, exiting"},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "abcd123"}, packageJsonVersion: "", name: "non-semver compatible", wantErr: true, errMsg: "non-semver compatible version vabcd123, exiting"},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v0.0.0", DroneSemverPrerelease: "test"}, packageJsonVersion: "v10-0", name: "skip check for test tags", wantErr: false},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v10.0.0"}, packageJsonVersion: "v10-0", name: "package.json version matches tag", wantErr: false},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v10.0.0"}, packageJsonVersion: "v9-5", name: "package.json doesn't match tag", wantErr: true, errMsg: "whatsNewUrl in package.json needs to be updated to https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v10-0/"},
}
for _, tt := range tests {
app := cli.NewApp()
app.Version = "1.0.0"
context := cli.NewContext(app, &flag.FlagSet{}, nil)
t.Run(tt.name, func(t *testing.T) {
setUpEnv(t, tt.envMap)
err := createTempPackageJson(t, tt.packageJsonVersion)
require.NoError(t, err)
err = WhatsNewChecker(context)
if tt.wantErr {
require.Error(t, err)
require.Equal(t, tt.errMsg, err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func setUpEnv(t *testing.T, envMap map[string]string) {
t.Helper()
os.Clearenv()
t.Setenv("DRONE_BUILD_NUMBER", "12345")
t.Setenv("DRONE_COMMIT", "abcd12345")
for k, v := range envMap {
t.Setenv(k, v)
}
}
func createTempPackageJson(t *testing.T, version string) error {
t.Helper()
grafanaData := Grafana{WhatsNewUrl: fmt.Sprintf("%s%s/", whatsNewUrl, version)}
data := PackageJSON{Grafana: grafanaData, Version: "1.2.3"}
file, _ := json.MarshalIndent(data, "", " ")
err := os.WriteFile("package.json", file, 0644)
require.NoError(t, err)
t.Cleanup(func() {
err := os.RemoveAll("package.json")
require.NoError(t, err)
})
return nil
}
+8
View File
@@ -0,0 +1,8 @@
.grafana/
bin/
*.tar.gz
*.txt
.idea/
dist/
grafana/
.DS_Store
+4
View File
@@ -0,0 +1,4 @@
# daggerbuild
This folder was copied from the repository [grafana-build](https://github.com/grafana/grafana-build). If anything looks
out-of-place, then that's probably why.
+102
View File
@@ -0,0 +1,102 @@
package arguments
import (
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
var (
DockerRegistryFlag = &cli.StringFlag{
Name: "registry",
Usage: "Prefix the image name with the registry provided",
Value: "docker.io",
}
DockerOrgFlag = &cli.StringFlag{
Name: "org",
Usage: "Overrides the organization of the images",
Value: "grafana",
}
AlpineImageFlag = &cli.StringFlag{
Name: "alpine-base",
Usage: "The image or image alias specified in the Dockerfile to be used as the base image when building the Alpine version of the Grafana docker image.",
Value: "alpine-base",
}
UbuntuImageFlag = &cli.StringFlag{
Name: "ubuntu-base",
Usage: "The image or image alias specified in the Dockerfile to be used as the base image when building the Ubuntu version of the Grafana docker image",
Value: "ubuntu-base",
}
TagFormatFlag = &cli.StringFlag{
Name: "tag-format",
Usage: "Provide a go template for formatting the docker tag(s) for images with an Alpine base",
Value: docker.DefaultTagFormat,
}
UbuntuTagFormatFlag = &cli.StringFlag{
Name: "ubuntu-tag-format",
Usage: "Provide a go template for formatting the docker tag(s) for images with a ubuntu base",
Value: docker.DefaultUbuntuTagFormat,
}
BoringTagFormatFlag = &cli.StringFlag{
Name: "boring-tag-format",
Usage: "Provide a go template for formatting the docker tag(s) for the boringcrypto build of Grafana Enterprise",
Value: docker.DefaultBoringTagFormat,
}
ProDockerRegistryFlag = &cli.StringFlag{
Name: "pro-registry",
Usage: "Prefix the image name with the registry provided",
Value: "docker.io",
}
ProDockerOrgFlag = &cli.StringFlag{
Name: "pro-org",
Usage: "Overrides the organization of the images",
Value: "grafana",
}
ProDockerRepoFlag = &cli.StringFlag{
Name: "pro-repo",
Usage: "Overrides the docker repository of the built images",
Value: "grafana-pro",
}
EntDockerRegistryFlag = &cli.StringFlag{
Name: "docker-enterprise-registry",
Usage: "Prefix the image name with the registry provided",
Value: "docker.io",
}
EntDockerOrgFlag = &cli.StringFlag{
Name: "docker-enterprise-org",
Usage: "Overrides the organization of the images",
Value: "grafana",
}
EntDockerRepoFlag = &cli.StringFlag{
Name: "docker-enterprise-repo",
Usage: "Overrides the docker repository of the built images",
Value: "grafana-enterprise",
}
HGTagFormatFlag = &cli.StringFlag{
Name: "hg-tag-format",
Usage: "Provide a go template for formatting the docker tag(s) for Hosted Grafana images",
Value: docker.DefaultHGTagFormat,
}
DockerRegistry = pipeline.NewStringFlagArgument(DockerRegistryFlag)
DockerOrg = pipeline.NewStringFlagArgument(DockerOrgFlag)
AlpineImage = pipeline.NewStringFlagArgument(AlpineImageFlag)
UbuntuImage = pipeline.NewStringFlagArgument(UbuntuImageFlag)
TagFormat = pipeline.NewStringFlagArgument(TagFormatFlag)
UbuntuTagFormat = pipeline.NewStringFlagArgument(UbuntuTagFormatFlag)
BoringTagFormat = pipeline.NewStringFlagArgument(BoringTagFormatFlag)
// The docker registry for Grafana Pro is often different than the one for Grafana & Enterprise
ProDockerRegistry = pipeline.NewStringFlagArgument(ProDockerRegistryFlag)
ProDockerOrg = pipeline.NewStringFlagArgument(ProDockerOrgFlag)
ProDockerRepo = pipeline.NewStringFlagArgument(ProDockerRepoFlag)
EntDockerRegistry = pipeline.NewStringFlagArgument(EntDockerRegistryFlag)
EntDockerOrg = pipeline.NewStringFlagArgument(EntDockerOrgFlag)
EntDockerRepo = pipeline.NewStringFlagArgument(EntDockerRepoFlag)
HGTagFormat = pipeline.NewStringFlagArgument(HGTagFormatFlag)
)
+4
View File
@@ -0,0 +1,4 @@
// Package arguments holds globally-defined arguments that are used throughout the program for shared data.
// A good candidate for an argument is a directory whose contents that may be used in the creation of multiple artifacts, like the Grafana source directory.
// Arguments are different than flags; a flag is a boolean argument in an artifact string which can set one or multiple preset values.
package arguments
@@ -0,0 +1 @@
package arguments
@@ -0,0 +1,53 @@
package arguments
import (
"context"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/golang"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
var GoBuildCache = pipeline.Argument{
Name: "go-cache-volume",
Description: "Mounted at GOCACHE when building Go backends",
ArgumentType: pipeline.ArgumentTypeCacheVolume,
Flags: []cli.Flag{},
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
return opts.Client.CacheVolume("go-build-cache"), nil
},
}
var GoModCache = pipeline.Argument{
Name: "go-mod-volume",
Description: "Stores downloaded Go modules when building Go backends",
ArgumentType: pipeline.ArgumentTypeCacheVolume,
Flags: []cli.Flag{},
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
vol := opts.Client.CacheVolume("go-mod-cache")
goVersion, err := opts.State.String(ctx, GoVersion)
if err != nil {
return nil, err
}
src, err := opts.State.Directory(ctx, GrafanaDirectory)
if err != nil {
return nil, err
}
c := golang.Container(opts.Client, opts.Platform, goVersion).
WithEnvVariable("GOMODCACHE", "/go/pkg/mod").
WithMountedCache("/go/pkg/mod", vol).
WithDirectory("/src", src, dagger.ContainerWithDirectoryOpts{
Include: []string{"**/*.mod", "**/*.sum", "**/*.work"},
}).
WithWorkdir("/src").
WithExec([]string{"go", "mod", "download"})
if _, err := c.Sync(ctx); err != nil {
return nil, err
}
return vol, nil
},
}
+41
View File
@@ -0,0 +1,41 @@
package arguments
import (
"context"
"strings"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
const (
DefaultViceroyVersion = "v0.4.0"
)
var GoVersion = pipeline.Argument{
Name: "go-version",
Description: "The Go version to use when compiling Grafana",
ArgumentType: pipeline.ArgumentTypeString,
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
src, err := opts.State.Directory(ctx, GrafanaDirectory)
if err != nil {
return nil, err
}
stdout, err := opts.Client.Container().From("alpine").
WithMountedFile("/src/go.mod", src.File("go.mod")).
WithWorkdir("/src").
WithExec([]string{"/bin/sh", "-c", `grep '^go ' go.mod | awk '{print $2}'`}).
Stdout(ctx)
return strings.TrimSpace(stdout), err
},
}
var ViceroyVersionFlag = &cli.StringFlag{
Name: "viceroy-version",
Usage: "This flag sets the base image of the container used to build the Grafana backend binaries for non-Linux distributions",
Value: DefaultViceroyVersion,
}
var ViceroyVersion = pipeline.NewStringFlagArgument(ViceroyVersionFlag)
+28
View File
@@ -0,0 +1,28 @@
package arguments
import (
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
var (
GPGPublicKeyFlag = &cli.StringFlag{
Name: "gpg-public-key-base64",
Usage: "Provides a public key encoded in base64 for GPG signing",
EnvVars: []string{"GPG_PUBLIC_KEY"},
}
GPGPrivateKeyFlag = &cli.StringFlag{
Name: "gpg-private-key-base64",
Usage: "Provides a private key encoded in base64 for GPG signing",
EnvVars: []string{"GPG_PRIVATE_KEY"},
}
GPGPassphraseFlag = &cli.StringFlag{
Name: "gpg-passphrase",
Usage: "Provides a private key passphrase encoded in base64 for GPG signing",
EnvVars: []string{"GPG_PASSPHRASE"},
}
GPGPublicKey = pipeline.NewStringFlagArgument(GPGPublicKeyFlag)
GPGPrivateKey = pipeline.NewStringFlagArgument(GPGPrivateKeyFlag)
GPGPassphrase = pipeline.NewStringFlagArgument(GPGPassphraseFlag)
)
+299
View File
@@ -0,0 +1,299 @@
package arguments
import (
"context"
"fmt"
"log/slog"
"path"
"path/filepath"
"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/daggerutil"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/git"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
const BusyboxImage = "busybox:1.36"
func InitializeEnterprise(d *dagger.Client, grafana *dagger.Directory, enterprise *dagger.Directory) *dagger.Directory {
hash := d.Container().From("alpine/git").
WithDirectory("/src/grafana-enterprise", enterprise).
WithWorkdir("/src/grafana-enterprise").
WithEntrypoint([]string{}).
WithExec([]string{"/bin/sh", "-c", "git rev-parse HEAD > .buildinfo.enterprise-commit"}).
File("/src/grafana-enterprise/.buildinfo.enterprise-commit")
return d.Container().From(BusyboxImage).
WithDirectory("/src/grafana", grafana).
WithDirectory("/src/grafana-enterprise", enterprise).
WithWorkdir("/src/grafana-enterprise").
WithFile("/src/grafana/.buildinfo.enterprise-commit", hash).
WithExec([]string{"/bin/sh", "build.sh"}).
WithExec([]string{"cp", "LICENSE", "../grafana"}).
Directory("/src/grafana")
}
// GrafnaaOpts are populated by the 'GrafanaFlags' flags.
// These options define how to mount or clone the grafana/enterprise source code.
type GrafanaDirectoryOpts struct {
// GrafanaDir is the path to the Grafana source tree.
// If GrafanaDir is empty, then we're most likely cloning Grafana and using that as a directory.
GrafanaDir string
EnterpriseDir string
// GrafanaRepo will clone Grafana from a different repository when cloning Grafana.
GrafanaRepo string
EnterpriseRepo string
// GrafanaRef will checkout a specific tag, branch, or commit when cloning Grafana.
GrafanaRef string
EnterpriseRef string
// GitHubToken is used when cloning Grafana/Grafana Enterprise.
GitHubToken string
PatchesRepo string
PatchesPath string
PatchesRef string
}
func githubToken(ctx context.Context, token string) (string, error) {
// Since GrafanaDir was not provided, we must clone it.
ght := token
// If GitHubToken was not set from flag
if ght != "" {
return ght, nil
}
token, err := git.LookupGitHubToken(ctx)
if err != nil {
return "", err
}
if token == "" {
return "", fmt.Errorf("unable to acquire github token")
}
return token, nil
}
func GrafanaDirectoryOptsFromFlags(c cliutil.CLIContext) *GrafanaDirectoryOpts {
return &GrafanaDirectoryOpts{
GrafanaRepo: c.String("grafana-repo"),
EnterpriseRepo: c.String("enterprise-repo"),
GrafanaDir: c.String("grafana-dir"),
EnterpriseDir: c.String("enterprise-dir"),
GrafanaRef: c.String("grafana-ref"),
EnterpriseRef: c.String("enterprise-ref"),
GitHubToken: c.String("github-token"),
PatchesRepo: c.String("patches-repo"),
PatchesPath: c.String("patches-path"),
PatchesRef: c.String("patches-ref"),
}
}
func cloneOrMount(ctx context.Context, client *dagger.Client, localPath, repo, ref string, ght string) (*dagger.Directory, error) {
if localPath != "" {
absolute, err := filepath.Abs(localPath)
if err != nil {
return nil, fmt.Errorf("error getting absolute path for local dir: %w", err)
}
localPath = absolute
slog.Info("Using local directory for repository", "path", localPath, "repo", repo)
return daggerutil.HostDir(client, localPath)
}
ght, err := githubToken(ctx, ght)
if err != nil {
return nil, fmt.Errorf("error acquiring GitHub token: %w", err)
}
return git.CloneWithGitHubToken(client, ght, repo, ref)
}
func applyPatches(ctx context.Context, client *dagger.Client, src *dagger.Directory, repo, patchesPath, ref, ght string) (*dagger.Directory, error) {
ght, err := githubToken(ctx, ght)
if err != nil {
return nil, fmt.Errorf("error acquiring GitHub token: %w", err)
}
// Clone the patches repository on 'main'
dir, err := git.CloneWithGitHubToken(client, ght, repo, ref)
if err != nil {
return nil, fmt.Errorf("error cloning patches repository: %w", err)
}
entries, err := dir.Entries(ctx, dagger.DirectoryEntriesOpts{
Path: patchesPath,
})
if err != nil {
return nil, fmt.Errorf("error listing entries in repository: %w", err)
}
if len(entries) == 0 {
return nil, fmt.Errorf("no patches in the given path")
}
container := client.Container().From(git.GitImage).
WithEntrypoint([]string{}).
WithMountedDirectory("/src", src).
WithMountedDirectory("/patches", dir).
WithWorkdir("/src").
WithExec([]string{"git", "config", "--local", "user.name", "grafana"}).
WithExec([]string{"git", "config", "--local", "user.email", "engineering@grafana.com"})
for _, v := range entries {
if filepath.Ext(v) != ".patch" {
continue
}
container = container.WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`git am --3way --ignore-whitespace --ignore-space-change --committer-date-is-author-date %s > /dev/null 2>&1`, path.Join("/patches", patchesPath, v))})
}
return container.Directory("/src"), nil
}
func grafanaDirectory(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
o := GrafanaDirectoryOptsFromFlags(opts.CLIContext)
src, err := cloneOrMount(ctx, opts.Client, o.GrafanaDir, o.GrafanaRepo, o.GrafanaRef, o.GitHubToken)
if err != nil {
return nil, err
}
gitContainer := opts.Client.Container().From("alpine/git").
WithWorkdir("/src").
WithMountedDirectory("/src/.git", src.Directory(".git")).
WithEntrypoint([]string{})
commitFile := gitContainer.
WithExec([]string{"/bin/sh", "-c", "git rev-parse HEAD > .buildinfo.grafana-commit"}).
File("/src/.buildinfo.grafana-commit")
branchFile := gitContainer.
WithExec([]string{"/bin/sh", "-c", "git rev-parse --abbrev-ref HEAD > .buildinfo.grafana-branch"}).
File("/src/.buildinfo.grafana-branch")
src = src.
WithFile(".buildinfo.commit", commitFile).
WithFile(".buildinfo.branch", branchFile)
if o.PatchesRepo != "" {
withPatches, err := applyPatches(ctx, opts.Client, src, o.PatchesRepo, o.PatchesPath, o.PatchesRef, o.GitHubToken)
if err != nil {
opts.Log.Debug("patch application skipped", "error", err)
} else {
// Only replace src when there was no error.
src = withPatches
}
}
nodeVersion, err := frontend.NodeVersion(opts.Client, src).Stdout(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get node version from source code: %w", err)
}
yarnCache, err := opts.State.CacheVolume(ctx, YarnCacheDirectory)
if err != nil {
return nil, err
}
container := frontend.YarnInstall(opts.Client, src, nodeVersion, yarnCache, opts.Platform)
if _, err := containers.ExitError(ctx, container); err != nil {
return nil, err
}
return container.Directory("/src"), nil
}
func enterpriseDirectory(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
// Get the Grafana directory...
o := GrafanaDirectoryOptsFromFlags(opts.CLIContext)
grafanaDir, err := grafanaDirectory(ctx, opts)
if err != nil {
return nil, fmt.Errorf("error initializing grafana directory: %w", err)
}
clone, err := cloneOrMount(ctx, opts.Client, o.EnterpriseDir, o.EnterpriseRepo, o.EnterpriseRef, o.GitHubToken)
if err != nil {
return nil, fmt.Errorf("error cloning or mounting Grafana Enterprise directory: %w", err)
}
return InitializeEnterprise(opts.Client, grafanaDir.(*dagger.Directory), clone), nil
}
var GrafanaDirectoryFlags = []cli.Flag{
&cli.StringFlag{
Name: "grafana-dir",
Usage: "Local Grafana dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "enterprise-dir",
Usage: "Local Grafana Enterprise dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "grafana-repo",
Usage: "Grafana repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana.git",
},
&cli.StringFlag{
Name: "enterprise-repo",
Usage: "Grafana Enterprise repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana-enterprise.git",
},
&cli.StringFlag{
Name: "grafana-ref",
Usage: "Grafana ref to clone, not valid if --grafana-dir is set",
Required: false,
Value: "main",
},
&cli.StringFlag{
Name: "enterprise-ref",
Usage: "Grafana Enterprise ref to clone, not valid if --grafana-dir is set",
Required: false,
Value: "main",
},
&cli.StringFlag{
Name: "github-token",
Usage: "GitHub token to use for git cloning, by default will be pulled from GitHub",
Required: false,
},
&cli.StringFlag{
Name: "patches-repo",
Usage: "GitHub repository that contains git patches to apply to the Grafana source code. Must be an https git URL",
},
&cli.StringFlag{
Name: "patches-path",
Usage: "Path to folder containing '.patch' files to apply",
},
&cli.StringFlag{
Name: "patches-ref",
Usage: "Ref to checkout in the patches repository",
Value: "main",
},
}
// GrafanaDirectory will provide the valueFunc that initializes and returns a *dagger.Directory that has Grafana in it.
// Where possible, when cloning and no authentication options are provided, the valuefunc will try to use the configured github CLI for cloning.
var GrafanaDirectory = pipeline.Argument{
Name: "grafana-dir",
Description: "The source tree of the Grafana repository",
Flags: GrafanaDirectoryFlags,
ValueFunc: grafanaDirectory,
}
// EnterpriseDirectory will provide the valueFunc that initializes and returns a *dagger.Directory that has Grafana Enterprise initialized it.
// Where possible, when cloning and no authentication options are provided, the valuefunc will try to use the configured github CLI for cloning.
var EnterpriseDirectory = pipeline.Argument{
Name: "enterprise-dir",
Description: "The source tree of Grafana Enterprise",
Flags: GrafanaDirectoryFlags,
ValueFunc: enterpriseDirectory,
}
@@ -0,0 +1,70 @@
package arguments
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
var HGDirectoryFlags = []cli.Flag{
&cli.StringFlag{
Name: "hosted-grafana-dir",
Usage: "Local clone of HG to use, instead of git cloning",
Required: false,
},
&cli.StringFlag{
Name: "hosted-grafana-repo",
Usage: "https `.git` repository to use for hosted-grafana",
Required: false,
Value: "https://github.com/grafana/hosted-grafana.git",
},
&cli.StringFlag{
Name: "hosted-grafana-ref",
Usage: "git ref to checkout",
Required: false,
Value: "main",
},
}
// HGDirectory will provide the valueFunc that initializes and returns a *dagger.Directory that has a repository that has the Grafana Pro/Enterprise docker image.
// Where possible, when cloning and no authentication options are provided, the valuefunc will try to use the configured github CLI for cloning.
var HGDirectory = pipeline.Argument{
Name: "hg-dir",
Description: "The source tree of that has the Dockerfile for Grafana Pro/Enterprise",
Flags: HGDirectoryFlags,
ValueFunc: hgDirectory,
}
type HGDirectoryOpts struct {
GitHubToken string
HGDir string
HGRepo string
HGRef string
}
func HGDirectoryOptsFromFlags(c cliutil.CLIContext) *HGDirectoryOpts {
return &HGDirectoryOpts{
GitHubToken: c.String("github-token"),
HGDir: c.String("hosted-grafana-dir"),
HGRepo: c.String("hosted-grafana-repo"),
HGRef: c.String("hosted-grafana-ref"),
}
}
func hgDirectory(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
o := HGDirectoryOptsFromFlags(opts.CLIContext)
ght, err := githubToken(ctx, o.GitHubToken)
if err != nil {
return nil, fmt.Errorf("could not get GitHub token: %w", err)
}
src, err := cloneOrMount(ctx, opts.Client, o.HGDir, o.HGRepo, o.HGRef, ght)
if err != nil {
return nil, err
}
return src, nil
}
+12
View File
@@ -0,0 +1,12 @@
package arguments
import "github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
func Join(f ...[]pipeline.Argument) []pipeline.Argument {
r := []pipeline.Argument{}
for _, v := range f {
r = append(r, v...)
}
return r
}
@@ -0,0 +1,69 @@
package arguments
import (
"context"
"strings"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/grafana/grafana/pkg/build/daggerbuild/stringutil"
"github.com/urfave/cli/v2"
)
var flagBuildID = &cli.StringFlag{
Name: "build-id",
Usage: "Build ID to use in package names",
Value: "local",
}
var BuildID = pipeline.Argument{
Name: "build-id",
Description: "The grafana backend binaries ('grafana', 'grafana-cli', 'grafana-server') in a directory",
Flags: []cli.Flag{
flagBuildID,
},
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
v := opts.CLIContext.String("build-id")
if v == "" {
v = stringutil.RandomString(8)
}
return v, nil
},
}
var flagVersion = &cli.StringFlag{
Name: "version",
Usage: "Explicit version number. If this is not set then one with will auto-detected based on the source repository",
}
var Version = pipeline.Argument{
Name: "version",
Description: "The version string that is shown in the UI, in the CLI, and in package metadata",
Flags: []cli.Flag{
flagVersion,
},
Requires: []pipeline.Argument{
GrafanaDirectory,
},
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
v := opts.CLIContext.String("version")
if v != "" {
return v, nil
}
src, err := opts.State.Directory(ctx, GrafanaDirectory)
if err != nil {
return "", err
}
buildID, err := opts.State.String(ctx, BuildID)
if err != nil {
return "", err
}
version, err := containers.GetJSONValue(ctx, opts.Client, src, "package.json", "version")
if err != nil {
return "", err
}
return strings.ReplaceAll(version, "pre", buildID), nil
},
}
+54
View File
@@ -0,0 +1,54 @@
package arguments
import (
"context"
"os"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
var YarnCacheDirFlag = &cli.StringFlag{
Name: "yarn-cache-dir",
Aliases: []string{"yarn-cache"},
Usage: "Path to the yarn cache directory to mount during 'yarn install' commands (if there is one)",
EnvVars: []string{"YARN_CACHE_FOLDER", "YARN_CACHE_DIR"},
Value: "",
}
var YarnCacheDirectory = pipeline.Argument{
Name: "yarn-cache-dir",
Description: YarnCacheDirFlag.Usage,
ArgumentType: pipeline.ArgumentTypeCacheVolume,
Flags: []cli.Flag{
YarnCacheDirFlag,
},
ValueFunc: func(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
vol := opts.CLIContext.String(YarnCacheDirFlag.Name)
// Prepopulate the cache with what's defined in YARN_CACHE_FOLDER
// or in the CLI
if val, ok := os.LookupEnv("YARN_CACHE_FOLDER"); ok {
vol = val
}
cache := opts.Client.CacheVolume("yarn-cache-dir")
if vol == "" {
return cache, nil
}
dir := opts.Client.Host().Directory(vol)
_, err := opts.Client.Container().
From("alpine").
WithMountedCache("/cache", cache).
WithMountedDirectory("/data", dir).
WithExec([]string{"/bin/sh", "-c", "cp -r /data/* /cache || return 0"}).
Sync(ctx)
if err != nil {
return nil, err
}
return cache, nil
},
}
+273
View File
@@ -0,0 +1,273 @@
package artifacts
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
)
func Action(r Registerer, c *cli.Context) error {
// ArtifactStrings represent an artifact with a list of boolean options, like
// targz:linux/amd64:enterprise
artifactStrings := c.StringSlice("artifacts")
logLevel := slog.LevelInfo
if c.Bool("verbose") {
logLevel = slog.LevelDebug
}
var (
ctx = c.Context
log = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: logLevel,
}))
parallel = c.Int64("parallel")
destination = c.String("destination")
platform = dagger.Platform(c.String("platform"))
verify = c.Bool("verify")
checksum = c.Bool("checksum")
)
if len(artifactStrings) == 0 {
return errors.New("no artifacts specified. At least 1 artifact is required using the '--artifact' or '-a' flag")
}
log.Debug("Connecting to dagger daemon...")
daggerOpts := []dagger.ClientOpt{}
if logLevel == slog.LevelDebug {
daggerOpts = append(daggerOpts, dagger.WithLogOutput(os.Stderr))
}
client, err := dagger.Connect(ctx, daggerOpts...)
if err != nil {
return err
}
log.Debug("Connected to dagger daemon")
var state pipeline.StateHandler = &pipeline.State{
Log: log,
Client: client,
CLIContext: c,
Platform: platform,
}
registered := r.Initializers()
log.Debug("Generating artifacts from artifact strings...")
// Initialize the artifacts that were specified by the artifacts commands.
// These are specified by using artifact strings, or comma-delimited lists of flags.
artifacts, err := ArtifactsFromStrings(ctx, log, artifactStrings, registered, state)
if err != nil {
return err
}
log.Debug("Done generating artifact metadata")
state = pipeline.StateWithLogger(
log.With("service", "state"),
state,
)
// The artifact store is responsible for storing built artifacts and issuing them to artifacts that use them as dependencies using the artifact's filename as the key.
store := pipeline.NewArtifactStore(log)
opts := &pipeline.ArtifactContainerOpts{
Client: client,
Log: log,
State: state,
Platform: platform,
Store: store,
}
// Build each artifact and their dependencies, essentially constructing a dag using Dagger.
for i, v := range artifacts {
filename, err := v.Handler.Filename(ctx)
if err != nil {
return fmt.Errorf("error processing artifact string '%s': %w", artifactStrings[i], err)
}
log := log.With("filename", filename, "artifact", v.ArtifactString)
log.Info("Adding artifact to dag...")
if err := BuildArtifact(ctx, log, v, opts); err != nil {
return err
}
log.Info("Done adding artifact")
}
wg := &errgroup.Group{}
sm := semaphore.NewWeighted(parallel)
log.Info("Exporting artifacts...")
// Export the files from the dag, causing the containers to trigger.
for _, v := range artifacts {
log := log.With("artifact", v.ArtifactString, "action", "export")
wg.Go(ExportArtifactFunc(ctx, client, sm, log, v, store, destination, checksum))
}
if verify {
// Export the files from the dag, causing the containers to trigger.
for _, v := range artifacts {
log := log.With("artifact", v.ArtifactString, "action", "validate")
wg.Go(VerifyArtifactFunc(ctx, client, sm, log, v, store, destination))
}
}
return wg.Wait()
}
func BuildArtifact(ctx context.Context, log *slog.Logger, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) error {
store := opts.Store
exists, err := store.Exists(ctx, a)
if err != nil {
return err
}
if exists {
return nil
}
// populate the dependency list
dependencies, err := a.Handler.Dependencies(ctx)
if err != nil {
return err
}
// Get the files / directories that the dependencies define,
// and store the result for re-use.
for _, v := range dependencies {
f, err := v.Handler.Filename(ctx)
if err != nil {
return err
}
log := log.With("artifact", v.ArtifactString, "filename", f)
if err := BuildArtifact(ctx, log, v, opts); err != nil {
return err
}
}
switch a.Type {
case pipeline.ArtifactTypeDirectory:
dir, err := BuildArtifactDirectory(ctx, a, opts)
if err != nil {
return err
}
return store.StoreDirectory(ctx, a, dir)
case pipeline.ArtifactTypeFile:
file, err := BuildArtifactFile(ctx, a, opts)
if err != nil {
return err
}
return store.StoreFile(ctx, a, file)
}
return nil
}
func Command(r Registerer) func(c *cli.Context) error {
return func(c *cli.Context) error {
if err := Action(r, c); err != nil {
return cli.Exit(err, 1)
}
return nil
}
}
func BuildArtifactFile(ctx context.Context, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
builder, err := a.Handler.Builder(ctx, opts)
if err != nil {
return nil, err
}
return a.Handler.BuildFile(ctx, builder, opts)
}
func BuildArtifactDirectory(ctx context.Context, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
builder, err := a.Handler.Builder(ctx, opts)
if err != nil {
return nil, err
}
return a.Handler.BuildDir(ctx, builder, opts)
}
func ExportArtifactFunc(ctx context.Context, d *dagger.Client, sm *semaphore.Weighted, log *slog.Logger, v *pipeline.Artifact, store pipeline.ArtifactStore, dst string, checksum bool) func() error {
return func() error {
log.Info("Started exporting artifact...")
log.Info("Acquiring semaphore")
if err := sm.Acquire(ctx, 1); err != nil {
log.Info("Error acquiring semaphore", "error", err)
return err
}
log.Info("Acquired semaphore")
defer sm.Release(1)
filename, err := v.Handler.Filename(ctx)
if err != nil {
return fmt.Errorf("error processing artifact string '%s': %w", v.ArtifactString, err)
}
log.Info("Exporting artifact")
paths, err := store.Export(ctx, d, v, dst, checksum)
if err != nil {
return fmt.Errorf("error exporting artifact '%s': %w", filename, err)
}
for _, v := range paths {
if _, err := fmt.Fprintf(Stdout, "%s\n", v); err != nil {
return fmt.Errorf("error writing to stdout: %w", err)
}
}
log.Info("Done exporting artifact")
return nil
}
}
func verifyArtifact(ctx context.Context, client *dagger.Client, v *pipeline.Artifact, store pipeline.ArtifactStore) error {
switch v.Type {
case pipeline.ArtifactTypeDirectory:
file, err := store.Directory(ctx, v)
if err != nil {
return err
}
if err := v.Handler.VerifyDirectory(ctx, client, file); err != nil {
return err
}
case pipeline.ArtifactTypeFile:
file, err := store.File(ctx, v)
if err != nil {
return err
}
if err := v.Handler.VerifyFile(ctx, client, file); err != nil {
return err
}
}
return nil
}
func VerifyArtifactFunc(ctx context.Context, d *dagger.Client, sm *semaphore.Weighted, log *slog.Logger, v *pipeline.Artifact, store pipeline.ArtifactStore, dst string) func() error {
return func() error {
log.Info("Started verifying artifact...")
log.Info("Acquiring semaphore")
if err := sm.Acquire(ctx, 1); err != nil {
log.Info("Error acquiring semaphore", "error", err)
return err
}
log.Info("Acquired semaphore")
defer sm.Release(1)
if err := verifyArtifact(ctx, d, v, store); err != nil {
return err
}
return nil
}
}
+254
View File
@@ -0,0 +1,254 @@
package artifacts
import (
"context"
"log/slog"
"os"
"path/filepath"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
BackendArguments = []pipeline.Argument{
arguments.GrafanaDirectory,
arguments.EnterpriseDirectory,
arguments.GoVersion,
arguments.ViceroyVersion,
}
BackendFlags = flags.JoinFlags(
flags.PackageNameFlags,
flags.DistroFlags(),
)
)
var BackendInitializer = Initializer{
InitializerFunc: NewBackendFromString,
Arguments: BackendArguments,
}
type Backend struct {
// Name allows different backend compilations to be different even if all other factors are the same.
// For example, Grafana Enterprise, Grafana, and Grafana Pro may be built using the same options,
// but are fundamentally different because of the source code of the binary.
Name packages.Name
Src *dagger.Directory
Distribution backend.Distribution
BuildOpts *backend.BuildOpts
GoVersion string
ViceroyVersion string
GoBuildCache *dagger.CacheVolume
GoModCache *dagger.CacheVolume
// Version is embedded in the binary at build-time
Version string
}
func (b *Backend) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return backend.Builder(
opts.Client,
opts.Log,
b.Distribution,
b.BuildOpts,
opts.Platform,
b.Src,
b.GoVersion,
b.ViceroyVersion,
b.GoBuildCache,
b.GoModCache,
)
}
func (b *Backend) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return nil, nil
}
func (b *Backend) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
panic("not implemented") // TODO: Implement
}
func (b *Backend) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
f, err := b.Filename(ctx)
if err != nil {
return nil, err
}
return backend.Build(
opts.Client,
builder,
b.Src,
b.Distribution,
f,
b.BuildOpts,
), nil
}
func (b *Backend) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (b *Backend) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (b *Backend) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (b *Backend) Filename(ctx context.Context) (string, error) {
return filepath.Join("bin", string(b.Name), string(b.Distribution)), nil
}
func (b *Backend) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Not a file
return nil
}
func (b *Backend) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
// Nothing to do (yet)
return nil
}
type NewBackendOpts struct {
Name packages.Name
Enterprise bool
Src *dagger.Directory
Distribution backend.Distribution
GoVersion string
ViceroyVersion string
Version string
Experiments []string
Tags []string
Static bool
WireTag string
GoBuildCache *dagger.CacheVolume
GoModCache *dagger.CacheVolume
}
func NewBackendFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
goVersion, err := state.String(ctx, arguments.GoVersion)
if err != nil {
return nil, err
}
viceroyVersion, err := state.String(ctx, arguments.ViceroyVersion)
if err != nil {
return nil, err
}
goModCache, err := state.CacheVolume(ctx, arguments.GoModCache)
if err != nil {
return nil, err
}
goBuildCache, err := state.CacheVolume(ctx, arguments.GoBuildCache)
if err != nil {
return nil, err
}
// 1. Figure out the options that were provided as part of the artifact string.
// For example, `linux/amd64:grafana`.
options, err := pipeline.ParseFlags(artifact, TargzFlags)
if err != nil {
return nil, err
}
static, err := options.Bool(flags.Static)
if err != nil {
return nil, err
}
wireTag, err := options.String(flags.WireTag)
if err != nil {
return nil, err
}
experiments, err := options.StringSlice(flags.GoExperiments)
if err != nil {
return nil, err
}
tags, err := options.StringSlice(flags.GoTags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
src, err := GrafanaDir(ctx, state, p.Enterprise)
if err != nil {
return nil, err
}
goCacheProg := ""
// If the caller has GOCACHEPROG set, then reuse it
if val, ok := os.LookupEnv("GOCACHEPROG"); ok {
goCacheProg = val
}
bopts := &backend.BuildOpts{
Version: p.Version,
Enterprise: p.Enterprise,
ExperimentalFlags: experiments,
GoCacheProg: goCacheProg,
Static: static,
WireTag: wireTag,
Tags: tags,
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: BackendFlags,
Handler: &Backend{
Name: p.Name,
Distribution: p.Distribution,
BuildOpts: bopts,
GoVersion: goVersion,
ViceroyVersion: viceroyVersion,
Src: src,
GoModCache: goModCache,
GoBuildCache: goBuildCache,
},
})
}
func NewBackend(ctx context.Context, log *slog.Logger, artifact string, opts *NewBackendOpts) (*pipeline.Artifact, error) {
bopts := &backend.BuildOpts{
Version: opts.Version,
Enterprise: opts.Enterprise,
ExperimentalFlags: opts.Experiments,
Tags: opts.Tags,
Static: opts.Static,
WireTag: opts.WireTag,
}
log.Info("Initializing backend artifact with options", "static", opts.Static, "version", opts.Version, "name", opts.Name, "distro", opts.Distribution)
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: BackendFlags,
Handler: &Backend{
Name: opts.Name,
Distribution: opts.Distribution,
BuildOpts: bopts,
GoVersion: opts.GoVersion,
ViceroyVersion: opts.ViceroyVersion,
Src: opts.Src,
GoModCache: opts.GoModCache,
GoBuildCache: opts.GoBuildCache,
},
})
}
+75
View File
@@ -0,0 +1,75 @@
package artifacts
import (
"sort"
"strings"
"log/slog"
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd/flags"
"github.com/urfave/cli/v2"
)
func ArtifactFlags(r Registerer) []cli.Flag {
artifactsFlag := &cli.StringSliceFlag{
Name: "artifacts",
Aliases: []string{"a"},
}
buildFlag := &cli.BoolFlag{
Name: "build",
Value: true,
}
publishFlag := &cli.BoolFlag{
Name: "publish",
Usage: "If true, then the artifacts that are built will be published. If `--build=false` and the artifacts are found in the --destination, then those artifacts are not built and are published instead.",
Value: true,
}
verifyFlag := &cli.BoolFlag{
Name: "verify",
Usage: "If true, then the artifacts that are built will be verified with e2e tests or similar after being exported, depending on the artifact",
Value: false,
}
flags := flags.Join(
[]cli.Flag{
artifactsFlag,
buildFlag,
publishFlag,
verifyFlag,
flags.Platform,
},
flags.PublishFlags,
flags.ConcurrencyFlags,
[]cli.Flag{
flags.Verbose,
},
)
// All of these artifacts are the registered artifacts. These should mostly stay the same no matter what.
initializers := r.Initializers()
// Add all of the CLI flags that are defined by each artifact's arguments.
m := map[string]cli.Flag{}
// For artifact arguments that specify flags, we'll coalesce them here and add them to the list of flags.
for _, n := range initializers {
for _, arg := range n.Arguments {
for _, f := range arg.Flags {
fn := strings.Join(f.Names(), ",")
m[fn] = f
slog.Debug("global flag added by argument in artifact", "flag", fn, "arg", arg.Name)
}
}
}
for _, v := range m {
flags = append(flags, v)
}
sort.Slice(flags, func(i, j int) bool {
return strings.Compare(flags[i].Names()[0], flags[j].Names()[0]) <= 0
})
return flags
}
+147
View File
@@ -0,0 +1,147 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"path/filepath"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
FrontendFlags = flags.PackageNameFlags
FrontendArguments = []pipeline.Argument{
arguments.YarnCacheDirectory,
}
)
var FrontendInitializer = Initializer{
InitializerFunc: NewFrontendFromString,
Arguments: FrontendArguments,
}
type Frontend struct {
Enterprise bool
Version string
Src *dagger.Directory
YarnCache *dagger.CacheVolume
}
// The frontend does not have any artifact dependencies.
func (f *Frontend) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return nil, nil
}
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
func (f *Frontend) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
}
func (f *Frontend) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
panic("not implemented") // Frontend doesn't return a file
}
func (f *Frontend) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return frontend.Build(builder, f.Version), nil
}
func (f *Frontend) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (f *Frontend) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *Frontend) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (f *Frontend) Filename(ctx context.Context) (string, error) {
n := "grafana"
if f.Enterprise {
n = "grafana-enterprise"
}
// Important note: this path is only used in two ways:
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
// 2. In a map to distinguish when the same artifact is being built more than once
return filepath.Join(f.Version, n, "public"), nil
}
func (f *Frontend) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Should never be called since this isn't a File.
return nil
}
func (f *Frontend) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
// Nothing to do to verify these (for now?)
return nil
}
func NewFrontendFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
options, err := pipeline.ParseFlags(artifact, FrontendFlags)
if err != nil {
return nil, err
}
enterprise, err := options.Bool(flags.Enterprise)
if err != nil {
return nil, err
}
src, err := GrafanaDir(ctx, state, enterprise)
if err != nil {
return nil, err
}
cache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
version, err := state.String(ctx, arguments.Version)
if err != nil {
return nil, err
}
return NewFrontend(ctx, log, artifact, version, enterprise, src, cache)
}
func NewFrontend(ctx context.Context, log *slog.Logger, artifact, version string, enterprise bool, src *dagger.Directory, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: FrontendFlags,
Handler: &Frontend{
Enterprise: enterprise,
Version: version,
Src: src,
YarnCache: cache,
},
})
}
func FrontendBuilder(
ctx context.Context,
src *dagger.Directory,
cache *dagger.CacheVolume,
opts *pipeline.ArtifactContainerOpts,
) (*dagger.Container, error) {
nodeVersion, err := frontend.NodeVersion(opts.Client, src).Stdout(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get node version from source code: %w", err)
}
return frontend.Builder(opts.Client, opts.Platform, src, nodeVersion, cache), nil
}
@@ -0,0 +1,16 @@
package artifacts
import (
"context"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
func GrafanaDir(ctx context.Context, state pipeline.StateHandler, enterprise bool) (*dagger.Directory, error) {
if enterprise {
return state.Directory(ctx, arguments.EnterpriseDirectory)
}
return state.Directory(ctx, arguments.GrafanaDirectory)
}
+114
View File
@@ -0,0 +1,114 @@
package artifacts
import (
"context"
"log/slog"
"path/filepath"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
NPMPackagesFlags = flags.PackageNameFlags
NPMPackagesArguments = []pipeline.Argument{
arguments.YarnCacheDirectory,
}
)
var NPMPackagesInitializer = Initializer{
InitializerFunc: NewNPMPackagesFromString,
Arguments: NPMPackagesArguments,
}
type NPMPackages struct {
Src *dagger.Directory
YarnCache *dagger.CacheVolume
Version string
}
// The frontend does not have any artifact dependencies.
func (f *NPMPackages) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return nil, nil
}
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
func (f *NPMPackages) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
}
func (f *NPMPackages) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
panic("not implemented") // NPMPackages doesn't return a file
}
func (f *NPMPackages) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return frontend.NPMPackages(builder, opts.Client, opts.Log, f.Src, strings.TrimPrefix(f.Version, "v"))
}
func (f *NPMPackages) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (f *NPMPackages) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *NPMPackages) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *NPMPackages) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Not a file
return nil
}
func (f *NPMPackages) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
// Nothing to verify (yet?)
return nil
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (f *NPMPackages) Filename(ctx context.Context) (string, error) {
// Important note: this path is only used in two ways:
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
// 2. In a map to distinguish when the same artifact is being built more than once
return filepath.Join(f.Version, "npm-packages"), nil
}
func NewNPMPackagesFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
grafanaDir, err := GrafanaDir(ctx, state, false)
if err != nil {
return nil, err
}
cache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
version, err := state.String(ctx, arguments.Version)
if err != nil {
return nil, err
}
return NewNPMPackages(ctx, log, artifact, grafanaDir, version, cache)
}
func NewNPMPackages(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: NPMPackagesFlags,
Handler: &NPMPackages{
Src: src,
YarnCache: cache,
Version: version,
},
})
}
@@ -0,0 +1,179 @@
package artifacts
import (
"context"
"log/slog"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/fpm"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
DebArguments = TargzArguments
DebFlags = flags.JoinFlags(
TargzFlags,
[]pipeline.Flag{
flags.NightlyFlag,
},
)
)
var DebInitializer = Initializer{
InitializerFunc: NewDebFromString,
Arguments: TargzArguments,
}
// PacakgeDeb uses a built tar.gz package to create a .deb installer for debian based Linux distributions.
type Deb struct {
Name packages.Name
Version string
BuildID string
Distribution backend.Distribution
Enterprise bool
NameOverride string
Tarball *pipeline.Artifact
// Src is the source tree of Grafana. This should only be used in the verify function.
Src *dagger.Directory
YarnCache *dagger.CacheVolume
}
func (d *Deb) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Tarball,
}, nil
}
func (d *Deb) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return fpm.Builder(opts.Client), nil
}
func debVersion(version string) string {
// If there is a `+security-` modifier to the version, simply use `-`
return strings.ReplaceAll(version, "+security-", "-")
}
func (d *Deb) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
targz, err := opts.Store.File(ctx, d.Tarball)
if err != nil {
return nil, err
}
return fpm.Build(builder, fpm.BuildOpts{
Name: d.Name,
Enterprise: d.Enterprise,
Version: debVersion(d.Version),
BuildID: d.BuildID,
Distribution: d.Distribution,
PackageType: fpm.PackageTypeDeb,
NameOverride: d.NameOverride,
ConfigFiles: [][]string{
{"/src/packaging/deb/default/grafana-server", "/pkg/etc/default/grafana-server"},
{"/src/packaging/deb/init.d/grafana-server", "/pkg/etc/init.d/grafana-server"},
{"/src/packaging/deb/systemd/grafana-server.service", "/pkg/usr/lib/systemd/system/grafana-server.service"},
},
AfterInstall: "/src/packaging/deb/control/postinst",
BeforeRemove: "/src/packaging/deb/control/prerm",
Depends: []string{
"adduser",
"musl",
},
EnvFolder: "/pkg/etc/default",
ExtraArgs: []string{
"--deb-no-default-config-files",
},
}, targz), nil
}
func (d *Deb) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("not implemented") // TODO: Implement
}
func (d *Deb) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (d *Deb) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (d *Deb) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *Deb) Filename(ctx context.Context) (string, error) {
name := d.Name
if d.NameOverride != "" {
name = packages.Name(d.NameOverride)
}
return packages.FileName(name, d.Version, d.BuildID, d.Distribution, "deb")
}
func (d *Deb) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return fpm.VerifyDeb(ctx, client, file, d.Src, d.YarnCache, d.Distribution, d.Enterprise)
}
func (d *Deb) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewDebFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
tarball, err := NewTarballFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
options, err := pipeline.ParseFlags(artifact, DebFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
if err != nil {
return nil, err
}
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
debname := string(p.Name)
if nightly, _ := options.Bool(flags.Nightly); nightly {
debname += "-nightly"
}
if rpi, _ := options.Bool(flags.RPI); rpi {
debname += "-rpi"
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &Deb{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distribution: p.Distribution,
Enterprise: p.Enterprise,
Tarball: tarball,
Src: src,
YarnCache: yarnCache,
NameOverride: debname,
},
Type: pipeline.ArtifactTypeFile,
Flags: TargzFlags,
})
}
@@ -0,0 +1,265 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
DockerArguments = arguments.Join(
TargzArguments,
[]pipeline.Argument{
arguments.DockerRegistry,
arguments.DockerOrg,
arguments.AlpineImage,
arguments.UbuntuImage,
arguments.TagFormat,
arguments.UbuntuTagFormat,
arguments.BoringTagFormat,
},
)
DockerFlags = flags.JoinFlags(
TargzFlags,
flags.DockerFlags,
)
)
var DockerInitializer = Initializer{
InitializerFunc: NewDockerFromString,
Arguments: DockerArguments,
}
// PacakgeDocker uses a built tar.gz package to create a docker image from the Dockerfile in the tar.gz
type Docker struct {
Name packages.Name
Version string
BuildID string
Distro backend.Distribution
Enterprise bool
Ubuntu bool
Registry string
Repositories []string
Org string
BaseImage string
TagFormat string
Tarball *pipeline.Artifact
// Src is the Grafana source code for running e2e tests when validating.
// The grafana source should not be used for anything else when building a docker image. All files in the Docker image, including the Dockerfile, should be
// from the tar.gz file.
Src *dagger.Directory
YarnCache *dagger.CacheVolume
}
func (d *Docker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Tarball,
}, nil
}
func (d *Docker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
targz, err := opts.Store.File(ctx, d.Tarball)
if err != nil {
return nil, err
}
return docker.Builder(opts.Client, opts.Client.Host().UnixSocket("/var/run/docker.sock"), targz), nil
}
func (d *Docker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
// Unlike most other things we push to, docker image tags do not support all characters.
// Specifically, the `+` character used in the `buildmetadata` section of semver.
version := strings.ReplaceAll(d.Version, "+", "-")
tags, err := docker.Tags(d.Org, d.Registry, d.Repositories, d.TagFormat, packages.NameOpts{
Name: d.Name,
Version: version,
BuildID: d.BuildID,
Distro: d.Distro,
})
if err != nil {
return nil, err
}
buildOpts := &docker.BuildOpts{
// Tags are provided as the '-t' argument, and can include the registry domain as well as the repository.
// Docker build supports building the same image with multiple tags.
// You might want to also include a 'latest' version of the tag.
Tags: tags,
Platform: backend.Platform(d.Distro),
BuildArgs: []string{
"GRAFANA_TGZ=grafana.tar.gz",
"GO_SRC=tgz-builder",
"JS_SRC=tgz-builder",
fmt.Sprintf("BASE_IMAGE=%s", d.BaseImage),
},
}
b := docker.Build(opts.Client, builder, buildOpts)
return docker.Save(b, buildOpts), nil
}
func (d *Docker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("This artifact does not produce directories")
}
func (d *Docker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
return opts.Client.Container().From("docker").WithUnixSocket("/var/run/docker.sock", socket), nil
}
func (d *Docker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented")
}
func (d *Docker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("This artifact does not produce directories")
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *Docker) Filename(ctx context.Context) (string, error) {
ext := "docker.tar.gz"
if d.Ubuntu {
ext = "ubuntu.docker.tar.gz"
}
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
}
func (d *Docker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Currently verifying riscv64 is unsupported (because alpine and ubuntu don't have riscv64 images yet)
if _, arch := backend.OSAndArch(d.Distro); arch == "riscv64" {
return nil
}
return docker.Verify(ctx, client, file, d.Src, d.YarnCache, d.Distro)
}
func (d *Docker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
options, err := pipeline.ParseFlags(artifact, DockerFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
tarball, err := NewTarballFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
ubuntu, err := options.Bool(flags.Ubuntu)
if err != nil {
return nil, err
}
// Ubuntu Version to use as the base for the Grafana docker image (if this is a ubuntu artifact)
// This shouldn't fail if it's not set by the user, instead it'll default to 22.04 or something.
ubuntuImage, err := state.String(ctx, arguments.UbuntuImage)
if err != nil {
return nil, err
}
// Same for Alpine
alpineImage, err := state.String(ctx, arguments.AlpineImage)
if err != nil {
return nil, err
}
registry, err := state.String(ctx, arguments.DockerRegistry)
if err != nil {
return nil, err
}
org, err := state.String(ctx, arguments.DockerOrg)
if err != nil {
return nil, err
}
repos, err := options.StringSlice(flags.DockerRepositories)
if err != nil {
return nil, err
}
format, err := state.String(ctx, arguments.TagFormat)
if err != nil {
return nil, err
}
ubuntuFormat, err := state.String(ctx, arguments.UbuntuTagFormat)
if err != nil {
return nil, err
}
boringFormat, err := state.String(ctx, arguments.BoringTagFormat)
if err != nil {
return nil, err
}
base := alpineImage
if ubuntu {
format = ubuntuFormat
base = ubuntuImage
}
if p.Name == packages.PackageEnterpriseBoring {
format = boringFormat
}
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
if err != nil {
return nil, err
}
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
log.Info("initializing Docker artifact", "Org", org, "registry", registry, "repos", repos, "tag", format)
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &Docker{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distro: p.Distribution,
Enterprise: p.Enterprise,
Tarball: tarball,
Ubuntu: ubuntu,
BaseImage: base,
Registry: registry,
Org: org,
Repositories: repos,
TagFormat: format,
Src: src,
YarnCache: yarnCache,
},
Type: pipeline.ArtifactTypeFile,
Flags: DockerFlags,
})
}
@@ -0,0 +1,202 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
EntDockerArguments = arguments.Join(
DebArguments,
[]pipeline.Argument{
arguments.HGDirectory,
arguments.EntDockerRegistry,
arguments.EntDockerOrg,
arguments.EntDockerRepo,
arguments.HGTagFormat,
},
)
EntDockerFlags = flags.JoinFlags(
DebFlags,
flags.DockerFlags,
)
)
var EntDockerInitializer = Initializer{
InitializerFunc: NewEntDockerFromString,
Arguments: EntDockerArguments,
}
// EntDocker uses a built deb installer to create a docker image
type EntDocker struct {
Name packages.Name
Version string
BuildID string
Distro backend.Distribution
EntDir *dagger.Directory
// EntRegistry is the docker registry when using the `enterprise` name. (e.g. hub.docker.io)
EntRegistry string
// EntOrg is the docker org when using the `enterprise` name. (e.g. grafana)
EntOrg string
// EntOrg is the docker repo when using the `enterprise` name. (e.g. grafana-enterprise)
EntRepo string
// TagFormat is the docker tag format when using the `enterprise` name. (e.g. {{ .version }}-{{ .os }}-{{ .arch }})
TagFormat string
Deb *pipeline.Artifact
}
func (d *EntDocker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Deb,
}, nil
}
func (d *EntDocker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
deb, err := opts.Store.File(ctx, d.Deb)
if err != nil {
return nil, fmt.Errorf("error getting deb from state: %w", err)
}
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
return opts.Client.Container().From("docker").
WithUnixSocket("/var/run/docker.sock", socket).
WithMountedDirectory("/src", d.EntDir).
WithMountedFile("/src/grafana.deb", deb).
WithWorkdir("/src"), nil
}
func (d *EntDocker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
tags, err := docker.Tags(d.EntOrg, d.EntRegistry, []string{d.EntRepo}, d.TagFormat, packages.NameOpts{
Name: d.Name,
Version: d.Version,
BuildID: d.BuildID,
Distro: d.Distro,
})
if err != nil {
return nil, err
}
builder = docker.Build(opts.Client, builder, &docker.BuildOpts{
Dockerfile: "./docker/hosted-grafana-all/Dockerfile",
Tags: tags,
Target: "hosted-grafana-localenterprise",
Platform: dagger.Platform("linux/amd64"),
BuildArgs: []string{
"RELEASE_TYPE=main",
// I think because deb files use a ~ as a version delimiter of some kind, so the hg docker image uses that instead of a -
fmt.Sprintf("GRAFANA_VERSION=%s", strings.Replace(d.Version, "-", "~", 1)),
},
})
// Save the resulting docker image to the local filesystem
return builder.WithExec([]string{"docker", "save", tags[0], "-o", "enterprise.tar"}).File("enterprise.tar"), nil
}
func (d *EntDocker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("This artifact does not produce directories")
}
func (d *EntDocker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented")
}
func (d *EntDocker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented")
}
func (d *EntDocker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("This artifact does not produce directories")
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *EntDocker) Filename(ctx context.Context) (string, error) {
ext := "docker-enterprise.tar.gz"
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
}
func (d *EntDocker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (d *EntDocker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewEntDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
options, err := pipeline.ParseFlags(artifact, DockerFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
deb, err := NewDebFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
entRegistry, err := state.String(ctx, arguments.EntDockerRegistry)
if err != nil {
return nil, err
}
entOrg, err := state.String(ctx, arguments.EntDockerOrg)
if err != nil {
return nil, err
}
entRepo, err := state.String(ctx, arguments.EntDockerRepo)
if err != nil {
return nil, err
}
tagFormat, err := state.String(ctx, arguments.HGTagFormat)
if err != nil {
return nil, err
}
dir, err := state.Directory(ctx, arguments.HGDirectory)
if err != nil {
return nil, err
}
log.Info("initializing Enterprise Docker artifact", "Org", entOrg, "registry", entRegistry, "repo", entRepo, "tag", tagFormat)
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &EntDocker{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distro: p.Distribution,
EntDir: dir,
Deb: deb,
EntRegistry: entRegistry,
EntOrg: entOrg,
EntRepo: entRepo,
TagFormat: tagFormat,
},
Type: pipeline.ArtifactTypeFile,
Flags: DockerFlags,
})
}
@@ -0,0 +1,203 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
ProDockerArguments = arguments.Join(
DebArguments,
[]pipeline.Argument{
arguments.HGDirectory,
arguments.ProDockerRegistry,
arguments.ProDockerOrg,
arguments.ProDockerRepo,
arguments.HGTagFormat,
},
)
ProDockerFlags = flags.JoinFlags(
DebFlags,
flags.DockerFlags,
)
)
var ProDockerInitializer = Initializer{
InitializerFunc: NewProDockerFromString,
Arguments: ProDockerArguments,
}
// ProDocker uses a built deb installer to create a docker image
type ProDocker struct {
Name packages.Name
Version string
BuildID string
Distro backend.Distribution
ProDir *dagger.Directory
// ProRegistry is the docker registry when using the `pro` name. (e.g. hub.docker.io)
ProRegistry string
// ProOrg is the docker org when using the `pro` name. (e.g. grafana)
ProOrg string
// ProOrg is the docker repo when using the `pro` name. (e.g. grafana-pro)
ProRepo string
// TagFormat is the docker tag format when using the `pro` name. (e.g. {{ .version }}-{{ .os }}-{{ .arch }})
TagFormat string
// Building the Pro image requires a Debian package instead of a tar.gz
Deb *pipeline.Artifact
}
func (d *ProDocker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Deb,
}, nil
}
func (d *ProDocker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
deb, err := opts.Store.File(ctx, d.Deb)
if err != nil {
return nil, fmt.Errorf("error getting deb from state: %w", err)
}
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
return opts.Client.Container().From("docker").
WithUnixSocket("/var/run/docker.sock", socket).
WithMountedDirectory("/src", d.ProDir).
WithMountedFile("/src/grafana.deb", deb).
WithWorkdir("/src"), nil
}
func (d *ProDocker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
tags, err := docker.Tags(d.ProOrg, d.ProRegistry, []string{d.ProRepo}, d.TagFormat, packages.NameOpts{
Name: d.Name,
Version: d.Version,
BuildID: d.BuildID,
Distro: d.Distro,
})
if err != nil {
return nil, err
}
builder = docker.Build(opts.Client, builder, &docker.BuildOpts{
Dockerfile: "./docker/hosted-grafana-all/Dockerfile",
Tags: tags,
Target: "hosted-grafana-localpro",
Platform: dagger.Platform("linux/amd64"),
BuildArgs: []string{
"RELEASE_TYPE=main",
// I think because deb files use a ~ as a version delimiter of some kind, so the hg docker image uses that instead of a -
fmt.Sprintf("GRAFANA_VERSION=%s", strings.Replace(d.Version, "-", "~", 1)),
},
})
// Save the resulting docker image to the local filesystem
return builder.WithExec([]string{"docker", "save", tags[0], "-o", "pro.tar"}).File("pro.tar"), nil
}
func (d *ProDocker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("This artifact does not produce directories")
}
func (d *ProDocker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented")
}
func (d *ProDocker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented")
}
func (d *ProDocker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("This artifact does not produce directories")
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *ProDocker) Filename(ctx context.Context) (string, error) {
ext := "docker-pro.tar.gz"
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
}
func (d *ProDocker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (d *ProDocker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewProDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
options, err := pipeline.ParseFlags(artifact, DockerFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
deb, err := NewDebFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
proRegistry, err := state.String(ctx, arguments.ProDockerRegistry)
if err != nil {
return nil, err
}
proOrg, err := state.String(ctx, arguments.ProDockerOrg)
if err != nil {
return nil, err
}
proRepo, err := state.String(ctx, arguments.ProDockerRepo)
if err != nil {
return nil, err
}
tagFormat, err := state.String(ctx, arguments.HGTagFormat)
if err != nil {
return nil, err
}
dir, err := state.Directory(ctx, arguments.HGDirectory)
if err != nil {
return nil, err
}
log.Info("initializing Pro Docker artifact", "Org", proOrg, "registry", proRegistry, "repo", proRepo, "tag", tagFormat)
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &ProDocker{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distro: p.Distribution,
ProDir: dir,
Deb: deb,
ProRegistry: proRegistry,
ProOrg: proOrg,
ProRepo: proRepo,
TagFormat: tagFormat,
},
Type: pipeline.ArtifactTypeFile,
Flags: DockerFlags,
})
}
@@ -0,0 +1,128 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/msi"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
MSIArguments = TargzArguments
MSIFlags = TargzFlags
)
var MSIInitializer = Initializer{
InitializerFunc: NewMSIFromString,
Arguments: TargzArguments,
}
// PacakgeMSI uses a built tar.gz package to create a .exe installer for exeian based Linux distributions.
type MSI struct {
Name packages.Name
Version string
BuildID string
Distribution backend.Distribution
Enterprise bool
Grafana *dagger.Directory
Tarball *pipeline.Artifact
}
func (d *MSI) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Tarball,
}, nil
}
func (d *MSI) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return msi.Builder(opts.Client, d.Grafana), nil
}
func (d *MSI) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
targz, err := opts.Store.File(ctx, d.Tarball)
if err != nil {
return nil, err
}
return msi.Build(opts.Client, builder, targz, d.Version, d.Enterprise)
}
func (d *MSI) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
// Not a directory so this shouldn't be called
return nil, nil
}
func (d *MSI) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return nil, nil
}
func (d *MSI) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (d *MSI) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
// Not a directory so this shouldn't be called
return nil
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *MSI) Filename(ctx context.Context) (string, error) {
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distribution, "msi")
}
func (d *MSI) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (d *MSI) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewMSIFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
targz, err := NewTarballFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
options, err := pipeline.ParseFlags(artifact, MSIFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
if !backend.IsWindows(p.Distribution) {
return nil, fmt.Errorf("distribution ('%s') for exe '%s' is not a Windows distribution", string(p.Distribution), artifact)
}
src, err := GrafanaDir(ctx, state, p.Enterprise)
if err != nil {
return nil, err
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &MSI{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distribution: p.Distribution,
Enterprise: p.Enterprise,
Tarball: targz,
Grafana: src,
},
Type: pipeline.ArtifactTypeFile,
Flags: ZipFlags,
})
}
@@ -0,0 +1,242 @@
package artifacts
import (
"context"
"encoding/base64"
"fmt"
"log/slog"
"strings"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/fpm"
"github.com/grafana/grafana/pkg/build/daggerbuild/gpg"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
RPMArguments = TargzArguments
RPMFlags = flags.JoinFlags(
TargzFlags,
[]pipeline.Flag{
flags.SignFlag,
flags.NightlyFlag,
},
)
)
var RPMInitializer = Initializer{
InitializerFunc: NewRPMFromString,
Arguments: arguments.Join(
TargzArguments,
[]pipeline.Argument{
arguments.GPGPublicKey,
arguments.GPGPrivateKey,
arguments.GPGPassphrase,
},
),
}
// PacakgeRPM uses a built tar.gz package to create a .rpm installer for RHEL-ish Linux distributions.
type RPM struct {
Name packages.Name
Version string
BuildID string
Distribution backend.Distribution
Enterprise bool
Sign bool
NameOverride string
GPGPublicKey string
GPGPrivateKey string
GPGPassphrase string
Src *dagger.Directory
YarnCache *dagger.CacheVolume
Tarball *pipeline.Artifact
}
func (d *RPM) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Tarball,
}, nil
}
func (d *RPM) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return fpm.Builder(opts.Client), nil
}
func rpmVersion(version string) string {
// https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/#_snapshots
// If there's a buildmeta revision, then use that as a snapshot version
return strings.ReplaceAll(version, "+", "^")
}
func (d *RPM) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
targz, err := opts.Store.File(ctx, d.Tarball)
if err != nil {
return nil, err
}
rpm := fpm.Build(builder, fpm.BuildOpts{
Name: d.Name,
Enterprise: d.Enterprise,
Version: rpmVersion(d.Version),
BuildID: d.BuildID,
Distribution: d.Distribution,
PackageType: fpm.PackageTypeRPM,
NameOverride: d.NameOverride,
ConfigFiles: [][]string{
{"/src/packaging/rpm/sysconfig/grafana-server", "/pkg/etc/sysconfig/grafana-server"},
{"/src/packaging/rpm/systemd/grafana-server.service", "/pkg/usr/lib/systemd/system/grafana-server.service"},
},
AfterInstall: "/src/packaging/rpm/control/postinst",
Depends: []string{
"/sbin/service",
},
ExtraArgs: []string{
"--rpm-posttrans=/src/packaging/rpm/control/posttrans",
"--rpm-digest=sha256",
},
EnvFolder: "/pkg/etc/sysconfig",
}, targz)
if !d.Sign {
return rpm, nil
}
return gpg.Sign(opts.Client, rpm, gpg.GPGOpts{
GPGPublicKey: d.GPGPublicKey,
GPGPrivateKey: d.GPGPrivateKey,
GPGPassphrase: d.GPGPassphrase,
}), nil
}
func (d *RPM) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("not implemented") // TODO: Implement
}
func (d *RPM) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (d *RPM) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (d *RPM) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *RPM) Filename(ctx context.Context) (string, error) {
name := d.Name
if d.NameOverride != "" {
name = packages.Name(d.NameOverride)
}
return packages.FileName(name, d.Version, d.BuildID, d.Distribution, "rpm")
}
func (d *RPM) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
// return fpm.VerifyRpm(ctx, client, file, d.Src, d.YarnCache, d.Distribution, d.Enterprise, d.Sign, d.GPGPublicKey, d.GPGPrivateKey, d.GPGPassphrase)
}
func (d *RPM) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func NewRPMFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
tarball, err := NewTarballFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
options, err := pipeline.ParseFlags(artifact, RPMFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
sign, err := options.Bool(flags.Sign)
if err != nil {
return nil, err
}
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
if err != nil {
return nil, err
}
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
var gpgPublicKey, gpgPrivateKey, gpgPassphrase string
if sign {
pubb64, err := state.String(ctx, arguments.GPGPublicKey)
if err != nil {
return nil, err
}
pub, err := base64.StdEncoding.DecodeString(pubb64)
if err != nil {
return nil, fmt.Errorf("gpg-private-key-base64 cannot be decoded %w", err)
}
privb64, err := state.String(ctx, arguments.GPGPrivateKey)
if err != nil {
return nil, err
}
priv, err := base64.StdEncoding.DecodeString(privb64)
if err != nil {
return nil, fmt.Errorf("gpg-private-key-base64 cannot be decoded %w", err)
}
pass, err := state.String(ctx, arguments.GPGPassphrase)
if err != nil {
return nil, err
}
gpgPublicKey = string(pub)
gpgPrivateKey = string(priv)
gpgPassphrase = pass
}
rpmname := string(p.Name)
if nightly, _ := options.Bool(flags.Nightly); nightly {
rpmname += "-nightly"
}
if rpi, _ := options.Bool(flags.RPI); rpi {
rpmname += "-rpi"
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &RPM{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distribution: p.Distribution,
Enterprise: p.Enterprise,
Tarball: tarball,
Sign: sign,
Src: src,
YarnCache: yarnCache,
GPGPublicKey: gpgPublicKey,
GPGPrivateKey: gpgPrivateKey,
GPGPassphrase: gpgPassphrase,
NameOverride: rpmname,
},
Type: pipeline.ArtifactTypeFile,
Flags: TargzFlags,
})
}
@@ -0,0 +1,386 @@
package artifacts
import (
"context"
"fmt"
"log/slog"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
"github.com/grafana/grafana/pkg/build/daggerbuild/e2e"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/grafana/grafana/pkg/build/daggerbuild/targz"
)
var (
TargzArguments = []pipeline.Argument{
// Tarballs need the Build ID and version for naming the package properly.
arguments.BuildID,
arguments.Version,
// The grafanadirectory has contents like the LICENSE.txt and such that need to be included in the package
arguments.GrafanaDirectory,
// The go version used to build the backend
arguments.GoVersion,
arguments.ViceroyVersion,
arguments.YarnCacheDirectory,
}
TargzFlags = flags.JoinFlags(
flags.StdPackageFlags(),
)
)
var TargzInitializer = Initializer{
InitializerFunc: NewTarballFromString,
Arguments: TargzArguments,
}
type Tarball struct {
Distribution backend.Distribution
Name packages.Name
BuildID string
Version string
GoVersion string
Enterprise bool
Grafana *dagger.Directory
YarnCache *dagger.CacheVolume
// Dependent artifacts
Backend *pipeline.Artifact
Frontend *pipeline.Artifact
NPMPackages *pipeline.Artifact
BundledPlugins *pipeline.Artifact
Storybook *pipeline.Artifact
}
func NewTarballFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
goVersion, err := state.String(ctx, arguments.GoVersion)
if err != nil {
return nil, err
}
viceroyVersion, err := state.String(ctx, arguments.ViceroyVersion)
if err != nil {
return nil, err
}
// 1. Figure out the options that were provided as part of the artifact string.
// For example, `linux/amd64:grafana`.
options, err := pipeline.ParseFlags(artifact, TargzFlags)
if err != nil {
return nil, err
}
static, err := options.Bool(flags.Static)
if err != nil {
return nil, err
}
wireTag, err := options.String(flags.WireTag)
if err != nil {
return nil, err
}
tags, err := options.StringSlice(flags.GoTags)
if err != nil {
return nil, err
}
experiments, err := options.StringSlice(flags.GoExperiments)
if err != nil {
return nil, err
}
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
goModCache, err := state.CacheVolume(ctx, arguments.GoModCache)
if err != nil {
return nil, err
}
goBuildCache, err := state.CacheVolume(ctx, arguments.GoBuildCache)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
log.Info("Initializing tar.gz artifact with options", "name", p.Name, "build ID", p.BuildID, "version", p.Version, "distro", p.Distribution, "static", static, "enterprise", p.Enterprise)
src, err := GrafanaDir(ctx, state, p.Enterprise)
if err != nil {
return nil, err
}
return NewTarball(ctx, log, artifact, p.Distribution, p.Enterprise, p.Name, p.Version, p.BuildID, src, yarnCache, goModCache, goBuildCache, static, wireTag, tags, goVersion, viceroyVersion, experiments)
}
// NewTarball returns a properly initialized Tarball artifact.
// There are a lot of options that can affect how a tarball is built; most of which define different ways for the backend to be built.
func NewTarball(
ctx context.Context,
log *slog.Logger,
artifact string,
distro backend.Distribution,
enterprise bool,
name packages.Name,
version string,
buildID string,
src *dagger.Directory,
cache *dagger.CacheVolume,
goModCache *dagger.CacheVolume,
goBuildCache *dagger.CacheVolume,
static bool,
wireTag string,
tags []string,
goVersion string,
viceroyVersion string,
experiments []string,
) (*pipeline.Artifact, error) {
backendArtifact, err := NewBackend(ctx, log, artifact, &NewBackendOpts{
Name: name,
Version: version,
Distribution: distro,
Src: src,
Static: static,
WireTag: wireTag,
Tags: tags,
GoVersion: goVersion,
ViceroyVersion: viceroyVersion,
Experiments: experiments,
Enterprise: enterprise,
GoBuildCache: goBuildCache,
GoModCache: goModCache,
})
if err != nil {
return nil, err
}
frontendArtifact, err := NewFrontend(ctx, log, artifact, version, enterprise, src, cache)
if err != nil {
return nil, err
}
bundledPluginsArtifact, err := NewBundledPlugins(ctx, log, artifact, src, version, cache)
if err != nil {
return nil, err
}
npmArtifact, err := NewNPMPackages(ctx, log, artifact, src, version, cache)
if err != nil {
return nil, err
}
storybookArtifact, err := NewStorybook(ctx, log, artifact, src, version, cache)
if err != nil {
return nil, err
}
tarball := &Tarball{
Name: name,
Distribution: distro,
Version: version,
GoVersion: goVersion,
BuildID: buildID,
Grafana: src,
Enterprise: enterprise,
YarnCache: cache,
Backend: backendArtifact,
Frontend: frontendArtifact,
NPMPackages: npmArtifact,
BundledPlugins: bundledPluginsArtifact,
Storybook: storybookArtifact,
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: tarball,
Type: pipeline.ArtifactTypeFile,
Flags: TargzFlags,
})
}
func (t *Tarball) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
version := t.Version
container := opts.Client.Container().
From("alpine:3.18.4").
WithExec([]string{"apk", "add", "--update", "tar"}).
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf("echo %s > VERSION", version)})
return container, nil
}
func (t *Tarball) BuildFile(ctx context.Context, b *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
var (
state = opts.State
log = opts.Log
)
log.Debug("Getting grafana dir from state...")
// The Grafana directory is used for other packaged data like Dockerfile, license.txt, etc.
grafanaDir := t.Grafana
backendDir, err := opts.Store.Directory(ctx, t.Backend)
if err != nil {
return nil, err
}
frontendDir, err := opts.Store.Directory(ctx, t.Frontend)
if err != nil {
return nil, err
}
npmDir, err := opts.Store.Directory(ctx, t.NPMPackages)
if err != nil {
return nil, err
}
storybookDir, err := opts.Store.Directory(ctx, t.Storybook)
if err != nil {
return nil, err
}
pluginsDir, err := opts.Store.Directory(ctx, t.BundledPlugins)
if err != nil {
return nil, err
}
version, err := state.String(ctx, arguments.Version)
if err != nil {
return nil, err
}
files := []targz.MappedFile{
targz.NewMappedFile("VERSION", b.File("VERSION")),
targz.NewMappedFile("LICENSE", grafanaDir.File("LICENSE")),
targz.NewMappedFile("NOTICE.md", grafanaDir.File("NOTICE.md")),
targz.NewMappedFile("README.md", grafanaDir.File("README.md")),
targz.NewMappedFile("Dockerfile", grafanaDir.File("Dockerfile")),
targz.NewMappedFile("tools/zoneinfo.zip", opts.Client.Container().From(fmt.Sprintf("golang:%s", t.GoVersion)).File("/usr/local/go/lib/time/zoneinfo.zip")),
}
directories := []targz.MappedDirectory{
targz.NewMappedDir("conf", grafanaDir.Directory("conf")),
targz.NewMappedDir("docs/sources", grafanaDir.Directory("docs/sources")),
targz.NewMappedDir("packaging/deb", grafanaDir.Directory("packaging/deb")),
targz.NewMappedDir("packaging/rpm", grafanaDir.Directory("packaging/rpm")),
targz.NewMappedDir("packaging/docker", grafanaDir.Directory("packaging/docker")),
targz.NewMappedDir("packaging/wrappers", grafanaDir.Directory("packaging/wrappers")),
targz.NewMappedDir("bin", backendDir),
targz.NewMappedDir("public", frontendDir),
targz.NewMappedDir("npm-artifacts", npmDir),
targz.NewMappedDir("storybook", storybookDir),
targz.NewMappedDir("plugins-bundled", pluginsDir),
}
root := fmt.Sprintf("grafana-%s", version)
return targz.Build(
b,
&targz.Opts{
Root: root,
Files: files,
Directories: directories,
},
), nil
}
func (t *Tarball) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("not implemented") // TODO: Implement
}
func (t *Tarball) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (t *Tarball) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
return nil
}
func (t *Tarball) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
func (t *Tarball) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Currently verifying riscv64 is unsupported (because alpine and ubuntu don't have riscv64 images yet)
// windows/darwin verification may never be supported.
os, arch := backend.OSAndArch(t.Distribution)
if os != "linux" || arch == "riscv64" {
return nil
}
return verifyTarball(ctx, client, file, t.Grafana, t.YarnCache, t.Distribution, t.Enterprise)
}
func (t *Tarball) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
func (t *Tarball) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
t.Backend,
t.Frontend,
t.NPMPackages,
t.BundledPlugins,
t.Storybook,
}, nil
}
func (t *Tarball) Filename(ctx context.Context) (string, error) {
return packages.FileName(t.Name, t.Version, t.BuildID, t.Distribution, "tar.gz")
}
func verifyTarball(
ctx context.Context,
d *dagger.Client,
pkg *dagger.File,
src *dagger.Directory,
yarnCache *dagger.CacheVolume,
distro backend.Distribution,
enterprise bool,
) error {
nodeVersion, err := frontend.NodeVersion(d, src).Stdout(ctx)
if err != nil {
return fmt.Errorf("failed to get node version from source code: %w", err)
}
var (
platform = backend.Platform(distro)
archive = containers.ExtractedArchive(d, pkg)
)
// This grafana service runs in the background for the e2e tests
service := d.Container(dagger.ContainerOpts{
Platform: platform,
}).From("ubuntu:22.04").
WithExec([]string{"apt-get", "update", "-yq"}).
WithExec([]string{"apt-get", "install", "-yq", "ca-certificates"}).
WithDirectory("/src", archive).
WithMountedTemp("/tmp").
WithWorkdir("/src")
if err := e2e.ValidateLicense(ctx, service, "/src/LICENSE", enterprise); err != nil {
return err
}
svc := service.
WithEnvVariable("GF_PATHS_PLUGINS", "/tmp").
WithEnvVariable("GF_LOG_LEVEL", "error").
WithExposedPort(3000).AsService(dagger.ContainerAsServiceOpts{
Args: []string{"./bin/grafana", "server"},
})
if _, err := containers.ExitError(ctx, e2e.ValidatePackage(d, svc, src, yarnCache, nodeVersion)); err != nil {
return err
}
return nil
}
@@ -0,0 +1,113 @@
package artifacts
import (
"context"
"log/slog"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/grafana/grafana/pkg/build/daggerbuild/zip"
)
var (
ZipArguments = TargzArguments
ZipFlags = TargzFlags
)
var ZipInitializer = Initializer{
InitializerFunc: NewZipFromString,
Arguments: TargzArguments,
}
// PacakgeZip uses a built tar.gz package to create a .zip package for zipian based Linux distributions.
type Zip struct {
Name packages.Name
Version string
BuildID string
Distribution backend.Distribution
Enterprise bool
Tarball *pipeline.Artifact
}
func (d *Zip) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{
d.Tarball,
}, nil
}
func (d *Zip) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return zip.Builder(opts.Client), nil
}
func (d *Zip) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
targz, err := opts.Store.File(ctx, d.Tarball)
if err != nil {
return nil, err
}
return zip.Build(builder, targz), nil
}
func (d *Zip) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
panic("not implemented") // TODO: Implement
}
func (d *Zip) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return nil, nil
}
func (d *Zip) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
return nil
}
func (d *Zip) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
func (d *Zip) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (d *Zip) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (d *Zip) Filename(ctx context.Context) (string, error) {
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distribution, "zip")
}
func NewZipFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
tarball, err := NewTarballFromString(ctx, log, artifact, state)
if err != nil {
return nil, err
}
options, err := pipeline.ParseFlags(artifact, ZipFlags)
if err != nil {
return nil, err
}
p, err := GetPackageDetails(ctx, options, state)
if err != nil {
return nil, err
}
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Handler: &Zip{
Name: p.Name,
Version: p.Version,
BuildID: p.BuildID,
Distribution: p.Distribution,
Enterprise: p.Enterprise,
Tarball: tarball,
},
Type: pipeline.ArtifactTypeFile,
Flags: TargzFlags,
})
}
@@ -0,0 +1,52 @@
package artifacts
import (
"context"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
type PackageDetails struct {
Name packages.Name
Enterprise bool
Version string
BuildID string
Distribution backend.Distribution
}
func GetPackageDetails(ctx context.Context, options *pipeline.OptionsHandler, state pipeline.StateHandler) (PackageDetails, error) {
distro, err := options.String(flags.Distribution)
if err != nil {
return PackageDetails{}, err
}
version, err := state.String(ctx, arguments.Version)
if err != nil {
return PackageDetails{}, err
}
buildID, err := state.String(ctx, arguments.BuildID)
if err != nil {
return PackageDetails{}, err
}
name, err := options.String(flags.PackageName)
if err != nil {
return PackageDetails{}, err
}
enterprise, err := options.Bool(flags.Enterprise)
if err != nil {
return PackageDetails{}, err
}
return PackageDetails{
Name: packages.Name(name),
Version: version,
BuildID: buildID,
Distribution: backend.Distribution(distro),
Enterprise: enterprise,
}, nil
}
@@ -0,0 +1,76 @@
package artifacts
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
ErrorArtifactCollision = errors.New("artifact argument specifies two different artifacts")
ErrorDuplicateArgument = errors.New("artifact argument specifies duplicate or incompatible arguments")
ErrorNoArtifact = errors.New("could not find compatible artifact for argument string")
ErrorFlagNotFound = errors.New("no option available for the given flag")
)
func findInitializer(val string, initializers map[string]Initializer) (Initializer, error) {
c := strings.Split(val, ":")
var initializer *Initializer
// Find the artifact that is requested by `val`.
// The artifact can be defined anywhere in the artifact string. Example: `linux/amd64:grafana:targz` or `linux/amd64:grafana:targz` are the same, where targz is the artifact.
for _, v := range c {
n, ok := initializers[v]
if !ok {
continue
}
if initializer != nil {
return Initializer{}, fmt.Errorf("%s: %w", val, ErrorArtifactCollision)
}
initializer = &n
}
if initializer == nil {
return Initializer{}, fmt.Errorf("%s: %w", val, ErrorNoArtifact)
}
return *initializer, nil
}
// The ArtifactsFromStrings function should provide all of the necessary arguments to produce each artifact
// dleimited by colons. It's a repeated flag, so all permutations are stored in 1 instance of the ArtifactsFlag struct.
// Examples:
// * targz:linux/amd64 -- Will produce a "Grafana" tar.gz for "linux/amd64".
// * targz:enterprise:linux/amd64 -- Will produce a "Grafana" tar.gz for "linux/amd64".
func ArtifactsFromStrings(ctx context.Context, log *slog.Logger, a []string, registered map[string]Initializer, state pipeline.StateHandler) ([]*pipeline.Artifact, error) {
artifacts := make([]*pipeline.Artifact, len(a))
for i, v := range a {
n, err := Parse(ctx, log, v, registered, state)
if err != nil {
return nil, err
}
artifacts[i] = n
}
return artifacts, nil
}
// Parse parses the artifact string `artifact` and finds the matching initializer.
func Parse(ctx context.Context, log *slog.Logger, artifact string, initializers map[string]Initializer, state pipeline.StateHandler) (*pipeline.Artifact, error) {
artifact = strings.TrimSpace(artifact)
initializer, err := findInitializer(artifact, initializers)
if err != nil {
return nil, err
}
initializerFunc := initializer.InitializerFunc
// TODO soon, the initializer might need more info about flags
return initializerFunc(ctx, log, artifact, state)
}
@@ -0,0 +1,38 @@
package artifacts_test
// var TestArtifact struct {
// }
//
// func TestParse(t *testing.T) {
// v := "artifact:flag1:flag2"
//
// exampleArtifact := &pipeline.Artifact{
// Name: "example",
// }
//
// argument1 := &pipeline.Argument{
// Name: "argument1",
// }
//
// argument2 := &pipeline.Argument{
// Name: "argument2",
// }
//
// res, err := artifacts.Parse(v, map[string]artifacts.ArgumentOption{
// "artifact": {Artifact: exampleArtifact},
// "argument1": {Arguments: []*pipeline.Argument{argument1}},
// "argument2": {Arguments: []*pipeline.Argument{argument2}},
// })
//
// if err != nil {
// t.Fatal(err)
// }
//
// if res.Artifact.Name != exampleArtifact.Name {
// t.Fatal("Parse should return the example artifact")
// }
//
// if len(res.Arguments) != 2 {
// t.Fatal("Parse should return 2 Arguments")
// }
// }
@@ -0,0 +1,91 @@
package artifacts
import (
"context"
"log/slog"
"path"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
BundledPluginsFlags = flags.PackageNameFlags
BundledPluginsArguments = []pipeline.Argument{
arguments.YarnCacheDirectory,
}
)
type BundledPlugins struct {
Name packages.Name
Src *dagger.Directory
YarnCache *dagger.CacheVolume
Version string
}
// The frontend does not have any artifact dependencies.
func (f *BundledPlugins) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return nil, nil
}
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
func (f *BundledPlugins) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
}
func (f *BundledPlugins) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
panic("not implemented") // BundledPlugins doesn't return a file
}
func (f *BundledPlugins) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return frontend.BuildPlugins(builder), nil
}
func (f *BundledPlugins) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return nil, nil
}
func (f *BundledPlugins) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *BundledPlugins) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
return nil
}
func (f *BundledPlugins) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (f *BundledPlugins) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (f *BundledPlugins) Filename(ctx context.Context) (string, error) {
// Important note: this path is only used in two ways:
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
// 2. In a map to distinguish when the same artifact is being built more than once
return path.Join("bin", "bundled-plugins"), nil
}
func NewBundledPlugins(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cacheVolume *dagger.CacheVolume) (*pipeline.Artifact, error) {
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: BundledPluginsFlags,
Handler: &BundledPlugins{
Src: src,
YarnCache: cacheVolume,
Version: version,
},
})
}
@@ -0,0 +1,13 @@
package artifacts
import "github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
type Initializer struct {
InitializerFunc pipeline.ArtifactInitializer
Arguments []pipeline.Argument
}
type Registerer interface {
Register(string, Initializer) error
Initializers() map[string]Initializer
}
@@ -0,0 +1 @@
package artifacts
@@ -0,0 +1,113 @@
package artifacts
import (
"context"
"log/slog"
"path/filepath"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
StorybookFlags = flags.PackageNameFlags
StorybookArguments = []pipeline.Argument{
arguments.YarnCacheDirectory,
}
)
var StorybookInitializer = Initializer{
InitializerFunc: NewStorybookFromString,
Arguments: StorybookArguments,
}
type Storybook struct {
Src *dagger.Directory
YarnCache *dagger.CacheVolume
Version string
}
// The frontend does not have any artifact dependencies.
func (f *Storybook) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return nil, nil
}
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
func (f *Storybook) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
}
func (f *Storybook) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
// Not a file
return nil, nil
}
func (f *Storybook) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return frontend.Storybook(builder, f.Src, f.Version), nil
}
func (f *Storybook) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
panic("not implemented") // TODO: Implement
}
func (f *Storybook) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *Storybook) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
func (f *Storybook) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
// Not a file
return nil
}
func (f *Storybook) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
return nil
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (f *Storybook) Filename(ctx context.Context) (string, error) {
// Important note: this path is only used in two ways:
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
// 2. In a map to distinguish when the same artifact is being built more than once
return filepath.Join(f.Version, "storybook"), nil
}
func NewStorybookFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
grafanaDir, err := GrafanaDir(ctx, state, false)
if err != nil {
return nil, err
}
cacheDir, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
if err != nil {
return nil, err
}
version, err := state.String(ctx, arguments.Version)
if err != nil {
return nil, err
}
return NewStorybook(ctx, log, artifact, grafanaDir, version, cacheDir)
}
func NewStorybook(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeDirectory,
Flags: StorybookFlags,
Handler: &Storybook{
Src: src,
YarnCache: cache,
Version: version,
},
})
}
@@ -0,0 +1,31 @@
package artifacts
import (
"io"
"os"
"sync"
)
// SyncWriter wraps a writer and makes its writes synchronous, preventing multiple threads writing to the same writer
// from creating wacky looking output.
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)
@@ -0,0 +1,94 @@
package artifacts
import (
"context"
"log/slog"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
)
var (
VersionArguments = []pipeline.Argument{
arguments.GrafanaDirectory,
arguments.Version,
}
VersionFlags = TargzFlags
)
var VersionInitializer = Initializer{
InitializerFunc: NewVersionFromString,
Arguments: VersionArguments,
}
type Version struct {
// Version is embedded in the binary at build-time
Version string
}
func (b *Version) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return opts.Client.Container().WithNewFile("/VERSION", b.Version), nil
}
func (b *Version) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
return []*pipeline.Artifact{}, nil
}
func (b *Version) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
return builder.File("/VERSION"), nil
}
func (b *Version) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return nil, nil
}
func (b *Version) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
return nil, nil
}
func (b *Version) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
return nil
}
func (b *Version) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
panic("not implemented") // TODO: Implement
}
// Filename should return a deterministic file or folder name that this build will produce.
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
// also affect the filename to ensure that there are no collisions.
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
func (b *Version) Filename(ctx context.Context) (string, error) {
return "VERSION", nil
}
func (b *Version) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
return nil
}
func (b *Version) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
return nil
}
func NewVersionFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
version, err := state.String(ctx, arguments.Version)
if err != nil {
return nil, err
}
return NewVersion(ctx, log, artifact, version)
}
func NewVersion(ctx context.Context, log *slog.Logger, artifact, version string) (*pipeline.Artifact, error) {
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
ArtifactString: artifact,
Type: pipeline.ArtifactTypeFile,
Flags: VersionFlags,
Handler: &Version{
Version: version,
},
})
}
+94
View File
@@ -0,0 +1,94 @@
package backend
import (
"fmt"
"log"
"path"
"strings"
"dagger.io/dagger"
)
type LDFlag struct {
Name string
Values []string
}
func GoLDFlags(flags []LDFlag) string {
ldflags := strings.Builder{}
for _, v := range flags {
if v.Values == nil {
ldflags.WriteString(v.Name + " ")
continue
}
for _, value := range v.Values {
// For example, "-X 'main.version=v1.0.0'"
ldflags.WriteString(fmt.Sprintf(`%s \"%s\" `, v.Name, value))
}
}
return ldflags.String()
}
// GoBuildCommand returns the arguments for go build to be used in 'WithExec'.
func GoBuildCommand(output string, ldflags []LDFlag, tags []string, main string) []string {
args := []string{"go", "build",
fmt.Sprintf("-ldflags=\"%s\"", GoLDFlags(ldflags)),
fmt.Sprintf("-o=%s", output),
"-trimpath",
fmt.Sprintf("-tags=%s", strings.Join(tags, ",")),
// Go is weird and paths referring to packages within a module to be prefixed with "./".
// Otherwise, the path is assumed to be relative to $GOROOT
"./" + main,
}
return args
}
func Build(
d *dagger.Client,
builder *dagger.Container,
src *dagger.Directory,
distro Distribution,
out string,
opts *BuildOpts,
) *dagger.Directory {
vcsinfo := GetVCSInfo(src, opts.Version, opts.Enterprise)
builder = WithVCSInfo(builder, vcsinfo, opts.Enterprise)
ldflags := LDFlagsDynamic(vcsinfo)
if opts.Static {
ldflags = LDFlagsStatic(vcsinfo)
}
cmd := []string{
"grafana",
"grafana-server",
"grafana-cli",
"grafana-example-apiserver",
}
os, _ := OSAndArch(distro)
for _, v := range cmd {
// Some CLI packages such as grafana-example-apiserver don't exist in earlier Grafana Versions <10.3
// Below check skips building them as needed
pkgPath := path.Join("pkg", "cmd", v)
out := path.Join(out, v)
if os == "windows" {
out += ".exe"
}
cmd := GoBuildCommand(out, ldflags, opts.Tags, pkgPath)
script := fmt.Sprintf(`if [ -d %s ]; then %s; fi`, pkgPath, strings.Join(cmd, " "))
log.Printf("Building with command '%s'", script)
builder = builder.
WithExec([]string{"/bin/sh", "-c", script})
}
return builder.Directory(out)
}
+202
View File
@@ -0,0 +1,202 @@
package backend
import (
"errors"
"fmt"
"log/slog"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
"github.com/grafana/grafana/pkg/build/daggerbuild/golang"
)
// BuildOpts are general options that can change the way Grafana is compiled regardless of distribution.
type BuildOpts struct {
Version string
ExperimentalFlags []string
Tags []string
WireTag string
GoCacheProg string
Static bool
Enterprise bool
}
func distroOptsFunc(log *slog.Logger, distro Distribution) (DistroBuildOptsFunc, error) {
if val, ok := DistributionGoOpts[distro]; ok {
return DistroOptsLogger(log, val), nil
}
return nil, errors.New("unrecognized distribution")
}
func WithGoEnv(log *slog.Logger, container *dagger.Container, distro Distribution, opts *BuildOpts) (*dagger.Container, error) {
fn, err := distroOptsFunc(log, distro)
if err != nil {
return nil, err
}
bopts := fn(distro, opts.ExperimentalFlags, opts.Tags)
return containers.WithEnv(container, GoBuildEnv(bopts)), nil
}
func WithViceroyEnv(log *slog.Logger, container *dagger.Container, distro Distribution, opts *BuildOpts) (*dagger.Container, error) {
fn, err := distroOptsFunc(log, distro)
if err != nil {
return nil, err
}
bopts := fn(distro, opts.ExperimentalFlags, opts.Tags)
return containers.WithEnv(container, ViceroyEnv(bopts)), nil
}
func ViceroyContainer(
d *dagger.Client,
log *slog.Logger,
distro Distribution,
goVersion string,
viceroyVersion string,
opts *BuildOpts,
) (*dagger.Container, error) {
containerOpts := dagger.ContainerOpts{
Platform: "linux/amd64",
}
// Instead of directly using the `arch` variable here to substitute in the GoURL, we have to be careful with the Go releases.
// Supported releases (in the names):
// * amd64
// * armv6l
// * arm64
goURL := golang.DownloadURL(goVersion, "amd64")
container := d.Container(containerOpts).From(fmt.Sprintf("rfratto/viceroy:%s", viceroyVersion))
// Install Go manually, and install make, git, and curl from the package manager.
container = container.WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "-yq", "curl", "make", "git"}).
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf("curl -L %s | tar -C /usr/local -xzf -", goURL)}).
WithEnvVariable("PATH", "/bin:/usr/bin:/usr/local/bin:/usr/local/go/bin:/usr/osxcross/bin")
return WithViceroyEnv(log, container, distro, opts)
}
func GolangContainer(
d *dagger.Client,
log *slog.Logger,
goVersion string,
viceroyVersion string,
platform dagger.Platform,
distro Distribution,
opts *BuildOpts,
) (*dagger.Container, error) {
os, _ := OSAndArch(distro)
// Only use viceroy for all darwin and only windows/amd64
if os == "darwin" || distro == DistWindowsAMD64 {
return ViceroyContainer(d, log, distro, goVersion, viceroyVersion, opts)
}
container := golang.Container(d, platform, goVersion).
WithExec([]string{"apk", "add", "--update", "wget", "build-base", "alpine-sdk", "musl", "musl-dev", "xz"}).
WithExec([]string{"wget", "-q", "https://dl.grafana.com/ci/zig-linux-x86_64-0.11.0.tar.xz"}).
WithExec([]string{"tar", "--strip-components=1", "-C", "/", "-xf", "zig-linux-x86_64-0.11.0.tar.xz"}).
WithExec([]string{"mv", "/zig", "/bin/zig"}).
// Install the toolchain specifically for armv7 until we figure out why it's crashing w/ zig container = container.
WithExec([]string{"mkdir", "/toolchain"}).
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"})
return WithGoEnv(log, container, distro, opts)
}
func withCue(c *dagger.Container, src *dagger.Directory) *dagger.Container {
return c.
WithDirectory("/src/cue.mod", src.Directory("cue.mod")).
WithDirectory("/src/kinds", src.Directory("kinds")).
WithDirectory("/src/packages/grafana-schema", src.Directory("packages/grafana-schema"), dagger.ContainerWithDirectoryOpts{
Include: []string{"**/*.cue"},
}).
WithDirectory("/src/public/app/plugins", src.Directory("public/app/plugins"), dagger.ContainerWithDirectoryOpts{
Include: []string{"**/*.cue", "**/plugin.json"},
}).
WithFile("/src/embed.go", src.File("embed.go"))
}
// Builder returns the container that is used to build the Grafana backend binaries.
// The build container:
// * Will be based on rfratto/viceroy for Darwin or Windows
// * Will be based on golang:x.y.z-alpine for all other ditsros
// * Will download & cache the downloaded Go modules
// * Will run `make gen-go` on the provided Grafana source
// - With the linux/amd64 arch/os combination, regardless of what the requested distro is.
//
// * And will have all of the environment variables necessary to run `go build`.
func Builder(
d *dagger.Client,
log *slog.Logger,
distro Distribution,
opts *BuildOpts,
platform dagger.Platform,
src *dagger.Directory,
goVersion string,
viceroyVersion string,
goBuildCache *dagger.CacheVolume,
goModCache *dagger.CacheVolume,
) (*dagger.Container, error) {
var (
version = opts.Version
)
// for some distros we use the golang official iamge. For others, we use viceroy.
builder, err := GolangContainer(d, log, goVersion, viceroyVersion, platform, distro, opts)
if err != nil {
return nil, err
}
builder = builder.
WithMountedCache("/root/.cache/go", goBuildCache).
WithEnvVariable("GOCACHE", "/root/.cache/go")
if prog := opts.GoCacheProg; prog != "" {
builder = builder.WithEnvVariable("GOCACHEPROG", prog)
}
commitInfo := GetVCSInfo(src, version, opts.Enterprise)
builder = withCue(builder, src).
WithDirectory("/src/", src, dagger.ContainerWithDirectoryOpts{
Include: []string{"**/*.mod", "**/*.sum", "**/*.work", ".git"},
}).
WithDirectory("/src/pkg", src.WithoutDirectory("pkg/build").Directory("pkg")).
WithDirectory("/src/apps", src.Directory("apps")).
WithDirectory("/src/emails", src.Directory("emails")).
WithFile("/src/pkg/server/wire_gen.go", Wire(d, src, platform, goVersion, opts.WireTag)).
WithFile("/src/.buildinfo.commit", commitInfo.Commit).
WithWorkdir("/src")
if opts.Enterprise {
builder = builder.WithFile("/src/.buildinfo.enterprise-commit", commitInfo.EnterpriseCommit)
}
builder = golang.WithCachedGoDependencies(
builder,
goModCache,
)
return builder, nil
}
func Wire(d *dagger.Client, src *dagger.Directory, platform dagger.Platform, goVersion string, wireTag string) *dagger.File {
// withCue is only required during `make gen-go` in 9.5.x or older.
return withCue(golang.Container(d, platform, goVersion), src).
WithExec([]string{"apk", "add", "make"}).
WithDirectory("/src/", src, dagger.ContainerWithDirectoryOpts{
Include: []string{"**/*.mod", "**/*.sum", "**/*.work", ".git"},
}).
WithDirectory("/src/pkg", src.Directory("pkg")).
WithDirectory("/src/apps", src.Directory("apps")).
WithDirectory("/src/.bingo", src.Directory(".bingo")).
WithDirectory("/src/.citools", src.Directory(".citools")).
WithFile("/src/Makefile", src.File("Makefile")).
WithWorkdir("/src").
WithExec([]string{"make", "gen-go", fmt.Sprintf("WIRE_TAGS=%s", wireTag)}).
File("/src/pkg/server/wire_gen.go")
}
@@ -0,0 +1,352 @@
package backend
import (
"fmt"
"log/slog"
"strings"
"dagger.io/dagger"
)
// Distribution is a string that represents the GOOS and GOARCH environment variables joined by a "/".
// Optionally, if there is an extra argument specific to that architecture, it will be the last segment of the string.
// Examples:
// - "linux/arm/v6" = GOOS=linux, GOARCH=arm, GOARM=6
// - "linux/arm/v7" = GOOS=linux, GOARCH=arm, GOARM=7
// - "linux/amd64/v7" = GOOS=linux, GOARCH=arm, GOARM=7
// - "linux/amd64/v2" = GOOS=linux, GOARCH=amd64, GOAMD64=v2
// The list of distributions is built from the command "go tool dist list".
// While not all are used, it at least represents the possible combinations.
type Distribution string
const (
DistDarwinAMD64 Distribution = "darwin/amd64"
DistDarwinAMD64v1 Distribution = "darwin/amd64/v1"
DistDarwinAMD64v2 Distribution = "darwin/amd64/v2"
DistDarwinAMD64v3 Distribution = "darwin/amd64/v3"
DistDarwinAMD64v4 Distribution = "darwin/amd64/v4"
DistDarwinARM64 Distribution = "darwin/arm64"
)
const (
DistFreeBSD386 Distribution = "freebsd/386"
DistFreeBSD386SSE2 Distribution = "freebsd/386/sse2"
DistFreeBSD386SoftFloat Distribution = "freebsd/386/softfloat"
DistFreeBSDAMD64 Distribution = "freebsd/amd64"
DistFreeBSDAMD64v1 Distribution = "freebsd/amd64/v1"
DistFreeBSDAMD64v2 Distribution = "freebsd/amd64/v2"
DistFreeBSDAMD64v3 Distribution = "freebsd/amd64/v3"
DistFreeBSDAMD64v4 Distribution = "freebsd/amd64/v4"
DistFreeBSDARM Distribution = "freebsd/arm"
DistFreeBSDARM64 Distribution = "freebsd/arm64"
DistFreeBSDRISCV Distribution = "freebsd/riscv64"
)
const (
DistIllumosAMD64 Distribution = "illumos/amd64"
DistIllumosAMD64v1 Distribution = "illumos/amd64/v1"
DistIllumosAMD64v2 Distribution = "illumos/amd64/v2"
DistIllumosAMD64v3 Distribution = "illumos/amd64/v3"
DistIllumosAMD64v4 Distribution = "illumos/amd64/v4"
)
const (
DistLinux386 Distribution = "linux/386"
DistLinux386SSE2 Distribution = "linux/386/sse2"
DistLinux386SoftFloat Distribution = "linux/386/softfloat"
DistLinuxAMD64 Distribution = "linux/amd64"
DistLinuxAMD64v1 Distribution = "linux/amd64/v1"
DistLinuxAMD64v2 Distribution = "linux/amd64/v2"
DistLinuxAMD64v3 Distribution = "linux/amd64/v3"
DistLinuxAMD64v4 Distribution = "linux/amd64/v4"
DistLinuxAMD64Dynamic Distribution = "linux/amd64/dynamic"
DistLinuxAMD64DynamicMusl Distribution = "linux/amd64/dynamic-musl"
DistLinuxARM Distribution = "linux/arm"
DistLinuxARMv6 Distribution = "linux/arm/v6"
DistLinuxARMv7 Distribution = "linux/arm/v7"
DistLinuxARM64 Distribution = "linux/arm64"
DistLinuxARM64Dynamic Distribution = "linux/arm64/dynamic"
DistLinuxLoong64 Distribution = "linux/loong64"
DistLinuxMips Distribution = "linux/mips"
DistLinuxMips64 Distribution = "linux/mips64"
DistLinuxMips64le Distribution = "linux/mips64le"
DistLinuxMipsle Distribution = "linux/mipsle"
DistLinuxPPC64 Distribution = "linux/ppc64"
DistLinuxPPC64le Distribution = "linux/ppc64le"
DistLinuxRISCV64 Distribution = "linux/riscv64"
DistLinuxS390X Distribution = "linux/s390x"
)
const (
DistOpenBSD386 Distribution = "openbsd/386"
DistOpenBSD386SSE2 Distribution = "openbsd/386/sse2"
DistOpenBSD386SoftFLoat Distribution = "openbsd/386/softfloat"
DistOpenBSDAMD64 Distribution = "openbsd/amd64"
DistOpenBSDAMD64v1 Distribution = "openbsd/amd64/v1"
DistOpenBSDAMD64v2 Distribution = "openbsd/amd64/v2"
DistOpenBSDAMD64v3 Distribution = "openbsd/amd64/v3"
DistOpenBSDAMD64v4 Distribution = "openbsd/amd64/v4"
DistOpenBSDARM Distribution = "openbsd/arm"
DistOpenBSDARMv6 Distribution = "openbsd/arm/v6"
DistOpenBSDARMv7 Distribution = "openbsd/arm/v7"
DistOpenBSDARM64 Distribution = "openbsd/arm64"
DistOpenBSDMips64 Distribution = "openbsd/mips64"
)
const (
DistPlan9386 Distribution = "plan9/386"
DistPlan9386SSE2 Distribution = "plan9/386/sse2"
DistPlan9386SoftFloat Distribution = "plan9/386/softfloat"
DistPlan9AMD64 Distribution = "plan9/amd64"
DistPlan9AMD64v1 Distribution = "plan9/amd64/v1"
DistPlan9AMD64v2 Distribution = "plan9/amd64/v2"
DistPlan9AMD64v3 Distribution = "plan9/amd64/v3"
DistPlan9AMD64v4 Distribution = "plan9/amd64/v4"
DistPlan9ARM Distribution = "plan9/arm/v6"
DistPlan9ARMv6 Distribution = "plan9/arm/v6"
DistPlan9ARMv7 Distribution = "plan9/arm/v7"
)
const (
DistSolarisAMD64 Distribution = "solaris/amd64"
DistSolarisAMD64v1 Distribution = "solaris/amd64/v1"
DistSolarisAMD64v2 Distribution = "solaris/amd64/v2"
DistSolarisAMD64v3 Distribution = "solaris/amd64/v3"
DistSolarisAMD64v4 Distribution = "solaris/amd64/v4"
)
const (
DistWindows386 Distribution = "windows/386"
DistWindows386SSE2 Distribution = "windows/386/sse2"
DistWindows386SoftFloat Distribution = "windows/386/softfloat"
DistWindowsAMD64 Distribution = "windows/amd64"
DistWindowsAMD64v1 Distribution = "windows/amd64/v1"
DistWindowsAMD64v2 Distribution = "windows/amd64/v2"
DistWindowsAMD64v3 Distribution = "windows/amd64/v3"
DistWindowsAMD64v4 Distribution = "windows/amd64/v4"
DistWindowsARM Distribution = "windows/arm"
DistWindowsARMv6 Distribution = "windows/arm/v6"
DistWindowsARMv7 Distribution = "windows/arm/v7"
DistWindowsARM64 Distribution = "windows/arm64"
)
func IsWindows(d Distribution) bool {
return strings.Split(string(d), "/")[0] == "windows"
}
func OSAndArch(d Distribution) (string, string) {
p := strings.Split(string(d), "/")
if len(p) < 2 {
return string(d), ""
}
return p[0], p[1]
}
func FullArch(d Distribution) string {
p := strings.Split(string(d), "/")
return strings.Join(p[1:], "/")
}
func ArchVersion(d Distribution) string {
p := strings.Split(string(d), "/")
if len(p) < 3 {
return ""
}
// ARM specifically must be specified without a 'v' prefix.
// GOAMD64, however, expects a 'v' prefix.
// Specifying the ARM version with the 'v' prefix and without is supported in Docker's platform argument, however.
if arch := p[1]; arch == "arm" {
return strings.TrimPrefix(p[2], "v")
}
return p[2]
}
func PackageArch(d Distribution) string {
_, arch := OSAndArch(d)
if arch == "arm" {
return "armhf"
}
return arch
}
// From the distribution, try to assume the docker platform (used in Docker's --platform argument or the (dagger.ContainerOpts).Platform field
func Platform(d Distribution) dagger.Platform {
p := strings.ReplaceAll(string(d), "/dynamic-musl", "")
p = strings.ReplaceAll(p, "/dynamic", "")
p = strings.ReplaceAll(p, "arm/v6", "arm/v7")
// for now let's just try to use the distro name as the platform and see if that works...
return dagger.Platform(p)
}
type DistroBuildOptsFunc func(distro Distribution, experiments []string, tags []string) *GoBuildOpts
func LDFlagsStatic(info *VCSInfo) []LDFlag {
return []LDFlag{
{"-w", nil},
{"-s", nil},
{"-X", info.X()},
{"-linkmode=external", nil},
{"-extldflags=-static", nil},
}
}
func LDFlagsDynamic(info *VCSInfo) []LDFlag {
return []LDFlag{
{"-X", info.X()},
}
}
func ZigCC(distro Distribution) string {
target, ok := ZigTargets[distro]
if !ok {
target = "x86_64-linux-musl" // best guess? should probably retun an error but i don't want to
}
return fmt.Sprintf("zig cc -target %s", target)
}
func ZigCXX(distro Distribution) string {
target, ok := ZigTargets[distro]
if !ok {
target = "x86_64-linux-musl" // best guess? should probably retun an error but i don't want to
}
return fmt.Sprintf("zig c++ -target %s", target)
}
var DefaultBuildOpts = func(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
os, arch := OSAndArch(distro)
return &GoBuildOpts{
CC: ZigCC(distro),
CXX: ZigCXX(distro),
ExperimentalFlags: experiments,
OS: os,
Arch: arch,
CGOEnabled: true,
}
}
// BuildOptsStaticARM builds Grafana statically for the armv6/v7 architectures (not aarch64/arm64)
func BuildOptsStaticARM(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
var (
os, _ = OSAndArch(distro)
arm = ArchVersion(distro)
)
return &GoBuildOpts{
CC: "/toolchain/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-gcc",
CXX: "/toolchain/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-cpp",
ExperimentalFlags: experiments,
OS: os,
Arch: "arm",
GoARM: GoARM(arm),
CGOEnabled: true,
}
}
// BuildOptsStaticS390X builds Grafana statically for the s390x arch
func BuildOptsStaticS390X(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
var (
os, _ = OSAndArch(distro)
)
return &GoBuildOpts{
CC: "/toolchain/s390x-linux-musl-cross/bin/s390x-linux-musl-gcc",
CXX: "/toolchain/s390x-linux-musl-cross/bin/s390x-linux-musl-cpp",
ExperimentalFlags: experiments,
OS: os,
Arch: "s390x",
CGOEnabled: true,
}
}
func StdZigBuildOpts(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
var (
os, arch = OSAndArch(distro)
)
return &GoBuildOpts{
CC: ZigCC(distro),
CXX: ZigCXX(distro),
ExperimentalFlags: experiments,
OS: os,
Arch: arch,
CGOEnabled: true,
}
}
func BuildOptsWithoutZig(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
var (
os, arch = OSAndArch(distro)
)
return &GoBuildOpts{
ExperimentalFlags: experiments,
OS: os,
Arch: arch,
CGOEnabled: true,
}
}
func ViceroyBuildOpts(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
var (
os, arch = OSAndArch(distro)
)
return &GoBuildOpts{
CC: "viceroycc",
ExperimentalFlags: experiments,
OS: os,
Arch: arch,
CGOEnabled: true,
}
}
var ZigTargets = map[Distribution]string{
DistLinuxAMD64: "x86_64-linux-musl",
DistLinuxAMD64Dynamic: "x86_64-linux-gnu",
DistLinuxAMD64DynamicMusl: "x86_64-linux-musl",
DistLinuxARM64: "aarch64-linux-musl",
DistLinuxARM64Dynamic: "aarch64-linux-musl",
DistLinuxARM: "arm-linux-musleabihf",
DistLinuxARMv6: "arm-linux-musleabihf",
DistLinuxARMv7: "arm-linux-musleabihf",
DistLinuxRISCV64: "riscv64-linux-musl",
DistWindowsAMD64: "x86_64-windows-gnu",
DistWindowsARM64: "aarch64-windows-gnu",
}
var DistributionGoOpts = map[Distribution]DistroBuildOptsFunc{
// The Linux distros should all have an equivalent zig target in the ZigTargets map
DistLinuxARM: BuildOptsStaticARM,
DistLinuxARMv6: BuildOptsStaticARM,
DistLinuxARMv7: BuildOptsStaticARM,
DistLinuxS390X: BuildOptsStaticS390X,
DistLinuxARM64: StdZigBuildOpts,
DistLinuxARM64Dynamic: StdZigBuildOpts,
DistLinuxAMD64: StdZigBuildOpts,
DistLinuxAMD64Dynamic: StdZigBuildOpts,
DistPlan9AMD64: StdZigBuildOpts,
DistLinuxRISCV64: StdZigBuildOpts,
// 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.
DistWindowsAMD64: ViceroyBuildOpts,
DistWindowsARM64: StdZigBuildOpts,
DistDarwinAMD64: ViceroyBuildOpts,
DistDarwinARM64: ViceroyBuildOpts,
DistLinuxAMD64DynamicMusl: BuildOptsWithoutZig,
}
func DistroOptsLogger(log *slog.Logger, fn DistroBuildOptsFunc) func(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
return func(distro Distribution, experiments []string, tags []string) *GoBuildOpts {
opts := fn(distro, experiments, tags)
log.Debug("Building with options", "distribution", distro, "experiments", experiments, "tags", tags, "os", opts.OS, "arch", opts.Arch, "arm", opts.GoARM, "CGO", opts.CGOEnabled, "386", opts.Go386, "CC", opts.CC, "CXX", opts.CXX)
return opts
}
}
+2
View File
@@ -0,0 +1,2 @@
// Package backend holds the functions that create containers, files, and directories for building Grafana's backend binaries.
package backend
+140
View File
@@ -0,0 +1,140 @@
package backend
import (
"strings"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
)
type (
BuildMode string
GoARM string
GoAMD64 string
Go386 string
LibC int
)
const (
BuildModeDefault BuildMode = "default"
BuildModeExe BuildMode = "exe"
)
const (
GOARM5 GoARM = "5"
GOARM6 GoARM = "6"
GOARM7 GoARM = "7"
)
const (
Go386SSE2 Go386 = "sse2"
Go386SoftFloat Go386 = "softfloat"
)
const (
Musl LibC = iota
GLibC
)
type GoBuildOpts struct {
// OS is value supplied to the GOOS environment variable
OS string
// Arch is value supplied to the GOARCH environment variable
Arch string
// ExperimentalFlags are Go build-time feature flags in the "GOEXPERIMENT" environment variable that enable experimental features.
ExperimentalFlags []string
// CGOEnabled defines whether or not the CGO_ENABLED flag is set.
CGOEnabled bool
// GOARM: For GOARCH=arm, the ARM architecture for which to compile.
// Valid values are 5, 6, 7.
GoARM GoARM
// GO386: For GOARCH=386, how to implement floating point instructions.
// Valid values are sse2 (default), softfloat.
Go386 Go386
// CC is the command to use to compile C code when CGO is enabled. (Sets the "CC" environment variable)
CC string
// CXX is the command to use to compile C++ code when CGO is enabled. (Sets the "CXX" environment variable)
CXX string
}
// GoBuildEnv returns the environment variables that must be set for a 'go build' command given the provided 'GoBuildOpts'.
func GoBuildEnv(opts *GoBuildOpts) []containers.Env {
var (
os = opts.OS
arch = opts.Arch
)
env := []containers.Env{containers.EnvVar("GOOS", os), containers.EnvVar("GOARCH", arch)}
if arch == "arm" {
env = append(env, containers.EnvVar("GOARM", string(opts.GoARM)))
}
if opts.CGOEnabled {
env = append(env, containers.EnvVar("GOARM", string(opts.GoARM)))
env = append(env, containers.EnvVar("CGO_ENABLED", "1"))
// https://github.com/mattn/go-sqlite3/issues/1164#issuecomment-1635253695
env = append(env, containers.EnvVar("CGO_CFLAGS", "-D_LARGEFILE64_SOURCE"))
} else {
env = append(env, containers.EnvVar("CGO_ENABLED", "0"))
}
if opts.ExperimentalFlags != nil {
env = append(env, containers.EnvVar("GOEXPERIMENT", strings.Join(opts.ExperimentalFlags, ",")))
}
if opts.CC != "" {
env = append(env, containers.EnvVar("CC", opts.CC))
}
if opts.CXX != "" {
env = append(env, containers.EnvVar("CXX", opts.CXX))
}
return env
}
// ViceroyEnv returns the environment variables that must be set for a 'go build' command given the provided 'GoBuildOpts'.
func ViceroyEnv(opts *GoBuildOpts) []containers.Env {
var (
os = opts.OS
arch = opts.Arch
)
env := []containers.Env{
containers.EnvVar("VICEROYOS", os),
containers.EnvVar("GOOS", os),
containers.EnvVar("VICEROYARCH", arch),
containers.EnvVar("GOARCH", arch),
}
if arch == "arm" {
env = append(env, containers.EnvVar("VICEROYARM", string(opts.GoARM)))
}
if opts.CGOEnabled {
env = append(env, containers.EnvVar("CGO_ENABLED", "1"))
// https://github.com/mattn/go-sqlite3/issues/1164#issuecomment-1635253695
env = append(env, containers.EnvVar("CGO_CFLAGS", "-D_LARGEFILE64_SOURCE"))
} else {
env = append(env, containers.EnvVar("CGO_ENABLED", "0"))
}
if opts.ExperimentalFlags != nil {
env = append(env, containers.EnvVar("GOEXPERIMENT", strings.Join(opts.ExperimentalFlags, ",")))
}
if opts.CC != "" {
env = append(env, containers.EnvVar("CC", "viceroycc"))
}
return env
}
+56
View File
@@ -0,0 +1,56 @@
package backend
import (
"fmt"
"strings"
"dagger.io/dagger"
)
type VCSInfo struct {
Version string
Commit *dagger.File
EnterpriseCommit *dagger.File
Branch *dagger.File
}
func WithVCSInfo(c *dagger.Container, info *VCSInfo, enterprise bool) *dagger.Container {
c = c.
WithFile(".buildinfo.commit", info.Commit).
WithFile(".buildinfo.branch", info.Branch)
if enterprise {
return c.WithFile(".buildinfo.enterprise-commit", info.EnterpriseCommit)
}
return c
}
// VCSInfo gets the VCS data from the directory 'src', writes them to a file on the given container, and returns the files which can be used in other containers.
func GetVCSInfo(src *dagger.Directory, version string, enterprise bool) *VCSInfo {
info := &VCSInfo{
Version: version,
Commit: src.File(".buildinfo.commit"),
Branch: src.File(".buildinfo.branch"),
}
if enterprise {
info.EnterpriseCommit = src.File(".buildinfo.enterprise-commit")
}
return info
}
func (v *VCSInfo) X() []string {
flags := []string{
fmt.Sprintf("main.version=%s", strings.TrimPrefix(v.Version, "v")),
`main.commit=$(cat ./.buildinfo.commit)`,
`main.buildBranch=$(cat ./.buildinfo.branch)`,
}
if v.EnterpriseCommit != nil {
flags = append(flags, `main.enterpriseCommit=$(cat ./.buildinfo.enterprise-commit)`)
}
return flags
}
+10
View File
@@ -0,0 +1,10 @@
package cliutil
type CLIContext interface {
Bool(string) bool
String(string) string
Set(string, string) error
StringSlice(string) []string
Path(string) string
Int64(string) int64
}
+65
View File
@@ -0,0 +1,65 @@
package cmd
import (
"github.com/grafana/grafana/pkg/build/daggerbuild/artifacts"
"github.com/urfave/cli/v2"
)
type CLI struct {
artifacts map[string]artifacts.Initializer
}
func (c *CLI) ArtifactsCommand() *cli.Command {
f := artifacts.ArtifactFlags(c)
flags := make([]cli.Flag, len(f))
copy(flags, f)
return &cli.Command{
Name: "artifacts",
Usage: "Use this command to declare a list of artifacts to be built and/or published",
Flags: flags,
Action: artifacts.Command(c),
}
}
func (c *CLI) App() *cli.App {
return &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,
},
}
}
func (c *CLI) Register(flag string, a artifacts.Initializer) error {
c.artifacts[flag] = a
return nil
}
func (c *CLI) Initializers() map[string]artifacts.Initializer {
return c.artifacts
}
var GlobalCLI = &CLI{
artifacts: map[string]artifacts.Initializer{},
}
+21
View File
@@ -0,0 +1,21 @@
package cmd
import (
"github.com/grafana/grafana/pkg/build/daggerbuild/artifacts"
)
var Artifacts = map[string]artifacts.Initializer{
"backend": artifacts.BackendInitializer,
"frontend": artifacts.FrontendInitializer,
"npm": artifacts.NPMPackagesInitializer,
"targz": artifacts.TargzInitializer,
"zip": artifacts.ZipInitializer,
"deb": artifacts.DebInitializer,
"rpm": artifacts.RPMInitializer,
"docker": artifacts.DockerInitializer,
"docker-pro": artifacts.ProDockerInitializer,
"docker-enterprise": artifacts.EntDockerInitializer,
"storybook": artifacts.StorybookInitializer,
"msi": artifacts.MSIInitializer,
"version": artifacts.VersionInitializer,
}
@@ -0,0 +1,19 @@
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,
),
}
+259
View File
@@ -0,0 +1,259 @@
package cmd
import (
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd/flags"
"github.com/urfave/cli/v2"
)
var FlagPackage = &cli.StringSliceFlag{
Name: "package",
Usage: "Path to a grafana.tar.gz package used as input. This command will process each package provided separately and produce an equal number of applicable outputs",
}
var FlagNameOverride = &cli.StringFlag{
Name: "name",
Usage: "Overrides any calculation for name in the package with the value provided here",
}
// PackageInputFlags are used for commands that require a grafana package as input.
// These commands are exclusively used outside of the CI process and are typically used in the CD process where a grafana.tar.gz has already been created.
var PackageInputFlags = []cli.Flag{
FlagPackage,
FlagNameOverride,
}
// GCPFlags are used in commands that need to authenticate with Google Cloud platform using the Google Cloud SDK
var GCPFlags = []cli.Flag{
&cli.StringFlag{
Name: "gcp-service-account-key-base64",
Usage: "Provides a service-account key encoded in base64 to use to authenticate with the Google Cloud SDK",
},
&cli.StringFlag{
Name: "gcp-service-account-key",
Usage: "Provides a service-account keyfile to use to authenticate with the Google Cloud SDK. If not provided or is empty, then $XDG_CONFIG_HOME/gcloud will be mounted in the container",
},
}
// NPMFlags are used in commands that need to authenticate with package registries to publish NPM packages
var NPMFlags = []cli.Flag{
&cli.StringFlag{
Name: "registry",
Usage: "The package registry to publish packages",
Value: "registry.npmjs.org",
},
&cli.StringFlag{
Name: "token",
Usage: "Provides a token to use to authenticate with the package registry",
Required: true,
},
&cli.StringSliceFlag{
Name: "tag",
Usage: "Provides the tags to use when publishing packages",
Required: true,
},
}
// PublishFlags are flags that are used in commands that create artifacts.
// Anything that creates an artifact should have the option to specify a local folder destination or a remote destination.
var PublishFlags = flags.PublishFlags
// GrafanaFlags are flags that are required when working with the grafana source code.
var GrafanaFlags = []cli.Flag{
&cli.BoolFlag{
Name: "grafana",
Usage: "If set, initialize Grafana",
Required: false,
Value: true,
},
&cli.StringFlag{
Name: "grafana-dir",
Usage: "Local Grafana dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "grafana-repo",
Usage: "Grafana repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana.git",
},
&cli.StringFlag{
Name: "grafana-ref",
Usage: "Grafana ref to clone, not valid if --grafana-dir is set",
Required: false,
Value: "main",
},
&cli.BoolFlag{
Name: "enterprise",
Usage: "If set, initialize Grafana Enterprise",
Value: false,
},
&cli.StringFlag{
Name: "enterprise-dir",
Usage: "Local Grafana Enterprise dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "enterprise-repo",
Usage: "Grafana Enterprise repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana-enterprise.git",
},
&cli.StringFlag{
Name: "enterprise-ref",
Usage: "Grafana Enterprise ref to clone, not valid if --enterprise-dir is set",
Required: false,
Value: "main",
},
&cli.StringFlag{
Name: "github-token",
Usage: "Github token to use for git cloning, by default will be pulled from GitHub",
Required: false,
},
&cli.StringSliceFlag{
Name: "env",
Aliases: []string{"e"},
Usage: "Set a build-time environment variable using the same syntax as 'docker run'. Example: `--env=GOOS=linux --env=GOARCH=amd64`",
},
&cli.StringSliceFlag{
Name: "go-tags",
Usage: "Sets the go `-tags` flag when compiling the backend",
},
&cli.StringFlag{
Name: "go-version",
Usage: "The version of Go to be used for building the Grafana backend",
Required: false,
Value: "1.21.8",
},
&cli.StringFlag{
Name: "yarn-cache",
Usage: "If there is a yarn cache directory, then mount that when running 'yarn install' instead of creating a cache directory",
},
}
// DockerFlags are used when producing docker images.
var DockerFlags = []cli.Flag{
arguments.DockerRegistryFlag,
arguments.AlpineImageFlag,
arguments.UbuntuImageFlag,
arguments.TagFormatFlag,
arguments.UbuntuTagFormatFlag,
arguments.DockerOrgFlag,
}
var DockerPublishFlags = []cli.Flag{
&cli.StringFlag{
Name: "username",
Usage: "The username to login to the docker registry when publishing images",
Required: true,
},
&cli.StringFlag{
Name: "password",
Usage: "The password to login to the docker registry when publishing images",
Required: true,
},
&cli.StringFlag{
Name: "repo",
Usage: "Overrides the repository of the images",
},
&cli.BoolFlag{
Name: "latest",
Usage: "Tags the published images as latest",
},
}
var FlagDistros = &cli.StringSliceFlag{
Name: "distro",
Usage: "See the list of distributions with 'go tool dist list'. For variations of the same distribution, like 'armv6' or 'armv7', append an extra path part. Example: 'linux/arm/v6', or 'linux/amd64/v3'",
Value: cli.NewStringSlice(flags.DefaultDistros...),
}
var ConcurrencyFlags = flags.ConcurrencyFlags
// PackageFlags are flags that are used when building packages or similar artifacts (like binaries) for different distributions
// from the grafana source code.
var PackageFlags = []cli.Flag{
FlagDistros,
&cli.StringFlag{
Name: "edition",
Usage: "Simply alters the naming of the '.tar.gz' package. The string set will override the '-{flavor}' part of the package name",
},
}
var ProImageFlags = []cli.Flag{
&cli.StringFlag{
Name: "github-token",
Usage: "Github token to use for git cloning, by default will be pulled from GitHub",
Required: false,
},
&cli.StringFlag{
Name: "grafana-repo",
Usage: "The Grafana repository",
Required: false,
Value: "https://github.com/grafana/grafana",
},
&cli.StringFlag{
Name: "grafana-version",
Usage: "The Grafana version",
Required: true,
},
&cli.StringFlag{
Name: "repo",
Usage: "The docker image repo",
Value: "hosted-grafana-pro",
Required: false,
},
&cli.StringFlag{
Name: "image-tag",
Usage: "The docker image tag",
Required: true,
},
&cli.StringFlag{
Name: "release-type",
Usage: "The Grafana release type",
Value: "prerelease",
},
&cli.BoolFlag{
Name: "push",
Usage: "Push the built image to the container registry",
Value: false,
},
&cli.StringFlag{
Name: "registry",
Usage: "The container registry that the image should be pushed to. Required if --push is set.",
Value: "docker.io",
},
}
var GCOMFlags = []cli.Flag{
&cli.StringFlag{
Name: "api-url",
Usage: "API URL used in requests to grafana.com",
Value: "https://grafana.com/api/grafana",
},
&cli.StringFlag{
Name: "api-key",
Usage: "API Key used in requests to grafana.com",
Required: true,
},
&cli.StringFlag{
Name: "download-url",
Usage: "URL used to download packages from grafana.com",
Required: true,
},
&cli.BoolFlag{
Name: "beta",
Usage: "Use when publishing a beta version",
},
&cli.BoolFlag{
Name: "nightly",
Usage: "Use when publishing a nightly version",
},
}
// JoinFlags combines several slices of flags into one slice of flags.
var JoinFlags = flags.Join
func JoinFlagsWithDefault(f ...[]cli.Flag) []cli.Flag {
// Kind of gross but ensures that DefaultFlags are registered before any others.
return JoinFlags(append([][]cli.Flag{flags.DefaultFlags}, f...)...)
}
@@ -0,0 +1,16 @@
package flags
import (
"runtime"
"github.com/urfave/cli/v2"
)
var ConcurrencyFlags = []cli.Flag{
&cli.Int64Flag{
Name: "parallel",
Usage: "The number of parallel pipelines to run. This can be particularly useful for building for multiple distributions at the same time",
DefaultText: "Just like with 'go test', this defaults to GOMAXPROCS",
Value: int64(runtime.GOMAXPROCS(0)),
},
}
@@ -0,0 +1,15 @@
package flags
import "github.com/urfave/cli/v2"
var Verbose = &cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Increase log verbosity. WARNING: This setting could potentially log sensitive data",
Value: false,
}
var DefaultFlags = []cli.Flag{
Platform,
Verbose,
}
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/amd64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/amd64"
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/arm64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/arm64"
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/amd64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/amd64"
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/arm64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/arm64"
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/amd64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/amd64"
@@ -0,0 +1,7 @@
package flags
// DefaultDistros are distributions that can quickly be built in an ideal scenario for the operating system on the above build tag.
var DefaultDistros = []string{"linux/arm64"}
// DefaultPlatform is the docker platform that will natively / most efficiently run on the OS/arch filtered by the above tag.
var DefaultPlatform = "linux/arm64"
+12
View File
@@ -0,0 +1,12 @@
package flags
import "github.com/urfave/cli/v2"
func Join(f ...[]cli.Flag) []cli.Flag {
flags := []cli.Flag{}
for _, v := range f {
flags = append(flags, v...)
}
return flags
}
@@ -0,0 +1,9 @@
package flags
import "github.com/urfave/cli/v2"
var Platform = &cli.StringFlag{
Name: "platform",
Usage: "The buildkit / dagger platform to run containers when building the backend",
Value: DefaultPlatform,
}
@@ -0,0 +1,16 @@
package flags
import "github.com/urfave/cli/v2"
var PublishFlags = []cli.Flag{
&cli.StringFlag{
Name: "destination",
Usage: "full URL to upload the artifacts to (examples: '/tmp/package.tar.gz', 'file://package.tar.gz', 'file:///tmp/package.tar.gz', 'gs://bucket/grafana/')",
Aliases: []string{"d"},
Value: "dist",
},
&cli.BoolFlag{
Name: "checksum",
Usage: "When enabled, also creates a `.sha256' checksum file in the destination that matches the checksum of the artifact(s) produced",
},
}
+11
View File
@@ -0,0 +1,11 @@
package cmd
import (
"github.com/urfave/cli/v2"
)
var GCOMCommand = &cli.Command{
Name: "gcom",
Description: "Executes requests to grafana.com",
Subcommands: []*cli.Command{GCOMPublishCommand},
}
+18
View File
@@ -0,0 +1,18 @@
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,
),
}
+47
View File
@@ -0,0 +1,47 @@
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
}
}
+18
View File
@@ -0,0 +1,18 @@
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,
),
}
@@ -0,0 +1,18 @@
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,
),
}
+13
View File
@@ -0,0 +1,13 @@
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),
}
+2
View File
@@ -0,0 +1,2 @@
// package containers holds functions to make it easier to work with dagger containers.
package containers
@@ -0,0 +1,29 @@
package containers
import (
"context"
"errors"
"fmt"
"dagger.io/dagger"
)
var (
ErrorNonZero = errors.New("container exited with non-zero exit code")
)
// ExitError functionally replaces '(*container).ExitCode' in a more usable way.
// It will return an error with the container's stderr and stdout if the exit code is not zero.
func ExitError(ctx context.Context, container *dagger.Container) (*dagger.Container, error) {
container, err := container.Sync(ctx)
if err == nil {
return container, nil
}
var e *dagger.ExecError
if errors.As(err, &e) {
return container, fmt.Errorf("%w\nstdout: %s\nstderr: %s", ErrorNonZero, e.Stdout, e.Stderr)
}
return container, err
}
@@ -0,0 +1,13 @@
package containers
import "dagger.io/dagger"
// ExtractedActive returns a directory that holds an extracted tar.gz
func ExtractedArchive(d *dagger.Client, f *dagger.File) *dagger.Directory {
return d.Container().From("busybox").
// Workaround for now (maybe unnecessary?): set a FILE environment variable so that we don't accidentally cache
WithFile("/src/archive.tar.gz", f).
WithExec([]string{"mkdir", "-p", "/src/archive"}).
WithExec([]string{"tar", "--strip-components=1", "-xzf", "/src/archive.tar.gz", "-C", "/src/archive"}).
Directory("/src/archive")
}
@@ -0,0 +1 @@
package containers
@@ -0,0 +1,132 @@
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
}
@@ -0,0 +1,16 @@
package containers
import "github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
// GCPOpts are options used when using Google Cloud Platform / the Google Cloud SDK.
type GCPOpts struct {
ServiceAccountKey string
ServiceAccountKeyBase64 string
}
func GCPOptsFromFlags(c cliutil.CLIContext) *GCPOpts {
return &GCPOpts{
ServiceAccountKeyBase64: c.String("gcp-service-account-key-base64"),
ServiceAccountKey: c.String("gcp-service-account-key"),
}
}
@@ -0,0 +1,42 @@
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"),
}
}
@@ -0,0 +1,73 @@
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)
}
@@ -0,0 +1 @@
package containers
@@ -0,0 +1,38 @@
package containers
import (
"errors"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
)
// PublishOpts fields are selectively used based on the protocol field of the destination.
// Be sure to fill out the applicable fields (or all of them) when calling a 'Publish' func.
type PublishOpts struct {
// Destination is any URL to publish an artifact(s) to.
// Examples:
// * '/tmp/package.tar.gz'
// * 'file:///tmp/package.tar.gz'
// * 'gcs://bucket/package.tar.gz'
Destination string
// Checksum defines if the PublishFile function should also produce / publish a checksum of the given `*dagger.File'
Checksum bool
}
func PublishOptsFromFlags(c cliutil.CLIContext) *PublishOpts {
return &PublishOpts{
Destination: c.String("destination"),
Checksum: c.Bool("checksum"),
}
}
var ErrorUnrecognizedScheme = errors.New("unrecognized scheme")
type PublishFileOpts struct {
File *dagger.File
PublishOpts *PublishOpts
GCPOpts *GCPOpts
Destination string
}
@@ -0,0 +1,62 @@
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
}
@@ -0,0 +1,13 @@
package containers
import (
"dagger.io/dagger"
)
// Sha256 returns a dagger.File which contains the sha256 for the provided file.
func Sha256(d *dagger.Client, file *dagger.File) *dagger.File {
return d.Container().From("busybox").
WithFile("/src/file", file).
WithExec([]string{"/bin/sh", "-c", "sha256sum /src/file | awk '{print $1}' > /src/file.sha256"}).
File("/src/file.sha256")
}
@@ -0,0 +1,11 @@
package containers
// func BackendTestShort(d *dagger.Client, platform dagger.Platform, dir *dagger.Directory) *dagger.Container {
// return GrafanaContainer(d, platform, GetGoImageAlpine("1.21.0"), dir).
// WithExec([]string{"go", "test", "-tags", "requires_buildifer", "-short", "-covermode", "atomic", "-timeout", "5m", "./pkg/..."})
// }
//
// func BackendTestIntegration(d *dagger.Client, platform dagger.Platform, dir *dagger.Directory) *dagger.Container {
// return GrafanaContainer(d, platform, GetGoImageAlpine("1.21.0"), dir).
// WithExec([]string{"go", "test", "-run", "Integration", "-covermode", "atomic", "-timeout", "5m", "./pkg/..."})
// }
@@ -0,0 +1,24 @@
package containers
import (
"context"
"fmt"
"strings"
"dagger.io/dagger"
)
// GetJSONValue gets the value of a JSON field from a JSON file in the 'src' directory.
func GetJSONValue(ctx context.Context, d *dagger.Client, src *dagger.Directory, file string, field string) (string, error) {
c := d.Container().From("alpine").
WithExec([]string{"apk", "--update", "add", "jq"}).
WithMountedDirectory("/src", src).
WithWorkdir("/src").
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf("cat %s | jq -r .%s", file, field)})
if stdout, err := c.Stdout(ctx); err == nil {
return strings.TrimSpace(stdout), nil
}
return c.Stderr(ctx)
}
@@ -0,0 +1 @@
package containers
@@ -0,0 +1,23 @@
package containers
import (
"dagger.io/dagger"
)
type Env struct {
Name string
Value string
}
func EnvVar(name, value string) Env {
return Env{Name: name, Value: value}
}
func WithEnv(c *dagger.Container, env []Env) *dagger.Container {
container := c
for _, v := range env {
container = container.WithEnvVariable(v.Name, v.Value)
}
return container
}
@@ -0,0 +1,22 @@
package daggerutil
import (
"errors"
"os"
"dagger.io/dagger"
)
// HostDir checks that the directory at 'path' exists and returns the dagger.Directory at 'path'.
func HostDir(d *dagger.Client, path string) (*dagger.Directory, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, errors.New("given hostdir is not a directory")
}
return d.Host().Directory(path), nil
}
+69
View File
@@ -0,0 +1,69 @@
package docker
import (
"fmt"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
)
type BuildOpts struct {
// Dockerfile is the path to the dockerfile with the '-f' command.
// If it's not provided, then the docker command will default to 'Dockerfile' in `pwd`.
Dockerfile string
// Tags are provided as the '-t' argument, and can include the registry domain as well as the repository.
// Docker build supports building the same image with multiple tags.
// You might want to also include a 'latest' version of the tag.
Tags []string
// BuildArgs are provided to the docker command as '--build-arg'
BuildArgs []string
// Set the target build stage to build as '--target'
Target string
// Platform, if set to the non-default value, will use buildkit's emulation to build the docker image. This can be useful if building a docker image for a platform that doesn't match the host platform.
Platform dagger.Platform
}
func Builder(d *dagger.Client, socket *dagger.Socket, targz *dagger.File) *dagger.Container {
extracted := containers.ExtractedArchive(d, targz)
// Instead of supplying the Platform argument here, we need to tell the host docker socket that it needs to build with the given platform.
return d.Container().From("docker").
WithUnixSocket("/var/run/docker.sock", socket).
WithWorkdir("/src").
WithMountedFile("/src/Dockerfile", extracted.File("Dockerfile")).
WithMountedFile("/src/packaging/docker/run.sh", extracted.File("packaging/docker/run.sh")).
WithMountedFile("/src/grafana.tar.gz", targz)
}
func Build(d *dagger.Client, builder *dagger.Container, opts *BuildOpts) *dagger.Container {
args := []string{"docker", "buildx", "build"}
if p := opts.Platform; p != "" {
args = append(args, fmt.Sprintf("--platform=%s", string(p)))
}
dockerfile := opts.Dockerfile
if dockerfile == "" {
dockerfile = "Dockerfile"
}
args = append(args, ".", "-f", dockerfile)
for _, v := range opts.BuildArgs {
args = append(args, fmt.Sprintf("--build-arg=%s", v))
}
for _, v := range opts.Tags {
args = append(args, "-t", v)
}
if opts.Target != "" {
args = append(args, "--target", opts.Target)
}
return builder.WithExec(args)
}
func Save(builder *dagger.Container, opts *BuildOpts) *dagger.File {
return builder.WithExec([]string{"docker", "save", opts.Tags[0], "-o", "image.tar.gz"}).File("image.tar.gz")
}
+36
View File
@@ -0,0 +1,36 @@
package docker
type DockerOpts struct {
// Registry is the docker Registry for the image.
// If using '--save', then this will have no effect.
// Uses docker hub by default.
// Example: us.gcr.io/12345
Registry string
// AlpineBase is supplied as a build-arg when building the Grafana docker image.
// When building alpine versions of Grafana it uses this image as its base.
AlpineBase string
// UbuntuBase is supplied as a build-arg when building the Grafana docker image.
// When building ubuntu versions of Grafana it uses this image as its base.
UbuntuBase string
// Username is supplied to login to the docker registry when publishing images.
Username string
// Password is supplied to login to the docker registry when publishing images.
Password string
// Org overrides the organization when when publishing images.
Org string
// Repository overrides the repository when when publishing images.
Repository string
// Latest is supplied to also tag as latest when publishing images.
Latest bool
// TagFormat and UbuntuTagFormat should be formatted using go template tags.
TagFormat string
UbuntuTagFormat string
}

Some files were not shown because too many files have changed in this diff Show More