CI: move grafana-build into pkg/build (#105640)
* move grafana-build into pkg/build
This commit is contained in:
+49
-34
@@ -488,24 +488,26 @@ steps:
|
||||
token:
|
||||
from_secret: drone_token
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --version
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
- /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
|
||||
- go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
|
||||
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
|
||||
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.3 --yarn-cache=$$YARN_CACHE_FOLDER
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3
|
||||
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
|
||||
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
|
||||
> packages.txt
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER
|
||||
--ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base
|
||||
}}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID
|
||||
}}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD > packages.txt
|
||||
- find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i
|
||||
depends_on:
|
||||
- yarn-install
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-package
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -1772,24 +1774,26 @@ steps:
|
||||
image: node:22.11.0-alpine
|
||||
name: build-frontend-packages
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --version
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --uninstall 'qemu-*'
|
||||
- docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
|
||||
- /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
|
||||
- go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
|
||||
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
|
||||
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.3 --yarn-cache=$$YARN_CACHE_FOLDER
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3
|
||||
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
|
||||
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
|
||||
> packages.txt
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER
|
||||
--ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base
|
||||
}}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID
|
||||
}}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD > packages.txt
|
||||
- find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i
|
||||
depends_on:
|
||||
- update-package-json-version
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-package
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -3626,9 +3630,12 @@ platform:
|
||||
services: []
|
||||
steps:
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GRAFANA_DIR=$$(pwd)
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- cd /src && ./scripts/drone_build_main.sh
|
||||
- ./pkg/build/daggerbuild/scripts/drone_build_main.sh
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
@@ -3647,7 +3654,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -3659,7 +3665,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-build
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -3701,9 +3707,12 @@ platform:
|
||||
services: []
|
||||
steps:
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GRAFANA_DIR=$$(pwd)
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- cd /src && ./scripts/drone_build_tag_grafana.sh
|
||||
- ./pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
@@ -3722,7 +3731,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -3734,7 +3742,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-build
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -3818,9 +3826,12 @@ platform:
|
||||
services: []
|
||||
steps:
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GRAFANA_DIR=$$(pwd)
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- cd /src && ./scripts/drone_build_tag_grafana.sh
|
||||
- ./pkg/build/daggerbuild/scripts/drone_build_tag_grafana.sh
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
@@ -3839,7 +3850,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -3851,7 +3861,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-build
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -4055,9 +4065,12 @@ platform:
|
||||
services: []
|
||||
steps:
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GRAFANA_DIR=$$(pwd)
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- cd /src && ./scripts/drone_build_nightly_grafana.sh
|
||||
- ./pkg/build/daggerbuild/scripts/drone_build_nightly_grafana.sh
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
@@ -4076,7 +4089,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -4088,7 +4100,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-build
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -4197,9 +4209,12 @@ steps:
|
||||
image: google/cloud-sdk:alpine
|
||||
name: rgm-copy
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GRAFANA_DIR=$$(pwd)
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- cd /src && ./scripts/drone_publish_nightly_grafana.sh
|
||||
- ./pkg/build/daggerbuild/scripts/drone_publish_nightly_grafana.sh
|
||||
depends_on:
|
||||
- rgm-copy
|
||||
environment:
|
||||
@@ -4220,7 +4235,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -4232,7 +4246,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-publish
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -4324,10 +4338,12 @@ steps:
|
||||
- name: github-app
|
||||
path: /github-app
|
||||
- commands:
|
||||
- wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz
|
||||
| tar zx -C /bin
|
||||
- apk add docker
|
||||
- export GITHUB_TOKEN=$(cat /github-app/token)
|
||||
- dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF}
|
||||
- dagger run --silent ./pkg/build/cmd artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF}
|
||||
--enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION}
|
||||
--go-version=1.24.3
|
||||
depends_on:
|
||||
- github-app-generate-token
|
||||
environment:
|
||||
@@ -4348,7 +4364,6 @@ steps:
|
||||
from_secret: grafana_api_key
|
||||
GCP_KEY_BASE64:
|
||||
from_secret: gcp_key_base64
|
||||
GO_VERSION: 1.24.3
|
||||
GPG_PASSPHRASE:
|
||||
from_secret: packages_gpg_passphrase
|
||||
GPG_PRIVATE_KEY:
|
||||
@@ -4360,7 +4375,7 @@ steps:
|
||||
STORYBOOK_DESTINATION:
|
||||
from_secret: rgm_storybook_destination
|
||||
UBUNTU_BASE: ubuntu:22.04
|
||||
image: grafana/grafana-build:main
|
||||
image: golang:1.24.3-alpine
|
||||
name: rgm-build
|
||||
pull: always
|
||||
volumes:
|
||||
@@ -5190,6 +5205,6 @@ kind: secret
|
||||
name: gcr_credentials
|
||||
---
|
||||
kind: signature
|
||||
hmac: 5f4ce778b0c86c6c2267b9045f6019917983e352c8c62df1f898e6db16c9e935
|
||||
hmac: 16b16fc72db02c6f5bd322703f1d7f13c2d1ac38f6efb324590ba14512aadce3
|
||||
|
||||
...
|
||||
|
||||
@@ -22,20 +22,14 @@ jobs:
|
||||
outputs:
|
||||
artifact: ${{ steps.artifact.outputs.artifact }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'grafana/grafana-build'
|
||||
ref: 'main'
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: ./grafana
|
||||
persist-credentials: false
|
||||
- run: echo "GRAFANA_GO_VERSION=$(grep "go 1." grafana/go.work | cut -d\ -f2)" >> "$GITHUB_ENV"
|
||||
- uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e
|
||||
with:
|
||||
verb: run
|
||||
args: go run ./cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir=grafana --go-version=${GRAFANA_GO_VERSION} > out.txt
|
||||
args: go -C grafana run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir="${PWD}/grafana" > out.txt
|
||||
- run: mv "$(cat out.txt)" grafana.tar.gz
|
||||
- run: echo "artifact=grafana-e2e-${{github.run_number}}" >> "$GITHUB_OUTPUT"
|
||||
id: artifact
|
||||
|
||||
@@ -96,8 +96,8 @@ require (
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.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/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.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
|
||||
@@ -325,10 +325,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
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/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=
|
||||
|
||||
@@ -113,8 +113,8 @@ require (
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // 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/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.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
|
||||
@@ -355,10 +355,10 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
|
||||
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
|
||||
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/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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
|
||||
@@ -74,8 +74,8 @@ require (
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // 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/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.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
@@ -189,10 +189,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
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/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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
|
||||
@@ -552,8 +552,8 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-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/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
|
||||
@@ -3345,15 +3345,15 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
||||
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/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/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
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/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.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
|
||||
+17
-1
@@ -600,6 +600,8 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
|
||||
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0=
|
||||
@@ -630,9 +632,13 @@ github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6i
|
||||
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
|
||||
github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0=
|
||||
github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
|
||||
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
||||
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUqU3jMrZiTuW0zkYeKlKDAFD+DG20IoO421/Y=
|
||||
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
|
||||
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.10+incompatible h1:ROMcuN61gI8SfQ+AEMh4d7GZ3gwTZLIhPjtd05TQCG4=
|
||||
@@ -1595,6 +1601,7 @@ github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46r
|
||||
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
||||
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
@@ -1676,6 +1683,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdqCysW5k=
|
||||
github.com/valyala/quicktemplate v1.8.0/go.mod h1:qIqW8/igXt8fdrUln5kOSb+KWMaJ4Y8QUsfd1k6L2jM=
|
||||
github.com/vektah/gqlparser/v2 v2.5.26 h1:REqqFkO8+SOEgZHR/eHScjjVjGS8Nk3RMO/juiTobN4=
|
||||
github.com/vektah/gqlparser/v2 v2.5.26/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 h1:+UB2BJA852UkGH42H+Oee69djmxS3ANzl2b/JtT1YiA=
|
||||
@@ -1916,6 +1925,7 @@ golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.11.1-0.20230711161743-2e82bdd1719d/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
@@ -1940,6 +1950,7 @@ golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
@@ -1959,6 +1970,7 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
@@ -2001,12 +2013,16 @@ golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
@@ -2115,6 +2131,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
@@ -2129,7 +2146,6 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN
|
||||
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
|
||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=
|
||||
|
||||
@@ -150,8 +150,8 @@ require (
|
||||
golang.org/x/tools v0.33.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/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.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
|
||||
@@ -484,10 +484,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
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/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=
|
||||
|
||||
@@ -42,7 +42,7 @@ require (
|
||||
golang.org/x/sync v0.14.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/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
@@ -149,8 +149,8 @@ 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/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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
|
||||
@@ -88,8 +88,8 @@ require (
|
||||
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/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
|
||||
|
||||
@@ -294,10 +294,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
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/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=
|
||||
|
||||
@@ -95,8 +95,8 @@ require (
|
||||
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/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.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
|
||||
@@ -337,10 +337,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
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/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=
|
||||
|
||||
+131
-135
@@ -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...)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.grafana/
|
||||
bin/
|
||||
*.tar.gz
|
||||
*.txt
|
||||
.idea/
|
||||
dist/
|
||||
grafana/
|
||||
.DS_Store
|
||||
@@ -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.
|
||||
@@ -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 alpine image to use as the base image when building the Alpine version of the Grafana docker image",
|
||||
Value: "alpine:latest",
|
||||
}
|
||||
UbuntuImageFlag = &cli.StringFlag{
|
||||
Name: "ubuntu-base",
|
||||
Usage: "The Ubuntu image to use as the base image when building the Ubuntu version of the Grafana docker image",
|
||||
Value: "ubuntu:latest",
|
||||
}
|
||||
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)
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -0,0 +1,296 @@
|
||||
package arguments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"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 GrafanaDir was provided, then we can just use that one.
|
||||
if path := localPath; path != "" {
|
||||
slog.Info("Using local Grafana found", "path", path)
|
||||
return daggerutil.HostDir(client, path)
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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)
|
||||
|
||||
ght, err := githubToken(ctx, o.GitHubToken)
|
||||
if err != nil {
|
||||
log.Println("No github token found:", err)
|
||||
}
|
||||
|
||||
src, err := cloneOrMount(ctx, opts.Client, o.GrafanaDir, o.GrafanaRepo, o.GrafanaRef, ght)
|
||||
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, ght)
|
||||
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)
|
||||
}
|
||||
|
||||
ght, err := githubToken(ctx, o.GitHubToken)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
src, err := cloneOrMount(ctx, opts.Client, o.EnterpriseDir, o.EnterpriseRepo, o.EnterpriseRef, ght)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return InitializeEnterprise(opts.Client, grafanaDir.(*dagger.Directory), src), 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
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
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/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
|
||||
}
|
||||
|
||||
bopts := &backend.BuildOpts{
|
||||
Version: p.Version,
|
||||
Enterprise: p.Enterprise,
|
||||
ExperimentalFlags: experiments,
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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), 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)
|
||||
}
|
||||
@@ -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,121 @@
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
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,382 @@
|
||||
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, version, artifact, 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", "libfontconfig1"}).
|
||||
WithDirectory("/src", archive).
|
||||
WithWorkdir("/src")
|
||||
|
||||
if err := e2e.ValidateLicense(ctx, service, "/src/LICENSE", enterprise); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service = service.
|
||||
WithExec([]string{"./bin/grafana", "server"}).
|
||||
WithExposedPort(3000)
|
||||
|
||||
if _, err := containers.ExitError(ctx, e2e.ValidatePackage(d, service.AsService(), 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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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", "-v", "-x",
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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
|
||||
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", "https://ziglang.org/download/0.11.0/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", "http://musl.cc/arm-linux-musleabihf-cross.tgz", "-P", "/toolchain"}).
|
||||
WithExec([]string{"tar", "-xvf", "/toolchain/arm-linux-musleabihf-cross.tgz", "-C", "/toolchain"}).
|
||||
WithExec([]string{"wget", "https://musl.cc/s390x-linux-musl-cross.tgz", "-P", "/toolchain"}).
|
||||
WithExec([]string{"tar", "-xvf", "/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")
|
||||
|
||||
commitInfo := GetVCSInfo(src, version, opts.Enterprise)
|
||||
|
||||
builder = withCue(builder, src).
|
||||
WithDirectory("/src/", src, dagger.ContainerWithDirectoryOpts{
|
||||
Include: []string{"**/*.mod", "**/*.sum", "**/*.work"},
|
||||
}).
|
||||
WithDirectory("/src/pkg", src.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"},
|
||||
}).
|
||||
WithDirectory("/src/pkg", src.Directory("pkg")).
|
||||
WithDirectory("/src/apps", src.Directory("apps")).
|
||||
WithDirectory("/src/.bingo", src.Directory(".bingo")).
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package backend holds the functions that create containers, files, and directories for building Grafana's backend binaries.
|
||||
package backend
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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{},
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
@@ -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},
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user