Jose A
Jose A

Reputation: 11129

GitHub Actions, Pulumi GCP, Artifact Registry and Docker - Cannot perform an interactive login from a non TTY device

There are dozens of Q/A in Stack Overflow. I've applied all the solutions out there, but I keep getting the same error:

Cannot perform an interactive login from a non TTY device

For context:

I'm using Pulumi in GitHub Actions to deploy to GCP's Artifact Registry. I have two docker containers, and this is my yaml:

name: Deploy to Staging
on:
  push:
    branches:
      - main
permissions:
  actions: read
  contents: read
  id-token: write
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      - name: Build affected apps
        run: pnpm exec nx affected -t build

  deploy:
    runs-on: ubuntu-latest
    environment: staging
    needs: [ci]
    steps:
      - uses: actions/checkout@v4
      - name: Create .env file
        run: |
          cat << EOF > libs/infrastructure/src/pulumi/.env
          PULUMI_MAIN_SERVICE_ACCOUNT_STAGING="${{ secrets.PULUMI_MAIN_SERVICE_ACCOUNT_STAGING }}"
          PULUMI_WORKLOAD_IDENTITY_PROVIDER_ID_STAGING="${{ secrets.PULUMI_WORKLOAD_IDENTITY_PROVIDER_ID_STAGING }}"
          PULUMI_DOPPLER_REMIX_PROJECT="remix-app"
          PULUMI_DOPPLER_REMIX_STAGING_TOKEN="${{ secrets.PULUMI_DOPPLER_REMIX_STAGING_TOKEN }}"
          PULUMI_DOPPLER_REMIX_STAGING_BRANCH_NAME="stg"
          PULUMI_DOPPLER_TEMPORAL_PROJECT="temporal-worker"
          PULUMI_DOPPLER_TEMPORAL_STAGING_TOKEN="${{ secrets.PULUMI_DOPPLER_TEMPORAL_STAGING_TOKEN }}"
          PULUMI_DOPPLER_TEMPORAL_STAGING_BRANCH_NAME="stg"
          PULUMI_DOPPLER_CLOUD_RUN_REMIX_STAGING_TOKEN="${{ secrets.PULUMI_DOPPLER_CLOUD_RUN_REMIX_STAGING_TOKEN }}"
          PULUMI_DOPPLER_CLOUD_RUN_TEMPORAL_STAGING_TOKEN="${{ secrets.PULUMI_DOPPLER_CLOUD_RUN_TEMPORAL_STAGING_TOKEN }}"
          EOF

      - name: Configure Workload Identity Federation
        id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.GCP_STAGING_WORKLOAD_IDENTITY_PROVIDER_ID }}
          project_id: ${{ secrets.GCP_STAGING_PROJECT_ID }}
          service_account: [email protected]
          token_format: 'access_token'

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Configure Docker for Artifact Registry
        run: |
          gcloud auth configure-docker us-east1-docker.pkg.dev

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Artifact Registry
        uses: docker/login-action@v3
        with:
          registry: us-east1-docker.pkg.dev
          username: oauth2accesstoken
          password: ${{ steps.auth.outputs.access_token }}

      - name: Run Pulumi
        uses: pulumi/actions@v6
        with:
          work-dir: 'libs/infrastructure/src/pulumi'
          command: 'up'
          stack-name: 'alertdown/alertdown-infra/dev'
          comment-on-pr: true
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}

Here's my infrastructure code:

package cloudrun

import (
    "errors"
    "fmt"
    "os"
    "path/filepath"

    "github.com/pulumi/pulumi-docker/sdk/v3/go/docker"
    "github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp"
    "github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp/artifactregistry"
    "github.com/pulumi/pulumi-gcp/sdk/v8/go/gcp/projects"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type createArtifactRegistryNewRepositoryInput struct {
    ctx                          *pulumi.Context
    artifactRegistryRepoName     string
    artifactRegistryRepoLocation string
    artifactRegistryService      *projects.Service `pulumi:"artifactRegistryService"`
    provider                     *gcp.Provider
}

func createArtifactRegistryNewRepository(input *createArtifactRegistryNewRepositoryInput) (*artifactregistry.Repository, error) {

    if input.artifactRegistryService == nil {
        return nil, errors.New("artifactRegistryService cannot be nil")
    }

    dependingResources := []pulumi.Resource{
        input.artifactRegistryService,
    }

    repo, err := artifactregistry.NewRepository(input.ctx, input.artifactRegistryRepoName, &artifactregistry.RepositoryArgs{
        Location:     pulumi.String(input.artifactRegistryRepoLocation),
        RepositoryId: pulumi.String(input.artifactRegistryRepoName),
        Format:       pulumi.String("DOCKER"),
        Description:  pulumi.String("The repository that will hold social-log Docker images."),
    }, pulumi.DependsOn(dependingResources), pulumi.Provider(input.provider))

    if err != nil {
        return nil, err
    }

    return repo, nil
}

type buildAndPushToArtifactRegistryInput struct {
    ctx                  *pulumi.Context
    artifactRegistryRepo *artifactregistry.Repository
    dockerImageName      string
    dockerImagePath      string
    dockerfileName       string
    provider             *gcp.Provider
}

func buildAndPushToArtifactRegistry(input *buildAndPushToArtifactRegistryInput) (*docker.Image, error) {
    if input.artifactRegistryRepo == nil {
        return nil, errors.New("artifactRegistryRepo cannot be nil")
    }

    dependingSources := []pulumi.Resource{
        input.artifactRegistryRepo,
    }

    // Get the absolute path to the project root
    workingDir, err := os.Getwd()
    if err != nil {
        return nil, fmt.Errorf("failed to get working directory: %w", err)
    }

    // Go up to the project root (from /libs/infrastructure/src/pulumi)
    projectRoot := filepath.Join(workingDir, "../../../../")
    projectRoot, err = filepath.Abs(projectRoot)
    if err != nil {
        return nil, fmt.Errorf("failed to get absolute path: %w", err)
    }

    // Construct the absolute path to the Dockerfile
    dockerfilePath := filepath.Join(projectRoot, "apps", input.dockerImagePath)

    // Verify the Dockerfile exists
    if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) {
        return nil, fmt.Errorf("dockerfile not found at path: %s", dockerfilePath)
    }

    // Create Docker build args using absolute paths
    buildArgs := &docker.DockerBuildArgs{
        Context: pulumi.String(dockerfilePath),
        ExtraOptions: pulumi.StringArray{
            pulumi.String("--platform=linux/amd64"),
            pulumi.String("--build-context"),
            pulumi.String(fmt.Sprintf("root=%v", projectRoot)),
        },
        Target: pulumi.String("production"),
    }

    // Construct the full image name with registry URL
    // Format: LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE
    registryUrl := pulumi.Sprintf("%s-docker.pkg.dev/%s/%s/%s",
        input.artifactRegistryRepo.Location,
        input.artifactRegistryRepo.Project,
        input.artifactRegistryRepo.RepositoryId,
        input.dockerImageName,
    )

    accessToken := os.Getenv("GOOGLE_ACCESS_TOKEN_FROM_CLI")

    image, err := docker.NewImage(input.ctx, input.dockerImageName, &docker.ImageArgs{
        Build:     buildArgs,
        ImageName: registryUrl, // Use the full registry URL instead of just the image name
        Registry: &docker.ImageRegistryArgs{
            Server:   pulumi.Sprintf("%s-docker.pkg.dev", input.artifactRegistryRepo.Location),
            Username: pulumi.String("oauth2accesstoken"),
            Password: pulumi.String(accessToken), // Replace with a valid access token

        },
    }, pulumi.DependsOn(dependingSources), pulumi.Provider(input.provider))

    if err != nil {
        return nil, fmt.Errorf("failed to create docker image: %w", err)
    }

    Return image, nil
}

This code works locally.

I've also checked that the service account has the right permissions and roles.

Note that I'm using Workload Identity Federation.

Any ideas?

Upvotes: 0

Views: 40

Answers (0)

Related Questions