diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dc9eca76448..f2f2e615ef9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -48,7 +48,10 @@ /docs/sources/developers/plugins/ @grafana/plugins-platform-frontend @grafana/plugins-platform-backend +/docs/sources/dashboards/share-dashboards-panels/_index.md @imatwawana @jtvdez +/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md @jtvdez /docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @imatwawana @baldm0mma +/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md @lwandz13 @irenerl24 # END Technical documentation # Backend code @@ -67,6 +70,7 @@ /scripts/go-workspace @grafana/grafana-app-platform-squad /scripts/ci/backend-tests @grafana/grafana-operator-experience-squad /hack/ @grafana/grafana-app-platform-squad +/.air.toml @macabu /pkg/apis/provisioning @grafana/grafana-git-ui-sync-team /public/app/features/provisioning @grafana/grafana-git-ui-sync-team @@ -76,8 +80,10 @@ /apps/dashboard/ @grafana/grafana-app-platform-squad @grafana/dashboards-squad /apps/folder/ @grafana/grafana-app-platform-squad /apps/playlist/ @grafana/grafana-app-platform-squad +/apps/secret/ @grafana/grafana-operator-experience-squad /apps/investigations/ @fcjack @matryer @svennergr /apps/advisor/ @grafana/plugins-platform-backend +/apps/iam/ @grafana/access-squad /pkg/api/ @grafana/grafana-backend-group /pkg/apis/ @grafana/grafana-app-platform-squad /pkg/apis/query @grafana/grafana-datasources-core-services @@ -133,7 +139,7 @@ /pkg/services/apikey/ @grafana/identity-squad /pkg/services/cleanup/ @grafana/grafana-backend-group /pkg/services/contexthandler/ @grafana/grafana-backend-group @grafana/grafana-app-platform-squad -/pkg/services/correlations/ @grafana/dataviz-squad +/pkg/services/correlations/ @grafana/datapro /pkg/services/dashboardimport/ @grafana/grafana-backend-group /pkg/services/dashboards/ @grafana/grafana-app-platform-squad /pkg/services/dashboardversion/ @grafana/grafana-backend-group @@ -166,7 +172,7 @@ /pkg/services/tag/ @grafana/grafana-search-and-storage /pkg/services/team/ @grafana/access-squad /pkg/services/temp_user/ @grafana/grafana-backend-group -/pkg/services/updatechecker/ @grafana/grafana-backend-group +/pkg/services/updatemanager/ @grafana/grafana-backend-group /pkg/services/user/ @grafana/access-squad /pkg/services/validations/ @grafana/grafana-backend-group /pkg/setting/ @grafana/grafana-backend-services-squad @@ -174,7 +180,7 @@ /pkg/tests/apis/ @grafana/grafana-app-platform-squad /pkg/tests/apis/query @grafana/grafana-datasources-core-services /pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend -/pkg/tests/api/correlations/ @grafana/dataviz-squad +/pkg/tests/api/correlations/ @grafana/datapro /pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/opentsdb/ @grafana/partner-datasources /pkg/util/ @grafana/grafana-backend-group @@ -188,8 +194,8 @@ /devenv/docker/blocks/auth/ @grafana/identity-access-team # Logs code, developers environment -/devenv/docker/blocks/loki* @grafana/observability-logs -/devenv/docker/blocks/elastic* @grafana/aws-datasources +/devenv/docker/blocks/loki* @grafana/oss-big-tent +/devenv/docker/blocks/elastic* @grafana/partner-datasources /devenv/docker/blocks/self-instrumentation* @grafana/oss-big-tent /devenv/bulk-dashboards/ @grafana/dashboards-squad @@ -226,7 +232,7 @@ /devenv/dev-dashboards/dashboards.go @grafana/dataviz-squad /devenv/dev-dashboards/home.json @grafana/dataviz-squad -/devenv/dev-dashboards/datasource-elasticsearch/ @grafana/aws-datasources +/devenv/dev-dashboards/datasource-elasticsearch/ @grafana/partner-datasources /devenv/dev-dashboards/datasource-opentsdb/ @grafana/partner-datasources /devenv/dev-dashboards/datasource-influxdb/ @grafana/partner-datasources /devenv/dev-dashboards/datasource-mssql/ @grafana/partner-datasources @@ -256,11 +262,11 @@ /devenv/docker/blocks/etcd @grafana/grafana-app-platform-squad /devenv/docker/blocks/grafana/ @grafana/grafana-as-code /devenv/docker/blocks/graphite/ @grafana/partner-datasources -/devenv/docker/blocks/graphite09/ @grafana/partner-datasources /devenv/docker/blocks/graphite1/ @grafana/partner-datasources /devenv/docker/blocks/influxdb/ @grafana/partner-datasources /devenv/docker/blocks/influxdb1/ @grafana/partner-datasources /devenv/docker/blocks/jaeger/ @grafana/oss-big-tent +/devenv/docker/blocks/jaegeronly/ @grafana/grafana-operator-experience-squad /devenv/docker/blocks/maildev/ @grafana/alerting-frontend /devenv/docker/blocks/mariadb/ @grafana/oss-big-tent /devenv/docker/blocks/memcached/ @grafana/grafana-backend-group @@ -330,8 +336,8 @@ # Observability backend code /pkg/tsdb/prometheus/ @grafana/oss-big-tent -/pkg/tsdb/elasticsearch/ @grafana/aws-datasources -/pkg/tsdb/loki/ @grafana/observability-logs +/pkg/tsdb/elasticsearch/ @grafana/partner-datasources +/pkg/tsdb/loki/ @grafana/oss-big-tent /pkg/tsdb/tempo/ @grafana/observability-traces-and-profiling /pkg/tsdb/grafana-pyroscope-datasource/ @grafana/observability-traces-and-profiling /pkg/tsdb/parca/ @grafana/oss-big-tent @@ -368,8 +374,8 @@ /public/app/features/gops/ @grafana/alerting-frontend # Library Services -/pkg/services/libraryelements/ @grafana/dashboards-squad -/pkg/services/librarypanels/ @grafana/dashboards-squad +/pkg/services/libraryelements/ @grafana/sharing-squad +/pkg/services/librarypanels/ @grafana/sharing-squad # Plugins /pkg/api/pluginproxy/ @grafana/plugins-platform-backend @@ -395,13 +401,15 @@ /public/app/core/internationalization/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform /e2e/cloud-plugins-suite/ @grafana/partner-datasources -/e2e/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend -/e2e/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend +/e2e-playwright/ @grafana/grafana-frontend-platform +/e2e-playwright/plugin-e2e/ @grafana/oss-big-tent @grafana/partner-datasources +/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend +/e2e-playwright/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend # Packages /packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend /packages/grafana-data/src/**/*logs* @grafana/observability-logs -/packages/grafana-data/src/transformations/ @grafana/dataviz-squad +/packages/grafana-data/src/transformations/ @grafana/datapro /packages/grafana-e2e-selectors/ @grafana/grafana-frontend-platform /packages/grafana-flamegraph/ @grafana/observability-traces-and-profiling /packages/grafana-o11y-ds-frontend/ @grafana/observability-logs @@ -438,6 +446,8 @@ /packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad /packages/grafana-ui/src/utils/storybook/ @grafana/grafana-frontend-platform /packages/grafana-alerting/ @grafana/alerting-frontend +/packages/grafana-i18n/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend +/packages/grafana-test-utils @grafana/grafana-frontend-platform # root files, mostly frontend /.browserslistrc @grafana/frontend-ops @@ -465,10 +475,12 @@ /stylelint.config.js @grafana/frontend-ops /tools/ @grafana/frontend-ops /lefthook.yml @grafana/frontend-ops +/lefthook.rc @grafana/frontend-ops /.husky/pre-commit @grafana/frontend-ops /cypress.config.js @grafana/grafana-frontend-platform /.levignore.js @grafana/plugins-platform-frontend playwright.config.ts @grafana/plugins-platform-frontend +playwright.storybook.config.ts @grafana/grafana-frontend-platform # public folder /public/app/api/ @grafana/grafana-frontend-platform @@ -480,7 +492,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/core/components/TimelineChart/ @grafana/dataviz-squad /public/app/core/components/Form/ @grafana/grafana-frontend-platform /public/app/core/components/OptionsUI/ @grafana/dashboards-squad @grafana/dataviz-squad - +/public/app/dev-utils.ts @grafana/grafana-frontend-platform /public/app/core/history/ @grafana/observability-traces-and-profiling /public/app/features/admin/ @grafana/identity-access-team @@ -491,32 +503,29 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/actions/ @grafana/dataviz-squad /public/app/features/auth-config/ @grafana/identity-squad /public/app/features/annotations/ @grafana/dashboards-squad -/public/app/features/api-keys/ @grafana/identity-squad /public/app/features/canvas/ @grafana/dataviz-squad /public/app/features/geo/ @grafana/dataviz-squad /public/app/features/visualization/data-hover/ @grafana/dataviz-squad -/public/app/features/commandPalette/ @grafana/grafana-frontend-platform +/public/app/features/commandPalette/ @grafana/grafana-search-navigate-organise /public/app/features/connections/ @grafana/plugins-platform-frontend -/public/app/features/correlations/ @grafana/dataviz-squad +/public/app/features/correlations/ @grafana/datapro /public/app/features/dashboard/ @grafana/dashboards-squad -/public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad +/public/app/features/dashboard/components/TransformationsEditor/ @grafana/datapro /public/app/features/dashboard-scene/ @grafana/dashboards-squad -/public/app/features/scopes/ @grafana/dashboards-squad +/public/app/features/scopes/ @grafana/grafana-operator-experience-squad /public/app/features/datasources/ @grafana/plugins-platform-frontend /public/app/features/dimensions/ @grafana/dataviz-squad /public/app/features/dataframe-import/ @grafana/dataviz-squad /public/app/features/explore/ @grafana/observability-traces-and-profiling /public/app/features/expressions/ @grafana/grafana-datasources-core-services -/public/app/features/folders/ @grafana/grafana-frontend-platform +/public/app/features/folders/ @grafana/grafana-search-navigate-organise /public/app/features/inspector/ @grafana/dashboards-squad -/public/app/features/invites/ @grafana/grafana-frontend-platform -/public/app/features/library-panels/ @grafana/dashboards-squad /public/app/features/logs/ @grafana/observability-logs /public/app/features/live/ @grafana/dashboards-squad /public/app/features/apiserver/ @grafana/grafana-app-platform-squad /public/app/features/manage-dashboards/ @grafana/dashboards-squad -/public/app/features/notifications/ @grafana/grafana-frontend-platform -/public/app/features/org/ @grafana/grafana-frontend-platform +/public/app/features/notifications/ @grafana/grafana-search-navigate-organise +/public/app/features/org/ @grafana/grafana-search-navigate-organise /public/app/features/panel/ @grafana/dashboards-squad /public/app/features/playlist/ @grafana/dashboards-squad /public/app/features/plugins/ @grafana/plugins-platform-frontend @@ -524,27 +533,27 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/runtime/ @ryantxu /public/app/features/query/ @grafana/dashboards-squad /public/app/features/sandbox/ @grafana/grafana-frontend-platform -/public/app/features/browse-dashboards/ @grafana/grafana-frontend-platform -/public/app/features/search/ @grafana/grafana-frontend-platform +/public/app/features/browse-dashboards/ @grafana/grafana-search-navigate-organise +/public/app/features/search/ @grafana/grafana-search-navigate-organise /public/app/features/serviceaccounts/ @grafana/identity-squad /public/app/features/teams/ @grafana/access-squad /public/app/features/templating/ @grafana/dashboards-squad /public/app/features/trails/ @grafana/observability-metrics -/public/app/features/transformers/ @grafana/dataviz-squad +/public/app/features/transformers/ @grafana/datapro /public/app/features/transformers/timeSeriesTable/ @grafana/dataviz-squad @grafana/app-o11y-visualizations /public/app/features/users/ @grafana/access-squad /public/app/features/variables/ @grafana/dashboards-squad /public/app/features/preferences/ @grafana/grafana-frontend-platform -/public/app/features/bookmarks/ @grafana/grafana-frontend-platform +/public/app/features/bookmarks/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/alertlist/ @grafana/alerting-frontend -/public/app/plugins/panel/annolist/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/annolist/ @grafana/dashboards-squad /public/app/plugins/panel/barchart/ @grafana/dataviz-squad /public/app/plugins/panel/bargauge/ @grafana/dataviz-squad -/public/app/plugins/panel/dashlist/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/dashlist/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/debug/ @ryantxu /public/app/plugins/panel/datagrid/ @grafana/dataviz-squad /public/app/plugins/panel/gauge/ @grafana/dataviz-squad -/public/app/plugins/panel/gettingstarted/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/gettingstarted/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/heatmap/ @grafana/dataviz-squad /public/app/plugins/panel/histogram/ @grafana/dataviz-squad /public/app/plugins/panel/logs/ @grafana/observability-logs @@ -563,12 +572,12 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/plugins/panel/canvas/ @grafana/dataviz-squad /public/app/plugins/panel/candlestick/ @grafana/dataviz-squad /public/app/plugins/panel/live/ @grafana/dashboards-squad -/public/app/plugins/panel/news/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/news/ @grafana/dataviz-squad /public/app/plugins/panel/stat/ @grafana/dataviz-squad -/public/app/plugins/panel/text/ @grafana/grafana-frontend-platform -/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/text/ @grafana/dataviz-squad +/public/app/plugins/panel/welcome/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/xychart/ @grafana/dataviz-squad -/public/app/routes/ @grafana/grafana-frontend-platform +/public/app/routes/ @grafana/grafana-search-navigate-organise /public/app/store/ @grafana/grafana-frontend-platform /public/app/types/ @grafana/grafana-frontend-platform /public/app/types/alerting.ts @grafana/alerting-frontend @@ -596,7 +605,6 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/explore/NodeGraph/ @grafana/observability-traces-and-profiling /public/app/features/explore/FlameGraph/ @grafana/observability-traces-and-profiling /public/app/features/explore/TraceView/ @grafana/observability-traces-and-profiling -/public/app/features/explore/QueryLibrary/ @grafana/grafana-frontend-platform /public/api-merged.json @grafana/grafana-backend-group /public/api-enterprise-spec.json @grafana/grafana-backend-group @@ -605,6 +613,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/dev.ts @grafana/frontend-ops /public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend /public/app/index.ts @grafana/frontend-ops +/public/app/initApp.ts @grafana/frontend-ops /public/app/AppWrapper.tsx @grafana/frontend-ops /public/app/partials/ @grafana/grafana-frontend-platform @@ -614,7 +623,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /scripts/circle-* @grafana/grafana-developer-enablement-squad /scripts/publish-npm-packages.sh @grafana/grafana-developer-enablement-squad @grafana/plugins-platform-frontend /scripts/validate-npm-packages.sh @grafana/grafana-developer-enablement-squad @grafana/plugins-platform-frontend -/scripts/ci-frontend-metrics.sh @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend @grafana/dataviz-squad +/scripts/ci-frontend-metrics.sh @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend @grafana/dataviz-squad @grafana/datapro /scripts/cli/ @grafana/grafana-frontend-platform /scripts/clean-git-or-error.sh @grafana/grafana-as-code /scripts/grafana-server/ @grafana/grafana-frontend-platform @@ -637,12 +646,11 @@ playwright.config.ts @grafana/plugins-platform-frontend /scripts/levitate-parse-json-report.js @grafana/plugins-platform-frontend /scripts/levitate-show-affected-plugins.js @grafana/plugins-platform-frontend /scripts/codemods/explicit-barrel-imports.cjs @grafana/frontend-ops +/scripts/rtk-client-generator/ @grafana/grafana-search-navigate-organise -/scripts/**/generate-transformations* @grafana/dataviz-squad +/scripts/**/generate-transformations* @grafana/datapro /scripts/webpack/ @grafana/frontend-ops /scripts/generate-a11y-report.sh @grafana/grafana-frontend-platform -.pa11yci.conf.js @grafana/grafana-frontend-platform -.pa11yci-pr.conf.js @grafana/grafana-frontend-platform .betterer.results @grafanabot .betterer.ts @grafana/grafana-frontend-platform @@ -652,14 +660,14 @@ playwright.config.ts @grafana/plugins-platform-frontend # Core datasources /public/app/plugins/datasource/dashboard/ @grafana/dashboards-squad /public/app/plugins/datasource/cloudwatch/ @grafana/aws-datasources -/public/app/plugins/datasource/elasticsearch/ @grafana/aws-datasources +/public/app/plugins/datasource/elasticsearch/ @grafana/partner-datasources /public/app/plugins/datasource/grafana/ @grafana/grafana-frontend-platform /public/app/plugins/datasource/grafana-testdata-datasource/ @grafana/plugins-platform-frontend /public/app/plugins/datasource/azuremonitor/ @grafana/partner-datasources /public/app/plugins/datasource/graphite/ @grafana/partner-datasources /public/app/plugins/datasource/influxdb/ @grafana/partner-datasources /public/app/plugins/datasource/jaeger/ @grafana/oss-big-tent -/public/app/plugins/datasource/loki/ @grafana/observability-logs +/public/app/plugins/datasource/loki/ @grafana/oss-big-tent @grafana/observability-logs /public/app/plugins/datasource/mixed/ @grafana/dashboards-squad /public/app/plugins/datasource/mssql/ @grafana/partner-datasources /public/app/plugins/datasource/mysql/ @grafana/oss-big-tent @@ -676,13 +684,23 @@ playwright.config.ts @grafana/plugins-platform-frontend # Grafana Sharing Squad /public/app/features/dashboard-scene/sharing/ @grafana/sharing-squad /public/app/features/dashboard/components/ShareModal/ @grafana/sharing-squad -/public/app/features/manage-dashboards/components/PublicDashboardListTable/ @grafana/sharing-squad -/public/app/features/dashboard/containers/PublicDashboardPage.tsx @grafana/sharing-squad /public/app/features/manage-dashboards/components/SnapshotListTable.tsx @grafana/sharing-squad -/pkg/api/render.go @grafana/sharing-squad /pkg/services/dashboardsnapshots/ @grafana/sharing-squad -/pkg/services/publicdashboards/ @grafana/sharing-squad -/pkg/services/rendering/ @grafana/sharing-squad +/public/app/features/explore/QueryLibrary/ @grafana/sharing-squad +/public/app/features/library-panels/ @grafana/sharing-squad +/public/app/features/invites/ @grafana/sharing-squad + +# Grafana Enterprise: Public Dashboards & Image Renderer +/pkg/api/render.go @grafana/grafana-operator-experience-squad +/pkg/services/publicdashboards/ @grafana/grafana-operator-experience-squad +/pkg/services/rendering/ @grafana/grafana-operator-experience-squad +/public/app/features/dashboard/containers/PublicDashboardPage* @grafana/grafana-operator-experience-squad +/public/app/features/dashboard/components/PublicDashboard/ @grafana/grafana-operator-experience-squad +/public/app/features/dashboard/components/PublicDashboardNotAvailable/ @grafana/grafana-operator-experience-squad +/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ @grafana/grafana-operator-experience-squad +/public/app/features/dashboard-scene/sharing/public-dashboards/ @grafana/grafana-operator-experience-squad +/public/app/features/manage-dashboards/components/PublicDashboardListTable/ @grafana/grafana-operator-experience-squad +/public/app/features/manage-dashboards/PublicDashboardListPage.tsx* @grafana/grafana-operator-experience-squad # SSE - Server Side Expressions /pkg/expr/ @grafana/grafana-datasources-core-services @@ -696,11 +714,11 @@ playwright.config.ts @grafana/plugins-platform-frontend /pkg/services/anonymous/ @grafana/identity-access-team /pkg/services/auth/ @grafana/identity-squad /pkg/services/authn/ @grafana/identity-squad +/pkg/services/scimutil/ @grafana/identity-squad /pkg/services/authz/ @grafana/access-squad /pkg/services/signingkeys/ @grafana/identity-squad /pkg/services/dashboards/accesscontrol.go @grafana/access-squad /pkg/services/datasources/guardian/ @grafana/access-squad -/pkg/services/guardian/ @grafana/access-squad /pkg/services/ldap/ @grafana/identity-squad /pkg/services/login/ @grafana/identity-squad /pkg/services/loginattempt/ @grafana/identity-squad @@ -733,11 +751,12 @@ embed.go @grafana/grafana-as-code /pkg/kinds/ @grafana/grafana-as-code /pkg/registry/ @grafana/grafana-as-code /pkg/registry/apis/ @grafana/grafana-app-platform-squad -/pkg/registry/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend +/pkg/registry/apis/folders @grafana/grafana-search-and-storage /pkg/registry/apis/query @grafana/grafana-datasources-core-services /pkg/registry/apis/secret @grafana/grafana-operator-experience-squad /pkg/registry/apis/userstorage @grafana/grafana-app-platform-squad @grafana/plugins-platform-backend /pkg/registry/apps/advisor @grafana/plugins-platform-backend +/pkg/registry/apps/alerting @grafana/alerting-backend /pkg/codegen/ @grafana/grafana-as-code /pkg/codegen/generators @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code @@ -753,13 +772,16 @@ embed.go @grafana/grafana-as-code /.github/commands.json @torkelo /.github/dependabot.yml @grafana/frontend-ops /.github/issue-opened.json @grafana/grafana-community-support -/.github/metrics-collector.json @torkelo /.github/pr-checks.json @tolzhabayev /.github/pr-commands.json @tolzhabayev /.github/renovate.json5 @grafana/frontend-ops +/.github/actions/check-jobs/action.yml @grafana/grafana-frontend-platform /.github/actions/setup-enterprise/action.yml @grafana/grafana-backend-group -/.github/actions/test-coverage-processor/action.yml @grafana/grafana-backend-group /.github/actions/setup-grafana-bench/ @Proximyst +/.github/actions/build-package @grafana/grafana-developer-enablement-squad +/.github/actions/change-detection @grafana/grafana-developer-enablement-squad +/.github/workflows/actionlint-format.txt @grafana/grafana-developer-enablement-squad +/.github/workflows/actionlint.yml @grafana/grafana-developer-enablement-squad /.github/workflows/add-to-whats-new.yml @grafana/docs-tooling /.github/workflows/auto-triager/ @grafana/plugins-platform-frontend /.github/workflows/alerting-swagger-gen.yml @grafana/alerting-backend @@ -767,7 +789,8 @@ embed.go @grafana/grafana-as-code /.github/workflows/auto-milestone.yml @grafana/grafana-developer-enablement-squad /.github/workflows/backend-code-checks.yml @grafana/grafana-backend-group /.github/workflows/backend-unit-tests.yml @grafana/grafana-backend-group -/.github/workflows/backport.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/backport-trigger.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/backport-workflow.yml @grafana/grafana-developer-enablement-squad /.github/workflows/bump-version.yml @grafana/grafana-developer-enablement-squad /.github/workflows/release-pr.yml @grafana/grafana-developer-enablement-squad /.github/workflows/release-comms.yml @grafana/grafana-developer-enablement-squad @@ -785,20 +808,22 @@ embed.go @grafana/grafana-as-code /.github/workflows/github-release.yml @grafana/grafana-developer-enablement-squad /.github/workflows/issue-opened.yml @grafana/grafana-community-support /.github/workflows/lint-build-docs.yml @grafana/docs-tooling -/.github/workflows/metrics-collector.yml @torkelo /.github/workflows/pr-checks.yml @tolzhabayev /.github/workflows/pr-codeql-analysis-javascript.yml @DanCech /.github/workflows/pr-codeql-analysis-python.yml @DanCech /.github/workflows/pr-commands.yml @tolzhabayev +/.github/workflows/pr-external-labelling.yml @Proximyst /.github/workflows/pr-patch-check-event.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/pr-patch-check.yml @grafana/grafana-developer-enablement-squad /.github/workflows/pr-test-integration.yml @grafana/grafana-backend-group -/.github/workflows/pr-backend-coverage.yml @grafana/grafana-backend-group +/.github/workflows/reject-gh-secrets.yml @grafana/grafana-developer-enablement-squad /.github/workflows/sync-mirror-event.yml @grafana/grafana-developer-enablement-squad /.github/workflows/publish-technical-documentation-next.yml @grafana/docs-tooling /.github/workflows/publish-technical-documentation-release.yml @grafana/docs-tooling /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad /.github/workflows/storybook-verification.yml @grafana/grafana-frontend-platform +/.github/workflows/storybook-verification-playwright.yml @grafana/grafana-frontend-platform /.github/workflows/update-make-docs.yml @grafana/docs-tooling /.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring /.github/workflows/scripts/create-security-branch/create-security-branch.sh @grafana/grafana-developer-enablement-squad @@ -807,29 +832,38 @@ embed.go @grafana/grafana-as-code /.github/workflows/verify-kinds.yml @grafana/platform-monitoring /.github/workflows/dashboards-issue-add-label.yml @grafana/dashboards-squad /.github/workflows/run-schema-v2-e2e.yml @grafana/dashboards-squad +/.github/workflows/e2e-dashboard-new-layouts.yml @grafana/dashboards-squad /.github/workflows/run-dashboard-search-e2e.yml @grafana/grafana-search-and-storage /.github/workflows/trigger-dashboard-search-e2e.yml @grafana/grafana-search-and-storage -/.github/workflows/ephemeral-instances-pr-comment.yml @grafana/grafana-backend-services-squad +/.github/workflows/ephemeral-instances-pr-comment.yml @grafana/grafana-operator-experience-squad /.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-developer-enablement-squad /.github/workflows/core-plugins-build-and-release.yml @grafana/plugins-platform-frontend @grafana/plugins-platform-backend /.github/workflows/i18n-crowdin-upload.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform -/.github/workflows/scripts/crowdin/create-tasks.js @grafana/grafana-frontend-platform +/.github/workflows/i18n-verify.yml @grafana/grafana-frontend-platform +/.github/workflows/deploy-storybook-preview.yml @grafana/grafana-frontend-platform +/.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform /.github/workflows/pr-go-workspace-check.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-dependabot-update-go-workspace.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-k8s-codegen-check.yml @grafana/grafana-app-platform-squad /.github/workflows/go-lint.yml @grafana/grafana-backend-services-squad /.github/workflows/trivy-scan.yml @grafana/grafana-backend-services-squad +/.github/workflows/trufflehog.yml @Proximyst /.github/workflows/changelog.yml @zserge +/.github/workflows/shellcheck.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/release-build.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/publish-artifact.yml @grafana/grafana-developer-enablement-squad /.github/actions/changelog @zserge +/.github/workflows/swagger-gen.yml @grafana/grafana-backend-group /.github/workflows/pr-frontend-unit-tests.yml @grafana/grafana-frontend-platform /.github/workflows/frontend-lint.yml @grafana/grafana-frontend-platform /.github/workflows/analytics-events-report.yml @grafana/grafana-frontend-platform /.github/workflows/pr-e2e-tests.yml @grafana/grafana-developer-enablement-squad -/.github/workflows/run-e2e-suite.yml @grafana/grafana-developer-enablement-squad /.github/workflows/skye-add-to-project.yml @grafana/grafana-frontend-platform /.github/zizmor.yml @grafana/grafana-developer-enablement-squad +/.github/license_finder.yaml @bergquist +/.github/actionlint.yaml @grafana/grafana-developer-enablement-squad # Generated files not requiring owner approval /packages/grafana-data/src/types/featureToggles.gen.ts @grafanabot @@ -849,3 +883,7 @@ embed.go @grafana/grafana-as-code /conf/provisioning/datasources/ @grafana/plugins-platform-backend /conf/provisioning/plugins/ @grafana/plugins-platform-backend /conf/provisioning/sample/ @grafana/grafana-git-ui-sync-team + +# Security +/relyance.yaml @grafana/security-team +/.github/workflows/relyance-scan.yml @grafana/security-team diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000000..d54ad5d2f1f --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,8 @@ +# These are just aliases to github-hosted runners +self-hosted-runner: + labels: + - github-hosted-ubuntu-arm64 + - github-hosted-ubuntu-arm64-large + - github-hosted-ubuntu-x64-small + - github-hosted-ubuntu-x64-large + - github-hosted-windows-x64-large diff --git a/.github/actions/build-package/action.yml b/.github/actions/build-package/action.yml new file mode 100644 index 00000000000..123484071e7 --- /dev/null +++ b/.github/actions/build-package/action.yml @@ -0,0 +1,152 @@ +name: Build and Package Grafana Enterprise / Pro +description: Creates Grafana artifacts using Dagger & `pkg/build/daggerbuild` +inputs: + artifacts: + description: | + Comma-delimited list of artifacts to build and package. + Artifacts follow a specific format of `{package-type}:{grafana-edition}:{architecture}`. + Not every combination of `package-type`, `grafana-edition`, and `architecture` are supported. + Examples: + * `grafana:linux/amd64:targz`, `grafana:linux/amd64:deb` + * `enterprise:linux/arm64:rpm, enterprise:linux/amd64:docker` + * `pro:docker:llinux/amd64` + required: true + type: string + grafana-path: + description: Path to a clone of the 'grafana' repo + default: grafana + type: string + grafana-enterprise-path: + description: Path to a clone of the 'grafana-enterprise' repo + default: grafana-enterprise + type: string + github-token: + type: string + required: true + version: + type: string + description: The version to embed in the grafana binary, example `v1.2.3`. If not provided, then the value in Grafana's package.json will be used + required: true + build-id: + type: string + description: an identifier number which can be traced back to the workflow run. + default: ${{github.run_id}} + required: false + patches-repo: + type: string + description: Repository to load for patches repo. If empty, patches won't be applied. Must be an HTTPS git URL. + required: false + default: "" + patches-ref: + type: string + description: git ref in the patches repo to check out. + required: false + default: main + patches-path: + type: string + description: Path in the repository where `.patch` files can be found. + required: false + default: main + checksum: + type: boolean + description: If true, then checksums will be produced for each file (with a '.sha256' extension) + required: false + default: false + verify: + type: boolean + description: If true, then the e2e smoke tests will run to verify the produced artifacts (--verify) + required: false + default: false + output: + type: string + description: Filename to redirect stdout to. Contains list of packages that were produced + default: packages.txt + required: false + docker-tag-format: + type: string + default: "{{ .version }}-{{ .arch }}" + description: Go template of Docker image tag + required: false + docker-tag-format-ubuntu: + type: string + default: "{{ .version }}-ubuntu-{{ .arch }}" + description: Go template of Docker image tag + required: false + docker-org: + type: string + description: Docker org of produced images + default: grafana + required: false + docker-registry: + type: string + description: Docker registry of produced images + default: docker.io + required: false + ubuntu-base: + type: string + default: 'ubuntu:22.04' + required: false + alpine-base: + type: string + default: 'alpine:3.22' + required: false +outputs: + dist-dir: + description: Directory where artifacts are placed + value: ${{ steps.output.outputs.dist_dir }} + file: + description: Path to file containing list of artifacts produced + value: ${{ steps.output.outputs.file }} + grafana-commit: + description: Commit hash of the HEAD of the grafana repository used to build grafana. + value: ${{ steps.output.outputs.grafana_commit }} + enterprise-commit: + description: Commit hash of the HEAD of the grafana-enterprise repository used to build grafana. + value: ${{ steps.output.outputs.enterprise_commit }} + version: + description: The `grafana` version that was embedded in the binary + value: ${{ steps.output.outputs.version }} +runs: + using: "composite" + steps: + - shell: bash + run: | # zizmor: ignore[github-env] + echo "GRAFANA_PATH=${{ github.workspace }}/${GRAFANA_DIR}" >> "$GITHUB_ENV" + echo "ENTERPRISE_PATH=${{ github.workspace }}/${ENTERPRISE_DIR}" >> "$GITHUB_ENV" + env: + GB_PATH: ${{ inputs.path }} + GRAFANA_DIR: ${{ inputs.grafana-path }} + ENTERPRISE_DIR: ${{ inputs.enterprise-path }} + - name: Build Grafana Enterprise packages + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + env: + VERSION: ${{ inputs.version }} + ARTIFACTS: ${{ inputs.artifacts }} + GITHUB_TOKEN: ${{ inputs.github-token }} + PATCHES_REPO: ${{ inputs.patches-repo }} + PATCHES_REF: ${{ inputs.patches-ref }} + PATCHES_PATH: ${{ inputs.patches-path }} + BUILD_ID: ${{ inputs.build-id }} + OUTFILE: ${{ inputs.output }} + DOCKER_ORG: ${{ inputs.docker-org }} + DOCKER_REGISTRY: ${{ inputs.docker-registry }} + TAG_FORMAT: ${{ inputs.docker-tag-format }} + UBUNTU_TAG_FORMAT: ${{ inputs.docker-tag-format-ubuntu }} + CHECKSUM: ${{ inputs.checksum }} + VERIFY: ${{ inputs.verify }} + ALPINE_BASE: ${{ inputs.alpine-base }} + UBUNTU_BASE: ${{ inputs.ubuntu-base }} + with: + verb: run + dagger-flags: --verbose=0 + args: go run -C ${GRAFANA_PATH} ./pkg/build/cmd artifacts --artifacts ${ARTIFACTS} --grafana-dir=${GRAFANA_PATH} --alpine-base=${ALPINE_BASE} --ubuntu-base=${UBUNTU_BASE} --enterprise-dir=${ENTERPRISE_PATH} --version=${VERSION} --patches-repo=${PATCHES_REPO} --patches-ref=${PATCHES_REF} --patches-path=${PATCHES_PATH} --build-id=${BUILD_ID} --tag-format="${TAG_FORMAT}" --ubuntu-tag-format="${UBUNTU_TAG_FORMAT}" --org=${DOCKER_ORG} --registry=${DOCKER_REGISTRY} --checksum=${CHECKSUM} --verify=${VERIFY} > $OUTFILE + - id: output + shell: bash + env: + OUTFILE: ${{ inputs.output }} + run: | + echo "dist_dir=dist" | tee -a $GITHUB_OUTPUT + echo "file=${OUTFILE}" | tee -a $GITHUB_OUTPUT + echo "grafana_commit=$(git -C ${GRAFANA_PATH} rev-parse HEAD)" | tee -a $GITHUB_OUTPUT + echo "enterprise_commit=$(git -C ${ENTERPRISE_PATH} rev-parse HEAD)" | tee -a $GITHUB_OUTPUT + echo "version=$(cat ${GRAFANA_BUILD_PATH}/dist/VERSION)" | tee -a $GITHUB_OUTPUT diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml new file mode 100644 index 00000000000..b8864e985e2 --- /dev/null +++ b/.github/actions/change-detection/action.yml @@ -0,0 +1,141 @@ +name: Detect changed files +description: Detects whether any matching files have changed in the current PR +inputs: + self: + description: The path to the calling workflow (e.g. .github/workflows/backend-unit-tests.yml). It is regarded as any category. + required: true +outputs: + self: + description: Whether the calling workflow has changed in any way + value: ${{ steps.changed-files.outputs.self_any_changed || 'true' }} + backend: + description: Whether the backend or self have changed in any way + value: ${{ steps.changed-files.outputs.backend_any_changed || 'true' }} + frontend: + description: Whether the frontend or self has changed in any way + value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }} + e2e: + description: Whether the e2e tests or self have changed in any way + value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' || + steps.changed-files.outputs.backend_any_changed == 'true' || + steps.changed-files.outputs.frontend_any_changed == 'true' || 'true' }} + dev-tooling: + description: Whether the dev tooling or self have changed in any way + value: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }} + docs: + description: Whether the docs or self have changed in any way + value: ${{ steps.changed-files.outputs.docs_any_changed || 'true' }} +runs: + using: composite + steps: + # Assumption: We've done a checkout with the actions/checkout action. + # It must persist credentials to allow the changed-files action to get more history. + - name: Detect changes + id: changed-files + if: github.event_name == 'pull_request' + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46 + with: + files_yaml: | + self: + - '.github/actions/change-detection/**' + - '${{ inputs.self }}' + backend: + - '!*.md' + - '!docs/**' + - '!.github/**' + - '.github/actions/setup-enterprise/**' + - '.github/actions/checkout/**' + - '**/go.mod' + - '**/go.sum' + - '**.go' + - 'pkg/**' + - '!pkg/**.md' + - 'apps/**' + - '!apps/**.md' + - 'build.sh' + - '.github/actions/change-detection/**' + - '**.cue' + - 'devenv/docker/blocks/*_tests/**' + - 'kindsv2/**' + - '${{ inputs.self }}' + frontend: + - '.github/actions/setup-enterprise/**' + - '.github/actions/checkout/**' + - 'public/**' + - '**.js' + - '**.jsx' + - '**.ts' + - '**.tsx' + - '**.css' + - '**.mjs' + - 'yarn.lock' + - 'package.json' + - '!**.md' + - '.github/actions/change-detection/**' + - '**.cue' + - '.prettier*' + - '.betterer*' + - '.yarnrc.yml' + - 'eslint.config.js' + - 'jest.config.js' + - 'nx.json' + - 'tsconfig.json' + - '.yarn/**' + - '${{ inputs.self }}' + e2e: + - 'e2e/**' + - '.github/actions/setup-enterprise/**' + - '.github/actions/checkout/**' + - 'emails/**' + - 'pkg/**' + - 'proto/**' + - '**/Makefile' + - 'scripts/**' + - '!scripts/drone/**' + - '!**.md' + - '.github/actions/change-detection/**' + - '**.cue' + - 'conf/**' + - 'cypress.config.js' + - '${{ inputs.self }}' + dev_tooling: + - '.github/actions/setup-enterprise/**' + - '.github/actions/checkout/**' + - '**.sh' + - '.trivyignore' + - '.prettierrc.js' + - '**/Makefile' + - 'proto/**.yaml' + - 'pkg/build/**' + - 'pkg/wire/**' + - 'scripts/**' + - '!**.md' + - '.citools/**' + - '.bingo/**' + - '.github/actions/change-detection/**' + - '${{ inputs.self }}' + docs: + - 'contribute/**' + - 'docs/**' + - '**.md' + - 'LICENSE' + - '.vale.ini' + - '.github/actions/change-detection/**' + - '${{ inputs.self }}' + - name: Print all change groups + shell: bash + run: | + echo "Self: ${{ steps.changed-files.outputs.self_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.self_all_changed_files }}" + echo "Backend: ${{ steps.changed-files.outputs.backend_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}" + echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}" + echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}" + echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}" + echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}" + echo "Dev Tooling: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.dev_tooling_all_changed_files }}" + echo "Docs: ${{ steps.changed-files.outputs.docs_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.docs_all_changed_files }}" diff --git a/.github/actions/changelog/index.js b/.github/actions/changelog/index.js index b658caf349c..7bfcfc82148 100644 --- a/.github/actions/changelog/index.js +++ b/.github/actions/changelog/index.js @@ -1,6 +1,7 @@ -import { appendFileSync, writeFileSync } from 'fs'; -import { exec as execCallback } from 'node:child_process'; -import { promisify } from 'node:util'; +import {appendFileSync, writeFileSync} from 'fs'; +import {exec as execCallback} from 'node:child_process'; +import {promisify} from 'node:util'; +import {findPreviousVersion, semverParse} from "./semver.js"; // // Github Action core utils: logging (notice + debug log levels), must escape @@ -9,35 +10,6 @@ import { promisify } from 'node:util'; const escapeData = (s) => s.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); const LOG = (msg) => console.log(`::notice::${escapeData(msg)}`); -// -// Semver utils: parse, compare, sort etc (using official regexp) -// https://regex101.com/r/Ly7O1x/3/ -// -const semverRegExp = - /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; - -const semverParse = (tag) => { - const m = tag.match(semverRegExp); - if (!m) { - return; - } - const [_, major, minor, patch, prerelease] = m; - return [+major, +minor, +patch, prerelease, tag]; -}; - -// semverCompare takes two parsed semver tags and comparest them more or less -// according to the semver specs -const semverCompare = (a, b) => { - for (let i = 0; i < 3; i++) { - if (a[i] !== b[i]) { - return a[i] < b[i] ? 1 : -1; - } - } - if (a[3] !== b[3]) { - return a[3] < b[3] ? 1 : -1; - } - return 0; -}; // Using `git tag -l` output find the tag (version) that goes semantically // right before the given version. This might not work correctly with some @@ -45,29 +17,32 @@ const semverCompare = (a, b) => { // into this action explicitly to avoid this step. const getPreviousVersion = async (version) => { const exec = promisify(execCallback); - const { stdout } = await exec('git tag -l'); - const prev = stdout + const {stdout} = await exec('git for-each-ref --sort=-creatordate --format \'%(refname:short)\' refs/tags'); + + const parsedTags = stdout .split('\n') .map(semverParse) - .filter((tag) => tag) - .sort(semverCompare) - .find((tag) => semverCompare(tag, semverParse(version)) > 0); + .filter(Boolean); + + const parsedVersion = semverParse(version); + const prev = findPreviousVersion(parsedTags, parsedVersion); if (!prev) { throw `Could not find previous git tag for ${version}`; } - return prev[4]; + return prev[5]; }; + // A helper for Github GraphQL API endpoint const graphql = async (ghtoken, query, variables) => { - const { env } = process; + const {env} = process; const results = await fetch('https://api.github.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ghtoken}`, }, - body: JSON.stringify({ query, variables }), + body: JSON.stringify({query, variables}), }); const res = await results.json(); @@ -100,7 +75,7 @@ const getCommitishDate = async (name, owner, target) => { } } `, - { name, owner, target } + {name, owner, target} ); return result.repository.object.committedDate; }; @@ -160,7 +135,7 @@ const getHistory = async (name, owner, from, to) => { let cursor; let nodes = []; - for (;;) { + for (; ;) { const result = await graphql(ghtoken, query, { name, owner, @@ -170,7 +145,7 @@ const getHistory = async (name, owner, from, to) => { }); LOG(`GraphQL: ${JSON.stringify(result)}`); nodes = [...nodes, ...result.repository.ref.compare.commits.nodes]; - const { hasNextPage, endCursor } = result.repository.ref.compare.commits.pageInfo; + const {hasNextPage, endCursor} = result.repository.ref.compare.commits.pageInfo; if (!hasNextPage) { break; } @@ -186,7 +161,7 @@ const getHistory = async (name, owner, from, to) => { // PR grouping relies on Github labels only, not on the PR contents. const getChangeLogItems = async (name, owner, from, to) => { // check if a node contains a certain label - const hasLabel = ({ labels }, label) => labels.nodes.some(({ name }) => name === label); + const hasLabel = ({labels}, label) => labels.nodes.some(({name}) => name === label); // get all the PRs between the two "commitish" items const history = await getHistory(name, owner, from, to); @@ -197,17 +172,17 @@ const getChangeLogItems = async (name, owner, from, to) => { return []; } const item = changes[0]; - const { number, url, labels } = item; + const {number, url, labels} = item; const title = item.title.replace(/^\[[^\]]+\]:?\s*/, ''); // for changelog PRs try to find a suitable category. // Note that we can not detect "deprecation notices" like that // as there is no suitable label yet. - const isBug = /fix/i.test(title) || hasLabel({ labels }, 'type/bug'); - const isBreaking = hasLabel({ labels }, 'breaking change'); + const isBug = /fix/i.test(title) || hasLabel({labels}, 'type/bug'); + const isBreaking = hasLabel({labels}, 'breaking change'); const isPlugin = - hasLabel({ labels }, 'area/grafana/ui') || - hasLabel({ labels }, 'area/grafana/toolkit') || - hasLabel({ labels }, 'area/grafana/runtime'); + hasLabel({labels}, 'area/grafana/ui') || + hasLabel({labels}, 'area/grafana/toolkit') || + hasLabel({labels}, 'area/grafana/runtime'); const author = item.commits.nodes[0].commit.author.user?.login; return { repo: name, @@ -227,7 +202,7 @@ const getChangeLogItems = async (name, owner, from, to) => { // ====================================================== LOG(`Changelog action started`); - +console.log(process.argv); const ghtoken = process.env.GITHUB_TOKEN || process.env.INPUT_GITHUB_TOKEN; if (!ghtoken) { throw 'GITHUB_TOKEN is not set and "github_token" input is empty'; @@ -286,15 +261,15 @@ const markdown = (changelog) => { : `### ${title} ${items - .map( - (item) => - `- ${item.title.replace(/^([^:]*:)/gm, '**$1**')} ${ - item.repo === 'grafana-enterprise' - ? '(Enterprise)' - : `${pullRequestLink(item.number)}${item.author ? ', ' + userLink(item.author) : ''}` - }` - ) - .join('\n')} + .map( + (item) => + `- ${item.title.replace(/^([^:]*:)/gm, '**$1**')} ${ + item.repo === 'grafana-enterprise' + ? '(Enterprise)' + : `${pullRequestLink(item.number)}${item.author ? ', ' + userLink(item.author) : ''}` + }` + ) + .join('\n')} `; // Render all present sections for the given changelog diff --git a/.github/actions/changelog/semver.js b/.github/actions/changelog/semver.js new file mode 100644 index 00000000000..461ce5195c8 --- /dev/null +++ b/.github/actions/changelog/semver.js @@ -0,0 +1,92 @@ +// +// Semver utils: parse, compare, sort etc (using official regexp) +// https://regex101.com/r/Ly7O1x/3/ +// +const semverRegExp = + /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + +export function semverParse(tag) { + const m = tag.match(semverRegExp); + if (!m) { + return; + } + const [_, major, minor, patch, prerelease, build] = m; + return [+major, +minor, +patch, prerelease, build, tag]; +}; + +// semverCompare takes two parsed semver tags and comparest them more or less +// according to the semver specs +export function semverCompare(a, b) { + for (let i = 0; i < 3; i++) { + if (a[i] !== b[i]) { + return a[i] < b[i] ? 1 : -1; + } + } + if (a[3] !== b[3]) { + return a[3] < b[3] ? 1 : -1; + } + return 0; +}; + + +// Finds the highest version that is lower than the target version. +// +// This function relies on the following invariant: versions are sorted by the release date. +// It will produce wrong result if invariant doesn't hold. +export const findPreviousVersion = (versionByDate, target) => { + let prev = null; + + for (let i = 0; i < versionByDate.length; i++) { + const version = versionByDate[i]; + + // version is greater than the target + if (semverCompare(target, version) > 0) { + continue; + } + + // we came across the target version, all versions seen previously have greater release date. + if (semverCompare(target, version) === 0 && target[4] === version[4]) { + prev = null; + continue; + } + + if (prev == null) { + prev = version; + continue; + } + + if (semverCompare(prev, version) > 0) { + prev = version; + } + } + + return prev; +}; + + +const versionsByDate = [ + "v10.4.19", "v12.0.1", "v11.6.2", "v11.5.5", "v11.4.5", "v11.3.7", "v11.2.10", "v12.0.0+security-01", "v11.2.9+security-01", "v11.3.6+security-01", + "v11.6.1+security-01", "v11.4.4+security-01", "v11.5.4+security-01", "v10.4.18+security-01", "v12.0.0", "v11.6.1", + "v11.5.4", "v11.4.4", "v11.3.6", "v11.2.9", "v10.4.18", "v11.6.0+security-01", "v11.5.3+security-01", "v11.4.3+security-01", + "v11.3.5+security-01", "v11.2.8+security-01", "v10.4.17+security-01", "v11.2.8", "v11.6.0", "v11.5.2", "v11.4.2", + "v11.3.4", "v11.2.7", "v11.1.12", "v11.0.11", "v10.4.16", "v11.5.1", "v11.5.0", "v11.3.3", "v11.1.11", "v11.2.6", + "v11.0.10", "v10.4.15", "v11.4.1", "v11.4.0", "v11.3.2", "v11.2.5", "v11.1.10", "v11.0.9", "v10.4.14", "v11.3.1", + "v11.2.4", "v11.1.9", "v11.0.8", "v10.4.13", "v11.0.2", "v10.4.6", "v10.3.8", "v10.2.9", "v11.1.0", "v11.0.1", + "v10.4.5", "v10.3.7", "v10.2.8", "v9.5.20", "v10.4.4", "v9.5.19", "v10.1.10", "v10.2.7", "v10.3.6", "v10.4.3", + "v11.0.0", "v10.4.2", "v11.0.0-preview", "v10.1.9", "v10.0.13", "v9.2.0", "v9.1.8", +].map(semverParse); + +function test(version, expected) { + const v1 = semverParse(version); + const prev = findPreviousVersion(versionsByDate, v1); + + const failureMessage = `FAIILED. Expected ${expected}, but was ${prev[5]}`; + + console.log(`Test ${version}, ${prev[5] === expected ? 'PASSED' : failureMessage}`); +} + +test("v11.5.4+security-01", "v11.5.4"); +test("v11.5.4", "v11.5.3+security-01"); +test("v12.0.0", "v11.6.1"); +test("v12.0.0+security-01", "v12.0.0"); +test("v11.0.0", "v11.0.0-preview"); diff --git a/.github/actions/check-jobs/action.yml b/.github/actions/check-jobs/action.yml new file mode 100644 index 00000000000..b4551730b96 --- /dev/null +++ b/.github/actions/check-jobs/action.yml @@ -0,0 +1,48 @@ +name: Check jobs results +description: Checks if any jobs have failed and exits with error if failures are found. Use to check the results of matrix test runs. +inputs: + needs: + description: JSON string containing the needs context from the workflow + required: true + failure-message: + description: Custom message to display when failures are found + required: false + default: "One or more jobs have failed" + success-message: + description: Custom message to display when all jobs pass + required: false + default: "All jobs passed successfully" +outputs: + any-failed: + description: Whether any jobs failed + value: ${{ steps.check-jobs.outputs.any-failed }} + +runs: + using: "composite" + steps: + - name: Check test suites + id: check-jobs + shell: bash + env: + NEEDS: ${{ inputs.needs }} + FAILURE_MSG: ${{ inputs.failure-message }} + SUCCESS_MSG: ${{ inputs.success-message }} + run: | + set -euo pipefail + + # Print the needs context, debugging + echo "$NEEDS" | jq + + # Extract failures + FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')" + + # Check if there are any failures + if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then + echo "❌ $FAILURE_MSG" + echo "Failed suites:" + echo "$FAILURES" | jq -r 'to_entries[] | "- \(.key): \(.value)"' + echo "any-failed=true" >> "$GITHUB_OUTPUT" + exit 1 + fi + + echo "✅ $SUCCESS_MSG" diff --git a/.github/actions/setup-enterprise/action.yml b/.github/actions/setup-enterprise/action.yml index 37c09911fc8..134a7e7f004 100644 --- a/.github/actions/setup-enterprise/action.yml +++ b/.github/actions/setup-enterprise/action.yml @@ -34,9 +34,9 @@ runs: GH_TOKEN: ${{ steps.generate_token.outputs.token }} run: | git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-enterprise.git ../grafana-enterprise; - + cd ../grafana-enterprise - + if git checkout ${GITHUB_HEAD_REF}; then echo "checked out ${GITHUB_HEAD_REF}" elif git checkout ${GITHUB_BASE_REF}; then @@ -44,5 +44,5 @@ runs: else git checkout main fi - - ./build.sh + + QUIET=1 ./build.sh diff --git a/.github/actions/test-coverage-processor/action.yml b/.github/actions/test-coverage-processor/action.yml deleted file mode 100644 index bd2458020c3..00000000000 --- a/.github/actions/test-coverage-processor/action.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: 'Go Coverage Processor' -description: 'Process Go test coverage files and generate reports' - -inputs: - test-type: - description: 'Type of test (e.g., be-unit, be-integration)' - required: true - type: string - coverage-file: - description: 'Path to the Go coverage file (.cov)' - required: true - type: string - codecov-token: - description: 'Token for CodeCov (required for CodeCov reporting)' - required: false - default: '' - codecov-flag: - description: 'Flag to categorize the upload to CodeCov' - required: false - default: '' - codecov-name: - description: 'Custom name for the upload to CodeCov' - required: false - default: '' - -runs: - using: 'composite' - steps: - - name: Process Go coverage output - shell: bash - env: - COVERAGE_FILE: ${{ inputs.coverage-file }} - run: | - # Ensure valid coverage file even if empty - if [ ! -s "$COVERAGE_FILE" ]; then - echo "Coverage file is empty, creating a minimal valid file" - echo "mode: set" > "$COVERAGE_FILE" - fi - - - name: Report coverage to CodeCov - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5 - if: inputs.codecov-token != '' - with: - files: ${{ inputs.coverage-file }} - flags: ${{ inputs.codecov-flag || inputs.test-type }} - name: ${{ inputs.codecov-name || inputs.test-type }} - slug: grafana/grafana - # This URL doesn't use the Google auth, but is much more locked down. As such, it requires OIDC or a CodeCov-provided token to do anything. - url: https://codecov-webhook.grafana-dev.net - token: ${{ inputs.codecov-token }} diff --git a/.github/commands.json b/.github/commands.json index f92e29a215b..0e78a9504e7 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -128,7 +128,7 @@ "name": "datasource/Loki", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/203" + "url": "https://github.com/orgs/grafana/projects/457" } }, { @@ -160,7 +160,7 @@ "name": "datasource/Elasticsearch", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/97" + "url": "https://github.com/orgs/grafana/projects/190" } }, { @@ -488,7 +488,23 @@ "name": "area/transformations", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/56" + "url": "https://github.com/orgs/grafana/projects/908" + } + }, + { + "type": "label", + "name": "area/correlations", + "action": "addToProject", + "addToProject": { + "url": "https://github.com/orgs/grafana/projects/908" + } + }, + { + "type": "label", + "name": "area/expressions/sql", + "action": "addToProject", + "addToProject": { + "url": "https://github.com/orgs/grafana/projects/908" } }, { @@ -648,7 +664,7 @@ "name": "area/frontend/library-panels", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/202" + "url": "https://github.com/orgs/grafana/projects/482" } }, { @@ -880,7 +896,7 @@ "name": "area/dashboard/library-panel", "action": "addToProject", "addToProject": { - "url": "https://github.com/orgs/grafana/projects/202" + "url": "https://github.com/orgs/grafana/projects/482" } }, { @@ -1194,5 +1210,13 @@ "addToProject": { "url": "https://github.com/orgs/grafana/projects/699" } + }, + { + "type": "label", + "name": "type/docs", + "action": "addToProject", + "addToProject": { + "url": "https://github.com/orgs/grafana/projects/69" + } } ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 263a75f43ab..3b1a02d0adb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,7 @@ updates: directories: - "/" - "/apps/playlist" + - "/apps/secret" - "/apps/investigations" - "/pkg/aggregator" - "/pkg/apimachinery" diff --git a/.github/license_finder.yaml b/.github/license_finder.yaml new file mode 100644 index 00000000000..f2fc8a144c4 --- /dev/null +++ b/.github/license_finder.yaml @@ -0,0 +1,128 @@ +--- +- - :permit + - MIT + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:11:50.696368005 Z +- - :permit + - Apache 2.0 + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:12:09.344787957 Z +- - :permit + - New BSD + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:12:09.344787957 Z +- - :permit + - Simplified BSD + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:12:09.344787957 Z +- - :permit + - Mozilla Public License 2.0 + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:12:09.344787957 Z +- - :permit + - ISC + - :who: Carl Bergquist + :why: Compatible license + :versions: [] + :when: 2021-03-25 11:12:09.344787957 +- - :license + - github.com/grafana/alerting + - GNU Affero GPL + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/advisor + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/alerting/notifications + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/dashboard + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/folder + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/investigations + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/apps/playlist + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/aggregator + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/apimachinery + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/apis/secret + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/apiserver + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/promlib + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z +- - :license + - github.com/grafana/grafana/pkg/semconv + - unknown + - :who: Carl Bergquist + :why: repository is owned by Grafana Labs + :versions: [] + :when: 2025-05-03 13:10:00.000000000 Z \ No newline at end of file diff --git a/.github/metrics-collector.json b/.github/metrics-collector.json deleted file mode 100644 index fc717a74908..00000000000 --- a/.github/metrics-collector.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "queries": [ - { - "name": "type_bug", - "query": "label:\"type/bug\" is:issue is:open" - }, - { - "name": "type_docs", - "query": "label:\"type/docs\" is:issue is:open" - }, - { - "name": "needs_investigation", - "query": "label:\"needs investigation\" is:issue is:open" - }, - { - "name": "needs_more_info", - "query": "label:\"needs more info\" is:issue is:open" - }, - { - "name": "triage_needs_confirmation", - "query": "label:\"triage/needs-confirmation\" is:issue is:open" - }, - { - "name": "unlabeled", - "query": "is:open is:issue no:label" - }, - { - "name": "open_prs", - "query": "is:open is:pull-request" - } - ] -} \ No newline at end of file diff --git a/.github/pr-commands.json b/.github/pr-commands.json index 6525ad2ca26..f97a0738700 100644 --- a/.github/pr-commands.json +++ b/.github/pr-commands.json @@ -253,38 +253,6 @@ "action": "updateLabel", "addLabel": "area/alerting" }, - { - "type": "label", - "name": "area/alerting", - "action": "addToProject", - "addToProject": { - "url": "https://github.com/orgs/grafana/projects/52" - } - }, - { - "type": "author", - "name": "pr/external", - "notMemberOf": { - "org": "grafana" - }, - "ignoreList": [ - "renovate[bot]", - "dependabot[bot]", - "grafana-delivery-bot[bot]", - "grafanabot", - "alerting-team[bot]" - ], - "action": "updateLabel", - "addLabel": "pr/external" - }, - { - "type": "label", - "name": "type/docs", - "action": "addToProject", - "addToProject": { - "url": "https://github.com/orgs/grafana/projects/69" - } - }, { "type": "changedfiles", "matches": [ diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 13edbbb0943..aa28c25a240 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -2,6 +2,15 @@ extends: ["config:recommended"], enabledManagers: ["npm"], ignoreDeps: [ + // ignoring these until we can upgrade to react 19 + // see epic here: https://github.com/grafana/grafana/issues/98813 + '@types/react', + '@types/react-dom', + 'eslint-plugin-react-hooks', + 'react', + 'react-dom', + 'react-refresh', + "@types/history", // this can be removed entirely when we upgrade history since v5 exposes types directly "history", // we should bump this together with react-router-dom (see https://github.com/grafana/grafana/issues/76744) "react-router", // we should bump this together with history and react-router-dom diff --git a/.github/workflows/actionlint-format.txt b/.github/workflows/actionlint-format.txt new file mode 100644 index 00000000000..6e60cd50399 --- /dev/null +++ b/.github/workflows/actionlint-format.txt @@ -0,0 +1,66 @@ +{ + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "GitHub Actions lint", + "version": {{ getVersion | json }}, + "informationUri": "https://github.com/rhysd/actionlint", + "rules": [ + {{$first := true}} + {{range $ := allKinds }} + {{if $first}}{{$first = false}}{{else}},{{end}} + { + "id": {{json $.Name}}, + "name": {{$.Name | toPascalCase | json}}, + "defaultConfiguration": { + "level": "error" + }, + "properties": { + "description": {{json $.Description}}, + "queryURI": "https://github.com/rhysd/actionlint/blob/main/docs/checks.md" + }, + "fullDescription": { + "text": {{json $.Description}} + }, + "helpUri": "https://github.com/rhysd/actionlint/blob/main/docs/checks.md" + } + {{end}} + ] + } + }, + "results": [ + {{$first := true}} + {{range $ := .}} + {{if $first}}{{$first = false}}{{else}},{{end}} + { + "ruleId": {{json $.Kind}}, + "message": { + "text": {{json $.Message}} + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": {{json $.Filepath}}, + "uriBaseId": "%SRCROOT%" + }, + "region": { + "startLine": {{$.Line}}, + "startColumn": {{$.Column}}, + "endColumn": {{$.EndColumn}}, + "snippet": { + "text": {{json $.Snippet}} + } + } + } + } + ] + } + {{end}} + ] + } + ] +} \ No newline at end of file diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 00000000000..de1051ef729 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,60 @@ +# This workflow depends on the ./actionlint-format.txt file. It is MIT licensed (thanks, rhysd!): https://github.com/rhysd/actionlint/blob/2ab3a12c7848f6c15faca9a92612ef4261d0e370/testdata/format/sarif_template.txt +name: Actionlint + +on: + push: + branches: + - main + - release-* + pull_request: + types: + - opened + - synchronize + - reopened + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + run-actionlint: + name: Lint GitHub Actions files + runs-on: ubuntu-latest + permissions: + contents: read # to check out the code + actions: read # to read the workflow files + security-events: write # for uploading the SARIF report + + env: + ACTIONLINT_VERSION: 1.7.7 + # curl -LXGET https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_checksums.txt | grep linux_amd64 + CHECKSUM: 023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757 + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + + # GitHub Actions only runs x86_64. This will break if that assumption changes. + - name: Download Actionlint + run: | + set -euo pipefail + curl -OLXGET https://github.com/rhysd/actionlint/releases/download/v"${ACTIONLINT_VERSION}"/actionlint_"${ACTIONLINT_VERSION}"_linux_amd64.tar.gz + echo "${CHECKSUM} actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | sha256sum -c - + tar xzf actionlint_"${ACTIONLINT_VERSION}"_linux_amd64.tar.gz + test -f actionlint + chmod +x actionlint + + - name: Run Actionlint + run: ./actionlint -format "$(cat .github/workflows/actionlint-format.txt)" | tee results.sarif + + - name: Upload to GitHub security events + if: success() || failure() + # If there are security problems, GitHub will automatically comment on the PR for us. + uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + with: + sarif_file: results.sarif + category: actionlint diff --git a/.github/workflows/alerting-update-module.yml b/.github/workflows/alerting-update-module.yml index 5bbf260e64a..dce80d065e4 100644 --- a/.github/workflows/alerting-update-module.yml +++ b/.github/workflows/alerting-update-module.yml @@ -37,7 +37,7 @@ jobs: id: current-commit run: | FROM_COMMIT=$(go list -m -json github.com/grafana/alerting | jq -r '.Version' | grep -oP '(?<=-)[a-f0-9]+$') - echo "from_commit=$FROM_COMMIT" >> $GITHUB_OUTPUT + echo "from_commit=$FROM_COMMIT" >> "$GITHUB_OUTPUT" - name: Get current branch name id: current-branch-name @@ -47,14 +47,14 @@ jobs: id: latest-commit env: GH_TOKEN: ${{ github.token }} + BRANCH: ${{ steps.current-branch-name.outputs.name }} run: | - BRANCH="${{ steps.current-branch-name.outputs.name }}" - TO_COMMIT=$(gh api repos/grafana/alerting/commits/$BRANCH --jq '.sha') + TO_COMMIT="$(gh api repos/grafana/alerting/commits/"$BRANCH" --jq '.sha')" if [ -z "$TO_COMMIT" ]; then echo "Branch $BRANCH not found in alerting repo, falling back to main branch" exit 1 fi - echo "to_commit=$TO_COMMIT" >> $GITHUB_OUTPUT + echo "to_commit=$TO_COMMIT" >> "$GITHUB_OUTPUT" - name: Compare commit hashes run: | @@ -74,26 +74,31 @@ jobs: id: check-commits env: GH_TOKEN: ${{ github.token }} + FROM_COMMIT: ${{ steps.current-commit.outputs.from_commit }} + TO_COMMIT: ${{ steps.latest-commit.outputs.to_commit }} run: | - # get all commits that contains 'Alerting:' in the message - ALERTING_COMMITS=$(gh api repos/grafana/alerting/compare/${{ steps.current-commit.outputs.from_commit }}...${{ steps.latest-commit.outputs.to_commit }} \ - --jq '.commits[].commit.message | split("\n")[0]') || true - + # get all commits that contains 'Alerting:' in the message + ALERTING_COMMITS="$(gh api repos/grafana/alerting/compare/"$FROM_COMMIT"..."$TO_COMMIT" \ + --jq '.commits[].commit.message | split("\n")[0]')" || true + # Use printf instead of echo -e for better multiline handling printf "%s\n" "$ALERTING_COMMITS" - + # make the list for markdown and replace PR numbers with links - ALERTING_COMMITS_FORMATTED=$(echo "$ALERTING_COMMITS" | while read -r line; do echo "- $line" | sed -E 's/\(#([0-9]+)\)/[#\1](https:\/\/github.com\/grafana\/grafana\/pull\/\1)/g'; done) - - echo "alerting_commits<> $GITHUB_OUTPUT - echo "$ALERTING_COMMITS_FORMATTED" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + ALERTING_COMMITS_FORMATTED="$(echo "$ALERTING_COMMITS" | while read -r line; do echo "- $line" | sed -E 's/\(#([0-9]+)\)/[#\1](https:\/\/github.com\/grafana\/grafana\/pull\/\1)/g'; done)" + + { + echo "alerting_commits<> "$GITHUB_OUTPUT" - name: Update alerting module env: GOSUMDB: off + PINNED_COMMIT: ${{ steps.latest-commit.outputs.to_commit }} run: | - go get github.com/grafana/alerting@${{ steps.latest-commit.outputs.to_commit }} + go get github.com/grafana/alerting@"$PINNED_COMMIT" make update-workspace - id: get-secrets @@ -124,7 +129,7 @@ jobs: Compare changes: https://github.com/grafana/alerting/compare/${{ steps.current-commit.outputs.from_commit }}...${{ steps.latest-commit.outputs.to_commit }}
Commits - + ${{ steps.check-commits.outputs.alerting_commits }}
@@ -132,6 +137,10 @@ jobs: Created by: [GitHub Action Job](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - name: Add PR URL to Summary if: steps.create-pr.outputs.pull-request-url != '' + env: + PR_URL: ${{ steps.create-pr.outputs.pull-request-url }} run: | - echo "## Pull Request Created" >> $GITHUB_STEP_SUMMARY - echo "🔗 [View Pull Request](${{ steps.create-pr.outputs.pull-request-url }})" >> $GITHUB_STEP_SUMMARY + { + echo "## Pull Request Created" + echo "🔗 [View Pull Request]($PR_URL)" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/analytics-events-report.yml b/.github/workflows/analytics-events-report.yml index 42f601b793b..af265157337 100644 --- a/.github/workflows/analytics-events-report.yml +++ b/.github/workflows/analytics-events-report.yml @@ -3,9 +3,13 @@ name: Analytics Events Report on: workflow_dispatch: +permissions: {} + jobs: generate-report: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index caf230e1f42..1c839432829 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -2,16 +2,10 @@ name: Backend Unit Tests on: pull_request: - paths-ignore: - - 'docs/**' - - '**/*.md' push: branches: - main - release-*.*.* - paths-ignore: - - 'docs/**' - - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,10 +14,29 @@ concurrency: permissions: {} jobs: + detect-changes: + name: Detect whether code changed + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.backend }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/backend-unit-tests.yml + grafana: - # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # Run this workflow only for PRs from forks # the `pr-backend-unit-tests-enterprise` workflow will run instead - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true + needs: detect-changes + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.changed == 'true' strategy: matrix: shard: [ @@ -47,18 +60,18 @@ jobs: uses: actions/setup-go@v5 with: go-version-file: go.mod - - name: Generate Go code - run: make gen-go - name: Run unit tests env: SHARD: ${{ matrix.shard }} run: | + set -euo pipefail readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/shard.sh -N"$SHARD")" go test -short -timeout=30m "${PACKAGES[@]}" grafana-enterprise: # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + needs: detect-changes + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.changed == 'true' strategy: matrix: shard: [ @@ -73,6 +86,7 @@ jobs: contents: read id-token: write steps: + # Set up repository clone - name: Checkout code uses: actions/checkout@v4 with: @@ -85,11 +99,48 @@ jobs: uses: ./.github/actions/setup-enterprise with: github-app-name: 'grafana-ci-bot' - - name: Generate Go code - run: make gen-go + + # Prepare what we need to upload test results + - run: echo "RESULTS_FILE=$(date --rfc-3339=seconds --utc | sed -s 's/ /-/g')_${SHARD/\//_}.xml" >> "$GITHUB_ENV" + env: + SHARD: ${{ matrix.shard }} + - run: go install github.com/jstemmer/go-junit-report/v2@85bf4716ac1f025f2925510a9f5e9f5bb347c009 + + # Run code - name: Run unit tests env: SHARD: ${{ matrix.shard }} run: | + set -euo pipefail + readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/shard.sh -N"$SHARD")" + # This tee requires pipefail to be set, otherwise `go test`'s exit code is thrown away. + # That means having no `-o pipefail` => failing tests => exit code 0, which is wrong. go test -short -timeout=30m "${PACKAGES[@]}" + + # This is the job that is actually required by rulesets. + # We need to require EITHER the OSS or the Enterprise job to pass. + # However, if one is skipped, GitHub won't flat-map the shards, + # so they won't be accepted by a ruleset. + required-backend-unit-tests: + needs: + - grafana + - grafana-enterprise + # always() is the best function here. + # success() || failure() will skip this function if any need is also skipped. + # That means conditional test suites will fail the entire requirement check. + if: always() + + name: All backend unit tests complete + runs-on: ubuntu-latest + steps: + - name: Check test suites + env: + NEEDS: ${{ toJson(needs) }} + run: | + FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')" + echo "$FAILURES" + if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then + exit 1 + fi + echo "All OK!" diff --git a/.github/workflows/backport-trigger.yml b/.github/workflows/backport-trigger.yml new file mode 100644 index 00000000000..3b3758c573b --- /dev/null +++ b/.github/workflows/backport-trigger.yml @@ -0,0 +1,47 @@ +# We need secrets to backport, but they're not available for actions ran by forks. +# So this workflow is used as a 'trigger', which the backport-workflow.yml will with +# via workflow_run + +name: Backport (trigger) +on: + pull_request: + types: + - closed + - labeled + +permissions: {} + +jobs: + trigger: + # Only run this job if the PR has been merged and has a label containing "backport v" + if: | + github.repository == 'grafana/grafana' && + github.event.pull_request.merged == true && + contains(join(github.event.pull_request.labels.*.name, ','), 'backport v') + runs-on: ubuntu-latest + steps: + # TODO: save this as job summary instead? + - name: Trigger + run: | + echo "Triggering workflow" + echo "See https://github.com/${{ github.repository }}/actions/workflows/workflow_run.yml for progress" + + # Create a JSON artifact with details of this PR to pass to the backport workflow. + # The { action: 'labelled', label: 'backport-1.23.x' } can only be determined from this event payload, + # and is needed to do a backport after a PR has been merged + # + # Important that we don't run *anything* from the PR which could modify the backport_data.json file + - name: Create action data + run: | + jq '{ + action: .action, + label: .label.name, + pr_number: .number, + }' "$GITHUB_EVENT_PATH" > /tmp/pr_info.json + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: pr_info + path: /tmp/pr_info.json + retention-days: 1 diff --git a/.github/workflows/backport-workflow.yml b/.github/workflows/backport-workflow.yml new file mode 100644 index 00000000000..925c6c54bb3 --- /dev/null +++ b/.github/workflows/backport-workflow.yml @@ -0,0 +1,88 @@ +# Runs the actual backport, after being triggered by the backport-trigger.yml workflow. + +name: Backport (workflow) +run-name: "Backport for ${{ github.event.workflow_run.head_branch }} #${{ github.event.workflow_run.run_number }}" +on: + workflow_run: # zizmor: ignore[dangerous-triggers] backport-trigger.yml does not run any user code + workflows: ["Backport (trigger)"] + types: + - completed + +permissions: {} + +jobs: + backport: + # Only run this job if the triggering workflow was not skipped (and on grafana repo) + if: github.repository == 'grafana/grafana' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + id-token: write + actions: read + steps: + - name: Get vault secrets + id: secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + export_env: false + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + APP_PEM=delivery-bot-app:PRIVATE_KEY + + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ fromJSON(steps.secrets.outputs.secrets).APP_PEM }} + + - name: Download PR info artifact + uses: actions/download-artifact@v4 + id: download-pr-info + with: + github-token: ${{ github.token }} + run-id: ${{ github.event.workflow_run.id }} + name: pr_info + + - name: Get PR info + id: pr-info + env: + PR_INFO_FILE: ${{ steps.download-pr-info.outputs.download-path }}/pr_info.json + # jq-magic to convert the JSON object into a list of key=value pairs for $GITHUB_OUTPUT + run: + jq -r 'to_entries[] | select(.value | type != "object") | "\(.key)=\(.value)"' "$PR_INFO_FILE" >> "$GITHUB_OUTPUT" + + - name: Print PR info + env: + PR_ACTION: ${{ steps.pr-info.outputs.action }} + PR_LABEL: ${{ steps.pr-info.outputs.label }} + PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }} + run: | + echo "PR action: $PR_ACTION" + echo "PR label: $PR_LABEL" + echo "PR number: $PR_NUMBER" + + - name: Checkout Grafana + uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + fetch-depth: 2 + fetch-tags: false + token: ${{ steps.generate_token.outputs.token }} + persist-credentials: true + + - name: Configure git user + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local --add --bool push.autoSetupRemote true + + - name: Run backport + uses: grafana/grafana-github-actions-go/backport@dev + with: + token: ${{ steps.generate_token.outputs.token }} + # If triggered by being labelled, only backport that label. + # Otherwise, the action will backport all labels. + pr_label: ${{ steps.pr-info.outputs.action == 'labeled' && steps.pr-info.outputs.label || '' }} + pr_number: ${{ steps.pr-info.outputs.pr_number }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index 673dc228fc2..00000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Backport PR Creator -on: - pull_request_target: - types: - - closed - - labeled - -permissions: - contents: write - pull-requests: write - -jobs: - main: - if: github.repository == 'grafana/grafana' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 # 4.2.2 - with: - persist-credentials: false - - run: git config --local user.name "github-actions[bot]" - - run: git config --local user.email "github-actions[bot]@users.noreply.github.com" - - run: git config --local --add --bool push.autoSetupRemote true - - name: Set remote URL - env: - GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" - - name: Run backport - uses: grafana/grafana-github-actions-go/backport@main # zizmor: ignore[unpinned-uses] - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 39fa0566d86..e9c902fe484 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -44,4 +44,4 @@ jobs: git add . git commit -m "bump version ${VERSION}" git push - gh pr create --dry-run=$DRY_RUN -l "type/ci" -l "no-changelog" -B "$REF_NAME" --title "Release: Bump version to ${VERSION}" --body "Updated version to ${VERSION}" + gh pr create --dry-run="$DRY_RUN" -l "type/ci" -l "no-changelog" -B "$REF_NAME" --title "Release: Bump version to ${VERSION}" --body "Updated version to ${VERSION}" diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 8e7fe54f018..e83ba74642e 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -22,11 +22,10 @@ on: required: false default: false type: boolean - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true + work_branch: + required: false + type: string + description: "Use specific branch for changelog" workflow_dispatch: inputs: @@ -50,6 +49,10 @@ on: required: false default: false type: boolean + work_branch: + required: false + type: string + description: "Use specific branch for changelog" permissions: {} @@ -67,25 +70,32 @@ jobs: contents: write pull-requests: write steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Checkout Grafana repo" uses: "actions/checkout@v4" with: ref: main sparse-checkout: | .github/workflows + .github/actions CHANGELOG.md .nvmrc .prettierignore .prettierrc.js fetch-depth: 0 fetch-tags: true - persist-credentials: false - name: Setup nodejs environment uses: actions/setup-node@v4 with: @@ -96,7 +106,20 @@ jobs: git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local --add --bool push.autoSetupRemote true - name: "Create branch" - run: git checkout -b "changelog/${RUN_ID}/${VERSION}" + run: | + if [[ "$WORK_BRANCH" == '' ]]; then + git switch -c "changelog/${RUN_ID}/${VERSION}" + exit 0 + fi + + # Checkout the changelog branch if exists, otherwise create a new one + if git show-ref --verify --quiet "refs/remotes/origin/$WORK_BRANCH"; then + git switch --track "origin/$WORK_BRANCH" + else + git switch -c "$WORK_BRANCH" + fi + env: + WORK_BRANCH: ${{ inputs.work_branch }} - name: "Generate changelog" id: changelog uses: ./.github/actions/changelog @@ -140,16 +163,29 @@ jobs: - name: "Commit changelog changes" run: git add CHANGELOG.md && git commit --allow-empty -m "Update changelog" CHANGELOG.md - name: "git push" - if: ${{ inputs.dry_run }} != true + if: inputs.dry_run != true run: git push - name: "Create changelog PR" - run: > - gh pr create \ - --dry-run=${DRY_RUN} \ - --label "no-backport" \ - --label "no-changelog" \ - -B "${TARGET}" \ - --title "Release: update changelog for ${VERSION}" \ - --body "Changelog changes for release ${VERSION}" + run: | + if gh pr view &>/dev/null; then + echo "Changelog pr has already been created" + else + + gh pr create \ + --dry-run="${DRY_RUN}" \ + --label "no-backport" \ + --label "no-changelog" \ + -B "${TARGET}" \ + --title "Release: update changelog for ${TARGET}" \ + --body "Changelog changes for release versions:" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Add release version to PR description" + if: inputs.dry_run != true + run: | + gh pr view --json body --jq .body > pr_body.md + echo " - ${VERSION}" >> pr_body.md + gh pr edit --body-file pr_body.md env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeowners-validator.yml b/.github/workflows/codeowners-validator.yml index 41afde3a822..da01ffcd3c4 100644 --- a/.github/workflows/codeowners-validator.yml +++ b/.github/workflows/codeowners-validator.yml @@ -4,9 +4,13 @@ on: pull_request: branches: [ main ] +permissions: {} + jobs: codeowners-validator: runs-on: ubuntu-latest + permissions: + contents: read steps: # Checks-out your repository, which is validated in the next step - uses: actions/checkout@v4 @@ -23,7 +27,7 @@ jobs: # "The comma-separated list of experimental checks that should be executed. By default, all experimental checks are turned off. Possible values: notowned,avoid-shadowing" experimental_checks: "notowned,avoid-shadowing" - + # The repository path in which CODEOWNERS file should be validated." repository_path: "." @@ -37,4 +41,4 @@ jobs: owner_checker_allow_unowned_patterns: "false" # Specifies whether only teams are allowed as owners of files. - owner_checker_owners_must_be_teams: "false" + owner_checker_owners_must_be_teams: "false" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c16c5eb353e..a07cb222898 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,8 +33,9 @@ jobs: fail-fast: false matrix: # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['javascript', 'go', 'python'] + # Supported options are listed here + # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#changing-the-languages-that-are-analyzed + language: ['actions', 'javascript', 'go'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 3c3987b549b..2c0f8586132 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -24,7 +24,7 @@ jobs: id: check shell: bash run: | - if [ "${{ github.repository }}" == "grafana/grafana" ] && [ -n "${{ secrets.GRAFANA_MISC_STATS_API_KEY }}" ]; then + if [ "${{ github.repository }}" == "grafana/grafana" ]; then echo "has-secrets=1" >> "$GITHUB_OUTPUT" fi @@ -42,15 +42,15 @@ jobs: with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Checkout Actions uses: actions/checkout@v4 # v4.2.2 @@ -65,6 +65,6 @@ jobs: - name: Run Commands uses: ./actions/commands with: - metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}} + metricsWriteAPIKey: "" token: ${{ steps.generate_token.outputs.token }} configPath: commands diff --git a/.github/workflows/community-release.yml b/.github/workflows/community-release.yml index 73c72749baa..bdf079f2217 100644 --- a/.github/workflows/community-release.yml +++ b/.github/workflows/community-release.yml @@ -11,11 +11,6 @@ on: required: false default: false description: When enabled, this workflow will print a preview instead of creating an actual post. - secrets: - GRAFANA_MISC_STATS_API_KEY: - required: true - GRAFANABOT_FORUM_KEY: - required: true workflow_dispatch: inputs: version: @@ -30,17 +25,25 @@ on: permissions: contents: read + id-token: write jobs: main: runs-on: ubuntu-latest steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/repo/grafana/grafana/community_release path in Vault + repo_secrets: | + GRAFANABOT_FORUM_KEY=community_release:GRAFANABOT_FORUM_KEY + - name: Run community-release (manually invoked) - uses: grafana/grafana-github-actions-go/community-release@main # zizmor: ignore[unpinned-uses] + uses: grafana/grafana-github-actions-go/community-release@main with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.version }} - metrics_api_key: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} - community_api_key: ${{ secrets.GRAFANABOT_FORUM_KEY }} + community_api_key: ${{ env.GRAFANABOT_FORUM_KEY }} community_api_username: grafanabot dry_run: ${{ inputs.dry_run }} diff --git a/.github/workflows/core-plugins-build-and-release.yml b/.github/workflows/core-plugins-build-and-release.yml index 66447cb9ffa..6b05b9863c8 100644 --- a/.github/workflows/core-plugins-build-and-release.yml +++ b/.github/workflows/core-plugins-build-and-release.yml @@ -48,7 +48,7 @@ jobs: persist-credentials: false - name: Verify inputs run: | - if [ -z $PLUGIN_ID ]; then echo "Missing plugin ID"; exit 1; fi + if [ -z "$PLUGIN_ID" ]; then echo "Missing plugin ID"; exit 1; fi - id: get-secrets uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] with: @@ -72,13 +72,13 @@ jobs: shell: bash id: get_dir run: | - dir=$(dirname \ - $(egrep -lir --include=plugin.json --exclude-dir=dist \ - '"id": "${PLUGIN_ID}"' \ + dir="$(dirname \ + "$(grep -Elir --include=plugin.json --exclude-dir=dist \ + '"id": "'"${PLUGIN_ID}"'"' \ public/app/plugins \ - ) \ - ) - echo "dir=${dir}" >> $GITHUB_OUTPUT + )" \ + )" + echo "dir=${dir}" >> "$GITHUB_OUTPUT" - name: Install frontend dependencies shell: bash working-directory: ${{ steps.get_dir.outputs.dir }} @@ -88,17 +88,17 @@ jobs: shell: sh working-directory: ${{ steps.get_dir.outputs.dir }} run: | - [ ! -d ./bin ] && mkdir -pv ./bin || true - curl -fL -o ./bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v$GRABPL_VERSION/grabpl + mkdir -pv ./bin + curl -fL -o ./bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v"$GRABPL_VERSION"/grabpl chmod 0755 ./bin/grabpl - name: Check backend id: check_backend shell: bash run: | - if egrep -qr --include=main.go 'datasource.Manage\("$PLUGIN_ID"' pkg/tsdb; then - echo "has_backend=true" >> $GITHUB_OUTPUT + if grep -Eqr --include=main.go 'datasource.Manage\('"$PLUGIN_ID" pkg/tsdb; then + echo "has_backend=true" >> "$GITHUB_OUTPUT" else - echo "has_backend=false" >> $GITHUB_OUTPUT + echo "has_backend=false" >> "$GITHUB_OUTPUT" fi - name: Setup golang environment uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 @@ -149,6 +149,8 @@ jobs: - name: build:frontend shell: bash id: build_frontend + env: + OUTPUT_DIR: ${{ steps.get_dir.outputs.dir }} run: | command="plugin:build:commit" if [ "$GITHUB_REF" != "refs/heads/main" ]; then @@ -156,15 +158,15 @@ jobs: command="plugin:build" fi yarn $command --scope="@grafana-plugins/$PLUGIN_ID" - version=$(cat ${{ steps.get_dir.outputs.dir }}/dist/plugin.json | jq -r .info.version) - echo "version=${version}" >> $GITHUB_OUTPUT + version="$(jq -r .info.version "$OUTPUT_DIR"/dist/plugin.json)" + echo "version=${version}" >> "$GITHUB_OUTPUT" - name: build:backend if: steps.check_backend.outputs.has_backend == 'true' shell: bash env: VERSION: ${{ steps.build_frontend.outputs.version }} run: | - make build-plugin-go PLUGIN_ID=$PLUGIN_ID + make build-plugin-go PLUGIN_ID="$PLUGIN_ID" - name: package working-directory: ${{ steps.get_dir.outputs.dir }} run: | @@ -177,16 +179,17 @@ jobs: env: GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }} VERSION: ${{ steps.build_frontend.outputs.version }} + GCOM_API: ${{ env.GCOM_API }} run: | - api_res=$(curl -X 'GET' -H "Authorization: Bearer $GCOM_TOKEN" \ - '${{ env.GCOM_API}}/api/plugins/$PLUGIN_ID?version=$VERSION' \ - -H 'accept: application/json') - api_res_code=$(echo $api_res | jq -r .code) + api_res="$(curl -X 'GET' -H "Authorization: Bearer $GCOM_TOKEN" \ + "$GCOM_API/api/plugins/$PLUGIN_ID?version=$VERSION" \ + -H 'accept: application/json')" + api_res_code="$(echo "$api_res" | jq -r .code)" if [ "$api_res_code" = "NotFound" ]; then echo "No existing release found" else echo "Expecting a missing release, got:" - echo $api_res + echo "$api_res" exit 1 fi - name: store build artifacts @@ -197,55 +200,46 @@ jobs: - name: Publish release to Google Cloud Storage working-directory: ${{ steps.get_dir.outputs.dir }} env: - VERSION: ${{ steps.build_frontend.outputs.version }} + VERSION: ${{ steps.build_frontend.outputs.version }} + GCP_BUCKET: ${{ env.GCP_BUCKET }} run: | echo "Publish release to Google Cloud Storage:" + set -x touch ci/packages/windows ci/packages/darwin ci/packages/linux ci/packages/any - gsutil -m cp -r ci/packages/*windows* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/windows - gsutil -m cp -r ci/packages/*linux* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux - gsutil -m cp -r ci/packages/*darwin* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin - gsutil -m cp -r ci/packages/*any* gs://${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/any + gsutil -m cp -r ci/packages/*windows* "gs://$GCP_BUCKET/$PLUGIN_ID/release/${VERSION}/windows" + gsutil -m cp -r ci/packages/*linux* "gs://$GCP_BUCKET/$PLUGIN_ID/release/${VERSION}/linux" + gsutil -m cp -r ci/packages/*darwin* "gs://$GCP_BUCKET/$PLUGIN_ID/release/${VERSION}/darwin" + gsutil -m cp -r ci/packages/*any* "gs://$GCP_BUCKET/$PLUGIN_ID/release/${VERSION}/any" - name: Publish new plugin version on grafana.com if: steps.check_backend.outputs.has_backend == 'true' working-directory: ${{ steps.get_dir.outputs.dir }} env: GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }} VERSION: ${{ steps.build_frontend.outputs.version }} + GCP_BUCKET: ${{ env.GCP_BUCKET }} + OUTPUT_DIR: ${{ steps.get_dir.outputs.dir }} + GCOM_API: ${{ env.GCOM_API }} run: | echo "Publish new plugin version on grafana.com:" echo "Plugin version: ${VERSION}" - result=`curl -H "Authorization: Bearer $GCOM_TOKEN" -H "Content-Type: application/json" ${{ env.GCOM_API}}/api/plugins -d "{ - \"url\": \"https://github.com/grafana/grafana/tree/main/${{ steps.get_dir.outputs.dir }}\", - \"download\": { - \"linux-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_amd64.zip\", - \"md5\": \"$(cat ci/packages/info-linux_amd64.json | jq -r .plugin.md5)\" - }, - \"linux-arm64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_arm64.zip\", - \"md5\": \"$(cat ci/packages/info-linux_arm64.json | jq -r .plugin.md5)\" - }, - \"linux-arm\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/linux/$PLUGIN_ID-${VERSION}.linux_arm.zip\", - \"md5\": \"$(cat ci/packages/info-linux_arm.json | jq -r .plugin.md5)\" - }, - \"windows-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/windows/$PLUGIN_ID-${VERSION}.windows_amd64.zip\", - \"md5\": \"$(cat ci/packages/info-windows_amd64.json | jq -r .plugin.md5)\" - }, - \"darwin-amd64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin/$PLUGIN_ID-${VERSION}.darwin_amd64.zip\", - \"md5\": \"$(cat ci/packages/info-darwin_amd64.json | jq -r .plugin.md5)\" - }, - \"darwin-arm64\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/darwin/$PLUGIN_ID-${VERSION}.darwin_arm64.zip\", - \"md5\": \"$(cat ci/packages/info-darwin_arm64.json | jq -r .plugin.md5)\" - } - } - }"` - if [[ "$(echo $result | jq -r .version)" == "null" ]]; then + + OUTPUT_URL="https://github.com/grafana/grafana/tree/$OUTPUT_DIR" \ + jq -n '{"url": env.OUTPUT_URL}' > body.json + osarchs=(linux_amd64 linux_arm64 linux_arm windows_amd64 darwin_amd64 darwin_arm64) + for osarch in "${osarchs[@]}"; do + echo "Processing $osarch" + KEY="${osarch//_/-}" \ + OSARCH="$osarch" \ + jq -s '. as $i | .[0] | .download[env.KEY] = { + "url": "https://storage.googleapis.com/\(env.GCP_BUCKET)/\(env.PLUGIN_ID)/release/\(env.VERSION)/linux/\(env.PLUGIN_ID)-\(env.VERSION).\(env.OSARCH).zip", + "md5": $i[1].plugin.md5 + }' body.json ci/packages/info-"$osarch".json > tmp.json && mv tmp.json body.json + done + + result="$(curl -H "Authorization: Bearer $GCOM_TOKEN" -H "Content-Type: application/json" "$GCOM_API"/api/plugins --data-binary '@body.json')" + if [[ "$(echo "$result" | jq -r .version)" == "null" ]]; then echo "Failed to publish plugin version. Got:" - echo $result + echo "$result" exit 1 fi - name: Publish new plugin version on grafana.com (frontend only) @@ -254,20 +248,29 @@ jobs: env: GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }} VERSION: ${{ steps.build_frontend.outputs.version }} + GCOM_API: ${{ env.GCOM_API }} + OUTPUT_DIR: ${{ steps.get_dir.outputs.dir }} + GCP_BUCKET: ${{ env.GCP_BUCKET }} run: | echo "Publish new plugin version on grafana.com:" echo "Plugin version: ${VERSION}" - result=`curl -H "Authorization: Bearer $GCOM_TOKEN" -H "Content-Type: application/json" ${{ env.GCOM_API}}/api/plugins -d "{ - \"url\": \"https://github.com/grafana/grafana/tree/main/${{ steps.get_dir.outputs.dir }}\", - \"download\": { - \"any\": { - \"url\": \"https://storage.googleapis.com/${{ env.GCP_BUCKET }}/$PLUGIN_ID/release/${VERSION}/any/$PLUGIN_ID-${VERSION}.any.zip\", - \"md5\": \"$(cat ci/packages/info-any.json | jq -r .plugin.md5)\" + + OUTPUT_URL="https://github.com/grafana/grafana/tree/$OUTPUT_DIR" \ + DOWNLOAD_URL="https://storage.googleapis.com/$GCP_BUCKET/$PLUGIN_ID/release/${VERSION}/any/$PLUGIN_ID-${VERSION}.any.zip" \ + MD5_CHECKSUM="$(jq -r '.plugin.md5' ci/packages/info-any.json)" \ + jq -rn '{ + "url": env.OUTPUT_URL, + "download": { + "any": { + "url": env.DOWNLOAD_URL, + "md5": env.MD5_CHECKSUM } } - }"` - if [[ "$(echo $result | jq -r .version)" == "null" ]]; then + }' > body.json + + result="$(curl -H "Authorization: Bearer $GCOM_TOKEN" -H "Content-Type: application/json" "$GCOM_API"/api/plugins --data-binary '@body.json')" + if [[ "$(echo "$result" | jq -r .version)" == "null" ]]; then echo "Failed to publish plugin version. Got:" - echo $result + echo "$result" exit 1 fi diff --git a/.github/workflows/create-next-release-branch.yml b/.github/workflows/create-next-release-branch.yml index 1107842a765..5f537a5418a 100644 --- a/.github/workflows/create-next-release-branch.yml +++ b/.github/workflows/create-next-release-branch.yml @@ -10,11 +10,6 @@ on: description: The release branch to increment (eg providing `release-11.2.3` will result in `release-11.2.4` being created) type: string required: true - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true outputs: branch: description: The new branch that was created @@ -27,23 +22,32 @@ on: description: The release branch to increment (eg providing `release-11.2.3` will result in `release-11.2.4` being created) type: string required: true - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true + +permissions: + contents: read + id-token: write + jobs: main: runs-on: ubuntu-latest outputs: branch: ${{ steps.branch.outputs.branch }} steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} + repositories: "[\"grafana\", \"grafana-enterprise\"]" + permissions: "{\"contents\": \"write\", \"pull_requests\": \"write\", \"workflows\":\"write\"}" - name: Create release branch id: branch uses: grafana/grafana-github-actions-go/bump-release@main # zizmor: ignore[unpinned-uses] diff --git a/.github/workflows/dashboards-issue-add-label.yml b/.github/workflows/dashboards-issue-add-label.yml index 4072f062fa7..2ea89c7a441 100644 --- a/.github/workflows/dashboards-issue-add-label.yml +++ b/.github/workflows/dashboards-issue-add-label.yml @@ -11,7 +11,7 @@ env: ORGANIZATION: ${{ github.repository_owner }} REPO: ${{ github.event.repository.name }} TARGET_PROJECT: 202 - LABEL_IDs: "LA_kwDOAOaWjc8AAAABT38U-A" + LABEL_IDS: "LA_kwDOAOaWjc8AAAABT38U-A" concurrency: group: issue-label-when-in-project-${{ github.event.number }} @@ -26,25 +26,26 @@ jobs: with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Check if issue is in target project env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} ISSUE_NUMBER: ${{ github.event.issue.number }} TARGET_PROJECT: ${{ env.TARGET_PROJECT }} run: | + # shellcheck disable=SC2016 # we don't want the $s to be expanded gh api graphql -f query=' - query($org: String!, $repo: String!) { + query($org: String!, $repo: String!, $issueNumber: Int!) { repository(name: $repo, owner: $org) { - issue (number: $ISSUE_NUMBER) { + issue (number: $issueNumber) { id projectItems(first:20) { nodes { @@ -55,15 +56,18 @@ jobs: } } } - }' -f org=$ORGANIZATION -f repo=$REPO > projects_data.json + }' -f org="$ORGANIZATION" -f repo="$REPO" -F issueNumber="$ISSUE_NUMBER" > projects_data.json - echo 'IN_TARGET_PROJ='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number=='"$TARGET_PROJECT"') | .project != null' projects_data.json) >> $GITHUB_ENV - echo 'ITEM_ID='$(jq '.data.repository.issue.id' projects_data.json) >> $GITHUB_ENV + { + echo "IN_TARGET_PROJ=$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number=='"$TARGET_PROJECT"') | .project != null' projects_data.json)" + echo "ITEM_ID=$(jq '.data.repository.issue.id' projects_data.json)" + } >> "$GITHUB_ENV" - name: Set up label array if: env.IN_TARGET_PROJ env: LABEL_IDS: ${{ env.LABEL_IDS }} run: | + # shellcheck disable=SC2153 # we define the variable on the line above in 'read' IFS=',' read -ra LABEL_IDs <<< "$LABEL_IDS" for item in "${LABEL_IDs[@]}"; do echo "Item: $item" @@ -74,6 +78,7 @@ jobs: GH_TOKEN: ${{ steps.generate_token.outputs.token }} LABEL_IDS: ${{ env.LABEL_IDS }} run: | + # shellcheck disable=SC2016 # we don't want the $s to be expanded gh api graphql -f query=' mutation ($labelableId: ID!, $labelIds: [ID!]!) { addLabelsToLabelable( @@ -81,4 +86,4 @@ jobs: ) { clientMutationId } - }' -f labelableId=$ITEM_ID -f labelIds=$LABEL_IDS + }' -f labelableId="$ITEM_ID" -f labelIds="$LABEL_IDS" diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml index a34586f1217..0a957ffae25 100644 --- a/.github/workflows/deploy-pr-preview.yml +++ b/.github/workflows/deploy-pr-preview.yml @@ -9,6 +9,8 @@ on: paths: - "docs/sources/**" +permissions: {} + jobs: deploy-pr-preview: permissions: diff --git a/.github/workflows/deploy-storybook-preview.yml b/.github/workflows/deploy-storybook-preview.yml new file mode 100644 index 00000000000..a1fecce67cb --- /dev/null +++ b/.github/workflows/deploy-storybook-preview.yml @@ -0,0 +1,93 @@ +name: Deploy Storybook preview + +on: + pull_request: + paths: + - 'packages/grafana-ui/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + deploy-storybook-preview: + name: Deploy Storybook preview + runs-on: ubuntu-latest + # Don't run from forks for the moment. If we find this useful we can do the workflow_run dance + # to make it work for forks. + if: github.event.pull_request.head.repo.fork == false + permissions: + contents: read + id-token: write + + env: + BUCKET_NAME: grafana-storybook-previews + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + key: node_modules-${{ hashFiles('yarn.lock') }} + restore-keys: | + node_modules- + + - name: Install dependencies + env: + # If the PR isn't from a fork then don't use the slower yarn checks + YARN_ENABLE_HARDENED_MODE: ${{ github.event.pull_request.head.repo.fork == false && '1' || '0' }} + run: yarn install --immutable + + - name: Build storybook + run: yarn storybook:build + + # Create the GCS folder name for the preview. Creates a consistent name for all deploys for the PR. + # Matches format of `pr__`. + # Where `SANITIZED_BRANCH` is the branch name with only alphanumeric and hyphens, limited to 30 characters. + - name: Create deploy name + id: create-deploy-name + env: + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Convert branch name to only contain alphanumeric and hyphens + SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr -cs "[:alnum:]-" "-" | sed "s/^-//;s/-$//") + + # Check if SANITIZED_BRANCH is empty and fail if it is + if [ -z "$SANITIZED_BRANCH" ]; then + echo "Error: Branch name resulted in empty string after sanitization" + exit 1 + fi + + echo "deploy-name=pr_${PR_NUMBER}_${SANITIZED_BRANCH:0:30}" >> "$GITHUB_OUTPUT" + + - name: Upload Storybook + uses: grafana/shared-workflows/actions/push-to-gcs@main + with: + environment: prod + bucket: ${{ env.BUCKET_NAME }} + bucket_path: ${{ steps.create-deploy-name.outputs.deploy-name }} + path: packages/grafana-ui/dist/storybook + service_account: github-gf-storybook-preview@grafanalabs-workload-identity.iam.gserviceaccount.com + parent: false + + - name: Write summary + env: + DEPLOY_NAME: ${{ steps.create-deploy-name.outputs.deploy-name }} + run: | + echo "## Storybook preview deployed! 🚀" >> $GITHUB_STEP_SUMMARY + echo "Check it out at https://storage.googleapis.com/${BUCKET_NAME}/${DEPLOY_NAME}/index.html" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 640b80a3dd5..c738c18bbac 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -12,6 +12,8 @@ on: pull_request: paths: - 'packages/**' + - '.nvmrc' + - '.github/workflows/detect-breaking-changes-levitate.yml' branches: - 'main' @@ -31,9 +33,10 @@ jobs: with: path: './pr' persist-credentials: false + - uses: actions/setup-node@v4 with: - node-version: 22.11.0 + node-version-file: './pr/.nvmrc' - name: Get yarn cache directory path id: yarn-cache-dir-path @@ -81,10 +84,11 @@ jobs: with: path: './base' ref: ${{ github.event.pull_request.base.ref }} + persist-credentials: false - uses: actions/setup-node@v4 with: - node-version: 22.11.0 + node-version-file: './base/.nvmrc' - name: Get yarn cache directory path id: yarn-cache-dir-path @@ -129,9 +133,12 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 with: - node-version: 22.11.0 + node-version-file: '.nvmrc' - name: Get built packages from pr uses: actions/download-artifact@v4 @@ -151,13 +158,15 @@ jobs: - id: 'auth' uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' + if: github.event.pull_request.head.repo.full_name == github.repository with: - workload_identity_provider: ${{ secrets.WIF_PROVIDER }} - service_account: ${{ secrets.LEVITATE_SA }} + workload_identity_provider: projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider + service_account: github-plugins-data-levitate@grafanalabs-workload-identity.iam.gserviceaccount.com project_id: 'grafanalabs-global' - name: 'Set up Cloud SDK' uses: 'google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a' + if: github.event.pull_request.head.repo.full_name == github.repository with: version: '>= 363.0.0' project_id: 'grafanalabs-global' @@ -168,11 +177,16 @@ jobs: run: ./scripts/check-breaking-changes.sh env: FORCE_COLOR: 3 + IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} # used in check-breaking-changes.sh and levitate-parse-json-report.js - name: Persisting the check output run: | mkdir -p ./levitate - echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.message }}\", \"pr_number\": \"${{ github.event.pull_request.number }}\" }" > ./levitate/result.json + echo "{ \"exit_code\": ${IS_BREAKING}, \"message\": \"${MESSAGE}\", \"pr_number\": \"${PR_NUMBER}\" }" > ./levitate/result.json + env: + IS_BREAKING: ${{ steps.breaking-changes.outputs.is_breaking }} + MESSAGE: ${{ steps.breaking-changes.outputs.message }} + PR_NUMBER: ${{ github.event.pull_request.number }} - name: Upload check output as artifact uses: actions/upload-artifact@v4 @@ -188,16 +202,27 @@ jobs: permissions: contents: read id-token: write + if: github.event.pull_request.head.repo.full_name == github.repository steps: - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + - id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@28361cdb22223e5f1e34358c86c20908e7248760 # get-vault-secrets-v1.1.0 with: - app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} - private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} + # Secrets placed in the ci/repo/grafana/grafana in vault + repo_secrets: | + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem + + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + with: + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - uses: actions/checkout@v4 + with: + persist-credentials: false - name: 'Download artifact' uses: actions/download-artifact@v4 @@ -205,7 +230,7 @@ jobs: name: levitate - name: Parsing levitate result - uses: actions/github-script@v6 + uses: actions/github-script@v7 id: levitate-run with: script: | @@ -216,7 +241,7 @@ jobs: # Check if label exists - name: Check if "levitate breaking change" label exists id: does-label-exist - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ github.event.pull_request.number }} with: @@ -295,7 +320,7 @@ jobs: "fields": [ { "type": "mrkdwn", - "text": "*PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>" + "text": "*PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>\n\nAuthor: ${{ github.event.pull_request.user.login }}" }, { "type": "mrkdwn", @@ -309,7 +334,7 @@ jobs: # Add the label - name: Add "levitate breaking change" label if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -325,7 +350,7 @@ jobs: # Remove label (no more breaking changes) - name: Remove "levitate breaking change" label if: steps.levitate-run.outputs.exit_code == 0 && steps.does-label-exist.outputs.result == 1 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -343,7 +368,7 @@ jobs: # Related issue: https://github.com/renovatebot/renovate/issues/1908 - name: Add "grafana/plugins-platform-frontend" as a reviewer if: steps.levitate-run.outputs.exit_code == 1 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -360,7 +385,7 @@ jobs: # Remove reviewers (no more breaking changes) - name: Remove "grafana/plugins-platform-frontend" from the list of reviewers if: steps.levitate-run.outputs.exit_code == 0 - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -376,9 +401,11 @@ jobs: - name: Exit run: | - if [ "${{ steps.levitate-run.outputs.exit_code }}" -ne 0 ]; then + if [ "${LV_EXIT_CODE}" -ne 0 ]; then echo "Breaking changes detected. Please check the levitate report in your pull request. This workflow won't block merging." fi - exit ${{ steps.levitate-run.outputs.exit_code }} + exit "${LV_EXIT_CODE}" shell: bash + env: + LV_EXIT_CODE: ${{ steps.levitate-run.outputs.exit_code }} diff --git a/.github/workflows/documentation-ci.yml b/.github/workflows/documentation-ci.yml index 30c2516412f..3dfde5d37b1 100644 --- a/.github/workflows/documentation-ci.yml +++ b/.github/workflows/documentation-ci.yml @@ -4,11 +4,18 @@ on: branches: ["main"] paths: ["docs/sources/**"] workflow_dispatch: + +permissions: {} + jobs: vale: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + security-events: write container: - image: grafana/vale:latest + image: grafana/vale:latest # zizmor: ignore[unpinned-images] steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/e2e-dashboard-new-layouts.yml b/.github/workflows/e2e-dashboard-new-layouts.yml new file mode 100644 index 00000000000..b2b5d1f8215 --- /dev/null +++ b/.github/workflows/e2e-dashboard-new-layouts.yml @@ -0,0 +1,42 @@ +name: Run e2e for dashboardNewLayouts + +on: + pull_request: + branches: + - '**' + paths: + - 'e2e/dashboard-new-layouts/**' + - 'public/app/features/dashboard-scene/**' + +env: + ARCH: linux-amd64 + +jobs: + dashboard-new-layouts-e2e: + runs-on: ubuntu-latest + continue-on-error: true + if: github.event.pull_request.draft == false + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Pin Go version to mod file + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go version + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + - name: Install dependencies + run: yarn install --immutable + - name: Build grafana + run: make build + - name: Install Cypress dependencies + uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f + with: + runTests: false + - name: Run dashboardNewLayouts e2e + run: yarn e2e:dashboard-new-layouts diff --git a/.github/workflows/ephemeral-instances-pr-comment.yml b/.github/workflows/ephemeral-instances-pr-comment.yml index ed6b98bbce2..9c094615a98 100644 --- a/.github/workflows/ephemeral-instances-pr-comment.yml +++ b/.github/workflows/ephemeral-instances-pr-comment.yml @@ -1,47 +1,48 @@ -name: 'Ephemeral instances' +name: "Ephemeral instances" + on: issue_comment: types: [created] pull_request: types: [closed] -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.EI_APP_ID != '' && - secrets.EI_APP_PRIVATE_KEY != '' && - secrets.EI_GCOM_HOST != '' && - secrets.EI_GCOM_TOKEN != '' && - secrets.EI_EPHEMERAL_INSTANCES_REGISTRY != '' && - secrets.EI_GCP_SERVICE_ACCOUNT_KEY_BASE64 != '' && - secrets.EI_EPHEMERAL_ORG_ID != '' - ) || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - handle-pull-request-event: - needs: config - if: needs.config.outputs.has-secrets && - ${{ github.event.issue.pull_request && (startsWith(github.event.comment.body, '/deploy-to-hg') || github.event.action == 'closed') }} +permissions: {} + +jobs: + handle-ephemeral-instances: + if: ${{ github.event.issue.pull_request && (startsWith(github.event.comment.body, '/deploy-to-hg') || github.event.action == 'closed') && github.repository_owner == 'grafana' }} runs-on: - labels: ubuntu-latest-8-cores + labels: ubuntu-latest-16-cores continue-on-error: true + permissions: + # For commenting. + pull-requests: write + # No contents permission is needed because we will impersonate an app to create the PR instead. + id-token: write # required for vault access + steps: + - name: Get vault secrets + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in ci/repo/grafana/grafana/ + repo_secrets: | + APP_ID=ephemeral-instances-bot:app-id + APP_PEM=ephemeral-instances-bot:app-private-key + GCOM_HOST=ephemeral-instances-bot:gcom-host + GCOM_TOKEN=ephemeral-instances-bot:gcom-token + REGISTRY=ephemeral-instances-bot:registry + GCP_SA_ACCOUNT_KEY_BASE64=ephemeral-instances-bot:sa-key + - name: Generate a GitHub app installation token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 # v1.8.0 with: - app_id: ${{ secrets.EI_APP_ID }} - private_key: ${{ secrets.EI_APP_PRIVATE_KEY }} + app_id: ${{ env.APP_ID }} + private_key: ${{ env.APP_PEM }} - name: Checkout ephemeral instances repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: repository: grafana/ephemeral-grafana-instances-github-action token: ${{ steps.generate_token.outputs.token }} @@ -52,11 +53,11 @@ jobs: - name: build and deploy ephemeral instance uses: ./ephemeral with: - github-token: ${{ steps.generate_token.outputs.token }} - gcom-host: ${{ secrets.EI_GCOM_HOST }} - gcom-token: ${{ secrets.EI_GCOM_TOKEN }} - registry: "${{ secrets.EI_EPHEMERAL_INSTANCES_REGISTRY }}" - gcp-service-account-key: "${{ secrets.EI_GCP_SERVICE_ACCOUNT_KEY_BASE64 }}" - ephemeral-org-id: "${{ secrets.EI_EPHEMERAL_ORG_ID }}" + github-token: ${{ steps.generate_token.outputs.token }} + gcom-host: ${{ env.GCOM_HOST }} + gcom-token: ${{ env.GCOM_TOKEN }} + registry: "${{ env.REGISTRY }}" + gcp-service-account-key: ${{ env.GCP_SA_ACCOUNT_KEY_BASE64 }} + ephemeral-org-id: ephemeral oss-or-enterprise: oss verbose: true diff --git a/.github/workflows/feature-toggles-ci.yml b/.github/workflows/feature-toggles-ci.yml index a6c9f5c52dc..ab1aa9b2dca 100644 --- a/.github/workflows/feature-toggles-ci.yml +++ b/.github/workflows/feature-toggles-ci.yml @@ -7,9 +7,15 @@ on: - 'pkg/services/featuremgmt/registry.go' - 'docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md' +permissions: {} + jobs: test: runs-on: ubuntu-latest + + permissions: + contents: read + steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 0042166d3c7..fb0362bb580 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -9,12 +9,35 @@ on: permissions: {} jobs: - lint-frontend-verify-i18n: - name: Verify i18n + detect-changes: + name: Detect whether code changed runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.frontend }} + prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/frontend-lint.yml + + lint-frontend-prettier: + needs: detect-changes permissions: contents: read id-token: write + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-prettier-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.prettier == 'true' + name: Lint + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: @@ -25,46 +48,21 @@ jobs: cache: 'yarn' cache-dependency-path: 'yarn.lock' - run: yarn install --immutable --check-cache - - run: | - extract_error_message='::error::Extraction failed. Make sure that you have no dynamic translation phrases, such as "t(`preferences.theme.{themeID}`, themeName)" and that no translation key is used twice. Search the output for '[warning]' to find the offending file.' - make i18n-extract || (echo "${extract_error_message}" && false) - - run: | - uncommited_error_message="::error::Translation extraction has not been committed. Please run 'make i18n-extract', commit the changes and push again." - file_diff=$(git diff --dirstat public/locales) - if [ -n "$file_diff" ]; then - echo $file_diff - echo "${uncommited_error_message}" - exit 1 - fi - lint-frontend-prettier: - permissions: - contents: read - id-token: write - # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, - # the `lint-frontend-prettier-enterprise` workflow will run instead - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true - name: Lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - cache-dependency-path: 'yarn.lock' - - run: yarn install --immutable --check-cache - run: yarn run prettier:check - run: yarn run lint lint-frontend-prettier-enterprise: + needs: detect-changes permissions: contents: read id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.prettier == 'true' name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -78,16 +76,19 @@ jobs: - run: yarn run prettier:check - run: yarn run lint lint-frontend-typecheck: + needs: detect-changes permissions: contents: read id-token: write # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, # the `lint-frontend-typecheck-enterprise` workflow will run instead - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.changed == 'true' name: Typecheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -96,15 +97,18 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run typecheck lint-frontend-typecheck-enterprise: + needs: detect-changes permissions: contents: read id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.changed == 'true' name: Typecheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -117,13 +121,17 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run typecheck lint-frontend-betterer: + needs: detect-changes permissions: contents: read id-token: write + if: needs.detect-changes.outputs.changed == 'true' name: Betterer runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 211c9d90fd2..bd8eab8c176 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -34,6 +34,7 @@ on: permissions: # contents: write allows the action(s) to create github releases contents: write + id-token: write jobs: main: @@ -44,6 +45,5 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} version: ${{ inputs.version }} - metrics_api_key: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} latest: ${{ inputs.latest }} dry_run: ${{ inputs.dry_run }} diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index cdc84874d01..78788f3570c 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -22,7 +22,6 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: ./go.mod - - run: make gen-go - name: golangci-lint uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd with: diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml index 60277aed365..e26d7806c5e 100644 --- a/.github/workflows/i18n-crowdin-create-tasks.yml +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -1,27 +1,13 @@ -name: Crowdin Create Tasks +name: Crowdin automatic task management on: workflow_dispatch: - # schedule: - # - cron: "0 0 * * *" + # once a month on the first day of the month at midnight + schedule: + - cron: "0 0 1 * *" jobs: create-tasks-in-crowdin: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - - name: Create tasks - env: - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} - run: node ./.github/workflows/scripts/crowdin/create-tasks.js + uses: grafana/grafana-github-actions/.github/workflows/crowdin-create-tasks.yml@main + with: + crowdin_project_id: 5 diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index 26f9588069f..e8e04c22029 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -7,153 +7,10 @@ on: jobs: download-sources-from-crowdin: - runs-on: ubuntu-latest - - permissions: - contents: write # needed to commit changes into the PR - pull-requests: write # needed to update PR description, labels, etc - id-token: write # needed to get vault secrets - - steps: - - name: Generate token - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} - private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - - - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - token: ${{ steps.generate_token.outputs.token }} - persist-credentials: false - - - name: Download sources - id: crowdin-download - uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 - with: - upload_sources: false - upload_translations: false - download_sources: false - download_translations: true - export_only_approved: true - localization_branch_name: i18n_crowdin_translations - create_pull_request: true - pull_request_title: 'I18n: Download translations from Crowdin' - pull_request_body: | - :robot: Automatic download of translations from Crowdin. - - This runs once per day and will merge automatically if all the required checks pass. - - If there's a conflict, close the pull request and **delete the branch**. - You can then either wait for the schedule to trigger a new PR, or rerun the action manually. - pull_request_labels: 'area/frontend, area/internationalization, no-changelog, no-backport' - pull_request_base_branch_name: 'main' - base_url: 'https://grafana.api.crowdin.com' - config: 'crowdin.yml' - source: 'public/locales/en-US/grafana.json' - translation: 'public/locales/%locale%/%original_file_name%' - # Magic details of the github-actions bot user, to pass CLA checks - github_user_name: "github-actions[bot]" - github_user_email: "41898282+github-actions[bot]@users.noreply.github.com" - env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} - - - name: Get pull request ID - if: steps.crowdin-download.outputs.pull_request_url - shell: bash - # Crowdin action returns us the URL of the pull request, but we need an ID for the GraphQL API - # that looks like 'PR_kwDOAOaWjc5mP_GU' - run: | - pr_id=$(gh pr view ${{ steps.crowdin-download.outputs.pull_request_url }} --json id -q .id) - echo "PULL_REQUEST_ID=$pr_id" >> "$GITHUB_ENV" - env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - - - name: Get project board ID - uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 - id: get-project-id - if: steps.crowdin-download.outputs.pull_request_url - with: - # Frontend Platform project - https://github.com/orgs/grafana/projects/78 - org: grafana - project_number: 78 - query: | - query getProjectId($org: String!, $project_number: Int!){ - organization(login: $org) { - projectV2(number: $project_number) { - title - id - } - } - } - env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - - - name: Add to project board - uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 - if: steps.crowdin-download.outputs.pull_request_url - with: - projectid: ${{ fromJson(steps.get-project-id.outputs.data).organization.projectV2.id }} - prid: ${{ env.PULL_REQUEST_ID }} - query: | - mutation addPullRequestToProject($projectid: ID!, $prid: ID!){ - addProjectV2ItemById(input: {projectId: $projectid, contentId: $prid}) { - item { - id - } - } - } - env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - - - name: Run auto-milestone - uses: grafana/grafana-github-actions-go/auto-milestone@main # zizmor: ignore[unpinned-uses] - if: steps.crowdin-download.outputs.pull_request_url - with: - pr: ${{ steps.crowdin-download.outputs.pull_request_number }} - token: ${{ steps.generate_token.outputs.token }} - - - name: Get vault secrets - id: vault-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@main # zizmor: ignore[unpinned-uses] - with: - # Secrets placed in ci/repo/grafana/grafana/grafana-pr-approver - repo_secrets: | - GRAFANA_PR_APPROVER_APP_ID=grafana-pr-approver:app-id - GRAFANA_PR_APPROVER_APP_PEM=grafana-pr-approver:private-key - - - name: Generate approver token - if: steps.crowdin-download.outputs.pull_request_url - id: generate_approver_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ env.GRAFANA_PR_APPROVER_APP_ID }} - private_key: ${{ env.GRAFANA_PR_APPROVER_APP_PEM }} - - - name: Approve and automerge PR - if: steps.crowdin-download.outputs.pull_request_url - shell: bash - # Only approve if: - # - the PR does not modify files other than json files under the public/locales/ directory - # - the PR does not modify the en-US locale - run: | - filesChanged=$(gh pr diff --name-only ${{ steps.crowdin-download.outputs.pull_request_url }}) - - if [[ $(echo $filesChanged | grep -v 'public/locales/[a-zA-Z\-]*/grafana.json' | wc -l) -ne 0 ]]; then - echo "Non-i18n changes detected, not approving" - exit 1 - fi - - if [[ $(echo $filesChanged | grep "public/locales/en-US" | wc -l) -ne 0 ]]; then - echo "public/locales/en-US changes detected, not approving" - exit 1 - fi - - echo "Approving and enabling automerge" - gh pr review ${{ steps.crowdin-download.outputs.pull_request_url }} --approve - gh pr merge --auto --squash ${{ steps.crowdin-download.outputs.pull_request_url }} - env: - GITHUB_TOKEN: ${{ steps.generate_approver_token.outputs.token }} + if: github.repository == 'grafana/grafana' + uses: grafana/grafana-github-actions/.github/workflows/crowdin-download.yml@main + with: + crowdin_project_id: 5 + pr_labels: 'area/frontend, area/internationalization, no-changelog, no-backport' + github_board_id: 78 # Frontend Platform project + en_paths: public/locales/en-US/grafana.json, public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json, public/app/plugins/datasource/mssql/locales/en-US/mssql.json, packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json, packages/grafana-sql/src/locales/en-US/grafana-sql.json diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml index 7165aa823fa..b61a7538bf0 100644 --- a/.github/workflows/i18n-crowdin-upload.yml +++ b/.github/workflows/i18n-crowdin-upload.yml @@ -5,31 +5,16 @@ on: push: paths: - 'public/locales/en-US/grafana.json' + - 'public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json' + - 'public/app/plugins/datasource/mssql/locales/en-US/mssql.json' + - 'packages/grafana-sql/src/locales/en-US/grafana-sql.json' + - 'packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json' branches: - main jobs: upload-sources-to-crowdin: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Upload sources - uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 - with: - upload_sources: true - upload_sources_args: '--dest=public/locales/en-US/grafana.json' - upload_translations: false - download_translations: false - create_pull_request: false - base_url: 'https://grafana.api.crowdin.com' - config: 'crowdin.yml' - source: 'public/locales/en-US/grafana.json' - translation: 'public/locales/%locale%/%original_file_name%' - env: - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + if: github.repository == 'grafana/grafana' + uses: grafana/grafana-github-actions/.github/workflows/crowdin-upload.yml@main + with: + crowdin_project_id: 5 diff --git a/.github/workflows/i18n-verify.yml b/.github/workflows/i18n-verify.yml new file mode 100644 index 00000000000..ccbed74d4ea --- /dev/null +++ b/.github/workflows/i18n-verify.yml @@ -0,0 +1,15 @@ +name: Verify i18n + +permissions: + contents: read + +on: + pull_request: + push: + branches: + - main + - release-*.*.* + +jobs: + verify-i18n: + uses: grafana/grafana-github-actions/.github/workflows/verify-i18n.yml@main diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index a5a5a822446..97694bd2d04 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -43,20 +43,19 @@ jobs: with: # Secrets placed in the ci/repo/grafana/grafana/plugins_platform_issue_commands_github_bot path in Vault repo_secrets: | - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=grafana_pr_automation_app:app_id + GITHUB_APP_PRIVATE_KEY=grafana_pr_automation_app:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Run Commands uses: ./actions/commands with: - metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}} token: ${{ steps.generate_token.outputs.token }} configPath: "issue-opened" @@ -77,18 +76,20 @@ jobs: repo_secrets: | AUTOTRIAGER_OPENAI_API_KEY=plugins_platform_issue_triager:AUTOTRIAGER_OPENAI_API_KEY AUTOTRIAGER_SLACK_WEBHOOK_URL=plugins_platform_issue_triager:AUTOTRIAGER_SLACK_WEBHOOK_URL - GH_APP_ID=plugins_platform_issue_commands_github_bot:app_id - GH_APP_PEM=plugins_platform_issue_commands_github_bot:app_pem + GITHUB_APP_ID=plugins_platform_issue_triager_github_bot:app_id + GITHUB_APP_PRIVATE_KEY=plugins_platform_issue_triager_github_bot:app_pem - - name: "Generate token" + - name: Generate token id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 with: - app_id: ${{ env.GH_APP_ID }} - private_key: ${{ env.GH_APP_PEM }} + app-id: ${{ env.GITHUB_APP_ID }} + private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} - name: Checkout uses: actions/checkout@v4 # v4.2.2 + with: + persist-credentials: false - name: Send issue to the auto triager action id: auto_triage diff --git a/.github/workflows/lint-build-docs.yml b/.github/workflows/lint-build-docs.yml index c9da22210b6..f4fd7d7a2f0 100644 --- a/.github/workflows/lint-build-docs.yml +++ b/.github/workflows/lint-build-docs.yml @@ -16,10 +16,16 @@ on: - 'packages/**/*.md' - 'latest.json' +permissions: {} + jobs: docs: name: Build & Verify Docs runs-on: ubuntu-latest + + permissions: + contents: read + steps: - name: Checkout code uses: actions/checkout@v4 @@ -29,7 +35,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '22.11.0' + node-version-file: '.nvmrc' cache: 'yarn' - name: Install dependencies @@ -45,18 +51,18 @@ jobs: run: | # Create and start a container from the docs-base image in detached mode docker run -d --name docs-builder grafana/docs-base:latest tail -f /dev/null - + # Create the directory structure inside the container docker exec docs-builder mkdir -p /hugo/content/docs/grafana/latest - + # Create the _index.md file docker exec docs-builder /bin/sh -c "echo -e '---\nredirectURL: /docs/grafana/latest/\ntype: redirect\nversioned: true\n---\n' > /hugo/content/docs/grafana/_index.md" - + # Copy the docs sources from the host to the container docker cp docs/sources/. docs-builder:/hugo/content/docs/grafana/latest/ - + # Run the make prod command inside the container docker exec -w /hugo docs-builder make prod || echo "Build completed with warnings" - + # Clean up the container docker rm -f docs-builder diff --git a/.github/workflows/metrics-collector.yml b/.github/workflows/metrics-collector.yml deleted file mode 100644 index 4e08bef9b10..00000000000 --- a/.github/workflows/metrics-collector.yml +++ /dev/null @@ -1,54 +0,0 @@ -# -# When triggered by the cron job it will also collect metrics for: -# * number of issues without label -# * number of issues with "needs more info" -# * number of issues with "needs investigation" -# * number of issues with label type/bug -# * number of open issues in current milestone -# -# https://github.com/grafana/grafana-github-actions/blob/main/metrics-collector/index.ts -# -name: Github issue metrics collection -on: - schedule: - - cron: "*/10 * * * *" - issues: - types: [opened, closed] - -permissions: - contents: read - -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_MISC_STATS_API_KEY != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - - main: - needs: config - if: needs.config.outputs.has-secrets - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v4 # v4.2.2 - with: - repository: "grafana/grafana-github-actions" - path: ./actions - ref: main - persist-credentials: false - - name: Install Actions - run: npm install --production --prefix ./actions - - name: Run metrics collector - uses: ./actions/metrics-collector - with: - metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}} - token: ${{secrets.GITHUB_TOKEN}} - configPath: "metrics-collector" diff --git a/.github/workflows/migrate-prs.yml b/.github/workflows/migrate-prs.yml index c40a34a6ebb..d690245be19 100644 --- a/.github/workflows/migrate-prs.yml +++ b/.github/workflows/migrate-prs.yml @@ -15,11 +15,6 @@ on: description: Owner/repo of the repository where the branch is created (e.g. 'grafana/grafana') required: true type: string - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true workflow_dispatch: inputs: from: @@ -34,24 +29,30 @@ on: description: Owner/repo of the repository where the branch is created (e.g. 'grafana/grafana') required: true type: string - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: - required: true - GRAFANA_DELIVERY_BOT_APP_PEM: - required: true + +permissions: + contents: read + id-token: write jobs: main: runs-on: ubuntu-latest steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: Migrate PRs - uses: grafana/grafana-github-actions-go/migrate-open-prs@main # zizmor: ignore[unpinned-uses] + uses: grafana/grafana-github-actions-go/migrate-open-prs@main with: token: ${{ steps.generate_token.outputs.token }} ownerRepo: ${{ inputs.ownerRepo }} diff --git a/.github/workflows/pr-backend-coverage.yml b/.github/workflows/pr-backend-coverage.yml deleted file mode 100644 index 12ed4423587..00000000000 --- a/.github/workflows/pr-backend-coverage.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Coverage - -on: - workflow_dispatch: - push: - branches: - - main - paths-ignore: - - 'docs/**' - - '**/*.md' - -permissions: - contents: read - id-token: write - -env: - EDITION: 'oss' - WIRE_TAGS: 'oss' - -jobs: - main: - name: Backend Unit Tests - runs-on: ubuntu-latest-8-cores - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y build-essential shared-mime-info - go install github.com/mfridman/tparse@c1754a1f484ac5cd422697b0fec635177ddc8507 # v0.17.0 - - name: Generate Go code - run: make gen-go - - name: Run unit tests - run: COVER_OPTS="-coverprofile=be-unit.cov -coverpkg=github.com/grafana/grafana/..." GO_TEST_OUTPUT="/tmp/unit.log" make test-go-unit-cov - - name: Process and upload coverage - uses: ./.github/actions/test-coverage-processor - with: - test-type: 'be-unit' - # Needs to be named 'unit.cov' based on the Makefile command `make test-go-unit` - coverage-file: 'unit.cov' - codecov-token: ${{ secrets.CODECOV_TOKEN }} - codecov-flag: 'be-unit' - codecov-name: 'be-unit' - - - name: Install Grafana Bench - # We can't allow forks here, as we need secret access. - if: ${{ github.event_name != 'pull_request' }} - uses: ./.github/actions/setup-grafana-bench - - - name: Process output for Bench - if: ${{ github.event_name != 'pull_request' }} - run: | - grafana-bench report \ - --trigger pr-backend-unit-tests-oss \ - --report-input go \ - --report-output log \ - --grafana-version "$(git rev-parse HEAD)" \ - --suite-name grafana-oss-unit-tests \ - /tmp/unit.log || true - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false diff --git a/.github/workflows/pr-codeql-analysis-python.yml b/.github/workflows/pr-codeql-analysis-python.yml index c5fe4b6a10c..df9cbf18f45 100644 --- a/.github/workflows/pr-codeql-analysis-python.yml +++ b/.github/workflows/pr-codeql-analysis-python.yml @@ -25,11 +25,24 @@ jobs: fetch-depth: 2 persist-credentials: false + - name: Check for Python files + id: check-python + run: | + if [ -z "$(find . -name '*.py' -type f)" ]; then + echo "No Python files found, skipping analysis" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "Python files found, proceeding with analysis" + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL + if: steps.check-python.outputs.skip != 'true' uses: github/codeql-action/init@v3 with: languages: "python" - name: Perform CodeQL Analysis + if: steps.check-python.outputs.skip != 'true' uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index 518c25dfeaa..da9134ed3ae 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -5,28 +5,14 @@ on: - labeled - opened - synchronize +permissions: {} concurrency: group: pr-commands-${{ github.event.number }} jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_PR_AUTOMATION_APP_ID != '' && - secrets.GRAFANA_PR_AUTOMATION_APP_PEM != '' && - secrets.GRAFANA_MISC_STATS_API_KEY != '' - ) || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - main: - needs: config - if: needs.config.outputs.has-secrets + permissions: + contents: read + pull-requests: write runs-on: ubuntu-latest steps: - name: Checkout Actions @@ -38,15 +24,8 @@ jobs: persist-credentials: false - name: Install Actions run: npm install --production --prefix ./actions - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_ID }} - private_key: ${{ secrets.GRAFANA_PR_AUTOMATION_APP_PEM }} - name: Run Commands uses: ./actions/commands with: - metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}} - token: ${{ steps.generate_token.outputs.token }} + token: ${{ secrets.GITHUB_TOKEN }} configPath: pr-commands diff --git a/.github/workflows/pr-dependabot-update-go-workspace.yml b/.github/workflows/pr-dependabot-update-go-workspace.yml index a83875c4644..82169bd69ac 100644 --- a/.github/workflows/pr-dependabot-update-go-workspace.yml +++ b/.github/workflows/pr-dependabot-update-go-workspace.yml @@ -65,5 +65,5 @@ jobs: if ! git diff --exit-code --quiet; then echo "Committing and pushing workspace changes" git commit -a -m "update workspace" - git push origin $BRANCH_NAME + git push origin "$BRANCH_NAME" fi diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index a4a5a71b906..5e7d116d0c2 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -7,61 +7,362 @@ on: - main - release-*.*.* -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +# TODO: re-enable this before merging +# concurrency: +# group: ${{ github.workflow }}-${{ github.ref }} +# cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +permissions: {} + +env: + ACTIONS_STEP_DEBUG: true + RUNNER_DEBUG: 1 jobs: + detect-changes: + name: Detect whether code changed + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.e2e }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/pr-e2e-tests.yml + build-grafana: + needs: detect-changes + if: needs.detect-changes.outputs.changed == 'true' name: Build & Package Grafana runs-on: ubuntu-latest-16-cores + permissions: + contents: read outputs: artifact: ${{ steps.artifact.outputs.artifact }} steps: - uses: actions/checkout@v4 with: - path: ./grafana persist-credentials: false - - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + + # TODO: add a cleanup workflow to remove the cache when the PR is closed + # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy + # TODO: maybe we could just use the cache to store the build, instead of uploading as an artifact? + - uses: actions/cache@v4 + id: cache + with: + key: "build-grafana-${{ runner.os }}-${{ hashFiles('yarn.lock', 'public/*', 'packages/*', 'pkg/**/*.go', '**/go.mod', '**/go.sum', '!**_test.go', '!**.test.ts', '!**.test.tsx') }}" + path: | + build-dir + + # If no cache hit, build Grafana + - name: Build Grafana + if: steps.cache.outputs.cache-hit != 'true' + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e with: verb: run - 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" + args: go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir="${PWD}" > out.txt + - name: Cat built artifact + if: steps.cache.outputs.cache-hit != 'true' + run: cat out.txt + - name: Move built artifact + if: steps.cache.outputs.cache-hit != 'true' + run: | + mkdir -p build-dir + mv "$(cat out.txt)" build-dir/grafana.tar.gz + + # If cache hit, validate the artifact is present + - name: Validate artifact + if: steps.cache.outputs.cache-hit == 'true' + run: | + if [ ! -f build-dir/grafana.tar.gz ]; then + echo "Error: build-dir/grafana.tar.gz not found in cache" + exit 1 + fi + + - name: Set artifact name + run: echo "artifact=grafana-server-${{github.run_number}}" >> "$GITHUB_OUTPUT" id: artifact + + - name: Upload artifact + uses: actions/upload-artifact@v4 + id: upload + with: + retention-days: 1 + name: ${{ steps.artifact.outputs.artifact }} + path: build-dir/grafana.tar.gz + + # TODO: we won't need this when we only have playwright + build-e2e-runner: + needs: detect-changes + if: needs.detect-changes.outputs.changed == 'true' + name: Build E2E test runner + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + artifact: ${{ steps.artifact.outputs.artifact }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: ${{ !github.event.pull_request.head.repo.fork }} + - name: Build E2E test runner + id: artifact + run: | + set -euo pipefail + # We want a static binary, so we need to set CGO_ENABLED=0 + CGO_ENABLED=0 go build -o ./e2e-runner ./e2e/ + echo "artifact=e2e-runner-${{github.run_number}}" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@v4 id: upload with: retention-days: 1 name: ${{ steps.artifact.outputs.artifact }} - path: grafana.tar.gz - e2e-matrix: + path: e2e-runner + + run-e2e-tests: + needs: + - build-grafana + - build-e2e-runner + strategy: + fail-fast: false + matrix: + include: + - suite: various-suite + path: e2e/various-suite + - suite: dashboards-suite + path: e2e/dashboards-suite + - suite: smoke-tests-suite + path: e2e/smoke-tests-suite + - suite: panels-suite + path: e2e/panels-suite + - suite: various-suite (old arch) + path: e2e/old-arch/various-suite + flags: --flags="--env dashboardScene=false" + - suite: dashboards-suite (old arch) + path: e2e/old-arch/dashboards-suite + flags: --flags="--env dashboardScene=false" + - suite: smoke-tests-suite (old arch) + path: e2e/old-arch/smoke-tests-suite + flags: --flags="--env dashboardScene=false" + - suite: panels-suite (old arch) + path: e2e/old-arch/panels-suite + flags: --flags="--env dashboardScene=false" name: ${{ matrix.suite }} - strategy: - matrix: - suite: - - various-suite - - dashboards-suite - - smoke-tests-suite - - panels-suite + runs-on: ubuntu-latest-8-cores + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-grafana.outputs.artifact }} + - uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-e2e-runner.outputs.artifact }} + - name: chmod +x + run: chmod +x ./e2e-runner + - name: Run E2E tests + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + with: + verb: run + args: go run ./pkg/build/e2e --package=grafana.tar.gz + --suite=${{ matrix.path }} + ${{ matrix.flags }} + - name: Set suite name + id: set-suite-name + if: success() || failure() + env: + SUITE: ${{ matrix.path }} + run: | + set -euo pipefail + echo "suite=$(echo "$SUITE" | sed 's/\//-/g')" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: ${{ steps.set-suite-name.outputs.suite }}-${{ github.run_number }} + path: videos + retention-days: 1 + + run-playwright-tests: needs: - build-grafana - uses: ./.github/workflows/run-e2e-suite.yml - with: - package: ${{ needs.build-grafana.outputs.artifact }} - suite: ${{ matrix.suite }} - e2e-matrix-old-arch: - name: ${{ matrix.suite }} (old arch) + name: Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) + runs-on: ubuntu-latest-8-cores + permissions: + contents: read + strategy: + fail-fast: false matrix: - suite: - - old-arch/various-suite - - old-arch/dashboards-suite - - old-arch/smoke-tests-suite - - old-arch/panels-suite + shard: [1, 2, 3, 4, 5, 6, 7, 8] + shardTotal: [8] + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-grafana.outputs.artifact }} + - name: Run E2E tests + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + with: + verb: run + args: go run ./pkg/build/e2e-playwright --package=grafana.tar.gz --shard=${{ matrix.shard }}/${{ matrix.shardTotal }} --blob-dir=./blob-report + - uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: playwright-blob-${{ github.run_number }}-${{ matrix.shard }} + path: ./blob-report + retention-days: 1 + + required-playwright-tests: + needs: + - run-playwright-tests + - build-grafana + if: ${{ !cancelled() }} + name: All Playwright tests complete + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: blobs + pattern: playwright-blob-* + merge-multiple: true + + - name: Check blob reports + run: | + if [ ! "$(ls -A ./blobs)" ]; then + echo "Error: No blob reports found in ./blobs directory" + echo "Did the Playwright tests run at all?" + exit 1 + fi + echo "Found blob reports in ./blobs:" + ls -lah ./blobs + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./blobs + + - name: Merge into JSON Report + env: + PLAYWRIGHT_JSON_OUTPUT_NAME: /tmp/playwright-results.json + run: npx playwright merge-reports --reporter=json ./blobs + + - name: Bench report + run: | + docker run --rm \ + --volume="/tmp/playwright-results.json:/home/bench/tests/playwright-results.json" \ + us-docker.pkg.dev/grafanalabs-global/docker-grafana-bench-prod/grafana-bench:v0.5.1 report \ + --grafana-url "http://localhost:3000" \ + --grafana-version "CI- ${{ github.sha }}" \ + --test-suite-name "FrontendCore" \ + --report-input playwright \ + --report-output log \ + --log-level DEBUG \ + /home/bench/tests/playwright-results.json + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: playwright-html-${{ github.run_number }} + path: playwright-report + retention-days: 7 + + - name: Check test suites + id: check-jobs + uses: ./.github/actions/check-jobs + continue-on-error: true # Failure will be reported on Show test results step + with: + needs: ${{ toJson(needs) }} + failure-message: "One or more E2E test suites have failed" + success-message: "All E2E test suites completed successfully" + + - name: Show test results + env: + FAILED: ${{ steps.check-jobs.outputs.any-failed }} + # sed removes the leading `../../src/` from the paths + run: | + npx playwright merge-reports --reporter list ./all-blob-reports | sed 's|\(\.\./\)\{1,\}src/|/|g' + if [ "$FAILED" = "true" ]; then + exit 1 + fi + + run-a11y-test: needs: - build-grafana - uses: ./.github/workflows/run-e2e-suite.yml - with: - package: ${{ needs.build-grafana.outputs.artifact }} - suite: ${{ matrix.suite }} + name: A11y test + runs-on: ubuntu-latest-8-cores + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-grafana.outputs.artifact }} + - name: Run PR a11y test + if: github.event_name == 'pull_request' + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + with: + verb: run + args: go run ./pkg/build/a11y --package=grafana.tar.gz + - name: Run non-PR a11y test + if: github.event_name != 'pull_request' + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + with: + verb: run + args: go run ./pkg/build/a11y --package=grafana.tar.gz --no-threshold-fail + + # This is the job that is actually required by rulesets. + # We want to only require one job instead of all the individual tests. + # Future work also allows us to start skipping some tests based on changed files. + required-e2e-tests: + needs: + - run-e2e-tests + - build-grafana + # a11y test is not listed on purpose: it is not an important E2E test. + # It is also totally fine to fail right now. + # always() is the best function here. + # success() || failure() will skip this function if any need is also skipped. + # That means conditional test suites will fail the entire requirement check. + if: always() + + name: All E2E tests complete + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Check test suites + uses: ./.github/actions/check-jobs + with: + needs: ${{ toJson(needs) }} + failure-message: "One or more E2E test suites have failed" + success-message: "All E2E test suites completed successfully" diff --git a/.github/workflows/pr-external-labelling.yml b/.github/workflows/pr-external-labelling.yml new file mode 100644 index 00000000000..62bd9655edd --- /dev/null +++ b/.github/workflows/pr-external-labelling.yml @@ -0,0 +1,25 @@ +name: External PR labelling + +on: + # We need "write" permissions on the PR to be able to add a label. + pull_request_target: # zizmor: ignore[dangerous-triggers] We need this to have labelling permissions. There are no user inputs here, so we should be fine. + types: + - opened + +permissions: {} + +jobs: + label-if-external: + name: Add 'pr/external' label if the PR is external + if: github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'OWNER' + runs-on: ubuntu-latest + permissions: + pull-requests: write # to write the label + + steps: + - name: Add the 'pr/external' label + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + echo "Adding 'pr/external' label to the PR" + gh pr edit "$PR_NUMBER" --add-label pr/external diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index fd7ded43f0d..9e217c6ee46 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -9,13 +9,32 @@ on: permissions: {} jobs: + detect-changes: + name: Detect whether code changed + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changed: ${{ steps.detect-changes.outputs.frontend }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true # required to get more history in the changed-files action + fetch-depth: 2 + - name: Detect changes + id: detect-changes + uses: ./.github/actions/change-detection + with: + self: .github/workflows/pr-frontend-unit-tests.yml + frontend-unit-tests: permissions: contents: read id-token: write # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, # the `frontend-unit-tests-enterprise` workflow will run instead - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true + needs: detect-changes + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true && needs.detect-changes.outputs.changed == 'true' runs-on: ubuntu-latest-8-cores name: "Unit tests (${{ matrix.chunk }} / 8)" strategy: @@ -43,7 +62,8 @@ jobs: contents: read id-token: write # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + needs: detect-changes + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false && needs.detect-changes.outputs.changed == 'true' runs-on: ubuntu-latest-8-cores name: "Unit tests (${{ matrix.chunk }} / 8)" strategy: @@ -52,6 +72,8 @@ jobs: chunk: [1, 2, 3, 4, 5, 6, 7, 8] steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -67,3 +89,30 @@ jobs: TEST_MAX_WORKERS: 2 TEST_SHARD: ${{ matrix.chunk }} TEST_SHARD_TOTAL: 8 + + # This is the job that is actually required by rulesets. + # We need to require EITHER the OSS or the Enterprise job to pass. + # However, if one is skipped, GitHub won't flat-map the shards, + # so they won't be accepted by a ruleset. + required-frontend-unit-tests: + needs: + - frontend-unit-tests + - frontend-unit-tests-enterprise + # always() is the best function here. + # success() || failure() will skip this function if any need is also skipped. + # That means conditional test suites will fail the entire requirement check. + if: always() + + name: All frontend unit tests complete + runs-on: ubuntu-latest + steps: + - name: Check test suites + env: + NEEDS: ${{ toJson(needs) }} + run: | + FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')" + echo "$FAILURES" + if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then + exit 1 + fi + echo "All OK!" diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index b274b86b87b..2b30e0fa375 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -1,63 +1,27 @@ -# Owned by grafana-delivery-squad -# Intended to be dropped into the base repo Ex: grafana/grafana name: Dispatch check for patch conflicts -run-name: dispatch-check-patch-conflicts-${{ github.base_ref }}-${{ github.head_ref }} on: - pull_request_target: + pull_request: types: - opened - reopened - synchronize branches: - "main" - - "v*.*.*" - "release-*" -permissions: {} +permissions: + id-token: write + contents: read # Since this is run on a pull request, we want to apply the patches intended for the # target branch onto the source branch, to verify compatibility before merging. jobs: dispatch-job: - permissions: - id-token: write - contents: read - actions: write - env: - HEAD_REF: ${{ github.head_ref }} - BASE_REF: ${{ github.base_ref }} - REPO: ${{ github.repository }} - SENDER: ${{ github.event.sender.login }} - SHA: ${{ github.sha }} - PR_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} - runs-on: ubuntu-latest - steps: - - name: "Generate token" - id: generate_token - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a - with: - # App needs Actions: Read/Write for the grafana/security-patch-actions repo - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: "Dispatch job" - uses: actions/github-script@v7 - with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - const {HEAD_REF, BASE_REF, REPO, SENDER, SHA, PR_COMMIT_SHA} = process.env; - - await github.rest.actions.createWorkflowDispatch({ - owner: 'grafana', - repo: 'security-patch-actions', - workflow_id: 'test-patches-event.yml', - ref: 'main', - inputs: { - src_repo: REPO, - src_ref: HEAD_REF, - src_merge_sha: SHA, - src_pr_commit_sha: PR_COMMIT_SHA, - patch_repo: REPO + '-security-patches', - patch_ref: BASE_REF, - triggering_github_handle: SENDER - } - }) + uses: grafana/grafana/.github/workflows/pr-patch-check.yml@main + with: + head_ref: ${{ github.head_ref }} + base_ref: ${{ github.base_ref }} + repo: ${{ github.repository }} + sender_login: ${{ github.event.sender.login }} + sha: ${{ github.sha }} + pr_commit_sha: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/pr-patch-check.yml b/.github/workflows/pr-patch-check.yml new file mode 100644 index 00000000000..5de7fcf89ed --- /dev/null +++ b/.github/workflows/pr-patch-check.yml @@ -0,0 +1,78 @@ +name: Dispatch check for patch conflicts +on: + workflow_call: + inputs: + head_ref: + type: string + required: true + base_ref: + type: string + required: true + repo: + type: string + required: true + sender_login: + type: string + required: true + sha: + type: string + required: true + pr_commit_sha: + type: string + required: true + +permissions: + id-token: write + contents: read + +# Since this is run on a pull request, we want to apply the patches intended for the +# target branch onto the source branch, to verify compatibility before merging. +jobs: + dispatch-job: + env: + HEAD_REF: ${{ inputs.head_ref }} + BASE_REF: ${{ github.base_ref }} + REPO: ${{ inputs.repo }} + SENDER: ${{ inputs.sender_login }} + SHA: ${{ inputs.sha }} + PR_COMMIT_SHA: ${{ inputs.pr_commit_sha }} + runs-on: ubuntu-latest + steps: + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY + - name: "Generate token" + id: generate_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + # App needs Actions: Read/Write for the grafana/security-patch-actions repo + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} + permissions: "{\"actions\": \"write\", \"workflows\": \"write\"}" + repositories: "[\"security-patch-actions\"]" + - name: "Dispatch job" + uses: actions/github-script@v7 + with: + github-token: ${{ steps.generate_token.outputs.token }} + script: | + const {HEAD_REF, BASE_REF, REPO, SENDER, SHA, PR_COMMIT_SHA} = process.env; + + await github.rest.actions.createWorkflowDispatch({ + owner: 'grafana', + repo: 'security-patch-actions', + workflow_id: 'test-patches-event.yml', + ref: 'main', + inputs: { + src_repo: REPO, + src_ref: HEAD_REF, + src_merge_sha: SHA, + src_pr_commit_sha: PR_COMMIT_SHA, + patch_repo: REPO + '-security-patches', + patch_ref: BASE_REF, + triggering_github_handle: SENDER + } + }) diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index 15541938eeb..76f2068172c 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -15,6 +15,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +permissions: {} + jobs: sqlite: strategy: @@ -27,6 +29,8 @@ jobs: name: Sqlite (${{ matrix.shard }}) runs-on: ubuntu-latest-8-cores + permissions: + contents: read steps: - name: Checkout code uses: actions/checkout@v4 @@ -37,12 +41,11 @@ jobs: with: go-version-file: go.mod cache: true - - name: Generate Go code - run: make gen-go - name: Run tests env: SHARD: ${{ matrix.shard }} run: | + set -euo pipefail readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)" go test -tags=sqlite -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}" mysql: @@ -56,6 +59,8 @@ jobs: name: MySQL (${{ matrix.shard }}) runs-on: ubuntu-latest-8-cores + permissions: + contents: read env: GRAFANA_TEST_DB: mysql MYSQL_HOST: 127.0.0.1 @@ -73,6 +78,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@v5 with: @@ -80,12 +87,11 @@ jobs: cache: true - name: Setup MySQL devenv run: mysql -h 127.0.0.1 -P 3306 -u root -prootpass < devenv/docker/blocks/mysql_tests/setup.sql - - name: Generate Go code - run: make gen-go - name: Run tests env: SHARD: ${{ matrix.shard }} run: | + set -euo pipefail readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)" go test -p=1 -tags=mysql -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}" postgres: @@ -117,6 +123,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@v5 with: @@ -124,11 +132,37 @@ jobs: cache: true - name: Setup Postgres devenv run: psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - name: Generate Go code - run: make gen-go - name: Run tests env: SHARD: ${{ matrix.shard }} run: | + set -euo pipefail readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)" go test -p=1 -tags=postgres -timeout=5m -run '^TestIntegration' "${PACKAGES[@]}" + + # This is the job that is actually required by rulesets. + # We want to only require one job instead of all the individual tests and shards. + # Future work also allows us to start skipping some tests based on changed files. + required-backend-integration-tests: + needs: + - mysql + - postgres + - sqlite + # always() is the best function here. + # success() || failure() will skip this function if any need is also skipped. + # That means conditional test suites will fail the entire requirement check. + if: always() + + name: All backend integration tests complete + runs-on: ubuntu-latest + steps: + - name: Check test suites + env: + NEEDS: ${{ toJson(needs) }} + run: | + FAILURES="$(echo "$NEEDS" | jq 'with_entries(select(.value.result == "failure")) | map_values(.result)')" + echo "$FAILURES" + if [ "$(echo "$FAILURES" | jq '. | length')" != "0" ]; then + exit 1 + fi + echo "All OK!" diff --git a/.github/workflows/publish-artifact.yml b/.github/workflows/publish-artifact.yml new file mode 100644 index 00000000000..1cc650858a7 --- /dev/null +++ b/.github/workflows/publish-artifact.yml @@ -0,0 +1,68 @@ +name: Publish artifacts to bucket +on: + workflow_call: + inputs: + pattern: + description: | + (From actinos/download-artifact) Glob pattern of artifacts (instead of `name`) + Be careful when using this option; the contents of the root of each artifact are coalesced, so ensure that they do not collide. + type: string + required: false + name: + description: (From actinos/download-artifact) Name of the GitHub artifact to upload (Ignored if `pattern` is set) + type: string + required: false + bucket: + description: Name of the GCS bucket + type: string + required: true + bucket-path: + description: Path in the GCS bucket + type: string + required: false + default: "." + environment: + description: "'prod' or 'dev'" + type: string + required: false + default: dev + run-id: + type: string + required: true + service-account: + type: string + required: false + default: github-prerelease-writer@grafanalabs-workload-identity.iam.gserviceaccount.com +jobs: + publish: + runs-on: github-hosted-ubuntu-x64-small + name: Publish + permissions: + id-token: write + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: ${{ inputs.name }} + pattern: ${{ inputs.pattern }} + run-id: ${{ inputs.run-id }} + path: ./artifact + - name: Log in to GCS + id: login-to-gcs + uses: grafana/shared-workflows/actions/login-to-gcs@login-to-gcs/v0.2.1 + with: + environment: ${{ inputs.environment }} + service_account: ${{ inputs.service-account }} + - name: Coalesce artifacts + run: | + mkdir out + find ./artifact -mindepth 2 -maxdepth 2 -exec cp -r {} out/ \; + ls -al out + - name: Upload artifacts + uses: grafana/shared-workflows/actions/push-to-gcs@push-to-gcs-v0.2.0 + with: + bucket: ${{ inputs.bucket }} + environment: ${{ inputs.environment }} + parent: false + path: out + bucket_path: ${{ inputs.bucket-path }} + service_account: ${{ inputs.service-account }} diff --git a/.github/workflows/publish-kinds-next.yml b/.github/workflows/publish-kinds-next.yml index b63ba0ef966..495cb35abae 100644 --- a/.github/workflows/publish-kinds-next.yml +++ b/.github/workflows/publish-kinds-next.yml @@ -8,25 +8,17 @@ on: - '**/*.cue' workflow_dispatch: -jobs: - config: - runs-on: "ubuntu-latest" - if: github.repository == 'grafana/grafana' - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_DELIVERY_BOT_APP_ID != '' &&secrets.GRAFANA_DELIVERY_BOT_APP_PEM != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi +permissions: {} +jobs: main: - needs: config - if: github.repository == 'grafana/grafana' && needs.config.outputs.has-secrets + if: github.repository == 'grafana/grafana' runs-on: "ubuntu-latest" + permissions: + contents: read # cloning repo + actions: read # reading .github/workflows/ dir + id-token: write # reading vault secrets + steps: - name: "Checkout Grafana repo" uses: "actions/checkout@v4" @@ -42,12 +34,20 @@ jobs: - name: "Verify kinds" run: go run .github/workflows/scripts/kinds/verify-kinds.go + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + # App needs Actions: Read/Write for the grafana/security-patch-actions repo + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Clone website-sync Action" run: "git clone --single-branch --no-tags --depth 1 -b master https://grafana-delivery-bot:${{ steps.generate_token.outputs.token }}@github.com/grafana/website-sync ./.github/actions/website-sync" diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 73962750ef2..03873c27641 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -10,25 +10,17 @@ on: - '**/*.cue' workflow_dispatch: -jobs: - config: - runs-on: "ubuntu-latest" - if: github.repository == 'grafana/grafana' - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GRAFANA_DELIVERY_BOT_APP_ID != '' && secrets.GRAFANA_DELIVERY_BOT_APP_PEM != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi +permissions: {} +jobs: main: - needs: config - if: github.repository == 'grafana/grafana' && needs.config.outputs.has-secrets + if: github.repository == 'grafana/grafana' runs-on: "ubuntu-latest" + permissions: + contents: read # cloning repo + actions: read # reading .github/workflows/ dir + id-token: write # reading vault secrets + steps: - name: "Checkout Grafana repo" uses: "actions/checkout@v4" @@ -50,6 +42,7 @@ jobs: with: repository: "grafana/grafana-github-actions" path: "./actions" + persist-credentials: false - name: "Install Actions from library" run: "npm install --production --prefix ./actions" @@ -62,12 +55,20 @@ jobs: release_tag_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" release_branch_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.x$" + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY - name: "Generate token" id: generate_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + # App needs Actions: Read/Write for the grafana/security-patch-actions repo + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - name: "Clone website-sync Action" if: "steps.has-matching-release-tag.outputs.bool == 'true'" diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml index f9c2adf0230..0047e2992e0 100644 --- a/.github/workflows/publish-technical-documentation-next.yml +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: grafana/writers-toolkit/publish-technical-documentation@publish-technical-documentation/v1 # zizmor: ignore[unpinned-uses] with: website_directory: content/docs/grafana/next diff --git a/.github/workflows/reject-gh-secrets.yml b/.github/workflows/reject-gh-secrets.yml new file mode 100644 index 00000000000..066b7353116 --- /dev/null +++ b/.github/workflows/reject-gh-secrets.yml @@ -0,0 +1,31 @@ +name: Reject GitHub secrets + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - main + - release-* + +permissions: {} + +jobs: + reject-gh-secrets: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + + - name: Grep for secrets accesses + run: | + if grep -E '\$\{\{\s*secrets\s*\.\s*[a-zA-Z0-9_\-]+\s*\}\}' .github/workflows/*.yml | grep -vF 'secrets.GITHUB_TOKEN' | grep -vF '# nolint:reject-gh-secrets'; then + echo "Found secrets access in the codebase. Please remove it in favour of Vault secrets." + echo "If you are sure this is correct, add '# nolint:reject-gh-secrets' to the end of the line. Be VERY careful with this." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 00000000000..35b8a219d62 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,190 @@ +name: Build Release Packages +on: + workflow_dispatch: + push: + branches: + - release-*.*.* + - main + +permissions: + contents: read + +# Builds the following artifacts: +# +# npm:grafana +# storybook +# targz:grafana:linux/amd64 +# targz:grafana:linux/arm64 +# targz:grafana:linux/arm/v6 +# targz:grafana:linux/arm/v7 +# deb:grafana:linux/amd64 +# deb:grafana:linux/arm64 +# deb:grafana:linux/arm/v6 +# deb:grafana:linux/arm/v7 +# rpm:grafana:linux/amd64:sign +# rpm:grafana:linux/arm64:sign +# docker:grafana:linux/amd64 +# docker:grafana:linux/arm64 +# docker:grafana:linux/arm/v7 +# docker:grafana:linux/amd64:ubuntu +# docker:grafana:linux/arm64:ubuntu +# docker:grafana:linux/arm/v7:ubuntu +# targz:grafana:windows/amd64 +# targz:grafana:windows/arm64 +# targz:grafana:darwin/amd64 +# targz:grafana:darwin/arm64 +# zip:grafana:windows/amd64 +# msi:grafana:windows/amd64 +jobs: + setup: + name: setup + runs-on: github-hosted-ubuntu-x64-small + if: github.repository == 'grafana/grafana' + outputs: + version: ${{ steps.output.outputs.version }} + grafana-commit: ${{ steps.output.outputs.grafana_commit }} + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up version (Release Branches) + if: startsWith(github.ref_name, 'release-') + run: echo "${REF_NAME#release-}" > VERSION + env: + REF_NAME: ${{ github.ref_name }} + - name: Set up version (Non-release branches) + if: ${{ !startsWith(github.ref_name, 'release-') }} + run: jq -r .version package.json | sed -s "s/pre/${BUILD_ID}/g" > VERSION + env: + REF_NAME: ${{ github.ref_name }} + BUILD_ID: ${{ github.run_id }} + - id: output + run: | + echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" + echo "grafana_commit=$(git rev-parse HEAD)" | tee -a "$GITHUB_OUTPUT" + # Triggers the same workflow in `grafana-enterprise` on the same ref + downstream: + runs-on: github-hosted-ubuntu-x64-small + needs: [setup] + permissions: + contents: read + id-token: write + name: Dispatch grafana-enterprise build + steps: + - id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} + repositories: '["grafana-enterprise"]' + permissions: '{"actions": "write"}' + - uses: actions/github-script@v7 + env: + REF: ${{ github.ref_name }} + VERSION: ${{ needs.setup.outputs.version }} + BUILD_ID: ${{ github.run_id }} + BUCKET: grafana-prerelease + GRAFANA_COMMIT: ${{ needs.setup.outputs.grafana-commit }} + with: + github-token: ${{ steps.generate_token.outputs.token }} + script: | + const {REF, VERSION, BUILD_ID, BUCKET, GRAFANA_COMMIT} = process.env; + + await github.rest.actions.createWorkflowDispatch({ + owner: 'grafana', + repo: 'grafana-enterprise', + workflow_id: 'release-build.yml', + ref: REF, + inputs: { + "version": VERSION, + "build-id": String(BUILD_ID), + "bucket": BUCKET, + "grafana-commit": GRAFANA_COMMIT, + } + }) + + build: + runs-on: github-hosted-ubuntu-x64-large + needs: [setup] + permissions: + contents: read + id-token: write + name: ${{ needs.setup.outputs.version }} / ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + # The artifacts in these lists are grouped by their os+arch because the + # build process can reuse the binaries for each artifact. + # The downside to this is that the frontend will be built for each one when it could be reused for all of them. + # This could be a future improvement. + include: + - name: linux-amd64 + artifacts: targz:grafana:linux/amd64,deb:grafana:linux/amd64,rpm:grafana:linux/amd64,docker:grafana:linux/amd64,docker:grafana:linux/amd64:ubuntu,npm:grafana,storybook + - name: linux-arm64 + artifacts: targz:grafana:linux/arm64,deb:grafana:linux/arm64,rpm:grafana:linux/arm64,docker:grafana:linux/arm64,docker:grafana:linux/arm64:ubuntu + - name: linux-s390x + artifacts: targz:grafana:linux/s390x,deb:grafana:linux/s390x,rpm:grafana:linux/s390x,docker:grafana:linux/s390x,docker:grafana:linux/s390x:ubuntu + - name: linux-armv7 + artifacts: targz:grafana:linux/arm/v7,deb:grafana:linux/arm/v7,docker:grafana:linux/arm/v7,docker:grafana:linux/arm/v7:ubuntu + - name: linux-armv6 + artifacts: targz:grafana:linux/arm/v6,deb:grafana:linux/arm/v6 + - name: windows-amd64 + artifacts: targz:grafana:windows/amd64,zip:grafana:windows/amd64,msi:grafana:windows/amd64 + - name: windows-arm64 + artifacts: targz:grafana:windows/arm64,zip:grafana:windows/arm64 + - name: darwin-amd64 + artifacts: targz:grafana:darwin/amd64 + - name: darwin-arm64 + artifacts: targz:grafana:darwin/arm64 + steps: + - uses: grafana/shared-workflows/actions/dockerhub-login@main + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 + with: + image: docker.io/tonistiigi/binfmt:qemu-v7.0.0-28 + - uses: ./.github/actions/build-package + id: build + with: + artifacts: ${{ matrix.artifacts }} + checksum: true + grafana-path: . + github-token: ${{ secrets.GITHUB_TOKEN }} + version: ${{ needs.setup.outputs.version }} + output: artifacts-${{ matrix.name }}.txt + verify: true + build-id: ${{ github.run_id }} + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: artifacts-list-${{ matrix.name }} + path: ${{ steps.build.outputs.file }} + retention-days: 1 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: artifacts-${{ matrix.name }} + path: ${{ steps.build.outputs.dist-dir }} + retention-days: 1 + publish-artifacts: + name: Upload artifacts + uses: grafana/grafana/.github/workflows/publish-artifact.yml@main + permissions: + id-token: write + needs: + - setup + - build + with: + bucket: grafana-prerelease + pattern: artifacts-* + run-id: ${{ github.run_id }} + bucket-path: ${{ needs.setup.outputs.version }}_${{ github.run_id }} + environment: prod diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml index c0c435f61d2..7d149fad297 100644 --- a/.github/workflows/release-comms.yml +++ b/.github/workflows/release-comms.yml @@ -21,6 +21,11 @@ on: - 'main' - 'release-*.*.*' +permissions: + contents: write + pull-requests: write + id-token: write + jobs: setup: if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/')) }} @@ -30,6 +35,8 @@ jobs: release_branch: ${{ steps.output.outputs.release_branch }} dry_run: ${{ steps.output.outputs.dry_run }} latest: ${{ steps.output.outputs.latest }} + private_key: ${{ steps.output.outputs.delivery_bot_pem }} + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} env: HEAD_REF: ${{ github.head_ref }} DRY_RUN: ${{ inputs.dry_run }} @@ -39,36 +46,34 @@ jobs: steps: - if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') }} run: | - echo "VERSION=$(echo ${HEAD_REF} | sed -e 's/release\/.*\//v/g')" >> $GITHUB_ENV - echo "DRY_RUN=${{ contains(github.event.pull_request.labels.*.name, 'release/dry-run') }}" >> $GITHUB_ENV - echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') && '1' || '0' }}" >> $GITHUB_ENV + { + echo "VERSION=$(echo "${HEAD_REF}" | sed -e 's/release\/.*\//v/g')" + echo "DRY_RUN=${{ contains(github.event.pull_request.labels.*.name, 'release/dry-run') }}" + echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') && '1' || '0' }}" + } >> "$GITHUB_ENV" - id: output run: | echo "dry_run: $DRY_RUN" echo "latest: $LATEST" echo "version: $VERSION" - echo "release_branch=$(echo $VERSION | sed -s 's/^v/release-/g')" >> "$GITHUB_OUTPUT" - echo "dry_run=$DRY_RUN" >> "$GITHUB_OUTPUT" - echo "latest=$LATEST" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + { + echo "release_branch=$(echo "$VERSION" | sed -s 's/^v/release-/g')" + echo "dry_run=$DRY_RUN" + echo "latest=$LATEST" + echo "version=$VERSION" + } >> "$GITHUB_OUTPUT" create_next_release_branch_grafana: name: Create next release branch (Grafana) needs: setup - uses: ./.github/workflows/create-next-release-branch.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + uses: grafana/grafana/.github/workflows/create-next-release-branch.yml@main with: ownerRepo: 'grafana/grafana' source: ${{ needs.setup.outputs.release_branch }} create_next_release_branch_enterprise: name: Create next release branch (Grafana Enterprise) needs: setup - uses: ./.github/workflows/create-next-release-branch.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + uses: grafana/grafana/.github/workflows/create-next-release-branch.yml@main with: ownerRepo: 'grafana/grafana-enterprise' source: ${{ needs.setup.outputs.release_branch }} @@ -92,10 +97,7 @@ jobs: needs: - setup - create_next_release_branch_grafana - uses: ./.github/workflows/migrate-prs.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + uses: grafana/grafana/.github/workflows/migrate-prs.yml@main with: ownerRepo: 'grafana/grafana' from: ${{ needs.setup.outputs.release_branch }} @@ -104,20 +106,14 @@ jobs: needs: - setup - create_next_release_branch_enterprise - uses: ./.github/workflows/migrate-prs.yml - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + uses: grafana/grafana/.github/workflows/migrate-prs.yml@main with: ownerRepo: 'grafana/grafana-enterprise' from: ${{ needs.setup.outputs.release_branch }} to: ${{ needs.create_next_release_branch_enterprise.outputs.branch }} post_changelog_on_forum: needs: setup - uses: ./.github/workflows/community-release.yml - secrets: - GRAFANA_MISC_STATS_API_KEY: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} - GRAFANABOT_FORUM_KEY: ${{ secrets.GRAFANABOT_FORUM_KEY }} + uses: grafana/grafana/.github/workflows/community-release.yml@main with: version: ${{ needs.setup.outputs.version }} dry_run: ${{ needs.setup.outputs.dry_run == 'true' }} @@ -126,7 +122,7 @@ jobs: # The github-release action retrieves the changelog using the /repos/grafana/grafana/contents/CHANGELOG.md API # endpoint. needs: setup - uses: ./.github/workflows/github-release.yml + uses: grafana/grafana/.github/workflows/github-release.yml@main with: version: ${{ needs.setup.outputs.version }} dry_run: ${{ needs.setup.outputs.dry_run == 'true' }} @@ -139,5 +135,5 @@ jobs: VERSION: ${{ needs.setup.outputs.version }} steps: - run: | - echo announce on slack that $VERSION has been released - echo dry run: $DRY_RUN + echo announce on slack that "$VERSION" has been released + echo dry run: "$DRY_RUN" diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 42dd7051b71..2c43ae43de4 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -15,15 +15,19 @@ on: version: required: true type: string - description: The version of Grafana that is being released + description: The version of Grafana that is being released (without the `v` prefix)` target: - required: true - type: string - description: The release branch pattern (eg v9.5.x) that these changes are being merged into - backport: required: false type: string - description: Branch to backport these changes to + description: 'Unused: left here for backwards compatibility' + changelog: + required: false + type: boolean + default: true + bump: + required: false + type: boolean + default: true dry_run: required: false default: false @@ -32,29 +36,63 @@ on: required: false default: false type: boolean + release_date: + required: false + type: string + description: "Release date in format YYYY-MM-DD" -permissions: {} +permissions: + contents: read jobs: + capture-date: + runs-on: ubuntu-latest + outputs: + release_date: ${{ steps.set_release_date.outputs.release_date }} + steps: + - name: compute_release_date + run: | + if [ -n "$DATE" ]; then + echo "release_date=$DATE" >> "$GITHUB_ENV" + exit 0 + fi + + echo "Fetching workflow run creation date..." + created_at=$(gh run view "$GITHUB_RUN_ID" --repo "$GH_REPO" --json createdAt -q .createdAt) + formatted_date=$(date -d "$created_at" +%Y-%m-%d) + echo "release_date=$formatted_date" >> "$GITHUB_ENV" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + DATE: ${{ inputs.release_date }} + + - id: set_release_date + run: echo "release_date=$release_date" >> "$GITHUB_OUTPUT" + push-changelog-to-main: + needs: capture-date permissions: contents: write + id-token: write pull-requests: write + name: Create PR to main to update the changelog uses: ./.github/workflows/changelog.yml + concurrency: + group: grafana-release-pr-update-changelog-main + cancel-in-progress: false with: previous_version: ${{inputs.previous_version}} version: ${{ inputs.version }} latest: ${{ inputs.latest }} dry_run: ${{ inputs.dry_run }} target: main - secrets: - GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + work_branch: changelog/update-changelog-${{ needs.capture-date.outputs.release_date }} create-prs: permissions: contents: write + id-token: write pull-requests: write name: Create Release PR runs-on: ubuntu-latest @@ -64,55 +102,62 @@ jobs: LATEST: ${{ inputs.latest }} DRY_RUN: ${{ inputs.dry_run }} steps: - - name: Get release branch - id: branch - uses: grafana/grafana-github-actions-go/latest-release-branch@main # zizmor: ignore[unpinned-uses] + - name: "Get vault secrets" + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main with: - token: ${{ secrets.GITHUB_TOKEN }} - ownerRepo: 'grafana/grafana' - pattern: ${{ inputs.target }} + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY + - name: Generate token + id: generate_changelog_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} + repositories: "[\"grafana\", \"grafana-enterprise\"]" + permissions: "{\"contents\": \"write\", \"pull_requests\": \"write\", \"workflows\":\"write\"}" + - run: echo "RELEASE_BRANCH=release-${VERSION}" >> "$GITHUB_ENV" - name: Checkout Grafana uses: actions/checkout@v4 with: - ref: ${{ steps.branch.outputs.branch }} + token: ${{ steps.generate_changelog_token.outputs.token }} + ref: ${{ env.RELEASE_BRANCH }} fetch-tags: true - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false + fetch-depth: 0 - name: Checkout Grafana (main) uses: actions/checkout@v4 with: + token: ${{ steps.generate_changelog_token.outputs.token }} ref: main fetch-depth: '0' - fetch-tags: 'false' path: .grafana-main - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false + - name: Setup nodejs environment uses: actions/setup-node@v4 with: node-version-file: .nvmrc + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod - name: Configure git user run: | - git config --local user.name "github-actions[bot]" - git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "grafana-delivery-bot[bot]" + git config --local user.email "grafana-delivery-bot[bot]@users.noreply.github.com" git config --local --add --bool push.autoSetupRemote true - name: Create branch - run: git checkout -b "release/${{ github.run_id }}/$VERSION" - - name: Generate changelog token - id: generate_changelog_token - uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 - with: - app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} - private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} + run: git checkout -b "release/${{ github.run_number }}/$VERSION" - name: Generate changelog id: changelog + if: ${{ inputs.changelog == true || inputs.changelog == 'true' }} uses: ./.grafana-main/.github/actions/changelog with: + previous: ${{inputs.previous_version}} github_token: ${{ steps.generate_changelog_token.outputs.token }} target: v${{ env.VERSION }} output_file: changelog_items.md - name: Patch CHANGELOG.md + if: ${{ inputs.changelog == true || inputs.changelog == 'true' }} run: | # Prepare CHANGELOG.md content with version delimiters ( @@ -144,58 +189,43 @@ jobs: git diff CHANGELOG.md - name: "Prettify CHANGELOG.md" + if: ${{ inputs.changelog == true || inputs.changelog == 'true' }} run: npx prettier --write CHANGELOG.md - name: Commit CHANGELOG.md changes + if: ${{ inputs.changelog == true || inputs.changelog == 'true' }} run: git add CHANGELOG.md && git commit --allow-empty -m "Update changelog" CHANGELOG.md - - - name: Update package.json versions - uses: ./.grafana-main/pkg/build/actions/bump-version + - name: Bump versions + if: ${{ inputs.bump == true || inputs.bump == 'true' }} + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e with: - version: 'patch' + verb: run + args: go run -C .grafana-main ./pkg/build/actions/bump-version -version="patch" + - name: make gen-cue + shell: bash + run: make gen-cue - name: Add package.json changes + if: ${{ inputs.bump == true || inputs.bump == 'true' }} run: | git add package.json lerna.json yarn.lock packages public - test -e e2e/test-plugins && git add e2e/test-plugins + test -e e2e-playwright/test-plugins && git add e2e-playwright/test-plugins git commit -m "Update version to $VERSION" - name: Git push - if: ${{ inputs.dry_run }} != true - run: git push --set-upstream origin "release/${{ github.run_id }}/$VERSION" - - - name: Create PR without backports - if: "${{ inputs.backport == '' }}" + run: git push + - name: Create PR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: ${{ steps.branch.outputs.branch }} + DRY_RUN: ${{ inputs.dry_run }} run: | - LATEST_FLAG="" + LATEST_FLAG=() if [ "$LATEST" = "true" ]; then - LATEST_FLAG='-l "release/latest"' + LATEST_FLAG=(-l "release/latest") fi gh pr create \ - $LATEST_FLAG \ + "${LATEST_FLAG[@]}" \ -l "no-changelog" \ --dry-run="$DRY_RUN" \ - -B "$BRANCH" \ - --title "Release: $VERSION" \ - --body "These code changes must be merged after a release is complete" - - - name: Create PR with backports - if: "${{ inputs.backport != '' }}" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: ${{ steps.branch.outputs.branch }} - run: | - LATEST_FLAG="" - if [ "$LATEST" = "true" ]; then - LATEST_FLAG='-l "release/latest"' - fi - gh pr create \ - $LATEST_FLAG \ - -l "product-approved" \ - -l "no-changelog" \ - --dry-run="$DRY_RUN" \ - -B "$BRANCH" \ + -B "${RELEASE_BRANCH}" \ --title "Release: $VERSION" \ --body "These code changes must be merged after a release is complete" diff --git a/.github/workflows/relyance-scan.yml b/.github/workflows/relyance-scan.yml new file mode 100644 index 00000000000..29d68e00416 --- /dev/null +++ b/.github/workflows/relyance-scan.yml @@ -0,0 +1,33 @@ +name: Relyance Compliance Inspection +on: + schedule: + - cron: '0 0 * * *' # Run daily at 00:00 UTC + workflow_dispatch: # Allow for manual trigger + +jobs: + relyance-compliance-inspector: + permissions: + contents: read + id-token: write # Needed for Vault access + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Get API key + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@5d7e361bc7e0a183cde8afe9899fb7b596d2659b # get-vault-secrets-v1.2.0 + with: + repo_secrets: | + API_KEY=relyance:API_KEY + + - name: Run Relyance scan + env: + API_KEY: "${{ fromJSON(steps.vault-secrets.outputs.secrets).API_KEY }}" + run: | + docker pull gcr.io/relyance-ext/compliance_inspector:release && \ + docker run --rm -v ${{ github.workspace }}:/repo --env "API_KEY=${{ env.API_KEY }}" gcr.io/relyance-ext/compliance_inspector:release diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 76d765f4fcf..6eebaf9320a 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -2,7 +2,7 @@ name: run-dashboard-search-e2e on: workflow_run: - workflows: + workflows: - trigger-dashboard-search-e2e types: - completed @@ -36,11 +36,11 @@ jobs: - run: go version - uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'yarn' - name: Cache Node Modules id: cache-node-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -56,7 +56,7 @@ jobs: runTests: false - name: Cache Grafana Build and Dependencies id: cache-grafana - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | bin/ @@ -64,7 +64,7 @@ jobs: tools/ public/ conf/ - e2e/test-plugins/ + e2e-playwright/test-plugins/ devenv/ key: ${{ runner.os }}-grafana-${{ hashFiles('go.mod', 'package-lock.json', 'Makefile', 'pkg/storage/**/*.go', 'public/app/features/search/**/*.ts', 'public/app/features/search/**/*.tsx') }} # only rebuild grafana if search files have changed ( or dependencies ) @@ -74,9 +74,11 @@ jobs: - name: Get list of .ini files id: get_files + env: + WORKSPACE: ${{ github.workspace }} run: | - INI_FILES=$(ls ${{ github.workspace }}/e2e/dashboards-search-suite/*.ini | jq -R -s -c 'split("\n")[:-1]') - echo "ini_files=$INI_FILES" >> $GITHUB_OUTPUT + INI_FILES="$(find "$WORKSPACE"/e2e/dashboards-search-suite/ -type f -name '*.ini' | jq -R -s -c 'split("\n")[:-1]')" + echo "ini_files=$INI_FILES" >> "$GITHUB_OUTPUT" shell: bash run_tests: @@ -95,8 +97,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Restore Cached Node Modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -104,7 +108,7 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} - name: Restore Cached Grafana Build and Dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | bin/ @@ -112,7 +116,7 @@ jobs: tools/ public/ conf/ - e2e/test-plugins/ + e2e-playwright/test-plugins/ devenv/ key: ${{ runner.os }}-grafana-${{ hashFiles('go.mod', 'package-lock.json', 'Makefile', 'pkg/storage/**/*.go', 'public/app/features/search/**/*.ts', 'public/app/features/search/**/*.tsx') }} - name: Set the step name @@ -120,11 +124,12 @@ jobs: env: INI_NAME: ${{ matrix.ini_file }} run: | - FILE_NAME=$(basename "$env.INI_NAME" .ini) - echo "FILE_NAME=$FILE_NAME" >> $GITHUB_OUTPUT + FILE_NAME="$(basename "$INI_NAME" .ini)" + echo "FILE_NAME=$FILE_NAME" >> "$GITHUB_OUTPUT" - name: Run tests for ${{ steps.set_file_name.outputs.FILE_NAME }} env: INI_NAME: ${{ matrix.ini_file }} + WORKSPACE: ${{ github.workspace }} run: | - cp -rf $INI_NAME ${{ github.workspace }}/scripts/grafana-server/custom.ini + cp -rf "$INI_NAME" "$WORKSPACE"/scripts/grafana-server/custom.ini yarn e2e:dashboards-search || echo "Test failed but marking as success since unified search is behind a feature flag and should not block PRs" diff --git a/.github/workflows/run-e2e-suite.yml b/.github/workflows/run-e2e-suite.yml deleted file mode 100644 index ae0cb4cfd3a..00000000000 --- a/.github/workflows/run-e2e-suite.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: e2e suite - -on: - workflow_call: - inputs: - package: - type: string - required: true - suite: - type: string - required: true - -jobs: - main: - runs-on: ubuntu-latest-8-cores - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.package }} - - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e - with: - verb: run - args: go run ./pkg/build/e2e --package=grafana.tar.gz --suite=${{ inputs.suite }} - - name: Set suite name - id: set-suite-name - if: always() - env: - SUITE: ${{ inputs.suite }} - run: | - echo "suite=$(echo $SUITE | sed 's/\//-/g')" >> $GITHUB_OUTPUT - - uses: actions/upload-artifact@v4 - if: always() - with: - name: e2e-${{ steps.set-suite-name.outputs.suite }}-${{github.run_number}} - path: videos - retention-days: 1 diff --git a/.github/workflows/run-schema-v2-e2e.yml b/.github/workflows/run-schema-v2-e2e.yml index aa8a11c4c7f..a13b953648b 100644 --- a/.github/workflows/run-schema-v2-e2e.yml +++ b/.github/workflows/run-schema-v2-e2e.yml @@ -6,7 +6,7 @@ on: - main pull_request: branches: - - '**' + - '**' env: ARCH: linux-amd64 @@ -28,7 +28,7 @@ jobs: - run: go version - uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' cache: 'yarn' - name: Install dependencies run: yarn install --immutable @@ -40,7 +40,7 @@ jobs: runTests: false - name: Run dashboard scenes e2e run: yarn e2e:schema-v2 || echo "Test failed but marking as success since schema V2 is behind a feature flag and should not block PRs" - + - name: Always succeed # This is a workaround to make the job pass even if the previous step fails if: failure() run: exit 0 diff --git a/.github/workflows/scripts/crowdin/create-tasks.js b/.github/workflows/scripts/crowdin/create-tasks.js deleted file mode 100644 index d3085f8afa0..00000000000 --- a/.github/workflows/scripts/crowdin/create-tasks.js +++ /dev/null @@ -1,84 +0,0 @@ -const crowdin = require('@crowdin/crowdin-api-client'); -const TRANSLATED_CONNECTOR_DESCRIPTION = '{{tos_service_type: premium}}'; - -const API_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; -if (!API_TOKEN) { - console.error('Error: CROWDIN_PERSONAL_TOKEN environment variable is not set'); - process.exit(1); -} - -const PROJECT_ID = process.env.CROWDIN_PROJECT_ID; -if (!PROJECT_ID) { - console.error('Error: CROWDIN_PROJECT_ID environment variable is not set'); - process.exit(1); -} - -const { tasksApi, projectsGroupsApi, sourceFilesApi } = new crowdin.default({ - token: API_TOKEN, - organization: 'grafana' -}); - -const languages = await getLanguages(); -const fileIds = await getFileIds(); -console.log('Languages: ', languages); -console.log('File IDs: ', fileIds); - -// for (const language of languages) { -// const { name, id } = language; -// await createTask(`Translate to ${name}`, id, fileIds); -// } - -async function getLanguages() { - try { - const project = await projectsGroupsApi.getProject(PROJECT_ID); - const languages = project.data.targetLanguages; - return languages; - } catch (error) { - console.error('Failed to fetch languages: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} - -async function getFileIds() { - try { - const response = await sourceFilesApi.listProjectFiles(PROJECT_ID); - const files = response.data; - const fileIds = files.map(file => file.data.id); - return fileIds; - } catch (error) { - console.error('Failed to fetch file IDs: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} - -async function createTask(title, languageId, fileIds) { - try { - const taskParams = { - title, - description: TRANSLATED_CONNECTOR_DESCRIPTION, - languageId, - type: 2, // Translation by vendor - workflowStepId: 78, // Translation step ID - skipAssignedStrings: true, - fileIds, - }; - - console.log(`Creating Crowdin task: "${title}" for language ${languageId}`); - - const response = await tasksApi.addTask(PROJECT_ID, taskParams); - console.log(`Task created successfully! Task ID: ${response.data.id}`); - return response.data; - } catch (error) { - console.error('Failed to create Crowdin task: ', error.message); - if (error.response && error.response.data) { - console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); - } - process.exit(1); - } -} diff --git a/.github/workflows/scripts/crowdin/create-tasks.ts b/.github/workflows/scripts/crowdin/create-tasks.ts new file mode 100644 index 00000000000..3a2535640c8 --- /dev/null +++ b/.github/workflows/scripts/crowdin/create-tasks.ts @@ -0,0 +1,110 @@ +import crowdinImport from '@crowdin/crowdin-api-client'; +const TRANSLATED_CONNECTOR_DESCRIPTION = '{{tos_service_type: premium}}'; +const TRANSLATE_BY_VENDOR_WORKFLOW_TYPE = 'TranslateByVendor' + +// TODO Remove this type assertion when https://github.com/crowdin/crowdin-api-client-js/issues/508 is fixed +// @ts-expect-error +const crowdin = crowdinImport.default as typeof crowdinImport; + +const API_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; +if (!API_TOKEN) { + console.error('Error: CROWDIN_PERSONAL_TOKEN environment variable is not set'); + process.exit(1); +} + +const PROJECT_ID = process.env.CROWDIN_PROJECT_ID ? parseInt(process.env.CROWDIN_PROJECT_ID, 10) : undefined; +if (!PROJECT_ID) { + console.error('Error: CROWDIN_PROJECT_ID environment variable is not set'); + process.exit(1); +} + +const credentials = { + token: API_TOKEN, + organization: 'grafana' +}; + +const { tasksApi, projectsGroupsApi, sourceFilesApi, workflowsApi } = new crowdin(credentials); + +const languages = await getLanguages(PROJECT_ID); +const fileIds = await getFileIds(PROJECT_ID); +const workflowStepId = await getWorkflowStepId(PROJECT_ID); + +for (const language of languages) { + const { name, id } = language; + await createTask(PROJECT_ID, `Translate to ${name}`, id, fileIds, workflowStepId); +} + +async function getLanguages(projectId: number) { + try { + const project = await projectsGroupsApi.getProject(projectId); + const languages = project.data.targetLanguages; + console.log('Fetched languages successfully!'); + return languages; + } catch (error) { + console.error('Failed to fetch languages: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function getFileIds(projectId: number) { + try { + const response = await sourceFilesApi.listProjectFiles(projectId); + const files = response.data; + const fileIds = files.map(file => file.data.id); + console.log('Fetched file ids successfully!'); + return fileIds; + } catch (error) { + console.error('Failed to fetch file IDs: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function getWorkflowStepId(projectId: number) { + try { + const response = await workflowsApi.listWorkflowSteps(projectId); + const workflowSteps = response.data; + const workflowStepId = workflowSteps.find(step => step.data.type === TRANSLATE_BY_VENDOR_WORKFLOW_TYPE)?.data.id; + if (!workflowStepId) { + throw new Error(`Workflow step with type "${TRANSLATE_BY_VENDOR_WORKFLOW_TYPE}" not found`); + } + console.log('Fetched workflow step ID successfully!'); + return workflowStepId; + } catch (error) { + console.error('Failed to fetch workflow step ID: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function createTask(projectId: number, title: string, languageId: string, fileIds: number[], workflowStepId: number) { + try { + const taskParams = { + title, + description: TRANSLATED_CONNECTOR_DESCRIPTION, + languageId, + workflowStepId, + skipAssignedStrings: true, + fileIds, + }; + + console.log(`Creating Crowdin task: "${title}" for language ${languageId}`); + + const response = await tasksApi.addTask(projectId, taskParams); + console.log(`Task created successfully! Task ID: ${response.data.id}`); + return response.data; + } catch (error) { + console.error('Failed to create Crowdin task: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 00000000000..c0159f448f3 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,29 @@ +name: Shellcheck + +on: + push: + branches: + - main + - release-* + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + shellcheck: + name: Shellcheck scripts + runs-on: ubuntu-latest + permissions: + contents: read # clone the repository + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Run Shellcheck + run: find scripts/ -name '*.sh' -type f -exec shellcheck -e SC1071 -e SC2162 '{}' + diff --git a/.github/workflows/skye-add-to-project.yml b/.github/workflows/skye-add-to-project.yml index 7aee160cbcb..7dbc08e3e85 100644 --- a/.github/workflows/skye-add-to-project.yml +++ b/.github/workflows/skye-add-to-project.yml @@ -1,15 +1,15 @@ -name: Add issues and PRs to Skye project board +name: Add issues to Skye project board on: workflow_dispatch: inputs: manual_issue_number: - description: 'Issue/PR number to add to project' + description: 'Issue to add to project' required: false type: number + # Ideally we could trigger this for PRs as well, but getting the secrets on that is tricky + # so we just won't bother for now issues: types: [opened] - pull_request: - types: [opened] permissions: contents: read @@ -47,26 +47,16 @@ jobs: app_id: ${{ env.GH_APP_ID }} private_key: ${{ env.GH_APP_PEM }} - # Check if the user is in the list from the secret + # We do the check in the Github Actions expression and then export it to the output + # to reuse it - name: Check if user is allowed id: check_user env: - ALLOWED_USERS: ${{ env.ALLOWED_USERS }} - USERNAME: ${{ github.event.sender.login }} + USER_IS_ALLOWED: ${{ contains(fromJSON(env.ALLOWED_USERS), github.event.sender.login) }} run: | - # Convert the comma-separated list to an array - IFS=',' read -ra ALLOWED_USERS <<< "$ALLOWED_USERS" + echo "user_allowed=${USER_IS_ALLOWED}" >> "$GITHUB_OUTPUT" - # Check if user is in the allowed list - for allowed_user in "${ALLOWED_USERS[@]}"; do - if [ "$allowed_user" = "$USERNAME" ]; then - echo "user_allowed=true" >> $GITHUB_OUTPUT - exit 0 - fi - done - echo "user_allowed=false" >> $GITHUB_OUTPUT - - # Convert the issue/PR number to a node ID for the GraphQL API + # Convert the issue to a node ID for the GraphQL API - name: Get node ID for item if: steps.check_user.outputs.user_allowed == 'true' id: get_node_id @@ -88,7 +78,7 @@ jobs: env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - # Finally, add the issue/PR to the project board + # Finally, add the issue to the project board - name: Add to project board if: steps.check_user.outputs.user_allowed == 'true' uses: octokit/graphql-action@51bf543c240dcd14761320e2efc625dc32ec0d32 diff --git a/.github/workflows/storybook-verification-playwright.yml b/.github/workflows/storybook-verification-playwright.yml new file mode 100644 index 00000000000..e3924a67a85 --- /dev/null +++ b/.github/workflows/storybook-verification-playwright.yml @@ -0,0 +1,47 @@ +name: Verify Storybook (Playwright) + +on: + pull_request: + paths: + - 'packages/grafana-ui/**' + - 'e2e-playwright/storybook/**' + - '!docs/**' + - '!*.md' + push: + branches: + - main + paths: + - 'packages/grafana-ui/**' + - 'e2e-playwright/storybook/**' + - '!docs/**' + - '!*.md' + +permissions: {} + +jobs: + verify-storybook: + name: Verify Storybook (Playwright) + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run Storybook and E2E tests + run: yarn e2e:playwright:storybook diff --git a/.github/workflows/storybook-verification.yml b/.github/workflows/storybook-verification.yml index c50b7533d0d..2777836d5cb 100644 --- a/.github/workflows/storybook-verification.yml +++ b/.github/workflows/storybook-verification.yml @@ -14,26 +14,30 @@ on: - '!docs/**' - '!*.md' +permissions: {} + jobs: verify-storybook: name: Verify Storybook runs-on: ubuntu-latest - + permissions: + contents: read + steps: - name: Checkout code uses: actions/checkout@v4 with: persist-credentials: false - + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version-file: 'package.json' + node-version-file: '.nvmrc' cache: 'yarn' - + - name: Install dependencies run: yarn install --immutable - + - name: Run Storybook and E2E tests uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f with: diff --git a/.github/workflows/swagger-gen.yml b/.github/workflows/swagger-gen.yml new file mode 100644 index 00000000000..ee73adc1c28 --- /dev/null +++ b/.github/workflows/swagger-gen.yml @@ -0,0 +1,51 @@ +name: Swagger generated code + +on: + push: + branches: + - main + - release-* + pull_request: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Verify committed API specs match + runs-on: ubuntu-latest + if: ${{ github.repository == 'grafana/grafana' }} + permissions: + contents: read # clone the repository + id-token: write # required for Vault access + steps: + # Set up repository clone + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Setup Enterprise + if: ${{ github.event.pull_request.head.repo.fork == false }} + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + + - name: Generate Swagger specs + run: make swagger-clean && make openapi3-gen + - name: Check for changes + run: | + git add -f public/api-merged.json public/openapi3.json + if [ -z "$(git diff --name-only --cached)" ]; then + echo "No changes detected in API specs." + else + echo "Changes detected in API specs. Please review the changes." + echo "You can regenerate them locally with: make swagger-clean && make openapi3-gen" + exit 1 + fi diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml new file mode 100644 index 00000000000..761916940fe --- /dev/null +++ b/.github/workflows/trufflehog.yml @@ -0,0 +1,35 @@ +name: Trufflehog + +on: + pull_request: + types: + - opened + - synchronize + - reopened + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + trufflehog: + name: Run Trufflehog + runs-on: ubuntu-latest + permissions: + contents: read # clone the repo + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + ref: ${{ github.head_ref }} + - name: Trufflehog + uses: trufflesecurity/trufflehog@90190deac64289cb10bb694894be8db9ead8790b # v3.88.29 + with: + base: ${{ github.event.pull_request.base.sha }} + head: ${{ github.event.pull_request.head.sha }} + extra_args: --results=verified diff --git a/.github/zizmor.yml b/.github/zizmor.yml index fba4a80be24..073f762be50 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -21,6 +21,8 @@ rules: - pr-frontend-unit-tests.yml - pr-test-integration.yml - publish-kinds-release.yml + - pr-e2e-tests.yml + - swagger-gen.yml dangerous-triggers: ignore: - auto-milestone.yml @@ -29,3 +31,6 @@ rules: - pr-commands.yml - pr-patch-check-event.yml - run-dashboard-search-e2e.yml + excessive-permissions: + ignore: + - release-comms.yml