Send environment key as part of job status update

What does this MR do?

Send environment key as part of job status update

With this change all suspended job will send environment key to rails as part of job update request.

Why was this MR needed?

Currently there is no way of sending environment key to the Rails server and this is needed because the orchestrator needs this key to trigger another job and resume the environment created by the suspended job.

What's the best way to test this MR?

  1. Check out both branches

    # GitLab repo — MR 239583
    # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/239583
    cd /path/to/gdk/gitlab
    git fetch origin
    git checkout ashvins/send-env-key-with-job-completion
    
    # Runner repo — MR 6813
    # https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6813
    cd /path/to/gitlab-runner
    git fetch origin
    git checkout ashvins/send-env-key-to-runner

    Run database migrations after checking out the GitLab branch:

    cd /path/to/gdk
    gdk rails db:migrate
  2. Enable feature flags

    There are two separate feature flags, one on each side.

    GitLab FF (ci_suspendable_environment_runner_routing) — enables Rails to persist the key. Open a Rails console (gdk rails console) and run:

    Feature.enable(:ci_suspendable_environment_runner_routing)
    Feature.enabled?(:ci_suspendable_environment_runner_routing)  # should return true
  3. Build the runner binary

    cd /path/to/gitlab-runner
    go build -o out/binaries/gitlab-runner ./main.go
    ./out/binaries/gitlab-runner --version
  4. Create a PAT

    In a GDK Rails console:

    token = User.find_by_username('root')
      .personal_access_tokens
      .create!(name: 'runner-test', scopes: ['api'], expires_at: 1.day.from_now)
    puts token.token   # copy this value
  5. Register a runner and write config.toml

    First, create a project-scoped runner token via the API. Replace <PAT> and <PROJECT_ID>:

    RUNNER_TOKEN=$(curl -s -X POST "https://gdk.test:3000/api/v4/user/runners" \
      -H "PRIVATE-TOKEN: <PAT>" \
      -H "Content-Type: application/json" \
      -d '{"runner_type":"project_type","project_id":<PROJECT_ID>,"description":"env-key-test","tag_list":"env-key-test"}' \
      | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
    echo "Runner token: $RUNNER_TOKEN"

    Then use gitlab-runner register to write the config. It contacts the GitLab instance, resolves the runner id automatically, and writes the full [[runners]] block:

    DOCKER_SOCKET=$(docker context inspect --format '{{.Endpoints.docker.Host}}' | sed 's|unix://||')
    echo "Docker socket: $DOCKER_SOCKET"
    
    /path/to/gitlab-runner/out/binaries/gitlab-runner register \
      --non-interactive \
      --config /tmp/runner-test-config.toml \
      --url "https://gdk.test:3000" \
      --token "$RUNNER_TOKEN" \
      --executor "docker" \
      --docker-image "ubuntu:22.04" \
      --docker-volumes "$DOCKER_SOCKET:/var/run/docker.sock" \
      --docker-host "unix://$DOCKER_SOCKET" \
      --tls-ca-file "<path to CA cert in case your GDK is TLS enabled>"

    After registration, append the runner feature flag to the generated config:

    cat >> /tmp/runner-test-config.toml << 'EOF'
    
      [runners.feature_flags]
        FF_SUSPENDABLE_ENVIRONMENTS = true
    EOF
  6. One-time project setup

    PAT="<PAT>"
    
    PROJECT=$(curl -s -X POST "https://gdk.test:3000/api/v4/projects" \
      -H "PRIVATE-TOKEN: $PAT" \
      -H "Content-Type: application/json" \
      -d '{"name":"env-key-test","visibility":"private","initialize_with_readme":"true"}')
    PROJECT_ID=$(echo $PROJECT | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
    echo "Project ID: $PROJECT_ID"

    This only needs to be done once. On repeat runs, skip to Step 7.

  7. Trigger a suspendable pipeline from the Rails console

    project = Project.find_by_full_path('root/env-key-test')
    user = User.find_by_username('root')
    
    ci_yaml = <<~YAML
      stages:
        - test
      env-key-test:
        stage: test
        tags:
          - env-key-test
        script:
          - echo "start"
          - date
          - echo "done"
    YAML
    
    result = Ci::CreatePipelineService.new(project, user, ref: 'main').execute(
      :web,
      content: ci_yaml,
      suspend_options: { suspend_on_success: true, suspend_on_failure: false }
    )
    puts "Pipeline #{result.payload.id} status=#{result.payload.status}"
    # => Pipeline 215 status=pending
  8. Run the runner

    /path/to/gitlab-runner/out/binaries/gitlab-runner run --config /tmp/runner-test-config.toml

    The runner will pick up the job, execute the script, and call Suspend() on the Docker containers. Watch the log for:

    Job is set up                       job=<ID> runner=...
    ...
    Job environment suspended: ...      ← Suspend() returned container IDs
    ...
    Submitting job to coordinator...    ← final PUT /api/v4/jobs/:id with environment_key
    Job succeeded                       job=<ID>
  9. Verify the result

    curl -s -X POST "https://gdk.test:3000/api/graphql" \
      -H "PRIVATE-TOKEN: <PAT>" \
      -H "Content-Type: application/json" \
      -d "{\"query\": \"{ project(fullPath: \\\"root/env-key-test\\\") { jobs { nodes { id status environmentKey } } } }\"}"

    Expected response for the successful job:

    {
      "id": "gid://gitlab/Ci::Build/<JOB_ID>",
      "status": "SUCCESS",
      "environmentKey": "<runner_id>/<system_id>/build-container-id=<sha>&helper-id=<sha>"
    }

What are the relevant issue numbers?

Closes #39463

Edited by Ashvin Sharma

Merge request reports

Loading