diff --git a/.github/actions/push-sandbox/action.yml b/.github/actions/push-sandbox/action.yml deleted file mode 100644 index 24669f421d..0000000000 --- a/.github/actions/push-sandbox/action.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: 'Build and push sandbox docker' -description: 'Pushes sandbox docker image to container registry' - -inputs: - github-actor: - description: 'Github actor' - required: true - github-secret: - description: 'Github secret' - required: true - dockerhub-username: - description: 'Dockerhub username' - required: true - dockerhub-token: - description: 'Dockerhub PAT w/ R+W' - required: true - github-sha: - description: 'Github Commit SHA Hash' - required: true - github-ref-name: - description: 'Github ref name' - required: true - dry-run: - description: 'Whether this is a dry run.' - required: true - type: 'boolean' - -runs: - using: 'composite' - steps: - - name: '📝 Print Inputs' - shell: 'bash' - env: - JSON_INPUTS: '${{ toJSON(inputs) }}' - run: 'echo "$JSON_INPUTS"' - - name: 'Checkout' - uses: 'actions/checkout@v4' - with: - ref: '${{ inputs.github-sha }}' - fetch-depth: 0 - - name: 'Install Dependencies' - shell: 'bash' - run: 'npm install' - - name: 'npm build' - shell: 'bash' - run: 'npm run build' - - name: 'Set up Docker Buildx' - uses: 'docker/setup-buildx-action@v3' - - name: 'Log in to GitHub Container Registry' - uses: 'docker/login-action@v3' - with: - registry: 'docker.io' - username: '${{ inputs.dockerhub-username }}' - password: '${{ inputs.dockerhub-token }}' - - name: 'determine image tag' - id: 'image_tag' - shell: 'bash' - run: |- - SHELL_TAG_NAME="${{ inputs.github-ref-name }}" - FINAL_TAG="${{ inputs.github-sha }}" - if [[ "$SHELL_TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then - echo "Release detected." - FINAL_TAG="${SHELL_TAG_NAME#v}" - else - echo "Development release detected. Using commit SHA as tag." - fi - echo "Determined image tag: $FINAL_TAG" - echo "FINAL_TAG=$FINAL_TAG" >> $GITHUB_OUTPUT - - name: 'build' - id: 'docker_build' - shell: 'bash' - env: - GEMINI_SANDBOX_IMAGE_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}' - GEMINI_SANDBOX: 'docker' - run: |- - npm run build:sandbox -- \ - --image google/gemini-cli-sandbox:${{ steps.image_tag.outputs.FINAL_TAG }} \ - --output-file final_image_uri.txt - echo "uri=$(cat final_image_uri.txt)" >> $GITHUB_OUTPUT - - name: 'publish' - shell: 'bash' - if: "${{ inputs.dry-run != 'true' }}" - run: |- - docker push "${{ steps.docker_build.outputs.uri }}" - - name: 'Create issue on failure' - if: |- - ${{ failure() }} - shell: 'bash' - env: - GITHUB_TOKEN: '${{ inputs.github-secret }}' - DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' - run: |- - gh issue create \ - --title "Docker build failed" \ - --body "The docker build failed. See the full run for details: ${DETAILS_URL}" \ - --label "kind/bug,release-failure" diff --git a/.github/workflows/build-sandbox.yml b/.github/workflows/build-sandbox.yml new file mode 100644 index 0000000000..1fbc5197a4 --- /dev/null +++ b/.github/workflows/build-sandbox.yml @@ -0,0 +1,115 @@ +name: 'Build Sandbox Image' + +on: + workflow_call: + inputs: + github-actor: + description: 'Github actor' + required: true + type: 'string' + github-secret: + description: 'Github secret' + required: true + type: 'string' + github-sha: + description: 'Github Commit SHA Hash' + required: true + type: 'string' + github-ref-name: + description: 'Github ref name' + required: true + type: 'string' + dry-run: + description: 'Whether this is a dry run.' + required: true + type: 'boolean' + npm-registry-scope: + description: 'NPM registry scope' + required: true + type: 'string' + npm-registry-url: + description: 'NPM registry URL' + required: true + type: 'string' + cli-package-name: + description: 'The name of the cli package.' + required: true + type: 'string' + outputs: + image-uri: + description: 'The URI of the built sandbox image.' + value: '${{ jobs.build-and-push.outputs.image-uri }}' + +jobs: + build-and-push: + runs-on: 'ubuntu-latest' + outputs: + image-uri: '${{ steps.docker_build.outputs.uri }}' + steps: + - name: '📝 Print Inputs' + shell: 'bash' + env: + JSON_INPUTS: '${{ toJSON(inputs) }}' + run: 'echo "$JSON_INPUTS"' + - name: 'Checkout' + uses: 'actions/checkout@v4' + with: + ref: '${{ inputs.github-sha }}' + fetch-depth: 0 + - name: 'Install Dependencies' + shell: 'bash' + run: 'npm install' + - name: 'Set up Docker Buildx' + uses: 'docker/setup-buildx-action@v3' + - name: 'Log in to GitHub Container Registry' + uses: 'docker/login-action@v3' + with: + registry: 'ghcr.io' + username: '${{ inputs.github-actor }}' + password: '${{ inputs.github-secret }}' + - name: 'determine image tag' + id: 'image_tag' + shell: 'bash' + run: |- + SHELL_TAG_NAME="${{ inputs.github-ref-name }}" + FINAL_TAG="${{ inputs.github-sha }}" + if [[ "$SHELL_TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then + echo "Release detected." + FINAL_TAG="${SHELL_TAG_NAME#v}" + else + echo "Development release detected. Using commit SHA as tag." + fi + echo "Determined image tag: $FINAL_TAG" + echo "FINAL_TAG=$FINAL_TAG" >> "$GITHUB_OUTPUT" + - name: 'build' + id: 'docker_build' + shell: 'bash' + env: + GEMINI_SANDBOX_IMAGE_TAG: '${{ steps.image_tag.outputs.FINAL_TAG }}' + GEMINI_SANDBOX: 'docker' + NPM_REGISTRY_SCOPE: '${{ inputs.npm-registry-scope }}' + NPM_REGISTRY_URL: '${{ inputs.npm-registry-url }}' + CLI_PACKAGE_NAME: '${{ inputs.cli-package-name }}' + GITHUB_TOKEN: '${{ inputs.github-secret }}' + run: |- + node scripts/build_sandbox.js \ + --image ghcr.io/${{ github.repository }}/cli-sandbox:${{ steps.image_tag.outputs.FINAL_TAG }} \ + --output-file final_image_uri.txt + echo "uri=$(cat final_image_uri.txt)" >> "$GITHUB_OUTPUT" + - name: 'publish' + shell: 'bash' + if: "${{ inputs.dry-run != 'true' }}" + run: |- + docker push "${{ steps.docker_build.outputs.uri }}" + - name: 'Create issue on failure' + if: |- + ${{ failure() }} + shell: 'bash' + env: + GITHUB_TOKEN: '${{ inputs.github-secret }}' + DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + run: |- + gh issue create \ + --title "Docker build failed" \ + --body "The docker build failed. See the full run for details: ${DETAILS_URL}" \ + --label "kind/bug,release-failure" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 11a39605e5..39ae017ada 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,13 +13,33 @@ on: description: 'The name of the CLI package to install' required: true type: 'string' + image-uri: + description: 'The URI of the built sandbox image.' + required: true + type: 'string' secrets: GEMINI_API_KEY: required: true jobs: + build-sandbox-image: + name: 'Build Sandbox Image' + runs-on: 'ubuntu-latest' + outputs: + image-uri: '${{ steps.build-and-push.outputs.image-uri }}' + steps: + - id: 'build-and-push' + uses: './.github/workflows/build-sandbox.yml' + with: + github-actor: '${{ github.actor }}' + github-secret: '${{ secrets.GITHUB_TOKEN }}' + github-sha: '${{ github.sha }}' + github-ref-name: '${{ github.ref_name }}' + dry-run: false + e2e_linux: name: 'E2E Test (Linux) - ${{ matrix.sandbox }}' + needs: 'build-sandbox-image' if: | (github.event_name == 'push' || github.event_name == 'merge_group' || @@ -57,16 +77,13 @@ jobs: - name: 'Install dependencies' run: 'npm install ${{ inputs.cli-package-name }}@${{ inputs.version }}' - - name: 'Set up Docker' - if: "matrix.sandbox == 'sandbox:docker'" - uses: 'docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435' # ratchet:docker/setup-buildx-action@v3 - - name: 'Run E2E tests' env: GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' KEEP_OUTPUT: 'true' VERBOSE: 'true' INTEGRATION_TEST_USE_INSTALLED_GEMINI: 'true' + GEMINI_SANDBOX_IMAGE: '${{ inputs.image-uri }}' shell: 'bash' run: | if [[ "${{ matrix.sandbox }}" == "sandbox:docker" ]]; then @@ -209,6 +226,7 @@ jobs: needs: - 'e2e_linux' - 'e2e_mac' + - 'build-sandbox-image' runs-on: 'ubuntu-latest' steps: - name: 'Check E2E test results' diff --git a/.github/workflows/orchestrator.yml b/.github/workflows/orchestrator.yml index 934d7da085..1c5741eeae 100644 --- a/.github/workflows/orchestrator.yml +++ b/.github/workflows/orchestrator.yml @@ -53,13 +53,28 @@ jobs: - id: 'get-vars' run: 'echo ''cli-package-name=''''${{ vars.CLI_PACKAGE_NAME }}'''''' >> "$GITHUB_OUTPUT"' + build-sandbox: + name: 'Build Sandbox Image' + needs: 'lint' + uses: './.github/workflows/build-sandbox.yml' + with: + github-actor: '${{ github.actor }}' + github-secret: '${{ github.token }}' + github-sha: '${{ github.sha }}' + github-ref-name: '${{ github.ref_name }}' + dry-run: false + npm-registry-scope: '${{ vars.NPM_REGISTRY_SCOPE }}' + npm-registry-url: '${{ vars.NPM_REGISTRY_URL }}' + cli-package-name: '${{ vars.CLI_PACKAGE_NAME }}' + e2e: name: 'E2E Checks' - needs: ['build-and-publish', 'get-vars'] + needs: ['build-and-publish', 'get-vars', 'build-sandbox'] uses: './.github/workflows/e2e.yml' with: version: '${{ needs.build-and-publish.outputs.version }}' cli-package-name: '${{ needs.get-vars.outputs.cli-package-name }}' + image-uri: '${{ needs.build-sandbox.outputs.image-uri }}' secrets: GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' diff --git a/Dockerfile b/Dockerfile index b41ea00368..37cccadd61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,35 @@ +# --- Builder Stage --- +FROM docker.io/library/node:20-slim AS builder + +ARG CLI_VERSION +ARG NPM_REGISTRY_SCOPE +ARG NPM_REGISTRY_URL +ARG CLI_PACKAGE_NAME + +# Set up npm global package folder +ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global +ENV PATH=$PATH:/usr/local/share/npm-global/bin + +# Configure npm to use GitHub Packages +RUN --mount=type=secret,id=GITHUB_TOKEN \ + echo "${NPM_REGISTRY_SCOPE}:registry=${NPM_REGISTRY_URL}" > /home/node/.npmrc && \ + echo "//npm.pkg.github.com/:_authToken=$(cat /run/secrets/GITHUB_TOKEN)" >> /home/node/.npmrc && \ + chown -R node:node /home/node/.npmrc + +# Switch to non-root user +USER node + +# Install the Gemini CLI package +RUN npm install -g ${CLI_PACKAGE_NAME}@${CLI_VERSION} && \ + npm cache clean --force + +# --- Final Stage --- FROM docker.io/library/node:20-slim ARG SANDBOX_NAME="gemini-cli-sandbox" -ARG CLI_VERSION_ARG ENV SANDBOX="$SANDBOX_NAME" -ENV CLI_VERSION=$CLI_VERSION_ARG -# install minimal set of packages, then clean up +# Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ make \ @@ -29,22 +53,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# set up npm global package folder under /usr/local/share -# give it to non-root user node, already set up in base image +# Set up npm global package folder and user RUN mkdir -p /usr/local/share/npm-global \ && chown -R node:node /usr/local/share/npm-global ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global ENV PATH=$PATH:/usr/local/share/npm-global/bin - -# switch to non-root user node USER node -# install gemini-cli and clean up -COPY packages/cli/dist/google-gemini-cli-*.tgz /tmp/gemini-cli.tgz -COPY packages/core/dist/google-gemini-cli-core-*.tgz /tmp/gemini-core.tgz -RUN npm install -g /tmp/gemini-cli.tgz /tmp/gemini-core.tgz \ - && npm cache clean --force \ - && rm -f /tmp/gemini-{cli,core}.tgz +# Copy installed package from the builder stage +COPY --from=builder /usr/local/share/npm-global /usr/local/share/npm-global -# default entrypoint when none specified +# Default entrypoint CMD ["gemini"] diff --git a/build.png b/build.png deleted file mode 100644 index 7b9bf77b7b..0000000000 Binary files a/build.png and /dev/null differ diff --git a/scripts/build_sandbox.js b/scripts/build_sandbox.js index 84ac96bc99..409bcab459 100644 --- a/scripts/build_sandbox.js +++ b/scripts/build_sandbox.js @@ -160,7 +160,7 @@ function buildImage(imageName, dockerfile) { execSync( `${sandboxCommand} build ${buildCommandArgs} ${ process.env.BUILD_SANDBOX_FLAGS || '' - } --build-arg CLI_VERSION_ARG=${npmPackageVersion} -f "${dockerfile}" -t "${finalImageName}" .`, + } --build-arg CLI_VERSION=${npmPackageVersion} --build-arg NPM_REGISTRY_SCOPE=${process.env.NPM_REGISTRY_SCOPE} --build-arg NPM_REGISTRY_URL=${process.env.NPM_REGISTRY_URL} --build-arg CLI_PACKAGE_NAME=${process.env.CLI_PACKAGE_NAME} --secret id=GITHUB_TOKEN,env=GITHUB_TOKEN -f "${dockerfile}" -t "${finalImageName}" .`, { stdio: buildStdout, shell: shellToUse }, ); console.log(`built ${finalImageName}`);