diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2300722 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +PROJECT_ID= +GCS_BUCKET= +REGION= +ARTIFACT_REGISTRY= +CLOUD_RUN_SERVICE= + +# Only set this in CI, the defaults in eval.yaml are good for local testing +GOOGLE_APPLICATION_CREDENTIALS= + + diff --git a/.gitignore b/.gitignore index ea3d1a8..c6e335c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ server # GitHub App credentials gha-creds-*.json +.env diff --git a/Dockerfile.eval b/Dockerfile.eval new file mode 100644 index 0000000..99df937 --- /dev/null +++ b/Dockerfile.eval @@ -0,0 +1,64 @@ +# Stage 1: Build the MCP Server +FROM debian:sid AS builder + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + curl \ + git \ + sudo \ + golang-go \ + && rm -rf /var/lib/apt/lists/* +COPY cicd-mcp-server/ ./cicd-mcp-server/ +COPY lib/ ./lib/ +COPY build.sh ./ + +RUN chmod +x build.sh +RUN ./build.sh + +# Stage 2: Build the eval image with dependencies +FROM node:20-slim + +RUN apt-get update && apt-get install -y \ + curl \ + procps \ + python3 \ + unzip \ + git \ + && rm -rf /var/lib/apt/lists/* + +# gcloud +RUN curl -sSL https://sdk.cloud.google.com | bash +ENV PATH=${PATH}:/root/google-cloud-sdk/bin + +# Terraform +RUN curl -LO https://releases.hashicorp.com/terraform/1.14.8/terraform_1.14.8_linux_amd64.zip \ + && unzip terraform_1.14.8_linux_amd64.zip \ + && mv terraform /usr/local/bin/ \ + && rm terraform_1.14.8_linux_amd64.zip + +# Configure cicd MCP server +COPY --from=builder /app/cicd-mcp-server/cicd-mcp-server /usr/local/bin/cicd-mcp-server +RUN mkdir -p /root/.gemini +COPY <<'EOF' /root/.gemini/settings.json +{ + "mcpServers": { + "cicd": { + "command": "/usr/local/bin/cicd-mcp-server", + "timeout": 300000, + "trust": true, + "env": { + "GOOGLE_APPLICATION_CREDENTIALS": "$GOOGLE_APPLICATION_CREDENTIALS" + } + } + } +} +EOF + +COPY < "$TOKEN_FILE"; then + # Set the gcloud property + gcloud config set auth/access_token_file "$TOKEN_FILE" + echo "Successfully set auth/access_token_file to $TOKEN_FILE" +else + echo "Failed to get access token" >&2 + rm -f "$TOKEN_FILE" + exit 1 +fi diff --git a/scripts/teardown-cloud-run.sh b/scripts/teardown-cloud-run.sh new file mode 100755 index 0000000..74a23af --- /dev/null +++ b/scripts/teardown-cloud-run.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Check for required environment variables +if [ -z "$CLOUD_RUN_SERVICE" ]; then + echo "Error: CLOUD_RUN_SERVICE environment variable is not set." + exit 1 +fi + +if [ -z "$PROJECT_ID" ]; then + echo "Error: PROJECT_ID environment variable is not set." + exit 1 +fi + +if [ -z "$REGION" ]; then + echo "Error: REGION environment variable is not set." + exit 1 +fi + +echo "Deleting Cloud Run service $CLOUD_RUN_SERVICE in project $PROJECT_ID and region $REGION..." + +gcloud run services delete "$CLOUD_RUN_SERVICE" \ + --project="$PROJECT_ID" \ + --region="$REGION" \ + --quiet + +echo "Cloud Run service $CLOUD_RUN_SERVICE deletion command completed." diff --git a/scripts/teardown-gcs.sh b/scripts/teardown-gcs.sh new file mode 100755 index 0000000..17e1c02 --- /dev/null +++ b/scripts/teardown-gcs.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Check if GCS_BUCKET is set +if [ -z "$GCS_BUCKET" ]; then + echo "Error: GCS_BUCKET environment variable is not set" >&2 + exit 1 +fi + +PROJECT_ARG="" +if [ -n "$PROJECT_ID" ]; then + PROJECT_ARG="--project=$PROJECT_ID" +fi + +# Attempt to delete the bucket and its contents +# We use gcloud storage rm -r to delete objects. +# We ignore errors because the bucket might be empty or not exist. +gcloud storage rm -r gs://$GCS_BUCKET/** $PROJECT_ARG &> /dev/null + +# Delete the bucket +gcloud storage buckets delete gs://$GCS_BUCKET $PROJECT_ARG --quiet &> /dev/null + +# Check if the bucket still exists +if gcloud storage buckets describe gs://$GCS_BUCKET $PROJECT_ARG &> /dev/null; then + echo "Error: Failed to delete bucket $GCS_BUCKET" >&2 + exit 1 +else + echo "Successfully deleted bucket $GCS_BUCKET" + exit 0 +fi diff --git a/scripts/validate-cloud-run-deployment.sh b/scripts/validate-cloud-run-deployment.sh new file mode 100755 index 0000000..0e5684c --- /dev/null +++ b/scripts/validate-cloud-run-deployment.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Support both PROJECT_NAME and PROJECT_ID +PROJECT_NAME=${PROJECT_NAME:-$PROJECT_ID} + +# Initialize checks +CHECK_EXISTS="false" +MSG_EXISTS="Not checked" +CHECK_REACHABLE="false" +MSG_REACHABLE="Not checked" + +# Check for required environment variables +MISSING_VARS=() +[ -z "$CLOUD_RUN_SERVICE" ] && MISSING_VARS+=("CLOUD_RUN_SERVICE") +[ -z "$PROJECT_NAME" ] && MISSING_VARS+=("PROJECT_NAME/PROJECT_ID") +[ -z "$REGION" ] && MISSING_VARS+=("REGION") + +if [ ${#MISSING_VARS[@]} -ne 0 ]; then + MSG_EXISTS="Missing environment variables: ${MISSING_VARS[*]}" +else + # 1. Check if service exists + if gcloud run services describe "$CLOUD_RUN_SERVICE" --project="$PROJECT_NAME" --region="$REGION" &>/dev/null; then + CHECK_EXISTS="true" + MSG_EXISTS="Service exists" + + + # 3. Check Reachable + URL=$(gcloud run services describe "$CLOUD_RUN_SERVICE" \ + --project="$PROJECT_NAME" \ + --region="$REGION" \ + --format='value(status.url)') + + if [ -n "$URL" ]; then + CURL_OPTS="-s -o /dev/null -w %{http_code}" + HTTP_STATUS=$(curl $CURL_OPTS "$URL") + + if [ "$HTTP_STATUS" == "000" ]; then + CHECK_REACHABLE="false" + MSG_REACHABLE="Could not connect to service URL" + elif [[ "$HTTP_STATUS" =~ ^5 ]]; then + CHECK_REACHABLE="false" + MSG_REACHABLE="Service returned server error (HTTP $HTTP_STATUS)" + else + CHECK_REACHABLE="true" + MSG_REACHABLE="Service is reachable (HTTP $HTTP_STATUS)" + fi + else + CHECK_REACHABLE="false" + MSG_REACHABLE="Service URL not found" + fi + + else + CHECK_EXISTS="false" + MSG_EXISTS="Service does not exist or is not accessible" + MSG_REACHABLE="Skipped: Service does not exist" + fi +fi + +# Calculate score +TOTAL_CHECKS=2 +PASSED_CHECKS=0 +[ "$CHECK_EXISTS" == "true" ] && ((PASSED_CHECKS++)) +[ "$CHECK_REACHABLE" == "true" ] && ((PASSED_CHECKS++)) + +SCORE=$(awk "BEGIN {printf \"%.2f\", $PASSED_CHECKS / $TOTAL_CHECKS}") + +# Construct JSON +cat < /dev/null; then + CHECK_EXISTS="true" + MSG_EXISTS="Bucket $GCS_BUCKET exists" + + # Check 2: Bucket contains files + FILES=$(gcloud storage ls gs://$GCS_BUCKET 2>/dev/null) + if [ -n "$FILES" ]; then + CHECK_CONTAINS="true" + MSG_CONTAINS="Bucket contains files" + + # Check 3: Public access + # Find a file to test + TARGET_FILE="" + if echo "$FILES" | grep -q "gs://$GCS_BUCKET/index.html"; then + TARGET_FILE="index.html" + else + TARGET_FILE=$(echo "$FILES" | head -n 1 | sed "s|gs://$GCS_BUCKET/||") + fi + + if [ -n "$TARGET_FILE" ]; then + URL="https://storage.googleapis.com/$GCS_BUCKET/$TARGET_FILE" + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL") + + if [ "$HTTP_CODE" = "200" ]; then + CHECK_PUBLIC="true" + MSG_PUBLIC="Successfully accessed $URL (HTTP 200)" + else + MSG_PUBLIC="Failed to access $URL (HTTP $HTTP_CODE)" + fi + else + MSG_PUBLIC="Could not determine a target file to test" + fi + else + MSG_CONTAINS="Bucket is empty" + fi +fi + +# Calculate score +TOTAL_CHECKS=3 +PASSED_CHECKS=0 +[ "$CHECK_EXISTS" == "true" ] && ((PASSED_CHECKS++)) +[ "$CHECK_CONTAINS" == "true" ] && ((PASSED_CHECKS++)) +[ "$CHECK_PUBLIC" == "true" ] && ((PASSED_CHECKS++)) + +SCORE=$(awk "BEGIN {printf \"%.2f\", $PASSED_CHECKS / $TOTAL_CHECKS}") + +# Construct JSON +cat <