diff --git a/.depot/workflows/pr.yml b/.depot/workflows/pr.yml index 506c2ac6b..75aefb5f8 100644 --- a/.depot/workflows/pr.yml +++ b/.depot/workflows/pr.yml @@ -2,11 +2,143 @@ on: pull_request: branches: [main] types: [opened, synchronize, reopened, ready_for_review] + push: + branches: [main] name: "depot: PR" +concurrency: + group: depot-pr-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: - noop: + + lint: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - name: Check go mod tidy + run: | + go mod tidy + git diff --exit-code -- go.mod go.sum || { + echo "::error::Please run 'go mod tidy' and commit changes" + exit 1 + } + - name: Check go fmt + run: | + if gofmt -d -s . | grep -q .; then + echo "::error::Please run 'gofmt -w .' and commit changes" + exit 1 + fi + - name: Build binary (needed for lint tests) + run: make build + - name: Run lint tests + env: + REPLICATED_API_TOKEN: ${{ secrets.REPLICATED_API_TOKEN }} + run: make test-lint + + unit-tests: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - run: make test-unit + + pact-tests: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - name: Install pact + run: | + curl -L -o /tmp/pact.tar.gz https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v2.0.0/pact-2.0.0-linux-x86_64.tar.gz + echo "94ea52ef7d6c9dacf431dde7138717d96714131a /tmp/pact.tar.gz" | shasum -c - + mkdir -p /tmp/pact-standalone + tar -C /tmp/pact-standalone --strip-components=1 -xzf /tmp/pact.tar.gz + echo "/tmp/pact-standalone/bin" >> "$GITHUB_PATH" + rm /tmp/pact.tar.gz + - name: Setup pact environment + if: github.ref == 'refs/heads/main' + run: | + echo "PACT_VERSION=${{ github.sha }}" >> "$GITHUB_ENV" + echo "PACT_BROKER_BASE_URL=${{ secrets.PACT_BROKER_BASE_URL }}" >> "$GITHUB_ENV" + echo "PACT_BROKER_TOKEN=${{ secrets.PACT_BROKER_TOKEN }}" >> "$GITHUB_ENV" + - run: make test-pact + - if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) + run: make publish-pact + - if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) + run: | + make can-i-deploy || echo "::warning:: can-i-deploy says no; provider(s) must successfully verify before release" + + integration-tests: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - run: make build + - run: make test-integration + + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - run: make build + + security: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Run semgrep + run: | + docker run --rm -v "${PWD}:/src" returntocorp/semgrep semgrep scan --config=p/golang /src + + gate: + if: always() + needs: [lint, unit-tests, pact-tests, integration-tests, build, security] runs-on: ubuntu-latest steps: - - run: echo "Depot PR workflow registered" + - name: Check all results + run: | + results=( + "${{ needs.lint.result }}" + "${{ needs.unit-tests.result }}" + "${{ needs.pact-tests.result }}" + "${{ needs.integration-tests.result }}" + "${{ needs.build.result }}" + "${{ needs.security.result }}" + ) + for r in "${results[@]}"; do + if [[ "$r" == "failure" || "$r" == "cancelled" ]]; then + echo "::error::Job failed: $r" + exit 1 + fi + done + echo "All checks passed" diff --git a/.depot/workflows/release.yml b/.depot/workflows/release.yml new file mode 100644 index 000000000..f526e7e74 --- /dev/null +++ b/.depot/workflows/release.yml @@ -0,0 +1,104 @@ +on: + push: + tags: + - 'v*' + +name: "depot: Release" + +jobs: + + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - run: make test-unit + - name: Install pact + run: | + curl -L -o /tmp/pact.tar.gz https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v2.0.0/pact-2.0.0-linux-x86_64.tar.gz + echo "94ea52ef7d6c9dacf431dde7138717d96714131a /tmp/pact.tar.gz" | shasum -c - + mkdir -p /tmp/pact-standalone + tar -C /tmp/pact-standalone --strip-components=1 -xzf /tmp/pact.tar.gz + echo "/tmp/pact-standalone/bin" >> "$GITHUB_PATH" + rm /tmp/pact.tar.gz + - run: make test-pact + - run: make build + + release: + needs: build-and-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - name: Install goreleaser + run: go install github.com/goreleaser/goreleaser/v2@v2.14.3 + - name: Run goreleaser + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: goreleaser release --clean + + docker-publish: + needs: build-and-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - name: Build binary with version + run: | + VERSION=${GITHUB_REF_NAME#v} + LDFLAGS="-ldflags \"-X github.com/replicatedhq/replicated/pkg/version.version=${VERSION}\"" + make build LDFLAGS="${LDFLAGS}" + - name: Build Docker image + run: | + VERSION=${GITHUB_REF_NAME#v} + MAJOR=$(echo $VERSION | cut -d. -f1) + MINOR=$(echo $VERSION | cut -d. -f1,2) + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "MAJOR=${MAJOR}" >> "$GITHUB_ENV" + echo "MINOR=${MINOR}" >> "$GITHUB_ENV" + docker build -f Dockerfile.release -t replicated/vendor-cli:${VERSION} . + docker tag replicated/vendor-cli:${VERSION} replicated/vendor-cli:latest + docker tag replicated/vendor-cli:${VERSION} replicated/vendor-cli:${MAJOR} + docker tag replicated/vendor-cli:${VERSION} replicated/vendor-cli:${MINOR} + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + - name: Push images + run: | + docker push replicated/vendor-cli:${VERSION} + docker push replicated/vendor-cli:latest + docker push replicated/vendor-cli:${MAJOR} + docker push replicated/vendor-cli:${MINOR} + + docs: + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + cache: true + cache-dependency-path: ./go.sum + - name: Build binary + run: make build + - name: Generate and publish docs + env: + GITHUB_TOKEN: ${{ secrets.DOCS_REPO_TOKEN }} + run: ./scripts/generate-docs.sh diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml deleted file mode 100644 index 32ee193f1..000000000 --- a/.github/workflows/main.yaml +++ /dev/null @@ -1,109 +0,0 @@ -name: PR/main branch CI - -on: - pull_request: - push: - branches: - - main - -jobs: - make-unit-tests: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version-file: 'go.mod' - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - run: make test-unit - - make-pact-tests: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version-file: 'go.mod' - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - uses: replicatedhq/action-install-pact@v1 - - name: setup pact environment - run: | - if [[ $GITHUB_REF_NAME == 'main' ]]; then - echo "Adding pact environment variables" - echo "PACT_VERSION=${{ github.sha }}" >> "$GITHUB_ENV" - echo "PACT_BROKER_BASE_URL=${{ secrets.PACT_BROKER_BASE_URL }}" >> "$GITHUB_ENV" - echo "PACT_BROKER_TOKEN=${{ secrets.PACT_BROKER_TOKEN }}" >> "$GITHUB_ENV" - fi - - run: make test-pact - - if: github.ref == 'refs/heads/main' && ( github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository ) ) - run: make publish-pact - - if: github.ref == 'refs/heads/main' && ( github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository ) ) - run: | - make can-i-deploy || echo "::warning:: can-i-deploy says no; provider(s) must successfully verify before release" - - make-integration-tests: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version-file: 'go.mod' - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - run: make test-integration - - make-build: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version-file: 'go.mod' - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: make build - run: make build - - dagger-build: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - name: Install Dagger - run: curl -fsSL https://dl.dagger.io/dagger/install.sh | sudo BIN_DIR=/usr/local/bin sh - - name: Dagger build - run: make dagger-build - - # dagger-goreleaser-dryrun: - # runs-on: ubuntu-24.04 - # steps: - # - uses: actions/checkout@v4 - # - name: Install Dagger - # run: curl -fsSL https://dl.dagger.io/dagger/install.sh | sudo BIN_DIR=/usr/local/bin sh - # - name: Goreleaser dryrun - # run: make dagger-goreleaser-dryrun diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 000000000..e32131277 --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,15 @@ +FROM alpine:latest + +RUN apk add --no-cache ca-certificates curl git nodejs npm \ + && update-ca-certificates \ + && npm install -g replicated-lint + +ENV IN_CONTAINER=1 + +LABEL com.replicated.vendor_cli="true" + +WORKDIR /out + +COPY bin/replicated /replicated + +ENTRYPOINT ["/replicated"] diff --git a/Makefile b/Makefile index df7ff1bd7..cbb09681d 100644 --- a/Makefile +++ b/Makefile @@ -91,28 +91,4 @@ build: -o bin/replicated \ cli/main.go -.PHONY: dagger-build -dagger-build: - dagger call build --progress plain export --path bin/replicated - -.PHONY: dagger-goreleaser-dryrun -dagger-goreleaser-dryrun: - dagger call goreleaser-dryrun --progress plain - -.PHONY: release -release: - dagger call release \ - --one-password-service-account-production env:OP_SERVICE_ACCOUNT_PRODUCTION \ - --version $(version) \ - --github-token env:GITHUB_TOKEN \ - --progress plain - @echo "" - @echo "✓ Release completed successfully" - @echo "Generating documentation PR..." - @$(MAKE) docs - -.PHONY: docs -docs: - dagger --progress plain call generate-docs \ - --github-token env:GITHUB_TOKEN \ - --progress plain + diff --git a/cli/cmd/enterprise_portal_preview.go b/cli/cmd/enterprise_portal_preview.go index 2a91ffc27..73a299a5d 100644 --- a/cli/cmd/enterprise_portal_preview.go +++ b/cli/cmd/enterprise_portal_preview.go @@ -241,48 +241,48 @@ func (r *runners) enterprisePortalPreview(cmd *cobra.Command, args []string) err // the /enterprise-portal/license envelope. Kept in sync with the LicenseResponse // struct in handlers/market-api/enterpriseportal/license.go. type previewLicense struct { - ID string `json:"id"` - AppID string `json:"appId"` - AppName string `json:"appName"` - AppIcon *string `json:"appIcon"` - Channels []previewLicenseChannel `json:"channels"` - CustomerID string `json:"customerId"` - CustomerName string `json:"customerName"` - CustomerEmail string `json:"customerEmail"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` - ExpireAt *string `json:"expireAt"` - IsExpired bool `json:"isExpired"` - IsArchived bool `json:"isArchived"` - LicenseType string `json:"licenseType"` - Sequence int64 `json:"sequence"` - IsAirgapSupported bool `json:"isAirgapSupported"` - IsGitopsSupported bool `json:"isGitopsSupported"` - IsIdentityServiceSupported bool `json:"isIdentityServiceSupported"` - IsGeoaxisSupported bool `json:"isGeoaxisSupported"` - IsSnapshotSupported bool `json:"isSnapshotSupported"` - IsDisasterRecoverySupported bool `json:"isDisasterRecoverySupported"` - IsSupportBundleUploadSupported bool `json:"isSupportBundleUploadSupported"` - IsEmbeddedClusterDownloadEnabled bool `json:"isEmbeddedClusterDownloadEnabled"` - IsEmbeddedClusterMultiNodeEnabled bool `json:"isEmbeddedClusterMultiNodeEnabled"` - IsKotsInstallEnabled bool `json:"isKotsInstallEnabled"` - IsHelmInstallEnabled bool `json:"isHelmInstallEnabled"` - IsKurlInstallEnabled bool `json:"isKurlInstallEnabled"` - IsHelmAirgapEnabled bool `json:"isHelmAirgapEnabled"` + ID string `json:"id"` + AppID string `json:"appId"` + AppName string `json:"appName"` + AppIcon *string `json:"appIcon"` + Channels []previewLicenseChannel `json:"channels"` + CustomerID string `json:"customerId"` + CustomerName string `json:"customerName"` + CustomerEmail string `json:"customerEmail"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + ExpireAt *string `json:"expireAt"` + IsExpired bool `json:"isExpired"` + IsArchived bool `json:"isArchived"` + LicenseType string `json:"licenseType"` + Sequence int64 `json:"sequence"` + IsAirgapSupported bool `json:"isAirgapSupported"` + IsGitopsSupported bool `json:"isGitopsSupported"` + IsIdentityServiceSupported bool `json:"isIdentityServiceSupported"` + IsGeoaxisSupported bool `json:"isGeoaxisSupported"` + IsSnapshotSupported bool `json:"isSnapshotSupported"` + IsDisasterRecoverySupported bool `json:"isDisasterRecoverySupported"` + IsSupportBundleUploadSupported bool `json:"isSupportBundleUploadSupported"` + IsEmbeddedClusterDownloadEnabled bool `json:"isEmbeddedClusterDownloadEnabled"` + IsEmbeddedClusterMultiNodeEnabled bool `json:"isEmbeddedClusterMultiNodeEnabled"` + IsKotsInstallEnabled bool `json:"isKotsInstallEnabled"` + IsHelmInstallEnabled bool `json:"isHelmInstallEnabled"` + IsKurlInstallEnabled bool `json:"isKurlInstallEnabled"` + IsHelmAirgapEnabled bool `json:"isHelmAirgapEnabled"` EntitlementFields []previewEntitlementField `json:"entitlementFields"` EntitlementValues []previewEntitlementValue `json:"entitlementValues"` } type previewLicenseChannel struct { - AppID string `json:"appId"` - LicenseID string `json:"licenseId"` - ChannelID string `json:"channelId"` - ChannelName string `json:"channelName"` - ChannelSlug string `json:"channelSlug"` - IsSemverRequired int `json:"isSemverRequired"` - IsDefault int `json:"isDefault"` - PinnedChannelSequence *int64 `json:"pinnedChannelSequence"` - HasKurlInstaller bool `json:"hasKurlInstaller"` + AppID string `json:"appId"` + LicenseID string `json:"licenseId"` + ChannelID string `json:"channelId"` + ChannelName string `json:"channelName"` + ChannelSlug string `json:"channelSlug"` + IsSemverRequired int `json:"isSemverRequired"` + IsDefault int `json:"isDefault"` + PinnedChannelSequence *int64 `json:"pinnedChannelSequence"` + HasKurlInstaller bool `json:"hasKurlInstaller"` } type previewEntitlementField struct { @@ -418,7 +418,7 @@ func portBusy(p int) bool { // escapeComposeScalar escapes a string for use as a YAML single-quoted scalar // inside a docker-compose file. Two layers: -// - YAML single-quote: ' → '' +// - YAML single-quote: ' → ” // - docker-compose variable interpolation: $ → $$. Compose interpolates $vars // over the whole file before YAML parsing, single-quoted strings included, // so a path like /Users/ca$h/repo would otherwise have "$h" treated as a diff --git a/cli/cmd/lint_types.go b/cli/cmd/lint_types.go index a7227ddaa..b9262a77d 100644 --- a/cli/cmd/lint_types.go +++ b/cli/cmd/lint_types.go @@ -9,13 +9,13 @@ import ( // JSONLintOutput represents the complete JSON output structure for lint results type JSONLintOutput struct { - Metadata LintMetadata `json:"metadata"` - HelmResults *HelmLintResults `json:"helm_results,omitempty"` - PreflightResults *PreflightLintResults `json:"preflight_results,omitempty"` - SupportBundleResults *SupportBundleLintResults `json:"support_bundle_results,omitempty"` - EmbeddedClusterResults *EmbeddedClusterLintResults `json:"embedded_cluster_results,omitempty"` - Summary LintSummary `json:"summary"` - Images *ImageExtractResults `json:"images,omitempty"` // Only if --verbose + Metadata LintMetadata `json:"metadata"` + HelmResults *HelmLintResults `json:"helm_results,omitempty"` + PreflightResults *PreflightLintResults `json:"preflight_results,omitempty"` + SupportBundleResults *SupportBundleLintResults `json:"support_bundle_results,omitempty"` + EmbeddedClusterResults *EmbeddedClusterLintResults `json:"embedded_cluster_results,omitempty"` + Summary LintSummary `json:"summary"` + Images *ImageExtractResults `json:"images,omitempty"` // Only if --verbose } // LintMetadata contains execution context and environment information diff --git a/cli/cmd/release_create.go b/cli/cmd/release_create.go index d60bf9721..5a5e8a3e3 100644 --- a/cli/cmd/release_create.go +++ b/cli/cmd/release_create.go @@ -42,7 +42,7 @@ If no flags are provided, the command will automatically use the configuration f .replicated file in the current directory (or parent directories). The config should specify charts and manifests to include. Charts will be automatically packaged using helm, and manifests will be collected using glob patterns.`, - Example: `# .replicated config: + Example: `# .replicated config: appSlug: "my-app" charts: - path: ./chart diff --git a/cli/cmd/release_create_test.go b/cli/cmd/release_create_test.go index 94c444981..4cc18766b 100644 --- a/cli/cmd/release_create_test.go +++ b/cli/cmd/release_create_test.go @@ -125,9 +125,9 @@ func TestSetKOTSDefaultReleaseParams_WithExistingYamlDir(t *testing.T) { // TestSetKOTSDefaultReleaseParams_PromoteMapsMainToUnstable tests branch name mapping func TestSetKOTSDefaultReleaseParams_PromoteMapsMainToUnstable(t *testing.T) { tests := []struct { - name string - branch string - wantPromote string + name string + branch string + wantPromote string }{ { name: "main branch maps to Unstable", diff --git a/dagger.json b/dagger.json deleted file mode 100644 index bc88cf95d..000000000 --- a/dagger.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "replicated", - "engineVersion": "v0.19.9", - "sdk": { - "source": "go" - }, - "dependencies": [ - { - "name": "onepassword", - "source": "github.com/replicatedhq/daggerverse/onepassword@0b03c8c560c2067f34dab800c92154abc3834841", - "pin": "0b03c8c560c2067f34dab800c92154abc3834841" - } - ], - "source": "dagger", - "disableDefaultFunctionCaching": true -} diff --git a/dagger/.gitattributes b/dagger/.gitattributes deleted file mode 100644 index 3a454933c..000000000 --- a/dagger/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/dagger.gen.go linguist-generated -/internal/dagger/** linguist-generated -/internal/querybuilder/** linguist-generated -/internal/telemetry/** linguist-generated diff --git a/dagger/.gitignore b/dagger/.gitignore deleted file mode 100644 index 773338bfb..000000000 --- a/dagger/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/dagger.gen.go -/internal/dagger -/internal/querybuilder -/internal/telemetry -/.env diff --git a/dagger/build.go b/dagger/build.go deleted file mode 100644 index bc848269a..000000000 --- a/dagger/build.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -// Build compiles the replicated CLI binary. -func (r *Replicated) Build( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) (*dagger.File, error) { - image, err := goImage(ctx, source) - if err != nil { - return nil, err - } - goModCache, goBuildCache, err := goCacheVolumes(ctx, source) - if err != nil { - return nil, err - } - - binary := dag.Container(dagger.ContainerOpts{ - Platform: "linux/amd64", - }). - From(image). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithoutFile("/go/src/github.com/replicatedhq/replicated/bin/replicated"). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache"). - With(CacheBustingExec([]string{"make", "build"})). - File("/go/src/github.com/replicatedhq/replicated/bin/replicated") - - return binary, nil -} diff --git a/dagger/compatibility.go b/dagger/compatibility.go deleted file mode 100644 index 457862adc..000000000 --- a/dagger/compatibility.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -func validateCompatibility( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - return nil -} diff --git a/dagger/docs.go b/dagger/docs.go deleted file mode 100644 index 13459ff83..000000000 --- a/dagger/docs.go +++ /dev/null @@ -1,282 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "context" - "dagger/replicated/internal/dagger" - "encoding/json" - "fmt" - "io" - "net/http" - "path/filepath" - "strings" - "time" - - "github.com/pkg/errors" -) - -func (r *Replicated) GenerateDocs( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, - - githubToken *dagger.Secret, -) error { - err := checkGitTree(ctx, source, githubToken) - if err != nil { - return errors.Wrap(err, "failed to check git tree") - } - - latestVersion, err := getLatestVersion(ctx, githubToken) - if err != nil { - return errors.Wrap(err, "failed to get latest version") - } - - docsContainer := dag.Container(). - From("alpine/git:latest"). - WithWorkdir("/"). - With(CacheBustingExec([]string{"git", "clone", "--depth", "1", "https://github.com/replicatedhq/replicated-docs.git", "/replicated-docs"})) - - rootDocsDirectory := docsContainer.Directory("/replicated-docs") - docsDirectory := rootDocsDirectory.Directory("/docs/reference/") - - // Remove existing CLI docs - existingDocs, err := docsDirectory.Entries(ctx) - if err != nil { - return errors.Wrap(err, "failed to get existing docs") - } - - for _, existingDoc := range existingDocs { - // 'replicated-cli-installing.mdx' is a special file that's not a CLI doc - if existingDoc == "replicated-cli-installing.mdx" { - continue - } - if !strings.HasPrefix(existingDoc, "replicated-cli") { - continue - } - - docsDirectory = docsDirectory.WithoutFile(existingDoc) - } - - // Generate CLI new docs - image, err := goImage(ctx, source) - if err != nil { - return errors.Wrap(err, "failed to detect go version") - } - goModCache, goBuildCache, err := goCacheVolumes(ctx, source) - if err != nil { - return errors.Wrap(err, "failed to create cache volumes") - } - - // generate the docs from this current commit - docs := dag.Container(). - From(image). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache"). - WithEnvVariable("CGO_ENABLED", "0"). - With(CacheBustingExec([]string{"go", "run", "./docs/gen.go"})) - - generatedDocs := docs.Directory("/go/src/github.com/replicatedhq/replicated/gen/docs") - - entries, err := generatedDocs.Entries(ctx) - if err != nil { - return errors.Wrap(err, "failed to get generated docs") - } - - // Add new CLI docs to the docs directory while updating file names and fixing up header level - newDocFilenames := []string{} - for _, entry := range entries { - file := generatedDocs.File(entry) - - content, err := file.Contents(ctx) - if err != nil { - return errors.Wrap(err, "failed to get generated doc contents") - } - - content = cleanContent(content, entries) - - destFilename := cobraFileNameToDocsFileName(entry) - - docsDirectory = docsDirectory.WithNewFile(destFilename, content) - newDocFilenames = append(newDocFilenames, destFilename) - } - - // Update sidebar config to include new CLI docs - sidebarFile := rootDocsDirectory.File("sidebars.js") - sidebarContent, err := sidebarFile.Contents(ctx) - if err != nil { - return errors.Wrap(err, "failed to get sidebar contents") - } - - sidebarContent, err = replaceFilenamesInSidebar(sidebarContent, newDocFilenames) - if err != nil { - return errors.Wrap(err, "failed to replace filenames in sidebar") - } - - rootDocsDirectory = rootDocsDirectory.WithNewFile("sidebars.js", sidebarContent) - - docsContainer = docsContainer. - WithMountedDirectory("/replicated-docs", rootDocsDirectory). - WithMountedDirectory("/replicated-docs/docs/reference", docsDirectory). - WithWorkdir("/replicated-docs"). - WithExec([]string{"git", "diff"}) - diffOut, err := docsContainer.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get diff") - } - - if diffOut == "" { - return errors.New("diff is empty") - } - - githubTokenPlaintext, err := githubToken.Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to get github token plaintext") - } - - branchName := fmt.Sprintf("update-cli-docs-%s-%s", latestVersion, time.Now().Format("2006-01-02-150405")) - docsContainer = docsContainer. - WithExec([]string{"git", "config", "user.email", "release@replicated.com"}). - WithExec([]string{"git", "config", "user.name", "Replicated Release Pipeline"}). - WithExec([]string{"git", "remote", "add", "dagger", fmt.Sprintf("https://%s@github.com/replicatedhq/replicated-docs.git", githubTokenPlaintext)}). - WithExec([]string{"git", "checkout", "-b", branchName}). - WithExec([]string{"git", "add", "."}). - WithExec([]string{"git", "commit", "-m", fmt.Sprintf("Update Replicated CLI docs for %s", latestVersion)}). - WithExec([]string{"git", "push", "dagger", branchName}) - - _, err = docsContainer.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get push output") - } - - err = createPullRequest(ctx, branchName, fmt.Sprintf("Update Replicated CLI docs for %s", latestVersion), "", githubTokenPlaintext) - if err != nil { - return errors.Wrap(err, "failed to create pull request") - } - - return nil -} - -// Change names like "replicated_channel_inspect.md" to "replicated-cli-channel-inspect.mdx" -func cobraFileNameToDocsFileName(filename string) string { - filename = strings.ReplaceAll(filename, "replicated_", "replicated-cli-") - filename = strings.ReplaceAll(filename, "_", "-") - if filepath.Ext(filename) == ".md" { - filename = strings.TrimSuffix(filename, filepath.Ext(filename)) + ".mdx" - } - return filename -} - -func cleanContent(content string, filenames []string) string { - // Header must be level 1 in order for white spaces to be rendered correctly ("replicated api get" vs "replicated_api_get") - if strings.HasPrefix(content, "## ") { - content = content[1:] - } - - // Replace all filenames in the content with the new filenames - for _, filename := range filenames { - topicLink := cobraFileNameToDocsFileName(filename) - topicLink = strings.TrimSuffix(topicLink, filepath.Ext(topicLink)) - content = strings.ReplaceAll(content, filename, topicLink) - } - - return content -} - -func replaceFilenamesInSidebar(sidebarContent string, newDocFilenames []string) (string, error) { - scanner := bufio.NewScanner(strings.NewReader(sidebarContent)) - - newDocLines := []string{} - foundCLILabel := false - wroteNewList := false - for scanner.Scan() { - line := scanner.Text() - - if strings.Contains(line, `reference/replicated-cli-`) { // CLI commands that are generated. - continue - } - - if strings.Contains(line, `"reference/replicated"`) { // CLI root command that is also generated. - continue - } - - if strings.Contains(line, `label: "Replicated CLI"`) { - newDocLines = append(newDocLines, ` label: "Replicated CLI", // This label is generated. Do not edit.`) - foundCLILabel = true - continue - } - - if foundCLILabel && !wroteNewList && strings.Contains(line, `items: [`) { - newDocLines = append(newDocLines, ` items: [ // This list is generated. Do not edit.`) - // "reference/replicated-cli-installing" is a special file that's not a CLI doc and should at the top of the list - newDocLines = append(newDocLines, ` "reference/replicated-cli-installing",`) - for _, newDocFilename := range newDocFilenames { - newDocLines = append(newDocLines, fmt.Sprintf(` "reference/%s",`, strings.TrimSuffix(newDocFilename, filepath.Ext(newDocFilename)))) - } - wroteNewList = true - continue - } - - newDocLines = append(newDocLines, line) - } - - if !wroteNewList { - return "", fmt.Errorf("no CLI list found in sidebar") - } - - return strings.Join(newDocLines, "\n"), nil -} - -func createPullRequest(ctx context.Context, branchName string, title string, body string, githubTokenPlaintext string) error { - requestData := map[string]string{ - "title": title, - "body": body, - "head": branchName, - "base": "main", - } - - requestBody, err := json.Marshal(requestData) - if err != nil { - return errors.Wrap(err, "failed to marshal request data") - } - - req, err := http.NewRequestWithContext(ctx, "POST", "https://api.github.com/repos/replicatedhq/replicated-docs/pulls", bytes.NewReader(requestBody)) - if err != nil { - return errors.Wrap(err, "failed to create pull request") - } - - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", githubTokenPlaintext)) - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("X-GitHub-Api-Version", "2022-11-28") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return errors.Wrap(err, "failed to do pull request") - } - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return errors.Wrap(err, "failed to read response body") - } - - if resp.StatusCode != http.StatusCreated { - return errors.Errorf("failed to create pull request: %s", string(respBody)) - } - - var pullRequest struct { - HTMLURL string `json:"html_url"` - } - if err := json.Unmarshal(respBody, &pullRequest); err != nil { - return errors.Wrap(err, "failed to unmarshal pull request") - } - - fmt.Printf("created pull request at: %s\n", pullRequest.HTMLURL) - - return nil -} diff --git a/dagger/exec_utils.go b/dagger/exec_utils.go deleted file mode 100644 index f8d21c797..000000000 --- a/dagger/exec_utils.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" - "fmt" - "strings" - "time" -) - -// goVersion reads the go directive from go.mod in the source directory and -// returns the major.minor version (e.g. "1.26" from "go 1.26.1"). -func goVersion(ctx context.Context, source *dagger.Directory) (string, error) { - contents, err := source.File("go.mod").Contents(ctx) - if err != nil { - return "", fmt.Errorf("failed to read go.mod: %w", err) - } - for _, line := range strings.Split(contents, "\n") { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "go ") { - version := strings.TrimPrefix(line, "go ") - // strip patch version if present (e.g. "1.26.1" -> "1.26") - parts := strings.SplitN(version, ".", 3) - if len(parts) >= 2 { - return parts[0] + "." + parts[1], nil - } - return version, nil - } - } - return "", fmt.Errorf("go directive not found in go.mod") -} - -// goImage returns the golang Docker image tag for the version in go.mod. -func goImage(ctx context.Context, source *dagger.Directory) (string, error) { - v, err := goVersion(ctx, source) - if err != nil { - return "", err - } - return "golang:" + v, nil -} - -// goCacheVolumes returns the mod and build cache volumes keyed by the Go version. -func goCacheVolumes(ctx context.Context, source *dagger.Directory) (*dagger.CacheVolume, *dagger.CacheVolume, error) { - v, err := goVersion(ctx, source) - if err != nil { - return nil, nil, err - } - // replace dots for a clean cache key suffix (e.g. "126" from "1.26") - suffix := strings.ReplaceAll(v, ".", "") - modCache := dag.CacheVolume("replicated-go-mod-" + suffix) - buildCache := dag.CacheVolume("replicated-go-build-" + suffix) - return modCache, buildCache, nil -} - -// CacheBustingExec is a helper function that will add a cache busting env var automatically -// to the container. This is useful when Exec target is a dynamic event acting on an entity outside -// of the container that you absolutely want to re-run every time. -// -// Temporary hack until cache controls are a thing: https://docs.dagger.io/cookbook/#invalidate-cache -func CacheBustingExec(args []string, opts ...dagger.ContainerWithExecOpts) dagger.WithContainerFunc { - return func(c *dagger.Container) *dagger.Container { - if c == nil { - panic("CacheBustingExec requires a container, but container was nil") - } - return c.WithEnvVariable("DAGGER_CACHEBUSTER_CBE", time.Now().String()).WithExec(args, opts...) - } -} diff --git a/dagger/functionality.go b/dagger/functionality.go deleted file mode 100644 index a56d69f32..000000000 --- a/dagger/functionality.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -func validateFunctionality( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - image, err := goImage(ctx, source) - if err != nil { - return err - } - goModCache, goBuildCache, err := goCacheVolumes(ctx, source) - if err != nil { - return err - } - - // unit tests - unitTest := dag.Container(). - From(image). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache"). - With(CacheBustingExec([]string{"make", "test-unit"})) - - _, err = unitTest.Stderr(ctx) - if err != nil { - return err - } - - return nil -} diff --git a/dagger/go.mod b/dagger/go.mod deleted file mode 100644 index ef540b9b3..000000000 --- a/dagger/go.mod +++ /dev/null @@ -1,58 +0,0 @@ -module dagger/replicated - -go 1.25.0 - -require ( - dagger.io/dagger v0.20.5-0.20260409204156-6e4822e59cbb - github.com/Khan/genqlient v0.8.1 - github.com/dagger/otel-go v1.43.0 - github.com/pkg/errors v0.9.1 - github.com/vektah/gqlparser/v2 v2.5.32 - go.opentelemetry.io/otel v1.43.0 - go.opentelemetry.io/otel/sdk v1.43.0 - go.opentelemetry.io/otel/trace v1.43.0 -) - -require ( - github.com/99designs/gqlgen v0.17.89 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect - go.opentelemetry.io/otel/log v0.19.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect - golang.org/x/sync v0.20.0 // indirect - google.golang.org/grpc v1.80.0 // indirect -) - -require ( - github.com/Masterminds/semver v1.5.0 - github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect - github.com/sosodev/duration v1.4.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - golang.org/x/net v0.52.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.35.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/protobuf v1.36.11 // indirect -) - -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 - -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 - -replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.16.0 - -replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.16.0 diff --git a/dagger/go.sum b/dagger/go.sum deleted file mode 100644 index 0df7dc607..000000000 --- a/dagger/go.sum +++ /dev/null @@ -1,101 +0,0 @@ -dagger.io/dagger v0.20.5-0.20260409204156-6e4822e59cbb h1:keYNVasNCAqW20rN5BmcoAE5QpU59STiTXEDfhw/ymk= -dagger.io/dagger v0.20.5-0.20260409204156-6e4822e59cbb/go.mod h1:ZXg8+pQZaZUC8rAw4V/gPP8aKvKARIJZ+pfcV+RC1es= -github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8= -github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ= -github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= -github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/dagger/otel-go v1.43.0 h1:AYCnAamWmxtSxigWPTgC+8EWqiWPcDZEegh8y05gdJ8= -github.com/dagger/otel-go v1.43.0/go.mod h1:83CTuXi70zcx1kaym5buqmb7RNzg1E9dEiQSFyLbLdU= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= -github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc= -github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= -go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= -go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= -go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/main.go b/dagger/main.go deleted file mode 100644 index 75e60d1ed..000000000 --- a/dagger/main.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -type Replicated struct{} diff --git a/dagger/performance.go b/dagger/performance.go deleted file mode 100644 index 5a8132631..000000000 --- a/dagger/performance.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -func validatePerformance( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - return nil -} diff --git a/dagger/release.go b/dagger/release.go deleted file mode 100644 index 5e1a4f11b..000000000 --- a/dagger/release.go +++ /dev/null @@ -1,435 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/Masterminds/semver" - "github.com/pkg/errors" -) - -var goreleaserVersion = "v2.14.3" - -func (r *Replicated) Release( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, - - version string, - - // +default=false - snapshot bool, - - // +default=false - clean bool, - - onePasswordServiceAccountProduction *dagger.Secret, - - githubToken *dagger.Secret, -) error { - // Check all required environment variables / secrets before starting work - if err := validateReleaseSecrets(ctx, githubToken, onePasswordServiceAccountProduction); err != nil { - return err - } - - err := checkGitTree(ctx, source, githubToken) - if err != nil { - return errors.Wrap(err, "failed to check git tree") - } - - previousVersionTag, err := getLatestVersion(ctx, githubToken) - if err != nil { - return errors.Wrap(err, "failed to get latest version") - } - - previousReleaseBranchName, err := getReleaseBranchName(ctx, previousVersionTag) - if err != nil { - return errors.Wrap(err, "failed to get release branch name") - } - - major, minor, patch, err := getNextVersion(ctx, previousVersionTag, version) - if err != nil { - return errors.Wrap(err, "failed to get next version") - } - - fmt.Printf("Releasing as version %d.%d.%d\n", major, minor, patch) - - // replace the version in the Makefile - buildFileContent, err := source.File("./pkg/version/build.go").Contents(ctx) - if err != nil { - return errors.Wrap(err, "failed to get build file contents") - } - buildFileContent = strings.ReplaceAll(buildFileContent, "const version = \"unknown\"", fmt.Sprintf("const version = \"%d.%d.%d\"", major, minor, patch)) - updatedSource := source.WithNewFile("./pkg/version/build.go", buildFileContent) - - releaseBranchName := fmt.Sprintf("release-%d.%d.%d", major, minor, patch) - githubTokenPlaintext, err := githubToken.Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to get github token plaintext") - } - - // mount that and commit the updated build.go to git (don't push) - // so that goreleaser won't have a dirty git tree error - gitCommitContainer := dag.Container(). - From("alpine/git:latest"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", updatedSource). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithExec([]string{"git", "config", "user.email", "release@replicated.com"}). - WithExec([]string{"git", "config", "user.name", "Replicated Release Pipeline"}). - WithExec([]string{"git", "remote", "add", "dagger", fmt.Sprintf("https://%s@github.com/replicatedhq/replicated.git", githubTokenPlaintext)}). - WithExec([]string{"git", "checkout", "-b", releaseBranchName}). - WithExec([]string{"git", "add", "pkg/version/build.go"}). - WithExec([]string{"git", "commit", "-m", fmt.Sprintf("Set version to %d.%d.%d", major, minor, patch)}). - WithExec([]string{"git", "push", "dagger", releaseBranchName}) - _, err = gitCommitContainer.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get git commit stdout") - } - updatedSource = gitCommitContainer.Directory("/go/src/github.com/replicatedhq/replicated") - - nextVersionTag := fmt.Sprintf("v%d.%d.%d", major, minor, patch) - - tagContainer := dag.Container(). - From("alpine/git:latest"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", updatedSource). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - With(CacheBustingExec([]string{"git", "tag", nextVersionTag})). - With(CacheBustingExec([]string{"git", "push", "dagger", nextVersionTag})). - With(CacheBustingExec([]string{"git", "fetch", "dagger", previousReleaseBranchName})). - With(CacheBustingExec([]string{"git", "fetch", "dagger", "--tags"})) - _, err = tagContainer.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get tag stdout") - } - - // copy the source that has the tag included in it - updatedSource = tagContainer.Directory("/go/src/github.com/replicatedhq/replicated") - - image, err := goImage(ctx, updatedSource) - if err != nil { - return errors.Wrap(err, "failed to detect go version") - } - goModCache, goBuildCache, err := goCacheVolumes(ctx, updatedSource) - if err != nil { - return errors.Wrap(err, "failed to create cache volumes") - } - - replicatedBinary := dag.Container(dagger.ContainerOpts{ - Platform: "linux/amd64", - }). - From(image). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", updatedSource). - WithoutFile("/go/src/github.com/replicatedhq/replicated/bin/replicated"). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache"). - With(CacheBustingExec([]string{"make", "build"})). - File("/go/src/github.com/replicatedhq/replicated/bin/replicated") - - dockerContainer := dag.Container(dagger.ContainerOpts{ - Platform: "linux/amd64", - }). - From("alpine:latest"). - WithExec([]string{"apk", "add", "--no-cache", "ca-certificates", "curl", "git", "nodejs", "npm"}). - WithExec([]string{"update-ca-certificates"}). - WithExec([]string{"npm", "install", "-g", "replicated-lint"}). - WithEnvVariable("IN_CONTAINER", "1"). - WithLabel("com.replicated.vendor_cli", "true"). - WithWorkdir("/out"). - WithEntrypoint([]string{"/replicated"}). - WithFile("/replicated", replicatedBinary) - _, err = dockerContainer.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get docker container stdout") - } - - username, err := dag.Onepassword().FindSecret( - onePasswordServiceAccountProduction, - "Developer Automation Production", - "Docker Hub Release Account", - "username", - ).Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to get docker hub username") - } - password := dag.Onepassword().FindSecret( - onePasswordServiceAccountProduction, - "Developer Automation Production", - "Docker Hub Release Account", - "password", - ) - - dockerContainer = dockerContainer.WithRegistryAuth("", username, password) - if _, err := dockerContainer.Publish(ctx, "replicated/vendor-cli:latest"); err != nil { - return errors.Wrap(err, "failed to publish latest docker container") - } - if _, err := dockerContainer.Publish(ctx, fmt.Sprintf("replicated/vendor-cli:%d", major)); err != nil { - return errors.Wrap(err, "failed to publish major docker container") - } - if _, err := dockerContainer.Publish(ctx, fmt.Sprintf("replicated/vendor-cli:%d.%d", major, minor)); err != nil { - return errors.Wrap(err, "failed to publish minor docker container") - } - if _, err := dockerContainer.Publish(ctx, fmt.Sprintf("replicated/vendor-cli:%d.%d.%d", major, minor, patch)); err != nil { - return errors.Wrap(err, "failed to publish patch docker container") - } - - args := []string{"goreleaser"} - if snapshot { - args = append(args, "release", "--snapshot") - } else { - args = append(args, "release") - } - if clean { - args = append(args, "--clean") - } - - ctr := newGoreleaserContainer(ctx, updatedSource). - WithSecretVariable("GITHUB_TOKEN", githubToken). - WithEnvVariable("GORELEASER_CURRENT_TAG", nextVersionTag). - WithEnvVariable("GORELEASER_PREVIOUS_TAG", previousVersionTag). - With(CacheBustingExec(args)) - - _, err = ctr.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to run goreleaser") - } - - return nil -} - -// newGoreleaserContainer returns a Go container with goreleaser installed and -// the source mounted, ready to run goreleaser commands. -func newGoreleaserContainer(ctx context.Context, source *dagger.Directory) *dagger.Container { - image, err := goImage(ctx, source) - if err != nil { - image = "golang:latest" - } - goModCache, goBuildCache, err := goCacheVolumes(ctx, source) - if err != nil { - goModCache = dag.CacheVolume("replicated-go-mod") - goBuildCache = dag.CacheVolume("replicated-go-build") - } - - return dag.Container(). - From(image). - WithExec([]string{"go", "install", "github.com/goreleaser/goreleaser/v2@" + goreleaserVersion}). - WithMountedDirectory("/src", source). - WithWorkdir("/src"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache") -} - -// GoreleaserDryrun runs a goreleaser snapshot build to verify the release -// configuration works. This is intended to be used as a PR check. -func (r *Replicated) GoreleaserDryrun( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - ctr := newGoreleaserContainer(ctx, source). - With(CacheBustingExec([]string{"goreleaser", "release", "--snapshot", "--clean"})) - - _, err := ctr.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to run goreleaser dryrun") - } - - return nil -} - -func validateReleaseSecrets(ctx context.Context, githubToken, onePasswordServiceAccountProduction *dagger.Secret) error { - var missing []string - - if githubToken == nil { - missing = append(missing, "GITHUB_TOKEN") - } else { - gt, err := githubToken.Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to read GITHUB_TOKEN secret") - } - if strings.TrimSpace(gt) == "" { - missing = append(missing, "GITHUB_TOKEN") - } - } - - if onePasswordServiceAccountProduction == nil { - missing = append(missing, "OP_SERVICE_ACCOUNT_PRODUCTION") - } else { - op, err := onePasswordServiceAccountProduction.Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to read OP_SERVICE_ACCOUNT_PRODUCTION secret") - } - if strings.TrimSpace(op) == "" { - missing = append(missing, "OP_SERVICE_ACCOUNT_PRODUCTION") - } - } - - if len(missing) > 0 { - return fmt.Errorf("required environment variables are not set: %s", strings.Join(missing, ", ")) - } - - return nil -} - -func getNextVersion(ctx context.Context, latestVersion string, version string) (int64, int64, int64, error) { - parsedLatestVersion, err := semver.NewVersion(latestVersion) - if err != nil { - return 0, 0, 0, err - } - - switch version { - case "major": - return parsedLatestVersion.Major() + 1, 0, 0, nil - case "minor": - return parsedLatestVersion.Major(), parsedLatestVersion.Minor() + 1, 0, nil - case "patch": - return parsedLatestVersion.Major(), parsedLatestVersion.Minor(), parsedLatestVersion.Patch() + 1, nil - default: - v, err := semver.NewVersion(version) - if err != nil { - return 0, 0, 0, err - } - return v.Major(), v.Minor(), v.Patch(), nil - } -} - -func getReleaseBranchName(ctx context.Context, latestVersion string) (string, error) { - parsedLatestVersion, err := semver.NewVersion(latestVersion) - if err != nil { - return "", err - } - - return fmt.Sprintf("release-%d.%d.%d", parsedLatestVersion.Major(), parsedLatestVersion.Minor(), parsedLatestVersion.Patch()), nil -} - -func getLatestVersion(ctx context.Context, githubToken *dagger.Secret) (string, error) { - req, err := http.NewRequest("GET", "https://api.github.com/repos/replicatedhq/replicated/releases/latest", nil) - if err != nil { - return "", err - } - - githubTokenPlaintext, err := githubToken.Plaintext(ctx) - if err != nil { - return "", errors.Wrap(err, "failed to get github token plaintext") - } - - req.Header.Set("Authorization", fmt.Sprintf("token %s", githubTokenPlaintext)) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return "", errors.Wrap(err, "failed to do github request") - } - - defer resp.Body.Close() - - var release struct { - TagName string `json:"tag_name"` - } - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return "", err - } - - return release.TagName, nil -} - -var ( - // ErrGitTreeNotClean = errors.New("Your git tree is not clean. You cannot release what's not commited.") - ErrMainBranch = errors.New("You must be on the main branch to release") - ErrCommitNotInGitHub = errors.New("You must merge your changes into the main branch before releasing") -) - -// checkGitTree will return nil if the local git tree is clean or an error if it's not -func checkGitTree(ctx context.Context, source *dagger.Directory, githubToken *dagger.Secret) error { - container := dag.Container(). - From("alpine/git:latest"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - With(CacheBustingExec([]string{"git", "status", "--porcelain"})) - - gitStatusOutput, err := container.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get git status") - } - - gitStatusOutput = strings.TrimSpace(gitStatusOutput) - - if len(gitStatusOutput) > 0 { - fmt.Printf("output: %s\n", gitStatusOutput) - return fmt.Errorf("error: dirty tree: %q", gitStatusOutput) - } - - container = dag.Container(). - From("alpine/git:latest"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - With(CacheBustingExec([]string{"git", "branch"})) - - gitBranchOutput, err := container.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get git branch") - } - - gitBranchOutput = strings.TrimSpace(gitBranchOutput) - - if !strings.Contains(gitBranchOutput, "* main") { - return ErrMainBranch - } - - container = dag.Container(). - From("alpine/git:latest"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - With(CacheBustingExec([]string{"git", "rev-parse", "HEAD"})) - - commit, err := container.Stdout(ctx) - if err != nil { - return errors.Wrap(err, "failed to get git commit") - } - - commit = strings.TrimSpace(commit) - - req, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/replicatedhq/replicated/commits/%s", commit), nil) - if err != nil { - return errors.Wrap(err, "failed to create github request") - } - - githubTokenPlaintext, err := githubToken.Plaintext(ctx) - if err != nil { - return errors.Wrap(err, "failed to get github token plaintext") - } - - req.Header.Set("Authorization", fmt.Sprintf("token %s", githubTokenPlaintext)) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return errors.Wrap(err, "failed to do github request") - } - defer resp.Body.Close() - - type GitHubResponse struct { - SHA string `json:"sha"` - NodeID string `json:"node_id"` - Status string `json:"status"` - } - - var ghResp GitHubResponse - if err := json.NewDecoder(resp.Body).Decode(&ghResp); err != nil { - return errors.Wrap(err, "failed to decode github response") - } - - if ghResp.Status == "422" { - return ErrCommitNotInGitHub - } - - return nil -} diff --git a/dagger/security.go b/dagger/security.go deleted file mode 100644 index 34a265505..000000000 --- a/dagger/security.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -func validateSecurity( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - goModCache, goBuildCache, err := goCacheVolumes(ctx, source) - if err != nil { - return err - } - - // run semgrep - semgrep := dag.Container(). - From("returntocorp/semgrep"). - WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). - WithWorkdir("/go/src/github.com/replicatedhq/replicated"). - WithMountedCache("/go/pkg/mod", goModCache). - WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). - WithMountedCache("/go/build-cache", goBuildCache). - WithEnvVariable("GOCACHE", "/go/build-cache"). - With(CacheBustingExec([]string{"semgrep", "scan", "--config=p/golang", "."})) - - _, err = semgrep.Stderr(ctx) - if err != nil { - return err - } - - return nil -} diff --git a/dagger/validate.go b/dagger/validate.go deleted file mode 100644 index e1807e55f..000000000 --- a/dagger/validate.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "context" - "dagger/replicated/internal/dagger" -) - -func (r *Replicated) Validate( - ctx context.Context, - - // +defaultPath="./" - source *dagger.Directory, -) error { - if err := validateSecurity(ctx, source); err != nil { - return err - } - - if err := validateFunctionality(ctx, source); err != nil { - return err - } - - if err := validateCompatibility(ctx, source); err != nil { - return err - } - - if err := validatePerformance(ctx, source); err != nil { - return err - } - - return nil -} diff --git a/pkg/integration/channel_test.go b/pkg/integration/channel_test.go index 8e0853b88..bd5bcea9d 100644 --- a/pkg/integration/channel_test.go +++ b/pkg/integration/channel_test.go @@ -22,10 +22,10 @@ func TestChannelReleases(t *testing.T) { releases interface{} wantFormat format wantLines int - wantOutput string // when set, asserted exactly - wantContain []string // substrings the output must contain - wantQuery map[string]string // query params asserted on the releases request - wantExit int // expected non-zero exit; 0 means must succeed + wantOutput string // when set, asserted exactly + wantContain []string // substrings the output must contain + wantQuery map[string]string // query params asserted on the releases request + wantExit int // expected non-zero exit; 0 means must succeed assertJSON func(t *testing.T, raw []byte) }{ { @@ -117,9 +117,9 @@ func TestChannelReleases(t *testing.T) { channels: []map[string]interface{}{ {"id": "chan-1", "name": "Stable", "channelSlug": "stable"}, }, - releases: []map[string]interface{}{}, - wantFormat: FormatTable, - wantOutput: "No releases in channel\n", + releases: []map[string]interface{}{}, + wantFormat: FormatTable, + wantOutput: "No releases in channel\n", }, { name: "platform channel releases table — legacy app regression guard", diff --git a/pkg/lint2/troubleshoot_common_test.go b/pkg/lint2/troubleshoot_common_test.go index a4b637969..0845263b6 100644 --- a/pkg/lint2/troubleshoot_common_test.go +++ b/pkg/lint2/troubleshoot_common_test.go @@ -233,7 +233,7 @@ func TestConvertTroubleshootResultToMessages_SupportBundle(t *testing.T) { {Line: 8, Message: "Missing collectors", Field: "spec.collectors"}, }, Warnings: []SupportBundleLintIssue{}, - Info: []SupportBundleLintIssue{}, + Info: []SupportBundleLintIssue{}, }, }, } diff --git a/pkg/tools/config.go b/pkg/tools/config.go index 3ee0c7f4e..81f10cec3 100644 --- a/pkg/tools/config.go +++ b/pkg/tools/config.go @@ -467,7 +467,6 @@ func mergeECLinterConfig(parent, child ECLinterConfig) ECLinterConfig { return result } - // boolPtr returns a pointer to a boolean value // Helper for creating pointer booleans in config defaults func boolPtr(b bool) *bool { diff --git a/pkg/tools/config_test.go b/pkg/tools/config_test.go index 72f45a8d2..96b462141 100644 --- a/pkg/tools/config_test.go +++ b/pkg/tools/config_test.go @@ -1495,8 +1495,8 @@ func TestApplyDefaults_LinterDefaults(t *testing.T) { config := &Config{ ReplLint: &ReplLintConfig{ Linters: LintersConfig{ - Helm: LinterConfig{Disabled: &trueVal}, // user disabled helm - Kots: LinterConfig{Disabled: &falseVal}, // user enabled kots + Helm: LinterConfig{Disabled: &trueVal}, // user disabled helm + Kots: LinterConfig{Disabled: &falseVal}, // user enabled kots EmbeddedCluster: ECLinterConfig{LinterConfig: LinterConfig{Disabled: &falseVal}}, // user enabled EC }, }, diff --git a/pkg/types/channel.go b/pkg/types/channel.go index cbe95ea55..e73509b47 100644 --- a/pkg/types/channel.go +++ b/pkg/types/channel.go @@ -63,27 +63,27 @@ type CustomerAdoption struct { } type ChannelRelease struct { - AirgapBuildError string `json:"airgapBuildError,omitempty"` - AirgapBuildStatus string `json:"airgapBuildStatus,omitempty"` - AirgapBundleImages []string `json:"airgapBundleImages,omitempty"` - ChannelIcon string `json:"channelIcon,omitempty"` - ChannelId string `json:"channelId,omitempty"` - ChannelName string `json:"channelName,omitempty"` - ChannelSequence int32 `json:"channelSequence,omitempty"` - Created time.Time `json:"created,omitempty"` - ProxyRegistryDomain string `json:"proxyRegistryDomain,omitempty"` - RegistrySecret string `json:"registrySecret,omitempty"` - ReleaseNotes string `json:"releaseNotes,omitempty"` - ReleasedAt time.Time `json:"releasedAt,omitempty"` - Semver string `json:"semver,omitempty"` - Sequence int32 `json:"sequence,omitempty"` - Updated time.Time `json:"updated,omitempty"` + AirgapBuildError string `json:"airgapBuildError,omitempty"` + AirgapBuildStatus string `json:"airgapBuildStatus,omitempty"` + AirgapBundleImages []string `json:"airgapBundleImages,omitempty"` + ChannelIcon string `json:"channelIcon,omitempty"` + ChannelId string `json:"channelId,omitempty"` + ChannelName string `json:"channelName,omitempty"` + ChannelSequence int32 `json:"channelSequence,omitempty"` + Created time.Time `json:"created,omitempty"` + ProxyRegistryDomain string `json:"proxyRegistryDomain,omitempty"` + RegistrySecret string `json:"registrySecret,omitempty"` + ReleaseNotes string `json:"releaseNotes,omitempty"` + ReleasedAt time.Time `json:"releasedAt,omitempty"` + Semver string `json:"semver,omitempty"` + Sequence int32 `json:"sequence,omitempty"` + Updated time.Time `json:"updated,omitempty"` // IsDemoted and DemotedAt intentionally omit `omitempty`: agents consuming // the JSON need to distinguish "explicitly not demoted" from "field absent", // and Go's omitempty on a bool would drop `false`. - IsDemoted bool `json:"isDemoted"` - DemotedAt *time.Time `json:"demotedAt"` - InstallationTypes InstallationTypes `json:"installationTypes,omitempty"` + IsDemoted bool `json:"isDemoted"` + DemotedAt *time.Time `json:"demotedAt"` + InstallationTypes InstallationTypes `json:"installationTypes,omitempty"` } type CreateChannelRequest struct { diff --git a/pkg/version/build.go b/pkg/version/build.go index 9865d720a..c5c65c7d7 100644 --- a/pkg/version/build.go +++ b/pkg/version/build.go @@ -1,3 +1,3 @@ package version -const version = "unknown" +var version = "unknown" diff --git a/scripts/generate-docs.sh b/scripts/generate-docs.sh new file mode 100755 index 000000000..3d7a2bd53 --- /dev/null +++ b/scripts/generate-docs.sh @@ -0,0 +1,103 @@ +#!/bin/bash +set -euo pipefail + +# Generate CLI docs and open a PR against replicatedhq/replicated-docs +# This replaces the Dagger GenerateDocs function. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +VERSION="${GITHUB_REF_NAME:-unknown}" +BRANCH_NAME="update-cli-docs-${VERSION}-$(date +%Y-%m-%d-%H%M%S)" + +# Build the binary and generate docs +cd "${REPO_ROOT}" +go run ./docs/gen.go + +# Clone replicated-docs +DOCS_DIR="/tmp/replicated-docs" +rm -rf "${DOCS_DIR}" +git clone --depth 1 "https://${GITHUB_TOKEN}@github.com/replicatedhq/replicated-docs.git" "${DOCS_DIR}" + +cd "${DOCS_DIR}" +git config user.email "release@replicated.com" +git config user.name "Replicated Release Pipeline" + +# Remove existing CLI docs (except installing) +for f in docs/reference/replicated-cli-*.mdx; do + if [ -f "$f" ] && [ "$(basename "$f")" != "replicated-cli-installing.mdx" ]; then + rm "$f" + fi +done + +# Remove the root CLI doc too +if [ -f "docs/reference/replicated.mdx" ]; then + rm "docs/reference/replicated.mdx" +fi + +# Copy generated docs +cd "${REPO_ROOT}" +for f in gen/docs/*.md; do + if [ ! -f "$f" ]; then + continue + fi + # Convert filename: replicated_channel_inspect.md -> replicated-cli-channel-inspect.mdx + dest_name=$(basename "$f" .md | sed 's/replicated_/replicated-cli-/g; s/_/-/g').mdx + + content=$(cat "$f") + + # Fix header level + if [[ "$content" == "## "* ]]; then + content="${content:1}" + fi + + # Replace internal links + for ref in gen/docs/*.md; do + ref_name=$(basename "$ref" .md) + dest_ref=$(echo "$ref_name" | sed 's/replicated_/replicated-cli-/g; s/_/-/g') + content="${content//${ref_name}.md/${dest_ref}}" + done + + echo "$content" > "${DOCS_DIR}/docs/reference/${dest_name}" +done + +# Update sidebars.js +cd "${DOCS_DIR}" +node -e " +const fs = require('fs'); +let sidebar = fs.readFileSync('sidebars.js', 'utf8'); + +// Find the Replicated CLI section and replace its items +const cliItems = ['reference/replicated-cli-installing']; +const files = fs.readdirSync('docs/reference') + .filter(f => (f === 'replicated.mdx' || f.startsWith('replicated-cli-')) && f !== 'replicated-cli-installing.mdx') + .map(f => 'reference/' + f.replace('.mdx', '')) + .sort(); +cliItems.push(...files); + +// Simple regex replacement for the items array +const pattern = /(label:\s*['\"]Replicated CLI['\"],?\s*\n\s*items:\s*\[)[^\]]*(\])/; +const replacement = '\$1\n ' + cliItems.map(i => '\"' + i + '\"').join(',\n ') + '\n \$2'; +sidebar = sidebar.replace(pattern, replacement); + +fs.writeFileSync('sidebars.js', sidebar); +" + +# Commit and push +git checkout -b "${BRANCH_NAME}" +git add . +git commit -m "Update Replicated CLI docs for ${VERSION}" +git push origin "${BRANCH_NAME}" + +# Open PR +response=$(curl -s -f -X POST \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/replicatedhq/replicated-docs/pulls \ + -d "{ + \"title\": \"Update Replicated CLI docs for ${VERSION}\", + \"head\": \"${BRANCH_NAME}\", + \"base\": \"main\" + }") +echo "${response}" | jq -r '.html_url' diff --git a/testdata/chart-with-lint-errors/.replicated b/testdata/chart-with-lint-errors/.replicated index ae1cd37b6..6b380ad3f 100644 --- a/testdata/chart-with-lint-errors/.replicated +++ b/testdata/chart-with-lint-errors/.replicated @@ -4,6 +4,8 @@ charts: [ path: "./charts/broken-chart" }, ] +manifests: + - "./manifests/*.yaml" repl-lint: version: 1 linters: @@ -13,4 +15,3 @@ repl-lint: disabled: false support-bundle: disabled: true - diff --git a/testdata/chart-with-lint-errors/manifests/helmchart.yaml b/testdata/chart-with-lint-errors/manifests/helmchart.yaml new file mode 100644 index 000000000..7c6988a16 --- /dev/null +++ b/testdata/chart-with-lint-errors/manifests/helmchart.yaml @@ -0,0 +1,10 @@ +apiVersion: kots.io/v1beta2 +kind: HelmChart +metadata: + name: broken-chart +spec: + chart: + name: broken-chart + chartVersion: 1.0.0 + weight: 1 + builder: {} diff --git a/testdata/chart-with-required-values/.replicated b/testdata/chart-with-required-values/.replicated index 3a97a7a8e..dc3b97770 100644 --- a/testdata/chart-with-required-values/.replicated +++ b/testdata/chart-with-required-values/.replicated @@ -4,6 +4,8 @@ charts: [ path: "./charts/required-values" }, ] +manifests: + - "./manifests/*.yaml" repl-lint: version: 1 linters: diff --git a/testdata/chart-with-required-values/manifests/helmchart.yaml b/testdata/chart-with-required-values/manifests/helmchart.yaml new file mode 100644 index 000000000..e2cee0887 --- /dev/null +++ b/testdata/chart-with-required-values/manifests/helmchart.yaml @@ -0,0 +1,10 @@ +apiVersion: kots.io/v1beta2 +kind: HelmChart +metadata: + name: required-values +spec: + chart: + name: required-values + chartVersion: 0.1.0 + weight: 1 + builder: {}