diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d5a74c944f..193a40467f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -937,8 +937,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 # Full history for changelog generation - name: Setup Node.js uses: actions/setup-node@v4 @@ -979,7 +977,7 @@ jobs: gpg --export-secret-keys >~/.gnupg/secring.gpg echo "GPG key exported successfully" - - name: Generate Terraform provider + - name: Generate and publish Terraform provider run: npm run publish-terraform-provider -- --version "${{ steps.version.outputs.version }}" --github-token "${{ secrets.SIMLARSEN_GITHUB_PAT }}" --github-repo-deploy-key "${{ secrets.TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY }}" diff --git a/Scripts/TerraformProvider/Core/TerraformProviderGenerator.ts b/Scripts/TerraformProvider/Core/TerraformProviderGenerator.ts index 9b267a1ed1..278cef585e 100644 --- a/Scripts/TerraformProvider/Core/TerraformProviderGenerator.ts +++ b/Scripts/TerraformProvider/Core/TerraformProviderGenerator.ts @@ -14,6 +14,7 @@ export class TerraformProviderGenerator { public async generateBuildScripts(): Promise { await this.generateMakefile(); + await this.generateGoReleaserConfig(); await this.generateInstallScript(); await this.generateBuildScript(); await this.generateTestScript(); @@ -83,6 +84,78 @@ clean: await this.fileGenerator.writeFile("Makefile", makefileContent); } + private async generateGoReleaserConfig(): Promise { + const goReleaserContent: string = ` +# GoReleaser config for Terraform Provider +# Used for parallel cross-compilation during CI builds +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json + +version: 2 + +env: + - CGO_ENABLED=0 + +builds: + - binary: terraform-provider-${this.config.providerName} + main: ./ + goos: + - darwin + - linux + - windows + - freebsd + - openbsd + - solaris + goarch: + - amd64 + - arm64 + - "386" + - arm + ignore: + - goos: darwin + goarch: "386" + - goos: darwin + goarch: arm + - goos: solaris + goarch: "386" + - goos: solaris + goarch: arm + - goos: solaris + goarch: arm64 + flags: + - -trimpath + ldflags: + - -s -w + +archives: + - format: zip + name_template: "terraform-provider-${this.config.providerName}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +checksum: + name_template: "terraform-provider-${this.config.providerName}_{{ .Version }}_SHA256SUMS" + algorithm: sha256 + +signs: + - artifacts: checksum + cmd: gpg + args: + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" + - "--output" + - "\${signature}" + - "--detach-sig" + - "\${artifact}" + +release: + disable: true # We handle release creation separately + +changelog: + disable: true +`.trim(); + + await this.fileGenerator.writeFile(".goreleaser.yml", goReleaserContent); + } + private async generateInstallScript(): Promise { const scriptContent: string = `#!/bin/bash set -e diff --git a/Scripts/TerraformProvider/GenerateProvider.ts b/Scripts/TerraformProvider/GenerateProvider.ts index 36f6fdaf7f..15f9491650 100644 --- a/Scripts/TerraformProvider/GenerateProvider.ts +++ b/Scripts/TerraformProvider/GenerateProvider.ts @@ -111,99 +111,72 @@ async function main(): Promise { Logger.info("🔨 Step 10: Generating build and installation scripts..."); await generator.generateBuildScripts(); - // Step 12: Run go mod tidy and update dependencies to latest - Logger.info( - "📦 Step 11: Running go mod tidy and fetching latest dependencies...", - ); + // Step 12: Run go mod tidy + Logger.info("📦 Step 11: Running go mod tidy..."); try { const originalCwd: string = process.cwd(); process.chdir(providerDir); await execAsync("go mod tidy"); - Logger.info("✅ go mod tidy completed successfully"); - - // Update all dependencies to their latest versions - Logger.info("📦 Updating dependencies to latest versions..."); - await execAsync("go get -u ./..."); - Logger.info("✅ Dependencies updated to latest versions"); - - // Run go mod tidy again to clean up after updates - await execAsync("go mod tidy"); process.chdir(originalCwd); - Logger.info("✅ Final go mod tidy completed successfully"); + Logger.info("✅ go mod tidy completed successfully"); } catch (error) { Logger.warn( - `⚠️ go mod tidy or dependency update failed: ${error instanceof Error ? error.message : "Unknown error"}`, + `⚠️ go mod tidy failed: ${error instanceof Error ? error.message : "Unknown error"}`, ); } - // Step 13: Build the provider for multiple platforms - Logger.info("🔨 Step 12: Building the provider for multiple platforms..."); + // Step 13: Build the provider for multiple platforms using GoReleaser + Logger.info( + "🔨 Step 12: Building the provider for multiple platforms with GoReleaser...", + ); try { const originalCwd: string = process.cwd(); process.chdir(providerDir); - // First build for current platform - await execAsync("go build"); - Logger.info("✅ go build completed successfully"); - - // Check if make is available for multi-platform build + // Use GoReleaser for parallel cross-compilation (build only, no publish) try { - await execAsync("which make"); - // Then build for all platforms (this creates the builds directory) - await execAsync("make release"); - Logger.info("✅ Multi-platform build completed successfully"); - } catch { - Logger.warn( - "⚠️ 'make' command not available, building platforms manually...", + await execAsync("which goreleaser"); + await execAsync( + "goreleaser build --snapshot --clean", + ); + Logger.info( + "✅ GoReleaser multi-platform build completed successfully", ); - // Create builds directory manually + // Move GoReleaser output to builds/ directory for compatibility + // GoReleaser puts binaries in dist// await execAsync("mkdir -p ./builds"); - - // Build for each platform manually - const platforms: Array<{ - os: string; - arch: string; - ext?: string; - }> = [ - { os: "darwin", arch: "amd64" }, - { os: "darwin", arch: "arm" }, - { os: "darwin", arch: "arm64" }, - { os: "linux", arch: "amd64" }, - { os: "linux", arch: "386" }, - { os: "linux", arch: "arm" }, - { os: "linux", arch: "arm64" }, - { os: "windows", arch: "amd64", ext: ".exe" }, - { os: "windows", arch: "386", ext: ".exe" }, - { os: "windows", arch: "arm", ext: ".exe" }, - { os: "windows", arch: "arm64", ext: ".exe" }, - { os: "freebsd", arch: "amd64" }, - { os: "freebsd", arch: "386" }, - { os: "freebsd", arch: "arm" }, - { os: "freebsd", arch: "arm64" }, - { os: "openbsd", arch: "amd64" }, - { os: "openbsd", arch: "386" }, - { os: "openbsd", arch: "arm" }, - { os: "openbsd", arch: "arm64" }, - { os: "solaris", arch: "amd64" }, - ]; - - for (const platform of platforms) { - const ext: string = platform.ext || ""; - const binaryName: string = `terraform-provider-oneuptime_${platform.os}_${platform.arch}${ext}`; - const buildCmd: string = `GOOS=${platform.os} GOARCH=${platform.arch} go build -o ./builds/${binaryName}`; - - try { - await execAsync(buildCmd); - Logger.info(`✅ Built ${binaryName}`); - } catch (platformError) { - Logger.warn( - `⚠️ Failed to build ${binaryName}: ${platformError instanceof Error ? platformError.message : "Unknown error"}`, - ); + const { stdout: distDirs } = await execAsync( + "find dist -name 'terraform-provider-oneuptime*' -type f", + ); + for (const binaryPath of distDirs.trim().split("\n")) { + if (binaryPath) { + // Extract os_arch from the dist directory name + const dirName: string = + binaryPath.split("/").slice(-2, -1)[0] || ""; + const binaryName: string = binaryPath.split("/").pop() || ""; + // GoReleaser dirs are like: terraform-provider-oneuptime_linux_amd64 + const ext: string = binaryName.endsWith(".exe") ? ".exe" : ""; + const destName: string = `terraform-provider-oneuptime_${dirName.replace("terraform-provider-oneuptime_", "")}${ext ? "" : ""}`; + await execAsync(`cp "${binaryPath}" "./builds/${destName}"`); } } - Logger.info("✅ Manual multi-platform build completed"); + Logger.info("✅ Binaries copied to builds/ directory"); + } catch { + Logger.warn( + "⚠️ GoReleaser not available, falling back to make release...", + ); + // Fallback to Makefile-based sequential build + try { + await execAsync("which make"); + await execAsync("make release"); + Logger.info("✅ Makefile multi-platform build completed"); + } catch { + Logger.warn("⚠️ make not available, building for current platform only..."); + await execAsync("go build"); + Logger.info("✅ Single-platform build completed"); + } } process.chdir(originalCwd); diff --git a/Scripts/TerraformProvider/publish-terraform-provider.sh b/Scripts/TerraformProvider/publish-terraform-provider.sh index 48be3ab38d..2bcf2ff3eb 100755 --- a/Scripts/TerraformProvider/publish-terraform-provider.sh +++ b/Scripts/TerraformProvider/publish-terraform-provider.sh @@ -597,12 +597,16 @@ EOF # Clean up rm -f "$release_notes_file" - - # Use existing builds from generation process and generate SHASUMS - generate_shasums - - # Upload release assets - upload_release_assets + + # Use GoReleaser to build archives, checksums, and sign if available + # Otherwise fall back to manual process + if command -v goreleaser &> /dev/null && [[ -f "$PROVIDER_FRAMEWORK_DIR/.goreleaser.yml" ]]; then + goreleaser_release_assets + else + # Fallback: Use existing builds from generation process and generate SHASUMS + generate_shasums + upload_release_assets + fi } @@ -634,6 +638,70 @@ publish_to_registry() { } +# Function to use GoReleaser for building archives, checksums, signing, and uploading +goreleaser_release_assets() { + print_step "Using GoReleaser to build archives, checksums, and sign..." + + cd "$PROVIDER_FRAMEWORK_DIR" + + # Get GPG fingerprint for signing + local gpg_fingerprint=$(gpg --list-secret-keys --keyid-format=long | grep -E "^sec" | head -1 | sed 's/.*\/\([A-F0-9]*\).*/\1/') + + if [[ -z "$gpg_fingerprint" ]]; then + print_error "No GPG secret key found for GoReleaser signing." + exit 1 + fi + + export GPG_FINGERPRINT="$gpg_fingerprint" + export GITHUB_TOKEN="$GITHUB_TOKEN" + + print_status "Using GPG key: $gpg_fingerprint" + print_status "Running GoReleaser to create archives, checksums, and signatures..." + + # GoReleaser builds archives + checksums + signs in one parallelized step + # We use --skip=publish since we already created the release and upload separately + goreleaser release \ + --clean \ + --skip=publish \ + --config .goreleaser.yml + + print_success "GoReleaser completed: archives, checksums, and signatures created" + + # Upload all GoReleaser dist artifacts to the GitHub release + print_status "Uploading GoReleaser artifacts to GitHub release..." + local dist_dir="dist" + + if [[ ! -d "$dist_dir" ]]; then + print_error "GoReleaser dist directory not found" + exit 1 + fi + + local files_uploaded=0 + for file in "$dist_dir"/*.zip "$dist_dir"/*SHA256SUMS* "$dist_dir"/*.sig "$dist_dir"/*.json; do + if [[ -f "$file" ]]; then + local filename=$(basename "$file") + print_status "Uploading $filename..." + if command -v gh &> /dev/null; then + gh release upload "v$VERSION" "$file" --repo "$GITHUB_ORG/$PROVIDER_REPO" --clobber + else + local release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_ORG/$PROVIDER_REPO/releases/tags/v$VERSION" | \ + jq -r '.id') + local upload_url="https://uploads.github.com/repos/$GITHUB_ORG/$PROVIDER_REPO/releases/$release_id/assets?name=$filename" + curl -s -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@$file" \ + "$upload_url" > /dev/null + fi + print_status "✓ Uploaded $filename" + files_uploaded=$((files_uploaded + 1)) + fi + done + + print_success "Uploaded $files_uploaded release assets via GoReleaser" +} + # Function to generate SHASUMS and signature files generate_shasums() { print_step "Generating SHASUMS and signature files..." @@ -961,7 +1029,6 @@ main() { parse_args "$@" validate_prerequisites - install_dependencies generate_provider push_to_repository create_github_release