diff --git a/.gitignore b/.gitignore index 88cc3f8af4..e5d40cb00e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ dist .secrets.json certs/ deployment/data/* +deployment/config/depot.json metald.db bin/ diff --git a/QUICKSTART-DEPLOY.md b/QUICKSTART-DEPLOY.md index fa33201f1e..f9f4085d04 100644 --- a/QUICKSTART-DEPLOY.md +++ b/QUICKSTART-DEPLOY.md @@ -9,9 +9,105 @@ This guide will help you get the Unkey deployment platform up and running locall - A terminal/command line - dnsmasq (for wildcard DNS setup) -## Step 1: Start the Platform +## Step 1: Configure Build Backend -1. Set up the API key for ctrl service authentication by adding it to `go/apps/ctrl/.env`: +**You must configure the build backend before starting Docker Compose.** This script generates the `.env` file that Docker Compose requires. + +### Why This Matters + +The platform builds **unknown user code** in isolation for security. When users deploy their applications, the system: + +1. **Isolates each build** - User code runs in separate containers/VMs to prevent interference +2. **Caches layers** - Speeds up rebuilds by reusing unchanged Docker layers +3. **Stores artifacts** - Uses S3-compatible storage for built images and artifacts + +You have two options: + +- **Docker**: Fully local setup, slower builds, good for development +- **Depot**: Fast remote builds with persistent layer caching + +Navigate to the deployment directory: + +```bash +cd deployment +``` + +### Option A: Local Docker + +Fully local setup using Docker with MinIO for S3 storage: + +```bash +./setup-build-backend.sh docker +``` + +This configures: + +- Local Docker builds (no external services) +- MinIO S3 storage running in Docker + +### Option B: Depot + +Remote builds with persistent caching for faster iteration. Create `deployment/config/depot.json`: + +```json +{ + "token": "depot_org_YOUR_TOKEN_HERE", + "s3_url": "https://your-s3-endpoint.com", + "s3_access_key_id": "your_access_key", + "s3_access_key_secret": "your_secret_key" +} +``` + +Then run: + +```bash +./setup-build-backend.sh depot +``` + +This configures: + +- Remote Depot infrastructure for builds +- Persistent layer caching across builds +- Your S3-compatible storage for artifacts + +**Note:** The token must start with `depot_org_` or the script will fail validation. + +### Kubernetes: Additional Setup for Depot and S3 + +If you're deploying to Kubernetes, you must create secrets for Depot and S3 credentials: + +**1. Create Depot credentials secret:** + +```bash +kubectl create secret generic depot-credentials \ + --from-literal=token=depot_org_YOUR_TOKEN_HERE \ + --from-literal=s3-url=https://your-s3-endpoint.com \ + --from-literal=s3-access-key-id=your_access_key \ + --from-literal=s3-access-key-secret=your_secret_key \ + --namespace=unkey +``` + +**2. Create Depot registry credentials secret:** + +```bash +kubectl create secret docker-registry depot-registry \ + --docker-server=registry.depot.dev \ + --docker-username=x-token \ + --docker-password=depot_org_YOUR_TOKEN_HERE \ + --namespace=unkey +``` + +**Critical:** These secrets must exist before deploying the `ctrl` service to Kubernetes. The `ctrl` deployment references these secrets for: + +- Building user code via Depot +- Storing build contexts in S3 +- Pulling built images from Depot's registry + +Without these secrets, deployments will fail with authentication errors. + +## Step 2: Configure API Keys + +1. Set up the API key for ctrl service authentication in `go/apps/ctrl/.env`: ```bash UNKEY_API_KEY="your-local-dev-key" @@ -24,41 +120,46 @@ CTRL_URL="http://127.0.0.1:7091" CTRL_API_KEY="your-local-dev-key" ``` -Note: Use the same API key value in both files for authentication to work properly. +**Critical:** Use the same API key value in both files for authentication to work. + +## Step 3: Start the Platform -3. Start all necessary services using Docker Compose: +From the project root directory, start all services: ```bash -docker compose -f ./deployment/docker-compose.yaml up mysql planetscale clickhouse redis s3 dashboard gw metald ctrl -d --build +docker compose -f ./deployment/docker-compose.yaml up mysql planetscale clickhouse redis s3 dashboard gw krane ctrl -d --build ``` -This will start: -- **mysql**: Database for storing workspace, project, and deployment data +This starts: + +- **mysql**: Database for workspace, project, and deployment data - **planetscale**: PlanetScale HTTP simulator for database access - **clickhouse**: Analytics database for metrics and logs - **redis**: Caching layer for session and temporary data -- **s3**: MinIO S3-compatible storage for assets and vault data +- **s3**: MinIO S3-compatible storage for assets and vault data (when using docker backend) - **dashboard**: Web UI for managing deployments (port 3000) - **gw**: Gateway service for routing traffic (ports 80/443) -- **metald**: VM/container management service (port 8090) +- **krane**: VM/container management service (port 8090) - **ctrl**: Control plane service for managing deployments (port 7091) -4. Set up wildcard DNS for `unkey.local`: +## Step 4: Set Up DNS and Certificates + +1. Set up wildcard DNS for `unkey.local`: ```bash ./deployment/setup-wildcard-dns.sh ``` -5. **OPTIONAL**: Install self-signed certificate for HTTPS (to avoid SSL errors): +2. **OPTIONAL**: Install self-signed certificate for HTTPS (to avoid SSL errors): ```bash # For macOS sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ./deployment/certs/unkey.local.crt ``` -Note: Certificates should be mounted to `deployment/certs`. You can skip this if you're fine with SSL errors in your browser. +Note: Certificates are in `deployment/certs`. You can skip this if you're fine with SSL warnings in your browser. -## Step 2: Set Up Your Workspace +## Step 5: Set Up Your Workspace 1. Open your browser and navigate to the dashboard: @@ -66,19 +167,23 @@ Note: Certificates should be mounted to `deployment/certs`. You can skip this if http://localhost:3000 ``` -2. Create a workspace and copy its id +2. Create a workspace and copy its ID -3. Create a new project by filling out the form: +3. Create a new project by going to: -Go to http://localhost:3000/projects +``` +http://localhost:3000/projects +``` + +Fill out the form: - **Name**: Choose any name (e.g., "My Test App") - **Slug**: This will auto-generate based on the name - **Git URL**: Optional, leave blank for testing -4. After creating the project, **copy the Project ID** from the project details. It will look like: +4. After creating the project, **copy the Project ID** from the project details -## Step 3: Deploy a Version +## Step 6: Deploy a Version 1. Navigate to the go directory: @@ -86,14 +191,16 @@ Go to http://localhost:3000/projects cd go ``` -2. Set up API key authentication for the CLI (choose one option): +2. Set up API key authentication for the CLI: **Option A: Environment variable (recommended)** + ```bash export API_KEY="your-local-dev-key" ``` **Option B: CLI flag** + Use `--api-key="your-local-dev-key"` in the command below. 3. Deploy using the CLI with your copied IDs: @@ -105,50 +212,52 @@ go run . deploy \ --project-id="REPLACE_ME" \ --control-plane-url="http://127.0.0.1:7091" \ --api-key="your-local-dev-key" \ - --keyspace-id="REPLACE_ME" # This is optional if you want key verifications + --keyspace-id="REPLACE_ME" # Optional, only needed for key verifications ``` Replace the placeholder values: -- `REPLACE_ME` with your actual workspace ID, project ID, and keyspace ID -- `your-local-dev-key` with the same API key value you set in steps 1 and 2 + +- `REPLACE_ME` with your actual workspace ID, project ID, and keyspace ID (if needed) +- `your-local-dev-key` with the same API key value you set in Step 2 - Keep `--context=./demo_api` as shown (there's a demo API in that folder) **Note**: If using Option A (environment variable), you can omit the `--api-key` flag from the command. -3. The CLI will: - - Build a Docker image from the demo_api code +4. The CLI will: + - Build a Docker image from the demo_api code (using your configured build backend) - Create a deployment on the Unkey platform - Show real-time progress through deployment stages - - Deploy using metald's VM/container backend + - Deploy using Krane's VM/container backend -## Step 4: Test Your Deployment +## Step 7: Test Your Deployment -1. Once deployment completes, test the API in the unkey root directory: +1. Once deployment completes, test the API from the project root directory: ```bash curl --cacert ./deployment/certs/unkey.local.crt https://REPLACE_ME/v1/liveness ``` -Replace: -- `REPLACE_ME` (URL) with your deployment domain +Replace `REPLACE_ME` with your deployment domain. **Note:** The liveness endpoint is public and doesn't require authentication. For protected endpoints, include an Authorization header: ```bash -curl --cacert ./deployment/certs/unkey.local.crt -H "Authorization: Bearer YOUR_API_KEY" https://YOUR_DOMAIN/protected/endpoint +curl --cacert ./deployment/certs/unkey.local.crt \ + -H "Authorization: Bearer YOUR_API_KEY" \ + https://YOUR_DOMAIN/protected/endpoint ``` -2. Return to the dashboard to monitor your deployment: +2. Monitor your deployment in the dashboard: ``` http://localhost:3000/deployments ``` -### Important: Your Application Must Listen on the PORT Environment Variable +## Important: Application Port Configuration -**Your deployed application MUST read the `PORT` environment variable and listen on that port.** The platform sets `PORT=8080` in the container, and your code needs to use this value. +**Your deployed application MUST read the `PORT` environment variable and listen on that port.** The platform sets `PORT=8080` in the container. -**Example for different languages:** +**Examples for different languages:** ```javascript // Node.js @@ -178,7 +287,31 @@ The demo_api already follows this pattern and listens on the PORT environment va ## Troubleshooting -- If you see "port is already allocated" errors, the system will automatically retry with a new random port -- Check container logs: `docker logs ` -- Verify the demo_api is listening on the PORT environment variable (should be 8080) -- Make sure your Dockerfile exposes the correct port (8080 in the demo_api example) +### Build Issues + +- **"depot login failed"**: Check your depot token in `deployment/config/depot.json` - it must start with `depot_org_` +- **"S3 connection failed"**: Verify S3 credentials in your depot.json or ensure MinIO is running (for docker backend) +- **Slow builds**: Switch to depot backend for faster builds with layer caching +- **Stuck builds**: If you are stuck with a deployment go to `http://localhost:9070/ui/invocations` and kill the ongoing invocation. + +### Kubernetes Issues + +- **"secret not found"**: Ensure you created both `depot-credentials` and `depot-registry` secrets before deploying ctrl +- **"imagePullBackOff"**: Verify depot-registry secret credentials are correct and token is valid +- **"build context upload failed"**: Check depot-credentials secret has valid S3 credentials + +### Deployment Issues + +- **"port is already allocated"**: The system will automatically retry with a new random port +- **Application not responding**: Verify your app listens on the PORT environment variable (should be 8080) +- **Dockerfile issues**: Ensure your Dockerfile exposes the correct port (8080 in the demo_api example) + +### Service Logs + +Check container logs for any service: + +```bash +docker logs +``` + +Service names: `mysql`, `planetscale`, `clickhouse`, `redis`, `s3`, `dashboard`, `gw`, `krane`, `ctrl` diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index 444c93e655..ad8bae7b22 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -167,7 +167,7 @@ services: image: bitnamilegacy/minio:2025.7.23-debian-12-r5 ports: - 3902:3902 - - 2903:2903 + - 3903:3903 environment: MINIO_ROOT_USER: minio_root_user MINIO_ROOT_PASSWORD: minio_root_password @@ -286,6 +286,10 @@ services: UNKEY_DOCKER_SOCKET: "/var/run/docker.sock" UNKEY_DEPLOYMENT_EVICTION_TTL: "10m" + UNKEY_REGISTRY_URL: "${UNKEY_REGISTRY_URL:-}" + UNKEY_REGISTRY_USERNAME: "${UNKEY_REGISTRY_USERNAME:-}" + UNKEY_REGISTRY_PASSWORD: "${UNKEY_REGISTRY_PASSWORD:-}" + restate: networks: - default @@ -355,8 +359,27 @@ services: UNKEY_VAULT_S3_ACCESS_KEY_SECRET: "minio_root_password" UNKEY_VAULT_MASTER_KEYS: "Ch9rZWtfMmdqMFBJdVhac1NSa0ZhNE5mOWlLSnBHenFPENTt7an5MRogENt9Si6wms4pQ2XIvqNSIgNpaBenJmXgcInhu6Nfv2U=" + # Build configuration + UNKEY_BUILD_S3_URL: "${UNKEY_BUILD_S3_URL:-http://s3:3902}" + UNKEY_BUILD_S3_EXTERNAL_URL: "${UNKEY_BUILD_S3_EXTERNAL_URL:-}" # For CLI/external access + UNKEY_BUILD_S3_BUCKET: "build-contexts" + UNKEY_BUILD_S3_ACCESS_KEY_ID: "${UNKEY_BUILD_S3_ACCESS_KEY_ID:-minio_root_user}" + UNKEY_BUILD_S3_ACCESS_KEY_SECRET: "${UNKEY_BUILD_S3_ACCESS_KEY_SECRET:-minio_root_password}" + # API key for simple authentication (temporary, will be replaced with JWT) UNKEY_API_KEY: "your-local-dev-key" + # Build backend configuration + UNKEY_BUILD_BACKEND: "${UNKEY_BUILD_BACKEND:-docker}" + UNKEY_BUILD_PLATFORM: "linux/amd64" + UNKEY_DOCKER_SOCKET: "/var/run/docker.sock" + UNKEY_DEPLOYMENT_EVICTION_TTL: "10m" + # Registry configuration (used by both Docker and Depot backends) + UNKEY_REGISTRY_URL: "${UNKEY_REGISTRY_URL:-registry.depot.dev}" + UNKEY_REGISTRY_USERNAME: "${UNKEY_REGISTRY_USERNAME:-x-token}" + UNKEY_REGISTRY_PASSWORD: "${UNKEY_REGISTRY_PASSWORD:-${DEPOT_TOKEN:-}}" + # Depot-specific configuration (only needed when UNKEY_BUILD_BACKEND=depot) + UNKEY_DEPOT_API_URL: "https://api.depot.dev" + UNKEY_DEPOT_PROJECT_REGION: "us-east-1" otel: networks: diff --git a/deployment/setup-build-backend.sh b/deployment/setup-build-backend.sh new file mode 100755 index 0000000000..84056fa01f --- /dev/null +++ b/deployment/setup-build-backend.sh @@ -0,0 +1,155 @@ +#!/bin/bash +set -e + +readonly CONFIG_FILE="./config/depot.json" + +print_usage() { + echo "Usage: $0 [depot|docker]" + echo "" + echo "Examples:" + echo " $0 depot # Switch to Depot builds" + echo " $0 docker # Switch to local Docker builds" + exit 1 +} + +load_from_config() { + local key="$1" + grep -o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$CONFIG_FILE" | sed "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/" +} + +read_config_file() { + [ ! -f "$CONFIG_FILE" ] && return + + echo "Reading configuration from $CONFIG_FILE..." + + DEPOT_TOKEN=$(load_from_config "token") + S3_URL=$(load_from_config "s3_url") + S3_ACCESS_KEY_ID=$(load_from_config "s3_access_key_id") + S3_ACCESS_KEY_SECRET=$(load_from_config "s3_access_key_secret") + + [ -n "$DEPOT_TOKEN" ] && echo "✓ Token loaded from config file" + [ -n "$S3_URL" ] && echo "✓ S3 URL loaded from config file" + [ -n "$S3_ACCESS_KEY_ID" ] && echo "✓ S3 Access Key ID loaded from config file" + [ -n "$S3_ACCESS_KEY_SECRET" ] && echo "✓ S3 Access Key Secret loaded from config file" +} + +prompt_if_empty() { + local var_name="$1" + local prompt_text="$2" + local is_secret="${3:-false}" + + local current_value + eval "current_value=\$$var_name" + + if [ -z "$current_value" ]; then + echo "$prompt_text" + if [ "$is_secret" = "true" ]; then + read -r -s new_value + echo "" + else + read -r new_value + fi + + if [ -z "$new_value" ]; then + echo "Error: $var_name is required" + exit 1 + fi + + eval "$var_name=\"$new_value\"" + fi +} + +validate_depot_token() { + if [[ ! "$DEPOT_TOKEN" =~ ^depot_org_ ]]; then + echo "Error: Invalid Depot token format. Token must start with 'depot_org_'" + exit 1 + fi +} + +login_depot_registry() { + if echo "$DEPOT_TOKEN" | docker login registry.depot.dev -u x-token --password-stdin >/dev/null 2>&1; then + echo "✓ Logged into depot registry" + else + echo "" + echo "Error: Docker login failed. Invalid token?" + exit 1 + fi +} + +setup_depot() { + read_config_file + + # Fall back to environment variables + DEPOT_TOKEN="${DEPOT_TOKEN:-${DEPOT_TOKEN:-}}" + S3_URL="${S3_URL:-${UNKEY_BUILD_S3_URL:-}}" + S3_ACCESS_KEY_ID="${S3_ACCESS_KEY_ID:-${UNKEY_BUILD_S3_ACCESS_KEY_ID:-}}" + S3_ACCESS_KEY_SECRET="${S3_ACCESS_KEY_SECRET:-${UNKEY_BUILD_S3_ACCESS_KEY_SECRET:-}}" + + prompt_if_empty "DEPOT_TOKEN" "Enter your Depot token:" true + validate_depot_token + login_depot_registry + + echo "" + echo "S3 Configuration for Depot:" + echo "----------------------------" + + prompt_if_empty "S3_URL" "Enter S3 URL (e.g., https://aaar2.cloudflarestorage.com):" + prompt_if_empty "S3_ACCESS_KEY_ID" "Enter S3 Access Key ID:" + prompt_if_empty "S3_ACCESS_KEY_SECRET" "Enter S3 Access Key Secret:" true + + cat >.env </dev/null || true + echo "✓ Switched to local Docker" + + cat >.env < characters { - return s[:characters] - } - return s -} +const ( + maxCommitMessageLength = 10240 + maxCommitAuthorHandleLength = 256 + maxCommitAuthorAvatarLength = 512 +) func (s *Service) CreateDeployment( ctx context.Context, @@ -35,7 +35,6 @@ func (s *Service) CreateDeployment( } return nil, connect.NewError(connect.CodeInternal, err) } - workspaceID := project.WorkspaceID env, err := db.Query.FindEnvironmentByProjectIdAndSlug(ctx, s.db.RO(), db.FindEnvironmentByProjectIdAndSlugParams{ @@ -63,8 +62,8 @@ func (s *Service) CreateDeployment( } // Validate git commit timestamp if provided (must be Unix epoch milliseconds) - if req.Msg.GetGitCommitTimestamp() != 0 { - timestamp := req.Msg.GetGitCommitTimestamp() + if req.Msg.GetGitCommit() != nil && req.Msg.GetGitCommit().GetTimestamp() != 0 { + timestamp := req.Msg.GetGitCommit().GetTimestamp() // Reject timestamps that are clearly in seconds format (< 1_000_000_000_000) // This corresponds to January 1, 2001 in milliseconds if timestamp < 1_000_000_000_000 { @@ -87,11 +86,49 @@ func (s *Service) CreateDeployment( deploymentID := uid.New(uid.DeploymentPrefix) now := time.Now().UnixMilli() - // Sanitize input values before persisting - gitCommitSha := req.Msg.GetGitCommitSha() - gitCommitMessage := trimLength(req.Msg.GetGitCommitMessage(), 10240) - gitCommitAuthorHandle := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorHandle()), 256) - gitCommitAuthorAvatarUrl := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorAvatarUrl()), 512) + var gitCommitSha, gitCommitMessage, gitCommitAuthorHandle, gitCommitAuthorAvatarURL string + var gitCommitTimestamp int64 + + if gitCommit := req.Msg.GetGitCommit(); gitCommit != nil { + gitCommitSha = gitCommit.GetCommitSha() + gitCommitMessage = trimLength(gitCommit.GetCommitMessage(), maxCommitMessageLength) + gitCommitAuthorHandle = trimLength(strings.TrimSpace(gitCommit.GetAuthorHandle()), maxCommitAuthorHandleLength) + gitCommitAuthorAvatarURL = trimLength(strings.TrimSpace(gitCommit.GetAuthorAvatarUrl()), maxCommitAuthorAvatarLength) + gitCommitTimestamp = gitCommit.GetTimestamp() + } + + var buildContextKey string + var dockerfilePath string + var dockerImage *string + + switch source := req.Msg.GetSource().(type) { + case *ctrlv1.CreateDeploymentRequest_BuildContext: + buildContextKey = source.BuildContext.GetBuildContextPath() + dockerfilePath = source.BuildContext.GetDockerfilePath() + if dockerfilePath == "" { + dockerfilePath = "./Dockerfile" + } + + case *ctrlv1.CreateDeploymentRequest_DockerImage: + image := source.DockerImage + dockerImage = &image + + default: + return nil, connect.NewError(connect.CodeInvalidArgument, + fmt.Errorf("source must be specified (either build_context or docker_image)")) + } + + // Log deployment source + if buildContextKey != "" { + s.logger.Info("deployment will build from source", + "deployment_id", deploymentID, + "context_key", buildContextKey, + "dockerfile", dockerfilePath) + } else { + s.logger.Info("deployment will use prebuilt image", + "deployment_id", deploymentID, + "image", *dockerImage) + } // Insert deployment into database err = db.Query.InsertDeployment(ctx, s.db.RW(), db.InsertDeploymentParams{ @@ -110,39 +147,49 @@ func (s *Service) CreateDeployment( UpdatedAt: sql.NullInt64{Int64: now, Valid: true}, GitCommitSha: sql.NullString{String: gitCommitSha, Valid: gitCommitSha != ""}, GitBranch: sql.NullString{String: gitBranch, Valid: true}, - GitCommitMessage: sql.NullString{String: gitCommitMessage, Valid: req.Msg.GetGitCommitMessage() != ""}, - GitCommitAuthorHandle: sql.NullString{String: gitCommitAuthorHandle, Valid: req.Msg.GetGitCommitAuthorHandle() != ""}, - GitCommitAuthorAvatarUrl: sql.NullString{String: gitCommitAuthorAvatarUrl, Valid: req.Msg.GetGitCommitAuthorAvatarUrl() != ""}, - GitCommitTimestamp: sql.NullInt64{Int64: req.Msg.GetGitCommitTimestamp(), Valid: req.Msg.GetGitCommitTimestamp() != 0}, + GitCommitMessage: sql.NullString{String: gitCommitMessage, Valid: gitCommitMessage != ""}, + GitCommitAuthorHandle: sql.NullString{String: gitCommitAuthorHandle, Valid: gitCommitAuthorHandle != ""}, + GitCommitAuthorAvatarUrl: sql.NullString{String: gitCommitAuthorAvatarURL, Valid: gitCommitAuthorAvatarURL != ""}, + GitCommitTimestamp: sql.NullInt64{Int64: gitCommitTimestamp, Valid: gitCommitTimestamp != 0}, }) if err != nil { s.logger.Error("failed to insert deployment", "error", err.Error()) return nil, connect.NewError(connect.CodeInternal, err) } - s.logger.Info("starting deployment workflow for deployment", + s.logger.Info("starting deployment workflow", "deployment_id", deploymentID, "workspace_id", workspaceID, "project_id", req.Msg.GetProjectId(), "environment", env.ID, + "context_key", buildContextKey, + "docker_image", dockerImage, ) - // Start the deployment workflow directly + // Start the deployment workflow keyspaceID := req.Msg.GetKeyspaceId() var keyAuthID *string if keyspaceID != "" { keyAuthID = &keyspaceID } + deployReq := &hydrav1.DeployRequest{ + DeploymentId: deploymentID, + KeyAuthId: keyAuthID, + } + + switch source := req.Msg.GetSource().(type) { + case *ctrlv1.CreateDeploymentRequest_BuildContext: + deployReq.BuildContextPath = proto.String(source.BuildContext.GetBuildContextPath()) + deployReq.DockerfilePath = source.BuildContext.DockerfilePath + case *ctrlv1.CreateDeploymentRequest_DockerImage: + deployReq.DockerImage = proto.String(source.DockerImage) + } + // Send deployment request asynchronously (fire-and-forget) invocation, err := s.deploymentClient(project.ID). Deploy(). - Send(ctx, &hydrav1.DeployRequest{ - DeploymentId: deploymentID, - DockerImage: req.Msg.GetDockerImage(), - KeyAuthId: keyAuthID, - }) - + Send(ctx, deployReq) if err != nil { s.logger.Error("failed to start deployment workflow", "error", err) return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("unable to start workflow: %w", err)) @@ -160,3 +207,10 @@ func (s *Service) CreateDeployment( return res, nil } + +func trimLength(s string, characters int) string { + if len(s) > characters { + return s[:characters] + } + return s +} diff --git a/go/apps/ctrl/services/deployment/create_deployment_simple_test.go b/go/apps/ctrl/services/deployment/create_deployment_simple_test.go index ce38c61928..af3c29478d 100644 --- a/go/apps/ctrl/services/deployment/create_deployment_simple_test.go +++ b/go/apps/ctrl/services/deployment/create_deployment_simple_test.go @@ -8,9 +8,10 @@ import ( "github.com/stretchr/testify/require" ctrlv1 "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1" "github.com/unkeyed/unkey/go/pkg/db" + "github.com/unkeyed/unkey/go/pkg/ptr" ) -// validateTimestamp applies the same validation logic as the CreateVersion service +// validateTimestamp applies the same validation logic as the CreateDeployment service func validateTimestamp(timestamp int64) bool { if timestamp == 0 { return true // Zero timestamps skip validation (optional field) @@ -68,15 +69,15 @@ func TestGitFieldValidation_SpecialCharacters(t *testing.T) { t.Parallel() // Test that special characters are preserved in protobuf - req := &ctrlv1.CreateDeploymentRequest{ - GitCommitMessage: tt.input, - GitCommitAuthorHandle: tt.input, - GitCommitAuthorAvatarUrl: tt.input, + gitCommit := &ctrlv1.GitCommitInfo{ + CommitMessage: tt.input, + AuthorHandle: tt.input, + AuthorAvatarUrl: tt.input, } - require.Equal(t, tt.expected, req.GetGitCommitMessage()) - require.Equal(t, tt.expected, req.GetGitCommitAuthorHandle()) - require.Equal(t, tt.expected, req.GetGitCommitAuthorAvatarUrl()) + require.Equal(t, tt.expected, gitCommit.GetCommitMessage()) + require.Equal(t, tt.expected, gitCommit.GetAuthorHandle()) + require.Equal(t, tt.expected, gitCommit.GetAuthorAvatarUrl()) // Test that special characters are preserved in database model deployment := db.Deployment{ @@ -97,18 +98,18 @@ func TestGitFieldValidation_NullHandling(t *testing.T) { t.Parallel() // Test empty protobuf fields - req := &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test", - GitCommitMessage: "", - GitCommitAuthorAvatarUrl: "", - GitCommitTimestamp: 0, + gitCommit := &ctrlv1.GitCommitInfo{ + CommitMessage: "", + AuthorHandle: "", + AuthorAvatarUrl: "", + Timestamp: 0, } // Empty strings should be returned as-is - require.Equal(t, "", req.GetGitCommitMessage()) - require.Equal(t, "", req.GetGitCommitAuthorHandle()) - require.Equal(t, "", req.GetGitCommitAuthorAvatarUrl()) - require.Equal(t, int64(0), req.GetGitCommitTimestamp()) + require.Equal(t, "", gitCommit.GetCommitMessage()) + require.Equal(t, "", gitCommit.GetAuthorHandle()) + require.Equal(t, "", gitCommit.GetAuthorAvatarUrl()) + require.Equal(t, int64(0), gitCommit.GetTimestamp()) // Test NULL database fields deployment := db.Deployment{ @@ -134,10 +135,10 @@ func TestTimestampConversion(t *testing.T) { nowMillis := now.UnixMilli() // Test protobuf timestamp - req := &ctrlv1.CreateDeploymentRequest{ - GitCommitTimestamp: nowMillis, + gitCommit := &ctrlv1.GitCommitInfo{ + Timestamp: nowMillis, } - require.Equal(t, nowMillis, req.GetGitCommitTimestamp()) + require.Equal(t, nowMillis, gitCommit.GetTimestamp()) // Test database timestamp deployment := db.Deployment{ @@ -151,42 +152,32 @@ func TestTimestampConversion(t *testing.T) { require.Equal(t, now.Unix(), retrievedTime.Unix()) // Compare at second precision } -// TestCreateVersionTimestampValidation_InvalidSecondsFormat tests timestamp validation -func TestCreateVersionTimestampValidation_InvalidSecondsFormat(t *testing.T) { +// TestCreateDeploymentTimestampValidation_InvalidSecondsFormat tests timestamp validation +func TestCreateDeploymentTimestampValidation_InvalidSecondsFormat(t *testing.T) { t.Parallel() // Create proto request directly with seconds timestamp (should be rejected) req := &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test456", - Branch: "main", - SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, - GitCommitSha: "abc123def456", - GitCommitTimestamp: time.Now().Unix(), // This is in seconds - should be rejected + ProjectId: "proj_test456", + Branch: "main", + EnvironmentSlug: "production", + Source: &ctrlv1.CreateDeploymentRequest_BuildContext{ + BuildContext: &ctrlv1.BuildContext{ + BuildContextPath: "test-key", + DockerfilePath: ptr.P("Dockerfile"), + }, + }, + GitCommit: &ctrlv1.GitCommitInfo{ + CommitSha: "abc123def456", + Timestamp: time.Now().Unix(), // This is in seconds - should be rejected + }, } // Use shared validation helper - isValid := validateTimestamp(req.GetGitCommitTimestamp()) + isValid := validateTimestamp(req.GetGitCommit().GetTimestamp()) require.False(t, isValid, "Seconds-based timestamp should be considered invalid") } -// TestCreateVersionTimestampValidation_ValidMillisecondsFormat tests valid timestamp -func TestCreateVersionTimestampValidation_ValidMillisecondsFormat(t *testing.T) { - t.Parallel() - - // Create proto request directly with milliseconds timestamp - req := &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test456", - Branch: "main", - SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, - GitCommitSha: "abc123def456", - GitCommitTimestamp: time.Now().UnixMilli(), // This is in milliseconds - should be accepted - } - - // Use shared validation helper - isValid := validateTimestamp(req.GetGitCommitTimestamp()) - require.True(t, isValid, "Milliseconds-based timestamp should be considered valid") -} - // TestTimestampValidationBoundaries tests edge cases for timestamp validation func TestTimestampValidationBoundaries(t *testing.T) { t.Parallel() @@ -245,8 +236,8 @@ func TestTimestampValidationBoundaries(t *testing.T) { } } -// TestCreateVersionFieldMapping tests the actual field mapping from protobuf to database params -func TestCreateVersionFieldMapping(t *testing.T) { +// TestCreateDeploymentFieldMapping tests the actual field mapping from protobuf to database params +func TestCreateDeploymentFieldMapping(t *testing.T) { t.Parallel() tests := []struct { @@ -259,10 +250,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid bool gitCommitMessage string gitCommitMessageValid bool - gitCommitAuthorName string - gitCommitAuthorNameValid bool - gitCommitAuthorUsername string - gitCommitAuthorUsernameValid bool + gitCommitAuthorHandle string + gitCommitAuthorHandleValid bool gitCommitAuthorAvatarUrl string gitCommitAuthorAvatarUrlValid bool gitCommitTimestamp int64 @@ -272,14 +261,22 @@ func TestCreateVersionFieldMapping(t *testing.T) { { name: "all_git_fields_populated", request: &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test456", - Branch: "feature/test-branch", - SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, - GitCommitSha: "abc123def456789", - GitCommitMessage: "feat: implement new feature", - GitCommitAuthorHandle: "janedoe", - GitCommitAuthorAvatarUrl: "https://github.com/janedoe.png", - GitCommitTimestamp: 1724251845123, // Fixed millisecond timestamp + ProjectId: "proj_test456", + Branch: "feature/test-branch", + EnvironmentSlug: "production", + Source: &ctrlv1.CreateDeploymentRequest_BuildContext{ + BuildContext: &ctrlv1.BuildContext{ + BuildContextPath: "test-key", + DockerfilePath: ptr.P("Dockerfile"), + }, + }, + GitCommit: &ctrlv1.GitCommitInfo{ + CommitSha: "abc123def456789", + CommitMessage: "feat: implement new feature", + AuthorHandle: "janedoe", + AuthorAvatarUrl: "https://github.com/janedoe.png", + Timestamp: 1724251845123, // Fixed millisecond timestamp + }, }, expected: struct { gitCommitSha string @@ -288,10 +285,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid bool gitCommitMessage string gitCommitMessageValid bool - gitCommitAuthorName string - gitCommitAuthorNameValid bool - gitCommitAuthorUsername string - gitCommitAuthorUsernameValid bool + gitCommitAuthorHandle string + gitCommitAuthorHandleValid bool gitCommitAuthorAvatarUrl string gitCommitAuthorAvatarUrlValid bool gitCommitTimestamp int64 @@ -303,10 +298,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid: true, gitCommitMessage: "feat: implement new feature", gitCommitMessageValid: true, - gitCommitAuthorName: "Jane Doe", - gitCommitAuthorNameValid: true, - gitCommitAuthorUsername: "janedoe", - gitCommitAuthorUsernameValid: true, + gitCommitAuthorHandle: "janedoe", + gitCommitAuthorHandleValid: true, gitCommitAuthorAvatarUrl: "https://github.com/janedoe.png", gitCommitAuthorAvatarUrlValid: true, gitCommitTimestamp: 1724251845123, @@ -316,14 +309,22 @@ func TestCreateVersionFieldMapping(t *testing.T) { { name: "empty_git_fields", request: &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test456", - Branch: "main", - SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, - GitCommitSha: "", - GitCommitMessage: "", - GitCommitAuthorHandle: "", - GitCommitAuthorAvatarUrl: "", - GitCommitTimestamp: 0, + ProjectId: "proj_test456", + Branch: "main", + EnvironmentSlug: "production", + Source: &ctrlv1.CreateDeploymentRequest_BuildContext{ + BuildContext: &ctrlv1.BuildContext{ + BuildContextPath: "test-key", + DockerfilePath: ptr.P("Dockerfile"), + }, + }, + GitCommit: &ctrlv1.GitCommitInfo{ + CommitSha: "", + CommitMessage: "", + AuthorHandle: "", + AuthorAvatarUrl: "", + Timestamp: 0, + }, }, expected: struct { gitCommitSha string @@ -332,10 +333,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid bool gitCommitMessage string gitCommitMessageValid bool - gitCommitAuthorName string - gitCommitAuthorNameValid bool - gitCommitAuthorUsername string - gitCommitAuthorUsernameValid bool + gitCommitAuthorHandle string + gitCommitAuthorHandleValid bool gitCommitAuthorAvatarUrl string gitCommitAuthorAvatarUrlValid bool gitCommitTimestamp int64 @@ -347,10 +346,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid: true, gitCommitMessage: "", gitCommitMessageValid: false, - gitCommitAuthorName: "", - gitCommitAuthorNameValid: false, - gitCommitAuthorUsername: "", - gitCommitAuthorUsernameValid: false, + gitCommitAuthorHandle: "", + gitCommitAuthorHandleValid: false, gitCommitAuthorAvatarUrl: "", gitCommitAuthorAvatarUrlValid: false, gitCommitTimestamp: 0, @@ -360,14 +357,22 @@ func TestCreateVersionFieldMapping(t *testing.T) { { name: "mixed_populated_and_empty_fields", request: &ctrlv1.CreateDeploymentRequest{ - ProjectId: "proj_test456", - Branch: "hotfix/urgent-fix", - SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, - GitCommitSha: "xyz789abc123", - GitCommitMessage: "fix: critical security issue", - GitCommitAuthorHandle: "", // Empty - GitCommitAuthorAvatarUrl: "", // Empty - GitCommitTimestamp: 1724251845999, + ProjectId: "proj_test456", + Branch: "hotfix/urgent-fix", + EnvironmentSlug: "production", + Source: &ctrlv1.CreateDeploymentRequest_BuildContext{ + BuildContext: &ctrlv1.BuildContext{ + BuildContextPath: "test-key", + DockerfilePath: ptr.P("Dockerfile"), + }, + }, + GitCommit: &ctrlv1.GitCommitInfo{ + CommitSha: "xyz789abc123", + CommitMessage: "fix: critical security issue", + AuthorHandle: "", // Empty + AuthorAvatarUrl: "", // Empty + Timestamp: 1724251845999, + }, }, expected: struct { gitCommitSha string @@ -376,10 +381,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid bool gitCommitMessage string gitCommitMessageValid bool - gitCommitAuthorName string - gitCommitAuthorNameValid bool - gitCommitAuthorUsername string - gitCommitAuthorUsernameValid bool + gitCommitAuthorHandle string + gitCommitAuthorHandleValid bool gitCommitAuthorAvatarUrl string gitCommitAuthorAvatarUrlValid bool gitCommitTimestamp int64 @@ -391,10 +394,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { gitBranchValid: true, gitCommitMessage: "fix: critical security issue", gitCommitMessageValid: true, - gitCommitAuthorName: "", - gitCommitAuthorNameValid: false, // Empty string should be invalid - gitCommitAuthorUsername: "", - gitCommitAuthorUsernameValid: false, // Empty string should be invalid + gitCommitAuthorHandle: "", + gitCommitAuthorHandleValid: false, // Empty string should be invalid gitCommitAuthorAvatarUrl: "", gitCommitAuthorAvatarUrlValid: false, // Empty string should be invalid gitCommitTimestamp: 1724251845999, @@ -407,20 +408,30 @@ func TestCreateVersionFieldMapping(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - // Simulate the mapping logic from create_version.go - // This tests the actual field wiring that happens in the service + // Extract git info (simulating service logic) + var gitCommitSha, gitCommitMessage, gitCommitAuthorHandle, gitCommitAuthorAvatarUrl string + var gitCommitTimestamp int64 + + if gitCommit := tt.request.GetGitCommit(); gitCommit != nil { + gitCommitSha = gitCommit.GetCommitSha() + gitCommitMessage = gitCommit.GetCommitMessage() + gitCommitAuthorHandle = gitCommit.GetAuthorHandle() + gitCommitAuthorAvatarUrl = gitCommit.GetAuthorAvatarUrl() + gitCommitTimestamp = gitCommit.GetTimestamp() + } + + // Simulate the mapping logic from create_deployment.go params := db.InsertDeploymentParams{ - ID: "test_deployment_id", - WorkspaceID: "ws_test123", // In production, this is inferred from ProjectID via DB lookup, just hardcoded for the test - ProjectID: tt.request.GetProjectId(), - EnvironmentID: "todo", - // Git field mappings - this is what we're testing - GitCommitSha: sql.NullString{String: tt.request.GetGitCommitSha(), Valid: tt.request.GetGitCommitSha() != ""}, + ID: "test_deployment_id", + WorkspaceID: "ws_test123", + ProjectID: tt.request.GetProjectId(), + EnvironmentID: "env_test", + GitCommitSha: sql.NullString{String: gitCommitSha, Valid: gitCommitSha != ""}, GitBranch: sql.NullString{String: tt.request.GetBranch(), Valid: true}, - GitCommitMessage: sql.NullString{String: tt.request.GetGitCommitMessage(), Valid: tt.request.GetGitCommitMessage() != ""}, - GitCommitAuthorHandle: sql.NullString{String: tt.request.GetGitCommitAuthorHandle(), Valid: tt.request.GetGitCommitAuthorHandle() != ""}, - GitCommitAuthorAvatarUrl: sql.NullString{String: tt.request.GetGitCommitAuthorAvatarUrl(), Valid: tt.request.GetGitCommitAuthorAvatarUrl() != ""}, - GitCommitTimestamp: sql.NullInt64{Int64: tt.request.GetGitCommitTimestamp(), Valid: tt.request.GetGitCommitTimestamp() != 0}, + GitCommitMessage: sql.NullString{String: gitCommitMessage, Valid: gitCommitMessage != ""}, + GitCommitAuthorHandle: sql.NullString{String: gitCommitAuthorHandle, Valid: gitCommitAuthorHandle != ""}, + GitCommitAuthorAvatarUrl: sql.NullString{String: gitCommitAuthorAvatarUrl, Valid: gitCommitAuthorAvatarUrl != ""}, + GitCommitTimestamp: sql.NullInt64{Int64: gitCommitTimestamp, Valid: gitCommitTimestamp != 0}, RuntimeConfig: []byte("{}"), OpenapiSpec: sql.NullString{String: "", Valid: false}, Status: db.DeploymentsStatusPending, @@ -438,8 +449,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { require.Equal(t, tt.expected.gitCommitMessage, params.GitCommitMessage.String, "GitCommitMessage string mismatch") require.Equal(t, tt.expected.gitCommitMessageValid, params.GitCommitMessage.Valid, "GitCommitMessage valid flag mismatch") - require.Equal(t, tt.expected.gitCommitAuthorUsername, params.GitCommitAuthorHandle.String, "GitCommitAuthorUsername string mismatch") - require.Equal(t, tt.expected.gitCommitAuthorUsernameValid, params.GitCommitAuthorHandle.Valid, "GitCommitAuthorUsername valid flag mismatch") + require.Equal(t, tt.expected.gitCommitAuthorHandle, params.GitCommitAuthorHandle.String, "GitCommitAuthorHandle string mismatch") + require.Equal(t, tt.expected.gitCommitAuthorHandleValid, params.GitCommitAuthorHandle.Valid, "GitCommitAuthorHandle valid flag mismatch") require.Equal(t, tt.expected.gitCommitAuthorAvatarUrl, params.GitCommitAuthorAvatarUrl.String, "GitCommitAuthorAvatarUrl string mismatch") require.Equal(t, tt.expected.gitCommitAuthorAvatarUrlValid, params.GitCommitAuthorAvatarUrl.Valid, "GitCommitAuthorAvatarUrl valid flag mismatch") diff --git a/go/apps/ctrl/services/deployment/service.go b/go/apps/ctrl/services/deployment/service.go index 941ee421b4..b0578df0d5 100644 --- a/go/apps/ctrl/services/deployment/service.go +++ b/go/apps/ctrl/services/deployment/service.go @@ -1,3 +1,9 @@ +// Package deployment manages the full deployment lifecycle including creation, +// promotion, and rollback operations. All operations are keyed by project ID +// in Restate to ensure only one operation runs per project at a time. +// +// Supports two deployment sources: build from source (with build context and +// Dockerfile path) or prebuilt Docker images. package deployment import ( @@ -10,12 +16,11 @@ import ( type Service struct { ctrlv1connect.UnimplementedDeploymentServiceHandler - db db.Database - partitionDB db.Database - - restate *restateingress.Client - - logger logging.Logger + db db.Database + partitionDB db.Database + restate *restateingress.Client + buildService ctrlv1connect.BuildServiceClient + logger logging.Logger } // deploymentClient creates a typed Restate ingress client for the DeploymentService @@ -25,19 +30,20 @@ func (s *Service) deploymentClient(projectID string) hydrav1.DeploymentServiceIn } type Config struct { - Database db.Database - PartitionDB db.Database - Restate *restateingress.Client - Logger logging.Logger + Database db.Database + PartitionDB db.Database + Restate *restateingress.Client + BuildService ctrlv1connect.BuildServiceClient + Logger logging.Logger } func New(cfg Config) *Service { - return &Service{ UnimplementedDeploymentServiceHandler: ctrlv1connect.UnimplementedDeploymentServiceHandler{}, db: cfg.Database, partitionDB: cfg.PartitionDB, restate: cfg.Restate, + buildService: cfg.BuildService, logger: cfg.Logger, } } diff --git a/go/apps/ctrl/workflows/deploy/deploy_handler.go b/go/apps/ctrl/workflows/deploy/deploy_handler.go index 723df944d2..ae4a9558fe 100644 --- a/go/apps/ctrl/workflows/deploy/deploy_handler.go +++ b/go/apps/ctrl/workflows/deploy/deploy_handler.go @@ -16,6 +16,7 @@ import ( partitionv1 "github.com/unkeyed/unkey/go/gen/proto/partition/v1" "github.com/unkeyed/unkey/go/pkg/db" partitiondb "github.com/unkeyed/unkey/go/pkg/partition/db" + "google.golang.org/protobuf/proto" ) // Deploy orchestrates the complete deployment of a new Docker image. @@ -37,8 +38,7 @@ import ( // The workflow uses a 5-minute polling loop to wait for instances to become ready, // checking Krane deployment status every second and logging progress every 10 seconds. func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) (*hydrav1.DeployResponse, error) { - - var finishedSuccessfully = false + finishedSuccessfully := false deployment, err := restate.Run(ctx, func(stepCtx restate.RunContext) (db.FindDeploymentByIdRow, error) { return db.Query.FindDeploymentById(stepCtx, w.db.RW(), req.DeploymentId) @@ -56,7 +56,6 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) if err := w.updateDeploymentStatus(ctx, deployment.ID, db.DeploymentsStatusFailed); err != nil { w.logger.Error("deployment failed but we can not set the status", "error", err.Error()) } - }() workspace, err := restate.Run(ctx, func(stepCtx restate.RunContext) (db.Workspace, error) { @@ -95,9 +94,81 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) return nil, err } - // Update version status to building + var dockerImage string + + if req.GetBuildContextPath() != "" { + // Build Docker image from uploaded context + if err = w.updateDeploymentStatus(ctx, deployment.ID, db.DeploymentsStatusBuilding); err != nil { + return nil, err + } + + _, err = restate.Run(ctx, func(stepCtx restate.RunContext) (restate.Void, error) { + return restate.Void{}, db.Query.InsertDeploymentStep(stepCtx, w.db.RW(), db.InsertDeploymentStepParams{ + WorkspaceID: deployment.WorkspaceID, + ProjectID: deployment.ProjectID, + DeploymentID: deployment.ID, + Status: "pending", + Message: "Building Docker image from source", + CreatedAt: time.Now().UnixMilli(), + }) + }, restate.WithName("logging build start")) + if err != nil { + return nil, err + } + + dockerImage, err = restate.Run(ctx, func(stepCtx restate.RunContext) (string, error) { + w.logger.Info("starting docker build", + "deployment_id", deployment.ID, + "build_context_path", req.GetBuildContextPath()) + + buildReq := connect.NewRequest(&ctrlv1.CreateBuildRequest{ + UnkeyProjectId: deployment.ProjectID, + DeploymentId: deployment.ID, + BuildContextPath: req.GetBuildContextPath(), + DockerfilePath: proto.String(req.GetDockerfilePath()), + }) + + buildResp, err := w.buildClient.CreateBuild(stepCtx, buildReq) + if err != nil { + return "", fmt.Errorf("build failed: %w", err) + } + + imageName := buildResp.Msg.GetImageName() + w.logger.Info("docker build completed", + "deployment_id", deployment.ID, + "image_name", imageName) + + return imageName, nil + }, restate.WithName("building docker image")) + if err != nil { + return nil, fmt.Errorf("failed to build docker image: %w", err) + } - if err = w.updateDeploymentStatus(ctx, deployment.ID, db.DeploymentsStatusBuilding); err != nil { + _, err = restate.Run(ctx, func(stepCtx restate.RunContext) (restate.Void, error) { + return restate.Void{}, db.Query.InsertDeploymentStep(stepCtx, w.db.RW(), db.InsertDeploymentStepParams{ + WorkspaceID: deployment.WorkspaceID, + ProjectID: deployment.ProjectID, + DeploymentID: deployment.ID, + Status: "pending", + Message: fmt.Sprintf("Docker image built successfully: %s", dockerImage), + CreatedAt: time.Now().UnixMilli(), + }) + }, restate.WithName("logging build complete")) + if err != nil { + return nil, err + } + + } else if req.GetDockerImage() != "" { + dockerImage = req.GetDockerImage() + w.logger.Info("using prebuilt docker image", + "deployment_id", deployment.ID, + "image", dockerImage) + } else { + return nil, fmt.Errorf("either build_context_path or docker_image must be specified") + } + + // Update version status to deploying + if err = w.updateDeploymentStatus(ctx, deployment.ID, db.DeploymentsStatusDeploying); err != nil { return nil, err } @@ -105,18 +176,17 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) // Create deployment request _, err := w.krane.CreateDeployment(stepCtx, connect.NewRequest(&kranev1.CreateDeploymentRequest{ - Deployment: &kranev1.DeploymentRequest{ Namespace: hardcodedNamespace, DeploymentId: deployment.ID, - Image: req.DockerImage, + Image: dockerImage, Replicas: 1, CpuMillicores: 512, MemorySizeMib: 512, }, })) if err != nil { - return restate.Void{}, fmt.Errorf("krane CreateDeployment failed for image %s: %w", req.DockerImage, err) + return restate.Void{}, fmt.Errorf("krane CreateDeployment failed for image %s: %w", dockerImage, err) } return restate.Void{}, nil @@ -127,10 +197,6 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) w.logger.Info("deployment created", "deployment_id", deployment.ID) - // Update version status to deploying - if err = w.updateDeploymentStatus(ctx, deployment.ID, db.DeploymentsStatusDeploying); err != nil { - return nil, err - } createdInstances, err := restate.Run(ctx, func(stepCtx restate.RunContext) ([]*kranev1.Instance, error) { // prevent updating the db unnecessarily @@ -208,7 +274,6 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) } openapiSpec, err := restate.Run(ctx, func(stepCtx restate.RunContext) (string, error) { - for _, instance := range createdInstances { openapiURL := fmt.Sprintf("http://%s/openapi.yaml", instance.GetAddress()) w.logger.Info("trying to scrape OpenAPI spec", "url", openapiURL, "host_port", instance.GetAddress(), "deployment_id", deployment.ID) @@ -237,25 +302,19 @@ func (w *Workflow) Deploy(ctx restate.ObjectContext, req *hydrav1.DeployRequest) } // not an error really, just no OpenAPI spec found return "", nil - }, restate.WithName("scrape openapi spec")) - if err != nil { return nil, err } if openapiSpec != "" { - _, err = restate.Run(ctx, func(innerCtx restate.RunContext) (restate.Void, error) { - return restate.Void{}, db.Query.UpdateDeploymentOpenapiSpec(innerCtx, w.db.RW(), db.UpdateDeploymentOpenapiSpecParams{ ID: deployment.ID, UpdatedAt: sql.NullInt64{Valid: true, Int64: time.Now().UnixMilli()}, OpenapiSpec: sql.NullString{Valid: true, String: openapiSpec}, }) - }, restate.WithName("update deployment openapi spec")) - } allDomains := buildDomains( diff --git a/go/apps/ctrl/workflows/deploy/service.go b/go/apps/ctrl/workflows/deploy/service.go index 8063275b22..0f5fa2a5d6 100644 --- a/go/apps/ctrl/workflows/deploy/service.go +++ b/go/apps/ctrl/workflows/deploy/service.go @@ -1,6 +1,7 @@ package deploy import ( + "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1/ctrlv1connect" hydrav1 "github.com/unkeyed/unkey/go/gen/proto/hydra/v1" "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect" "github.com/unkeyed/unkey/go/pkg/db" @@ -25,6 +26,7 @@ type Workflow struct { partitionDB db.Database logger logging.Logger krane kranev1connect.DeploymentServiceClient + buildClient ctrlv1connect.BuildServiceClient defaultDomain string } @@ -44,6 +46,9 @@ type Config struct { // Krane is the client for container orchestration operations. Krane kranev1connect.DeploymentServiceClient + // BuildClient is the client for building Docker images from source. + BuildClient ctrlv1connect.BuildServiceClient + // DefaultDomain is the apex domain for generated deployment URLs (e.g., "unkey.app"). DefaultDomain string } @@ -55,6 +60,7 @@ func New(cfg Config) *Workflow { partitionDB: cfg.PartitionDB, logger: cfg.Logger, krane: cfg.Krane, + buildClient: cfg.BuildClient, defaultDomain: cfg.DefaultDomain, } } diff --git a/go/apps/krane/backend/docker/create_deployment.go b/go/apps/krane/backend/docker/create_deployment.go index b3e33644fe..4eb99c41fe 100644 --- a/go/apps/krane/backend/docker/create_deployment.go +++ b/go/apps/krane/backend/docker/create_deployment.go @@ -18,12 +18,17 @@ import ( // immediately ready. func (d *docker) CreateDeployment(ctx context.Context, req *connect.Request[kranev1.CreateDeploymentRequest]) (*connect.Response[kranev1.CreateDeploymentResponse], error) { deployment := req.Msg.GetDeployment() - d.logger.Info("creating deployment", "deployment_id", deployment.GetDeploymentId(), "image", deployment.GetImage(), ) + // Ensure image exists locally (pull if not present) + if err := d.ensureImageExists(ctx, deployment.GetImage()); err != nil { + return nil, connect.NewError(connect.CodeInternal, + fmt.Errorf("failed to ensure image exists: %w", err)) + } + // Configure port mapping exposedPorts := nat.PortSet{ "8080/tcp": struct{}{}, @@ -44,7 +49,6 @@ func (d *docker) CreateDeployment(ctx context.Context, req *connect.Request[kran // Container configuration containerConfig := &container.Config{ - Image: deployment.GetImage(), Labels: map[string]string{ "unkey.deployment.id": deployment.GetDeploymentId(), @@ -58,7 +62,6 @@ func (d *docker) CreateDeployment(ctx context.Context, req *connect.Request[kran // Host configuration hostConfig := &container.HostConfig{ - PortBindings: portBindings, RestartPolicy: container.RestartPolicy{ Name: "unless-stopped", diff --git a/go/apps/krane/backend/docker/service.go b/go/apps/krane/backend/docker/service.go index e618370a8a..313a2d05dc 100644 --- a/go/apps/krane/backend/docker/service.go +++ b/go/apps/krane/backend/docker/service.go @@ -2,30 +2,57 @@ package docker import ( "context" + "encoding/base64" + "encoding/json" "fmt" + "io" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" "github.com/unkeyed/unkey/go/gen/proto/krane/v1/kranev1connect" "github.com/unkeyed/unkey/go/pkg/otel/logging" ) // docker implements kranev1connect.DeploymentServiceHandler using Docker Engine API. +// +// This backend is designed for single-node deployments where container images +// are built locally using the Docker daemon and deployed on the same host. +// It does not support multi-node clusters or remote image registries. type docker struct { - logger logging.Logger - client *client.Client + logger logging.Logger + client *client.Client + registryAuth string // base64 encoded auth for pulls + kranev1connect.UnimplementedDeploymentServiceHandler } var _ kranev1connect.DeploymentServiceHandler = (*docker)(nil) +// Config holds configuration for the Docker backend +type Config struct { + SocketPath string + RegistryURL string + RegistryUsername string + RegistryPassword string +} + // New creates a Docker backend instance and validates daemon connectivity. -func New(logger logging.Logger, socketPath string) (*docker, error) { +// +// The socketPath parameter specifies the Docker daemon socket location. +// Common values are "/var/run/docker.sock" on Linux and macOS. +// +// If registry credentials are provided, authenticates with the registry on startup. +// +// Returns an error if the Docker daemon is unreachable or the socket +// path is invalid. The socket must be accessible with appropriate permissions. +func New(logger logging.Logger, cfg Config) (*docker, error) { // Create Docker client with configurable socket path // socketPath must not include protocol (e.g., "var/run/docker.sock") dockerClient, err := client.NewClientWithOpts( client.FromEnv, client.WithAPIVersionNegotiation(), - client.WithHost(fmt.Sprintf("unix:///%s", socketPath)), + client.WithHost(fmt.Sprintf("unix:///%s", cfg.SocketPath)), ) if err != nil { return nil, fmt.Errorf("failed to create Docker client: %w", err) @@ -35,13 +62,78 @@ func New(logger logging.Logger, socketPath string) (*docker, error) { ctx := context.Background() _, err = dockerClient.Ping(ctx) if err != nil { - return nil, fmt.Errorf("failed to ping Docker daemon at %s (ensure Docker socket is accessible): %w", socketPath, err) + return nil, fmt.Errorf("failed to ping Docker daemon at %s (ensure Docker socket is accessible): %w", cfg.SocketPath, err) } - logger.Info("Docker client initialized successfully", "socket", socketPath) + logger.Info("Docker client initialized successfully", "socket", cfg.SocketPath) - return &docker{ + d := &docker{ logger: logger, client: dockerClient, - }, nil + } + + // Encode registry credentials if provided. + // These will be passed to ImagePull for authentication. + if cfg.RegistryURL != "" && cfg.RegistryUsername != "" && cfg.RegistryPassword != "" { + authConfig := registry.AuthConfig{ + Username: cfg.RegistryUsername, + Password: cfg.RegistryPassword, + ServerAddress: cfg.RegistryURL, + } + + authJSON, err := json.Marshal(authConfig) + if err != nil { + return nil, fmt.Errorf("failed to encode registry auth: %w", err) + } + d.registryAuth = base64.URLEncoding.EncodeToString(authJSON) + + logger.Info("Registry credentials configured", + "registry", cfg.RegistryURL, + "username", cfg.RegistryUsername) + } else { + logger.Info("No registry credentials configured - will only use local images", + "registry_url", cfg.RegistryURL, + "registry_username", cfg.RegistryUsername, + "registry_password_set", cfg.RegistryPassword != "") + } + + return d, nil +} + +// ensureImageExists checks if an image exists locally, and pulls it if not. +// This implements a "pull if not present" policy similar to Docker's default behavior. +func (d *docker) ensureImageExists(ctx context.Context, imageName string) error { + // Check if image exists locally + _, err := d.client.ImageInspect(ctx, imageName) + if err == nil { + // Image exists locally + d.logger.Info("using existing local image", "image", imageName) + return nil + } + + // Image doesn't exist, pull it + hasAuth := d.registryAuth != "" + d.logger.Info("image not found locally, pulling", + "image", imageName, + "using_registry_auth", hasAuth) + + // Pass registry auth if we have it + pullOpts := image.PullOptions{ + RegistryAuth: d.registryAuth, + } + + pullResp, err := d.client.ImagePull(ctx, imageName, pullOpts) + if err != nil { + return fmt.Errorf("failed to pull image: %w", err) + } + defer pullResp.Close() + + // Wait for pull to complete - discard output but check for errors + _, err = io.Copy(io.Discard, pullResp) + if err != nil { + return fmt.Errorf("failed to complete image pull: %w", err) + } + + d.logger.Info("image pulled successfully", "image", imageName) + return nil } diff --git a/go/apps/krane/backend/kubernetes/create_deployment.go b/go/apps/krane/backend/kubernetes/create_deployment.go index 72bd4daa6a..4c4a03686e 100644 --- a/go/apps/krane/backend/kubernetes/create_deployment.go +++ b/go/apps/krane/backend/kubernetes/create_deployment.go @@ -3,6 +3,7 @@ package kubernetes import ( "context" "fmt" + "strings" "connectrpc.com/connect" kranev1 "github.com/unkeyed/unkey/go/gen/proto/krane/v1" @@ -83,7 +84,6 @@ func (k *k8s) CreateDeployment(ctx context.Context, req *connect.Request[kranev1 // // I believe going forward we need to re-evaluate that cause it's the wrong abstraction. &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ Name: k8sDeploymentID, Namespace: req.Msg.GetDeployment().GetNamespace(), @@ -115,7 +115,6 @@ func (k *k8s) CreateDeployment(ctx context.Context, req *connect.Request[kranev1 }, metav1.CreateOptions{}, ) - if err != nil { return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create service: %w", err)) } @@ -148,10 +147,20 @@ func (k *k8s) CreateDeployment(ctx context.Context, req *connect.Request[kranev1 Annotations: map[string]string{}, }, Spec: corev1.PodSpec{ + ImagePullSecrets: func() []corev1.LocalObjectReference { + // Only add imagePullSecrets if using Depot registry + if strings.HasPrefix(req.Msg.GetDeployment().GetImage(), "registry.depot.dev/") { + return []corev1.LocalObjectReference{ + { + Name: "depot-registry", + }, + } + } + return nil + }(), RestartPolicy: corev1.RestartPolicyAlways, Containers: []corev1.Container{ - - corev1.Container{ + { Name: "todo", Image: req.Msg.GetDeployment().GetImage(), Ports: []corev1.ContainerPort{ @@ -180,7 +189,6 @@ func (k *k8s) CreateDeployment(ctx context.Context, req *connect.Request[kranev1 }, }, }, metav1.CreateOptions{}) - if err != nil { k.logger.Info("Deleting service, because deployment creation failed") // Delete service diff --git a/go/apps/krane/config.go b/go/apps/krane/config.go index 90aba173bb..f61178658e 100644 --- a/go/apps/krane/config.go +++ b/go/apps/krane/config.go @@ -59,6 +59,23 @@ type Config struct { // The path must be accessible by the krane process with appropriate permissions. DockerSocketPath string + // DepotToken is the authentication token for Depot's build infrastructure. + // Required when BuildBackend is BuildBackendDepot. + // Obtain from https://depot.dev and store securely (e.g., environment variable). + DepotToken string + + // RegistryURL is the URL of the container registry for pulling images. + // Example: "registry.depot.dev" + RegistryURL string + + // RegistryUsername is the username for authenticating with the container registry. + // Example: "x-token", "depot", or any registry-specific username. + RegistryUsername string + + // RegistryPassword is the password/token for authenticating with the container registry. + // Should be stored securely (e.g., environment variable). + RegistryPassword string + // OtelEnabled controls whether OpenTelemetry observability data is collected // and sent to configured collectors. When enabled, the service exports // distributed traces, metrics, and structured logs. diff --git a/go/apps/krane/run.go b/go/apps/krane/run.go index 3e91b1acb3..826a0d6327 100644 --- a/go/apps/krane/run.go +++ b/go/apps/krane/run.go @@ -82,7 +82,12 @@ func Run(ctx context.Context, cfg Config) error { } case Docker: { - svc, err = docker.New(logger, cfg.DockerSocketPath) + svc, err = docker.New(logger, docker.Config{ + SocketPath: cfg.DockerSocketPath, + RegistryURL: cfg.RegistryURL, + RegistryUsername: cfg.RegistryUsername, + RegistryPassword: cfg.RegistryPassword, + }) if err != nil { return fmt.Errorf("unable to init docker backend: %w", err) } @@ -92,7 +97,6 @@ func Run(ctx context.Context, cfg Config) error { } // Create the service handlers with interceptors - // mux.Handle(kranev1connect.NewDeploymentServiceHandler(svc)) // Configure server diff --git a/go/cmd/ctrl/main.go b/go/cmd/ctrl/main.go index caaf014692..8f6818dbe9 100644 --- a/go/cmd/ctrl/main.go +++ b/go/cmd/ctrl/main.go @@ -71,6 +71,34 @@ var Cmd = &cli.Command{ cli.String("vault-s3-access-key-secret", "S3 secret access key", cli.Required(), cli.EnvVar("UNKEY_VAULT_S3_ACCESS_KEY_SECRET")), + // Build Configuration + cli.String("build-backend", "Build backend to use: 'docker' for local, 'depot' for production. Default: depot", + cli.Default("depot"), cli.EnvVar("UNKEY_BUILD_BACKEND")), + cli.String("build-s3-url", "S3 Compatible Endpoint URL for build contexts (internal)", + cli.Required(), cli.EnvVar("UNKEY_BUILD_S3_URL")), + cli.String("build-s3-external-url", "S3 Compatible Endpoint URL for build contexts (external/public)", + cli.EnvVar("UNKEY_BUILD_S3_EXTERNAL_URL")), + cli.String("build-s3-bucket", "S3 bucket name for build contexts", + cli.Required(), cli.EnvVar("UNKEY_BUILD_S3_BUCKET")), + cli.String("build-s3-access-key-id", "S3 access key ID for build contexts", + cli.Required(), cli.EnvVar("UNKEY_BUILD_S3_ACCESS_KEY_ID")), + cli.String("build-s3-access-key-secret", "S3 secret access key for build contexts", + cli.Required(), cli.EnvVar("UNKEY_BUILD_S3_ACCESS_KEY_SECRET")), + + cli.String("registry-url", "URL of the container registry for pulling images. Example: registry.depot.dev", + cli.EnvVar("UNKEY_REGISTRY_URL")), + cli.String("registry-username", "Username for authenticating with the container registry.", + cli.EnvVar("UNKEY_REGISTRY_USERNAME")), + cli.String("registry-password", "Password/token for authenticating with the container registry.", + cli.EnvVar("UNKEY_REGISTRY_PASSWORD")), + cli.String("build-platform", "Run builds on this platform ('dynamic', 'linux/amd64', 'linux/arm64')", + cli.EnvVar("UNKEY_BUILD_PLATFORM"), cli.Default("linux/amd64")), + // Depot Build Backend Configuration + cli.String("depot-api-url", "Depot API endpoint URL", + cli.EnvVar("UNKEY_DEPOT_API_URL")), + cli.String("depot-project-region", "Build data will be stored in the chosen region ('us-east-1','eu-central-1')", + cli.EnvVar("UNKEY_DEPOT_PROJECT_REGION"), cli.Default("us-east-1")), + cli.Bool("acme-enabled", "Enable Let's Encrypt for acme challenges", cli.EnvVar("UNKEY_ACME_ENABLED")), cli.Bool("acme-cloudflare-enabled", "Enable Cloudflare for wildcard certificates", cli.EnvVar("UNKEY_ACME_CLOUDFLARE_ENABLED")), cli.String("acme-cloudflare-api-token", "Cloudflare API token for Let's Encrypt", cli.EnvVar("UNKEY_ACME_CLOUDFLARE_API_TOKEN")), @@ -110,11 +138,15 @@ func action(ctx context.Context, cmd *cli.Command) error { config := ctrl.Config{ // Basic configuration - Platform: cmd.String("platform"), - Image: cmd.String("image"), - HttpPort: cmd.Int("http-port"), - Region: cmd.String("region"), - InstanceID: cmd.String("instance-id"), + Platform: cmd.String("platform"), + BuildPlatform: cmd.String("build-platform"), + Image: cmd.String("image"), + HttpPort: cmd.Int("http-port"), + Region: cmd.String("region"), + InstanceID: cmd.String("instance-id"), + RegistryURL: cmd.String("registry-url"), + RegistryUsername: cmd.String("registry-username"), + RegistryPassword: cmd.String("registry-password"), // Database configuration DatabasePrimary: cmd.String("database-primary"), @@ -142,6 +174,22 @@ func action(ctx context.Context, cmd *cli.Command) error { AccessKeyID: cmd.String("vault-s3-access-key-id"), }, + // Build configuration + BuildBackend: ctrl.BuildBackend(cmd.String("build-backend")), + BuildS3: ctrl.S3Config{ + URL: cmd.String("build-s3-url"), + ExternalURL: cmd.String("build-s3-external-url"), + Bucket: cmd.String("build-s3-bucket"), + AccessKeySecret: cmd.String("build-s3-access-key-secret"), + AccessKeyID: cmd.String("build-s3-access-key-id"), + }, + + // Depot build backend configuration + Depot: ctrl.DepotConfig{ + APIUrl: cmd.String("depot-api-url"), + ProjectRegion: cmd.String("depot-project-region"), + }, + // Acme configuration Acme: ctrl.AcmeConfig{ Enabled: cmd.Bool("acme-enabled"), diff --git a/go/cmd/deploy/build_docker.go b/go/cmd/deploy/build_docker.go deleted file mode 100644 index a794d2d3ff..0000000000 --- a/go/cmd/deploy/build_docker.go +++ /dev/null @@ -1,479 +0,0 @@ -package deploy - -import ( - "bufio" - "context" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "sync" - "time" - - "github.com/unkeyed/unkey/go/pkg/git" -) - -const ( - // Timeouts - DockerBuildTimeout = 10 * time.Minute - - // Build arguments - VersionBuildArg = "VERSION" - - // Progress messages - ProgressBuilding = "Building..." - - // Limits - MaxOutputLines = 5 - MaxErrorLines = 3 -) - -var ( - ErrDockerNotFound = errors.New("docker command not found - please install Docker") - ErrDockerBuildFailed = errors.New("docker build failed") - ErrInvalidContext = errors.New("invalid build context") - ErrDockerfileNotFound = errors.New("dockerfile not found") - ErrBuildTimeout = errors.New("docker build timed out") -) - -// Precompiled regexes for Docker tag sanitization -var ( - // reInvalid matches any characters not allowed in Docker tags (after normalization to lowercase) - reInvalid = regexp.MustCompile(`[^a-z0-9._-]+`) - - // reFirstOK matches valid first characters for Docker tags (after normalization) - reFirstOK = regexp.MustCompile(`^[a-z0-9_]`) - - // reMultiDash matches consecutive dashes - reMultiDash = regexp.MustCompile(`-{2,}`) - - // reFinal validates the complete tag format (after normalization) - reFinal = regexp.MustCompile(`^[a-z0-9_][a-z0-9._-]*$`) -) - -// sanitizeDockerTag sanitizes a string to be valid for Docker tags -// Official Docker tag grammar: /[\w][\w.-]{0,127}/ -// - First char: word character (a-zA-Z0-9_) -// - Remaining chars: word characters, periods, or dashes -// - Maximum 128 characters total (enforced later in generateImageTag) -// - Lowercasing is a policy decision to ensure consistency (Docker tags may be mixed-case) -func sanitizeDockerTag(input string) string { - if input == "" { - return "main" - } - - // Convert to lowercase as a policy decision for consistency - // (Docker tags may be mixed-case but we normalize for predictability) - result := strings.ToLower(input) - - // Replace invalid characters with dashes using precompiled regex - // Keep only: a-z, 0-9, _, ., - - result = reInvalid.ReplaceAllString(result, "-") - - // Ensure first character is a word character (a-z0-9_) - // If it starts with . or -, prepend with a valid character - if len(result) > 0 && !reFirstOK.MatchString(result) { - result = "v" + result - } - - // Remove consecutive dashes for cleaner tags using precompiled regex - result = reMultiDash.ReplaceAllString(result, "-") - - // Final safety check - ensure we have a valid tag using precompiled regex - if result == "" || !reFinal.MatchString(result) { - return "main" - } - - return result -} - -// generateImageTag creates a unique tag for the Docker image -func generateImageTag(opts DeployOptions, gitInfo git.Info) string { - // Sanitize branch name for Docker tag compatibility - cleanBranch := sanitizeDockerTag(opts.Branch) - - // Calculate suffix length to determine maximum branch length - var suffix string - var suffixLen int - - if gitInfo.ShortSHA != "" { - suffix = gitInfo.ShortSHA - suffixLen = len(suffix) + 1 // +1 for the hyphen - } else { - timestamp := time.Now().Unix() - suffix = fmt.Sprintf("%d", timestamp) - suffixLen = len(suffix) + 1 // +1 for the hyphen - } - - // Calculate maximum allowed branch length to keep final tag ≤ 128 chars - const maxTagLength = 128 - maxBranchLen := maxTagLength - suffixLen - - // Trim branch to fit within the calculated limit (using rune count for multibyte safety) - branchRunes := []rune(cleanBranch) - if len(branchRunes) > maxBranchLen { - branchRunes = branchRunes[:maxBranchLen] - cleanBranch = string(branchRunes) - // Ensure it doesn't end with a dash after trimming - cleanBranch = strings.TrimRight(cleanBranch, "-") - } - - // Format the final tag - return fmt.Sprintf("%s-%s", cleanBranch, suffix) -} - -// isDockerAvailable checks if Docker is installed and accessible -func isDockerAvailable() error { - cmd := exec.Command("docker", "--version") - if err := cmd.Run(); err != nil { - return ErrDockerNotFound - } - return nil -} - -// buildImage builds the Docker image with proper error hierarchy -func buildImage(ctx context.Context, opts DeployOptions, dockerImage string, ui *UI) error { - // Sub-step 1: Validate inputs - if err := validateImagePath(opts); err != nil { - ui.PrintStepError("Validation failed") - ui.PrintErrorDetails(err.Error()) - return err - } - ui.PrintStepSuccess("Build inputs validated") - - // Sub-step 2: Prepare build command - buildArgs := []string{"build"} - if opts.Dockerfile != DefaultDockerfile { - buildArgs = append(buildArgs, "-f", opts.Dockerfile) - } - if opts.Linux { - buildArgs = append(buildArgs, "--platform", "linux/amd64") - } - buildArgs = append(buildArgs, - "-t", dockerImage, - "--build-arg", fmt.Sprintf("%s=%s", VersionBuildArg, opts.Commit), - opts.Context, - ) - - ui.PrintStepSuccess("Build command prepared") - if opts.Verbose { - ui.PrintBuildProgress(fmt.Sprintf("Running: docker %s", strings.Join(buildArgs, " "))) - } - - // Sub-step 3: Execute Docker build - ui.PrintStepSuccess("Starting Docker build") - buildCtx, cancel := context.WithTimeout(ctx, DockerBuildTimeout) - defer cancel() - - cmd := exec.CommandContext(buildCtx, "docker", buildArgs...) - - var buildErr error - if opts.Verbose { - buildErr = buildImageVerbose(cmd, buildCtx, ui) - } else { - buildErr = buildImageWithSpinner(cmd, buildCtx, ui) - } - - if buildErr != nil { - ui.PrintStepError("Docker build failed") - ui.PrintErrorDetails(buildErr.Error()) - return buildErr - } - - return nil -} - -// buildImageVerbose shows all Docker output in real-time -func buildImageVerbose(cmd *exec.Cmd, buildCtx context.Context, ui *UI) error { - // Set up pipes for real-time output - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("failed to create stdout pipe: %w", err) - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return fmt.Errorf("failed to create stderr pipe: %w", err) - } - - // Start the command - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start docker build: %w", err) - } - - // Stream stdout - go func() { - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - ui.PrintBuildProgress(scanner.Text()) - } - }() - - // Stream stderr - go func() { - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - ui.PrintBuildError(scanner.Text()) - } - }() - - // Wait for completion - done := make(chan error, 1) - go func() { - done <- cmd.Wait() - }() - - select { - case <-buildCtx.Done(): - if buildCtx.Err() == context.DeadlineExceeded { - return fmt.Errorf("%w after %v", ErrBuildTimeout, DockerBuildTimeout) - } - return buildCtx.Err() - case err := <-done: - if err != nil { - return fmt.Errorf("%w: %v", ErrDockerBuildFailed, err) - } - } - - return nil -} - -// buildImageWithSpinner shows progress spinner with current step -func buildImageWithSpinner(cmd *exec.Cmd, buildCtx context.Context, ui *UI) error { - // Set up pipes to capture output for progress tracking - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("failed to create stdout pipe: %w", err) - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return fmt.Errorf("failed to create stderr pipe: %w", err) - } - - // Start the command - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start docker build: %w", err) - } - - // Track all output for error reporting - var outputMu sync.Mutex - allOutput := []string{} - - // Start progress spinner - ui.StartStepSpinner(ProgressBuilding) - - // Stream stdout and track progress - go func() { - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - line := scanner.Text() - - outputMu.Lock() - allOutput = append(allOutput, line) - outputMu.Unlock() - - // Update spinner with current step - if step := extractDockerStep(line); step != "" { - ui.UpdateStepSpinner(fmt.Sprintf("%s %s", ProgressBuilding, step)) - } - } - }() - - // Stream stderr - go func() { - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - line := scanner.Text() - - outputMu.Lock() - allOutput = append(allOutput, line) - outputMu.Unlock() - } - }() - - // Wait for completion - done := make(chan error, 1) - go func() { - done <- cmd.Wait() - }() - - select { - case <-buildCtx.Done(): - ui.CompleteCurrentStep("Build timed out", false) - if buildCtx.Err() == context.DeadlineExceeded { - return fmt.Errorf("%w after %v", ErrBuildTimeout, DockerBuildTimeout) - } - return buildCtx.Err() - case err := <-done: - if err != nil { - ui.CompleteCurrentStep("Build failed", false) - - outputMu.Lock() - outputCopy := make([]string, len(allOutput)) - copy(outputCopy, allOutput) - outputMu.Unlock() - - // Show last few lines of output for debugging - if len(outputCopy) > 0 { - ui.PrintBuildError("Last few lines of output:") - lines := outputCopy - if len(lines) > MaxOutputLines { - lines = lines[len(lines)-MaxOutputLines:] - } - for _, line := range lines { - ui.PrintBuildProgress(line) - } - } - return fmt.Errorf("%w: %s", ErrDockerBuildFailed, classifyError(strings.Join(outputCopy, "\n"))) - } - } - - ui.CompleteCurrentStep("Docker build completed successfully", true) - return nil -} - -// extractDockerStep extracts meaningful step info from Docker output -func extractDockerStep(line string) string { - line = strings.TrimSpace(line) - - // Look for Docker build steps - if strings.HasPrefix(line, "#") && strings.Contains(line, "[") { - // Extract step like "#5 [builder 1/6] FROM docker.io/library/golang" - if idx := strings.Index(line, "]"); idx > 0 { - step := line[:idx+1] - // Clean up the step display - step = strings.ReplaceAll(step, "#", "Step ") - return step - } - } - - // Look for other meaningful progress - switch { - case strings.Contains(line, "DONE"): - return "Step completed" - case strings.Contains(line, "CACHED"): - return "Using cache" - case strings.Contains(line, "exporting"): - return "Exporting image" - case strings.Contains(line, "naming to"): - return "Tagging image" - } - - return "" -} - -// pushImage pushes the built Docker image to the registry -func pushImage(ctx context.Context, dockerImage, registry string) error { - cmd := exec.CommandContext(ctx, "docker", "push", dockerImage) - output, err := cmd.CombinedOutput() - if err != nil { - detailedMsg := classifyPushError(string(output), registry) - return fmt.Errorf("%s: %w", detailedMsg, err) - } - - // Show push output - if len(output) > 0 { - fmt.Printf("%s\n", string(output)) - } - - return nil -} - -// validateImagePath - pre-flight checks using error constants -func validateImagePath(opts DeployOptions) error { - // Context directory exists? - if _, err := os.Stat(opts.Context); os.IsNotExist(err) { - return fmt.Errorf("%w: directory '%s' does not exist", ErrInvalidContext, opts.Context) - } - - // Dockerfile exists? - dockerfilePath := opts.Dockerfile - if !filepath.IsAbs(dockerfilePath) { - dockerfilePath = filepath.Join(opts.Context, opts.Dockerfile) - } - - if _, err := os.Stat(dockerfilePath); os.IsNotExist(err) { - return fmt.Errorf("%w: '%s' not found", ErrDockerfileNotFound, dockerfilePath) - } - - return nil -} - -// classifyError provides helpful error messages -func classifyError(output string) string { - output = strings.ToLower(output) - - switch { - case strings.Contains(output, "dockerfile"): - return "Dockerfile issue - check path and syntax" - case strings.Contains(output, "no such file"): - return "File not found - check COPY/ADD paths" - case strings.Contains(output, "permission denied"): - return "Permission denied - check file permissions" - case strings.Contains(output, "network") || strings.Contains(output, "timeout"): - return "Network error - check internet connection" - case strings.Contains(output, "manifest unknown"): - return "Base image not found - check FROM instruction" - case strings.Contains(output, "space") || strings.Contains(output, "disk"): - return "Insufficient disk space" - default: - // Return last few lines for debugging - lines := strings.Split(strings.TrimSpace(output), "\n") - if len(lines) > MaxErrorLines { - return strings.Join(lines[len(lines)-MaxErrorLines:], "\n") - } - return strings.TrimSpace(output) - } -} - -// classifyPushError provides detailed error messages for push failures -func classifyPushError(output, registry string) string { - output = strings.TrimSpace(output) - registryHost := getRegistryHost(registry) - - switch { - case strings.Contains(output, "denied"): - return fmt.Sprintf("registry access denied. try: docker login %s", registryHost) - case strings.Contains(output, "not found") || strings.Contains(output, "404"): - return "registry not found. create repository or use --registry=your-registry/your-app" - case strings.Contains(output, "unauthorized"): - return fmt.Sprintf("authentication required. run: docker login %s", registryHost) - default: - return output - } -} - -// getRegistryHost extracts the registry hostname from a full registry path -func getRegistryHost(registry string) string { - parts := strings.Split(registry, "/") - if len(parts) > 0 { - return parts[0] - } - return DefaultRegistry -} - -// UI methods for build progress -func (ui *UI) PrintBuildProgress(message string) { - ui.mu.Lock() - defer ui.mu.Unlock() - fmt.Printf(" %s\n", message) -} - -func (ui *UI) PrintBuildError(message string) { - ui.mu.Lock() - defer ui.mu.Unlock() - fmt.Printf(" %s⚠%s %s\n", ColorYellow, ColorReset, message) -} - -// UpdateStepSpinner updates the current step spinner message -func (ui *UI) UpdateStepSpinner(message string) { - ui.mu.Lock() - defer ui.mu.Unlock() - if ui.stepSpinning { - ui.currentStep = message - } -} diff --git a/go/cmd/deploy/control_plane.go b/go/cmd/deploy/control_plane.go index e1403dade6..a99577fcac 100644 --- a/go/cmd/deploy/control_plane.go +++ b/go/cmd/deploy/control_plane.go @@ -4,7 +4,11 @@ import ( "context" "errors" "fmt" + "io" "net/http" + "os" + "os/exec" + "path/filepath" "strings" "time" @@ -15,6 +19,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/git" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" ) // DeploymentStatusEvent represents a status change event @@ -34,47 +39,191 @@ type DeploymentStepEvent struct { // ControlPlaneClient handles API operations with the control plane type ControlPlaneClient struct { - client ctrlv1connect.DeploymentServiceClient - opts DeployOptions + deploymentClient ctrlv1connect.DeploymentServiceClient + buildClient ctrlv1connect.BuildServiceClient + opts DeployOptions } // NewControlPlaneClient creates a new control plane client func NewControlPlaneClient(opts DeployOptions) *ControlPlaneClient { httpClient := &http.Client{} - client := ctrlv1connect.NewDeploymentServiceClient(httpClient, opts.ControlPlaneURL) + deploymentClient := ctrlv1connect.NewDeploymentServiceClient(httpClient, opts.ControlPlaneURL) + buildClient := ctrlv1connect.NewBuildServiceClient(httpClient, opts.ControlPlaneURL) return &ControlPlaneClient{ - client: client, - opts: opts, + deploymentClient: deploymentClient, + buildClient: buildClient, + opts: opts, } } +// UploadBuildContext uploads the build context to S3 and returns the context key +func (c *ControlPlaneClient) UploadBuildContext(ctx context.Context, contextPath string) (string, error) { + uploadReq := connect.NewRequest(&ctrlv1.GenerateUploadURLRequest{ + UnkeyProjectId: c.opts.ProjectID, + }) + + authHeader := c.opts.APIKey + if authHeader == "" { + authHeader = c.opts.AuthToken + } + uploadReq.Header().Set("Authorization", "Bearer "+authHeader) + + uploadResp, err := c.buildClient.GenerateUploadURL(ctx, uploadReq) + if err != nil { + return "", fmt.Errorf("failed to generate upload URL: %w", err) + } + + uploadURL := uploadResp.Msg.GetUploadUrl() + buildContextPath := uploadResp.Msg.GetBuildContextPath() + + if uploadURL == "" || buildContextPath == "" { + return "", fmt.Errorf("empty upload URL or context key returned") + } + + tarPath, err := createContextTar(contextPath) + if err != nil { + return "", fmt.Errorf("failed to create tar archive: %w", err) + } + defer os.Remove(tarPath) + + if err := uploadToPresignedURL(ctx, uploadURL, tarPath); err != nil { + return "", fmt.Errorf("failed to upload build context: %w", err) + } + + return buildContextPath, nil +} + +// uploadToPresignedURL uploads a file to a presigned S3 URL +func uploadToPresignedURL(ctx context.Context, presignedURL, filePath string) error { + file, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return fmt.Errorf("failed to stat file: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "PUT", presignedURL, file) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.ContentLength = stat.Size() + req.Header.Set("Content-Type", "application/gzip") + + client := &http.Client{ + Timeout: 5 * time.Minute, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("upload request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, string(body)) + } + + return nil +} + +// createContextTar creates a tar.gz from the given directory path +// and returns the absolute path to the created tar file +func createContextTar(contextPath string) (string, error) { + info, err := os.Stat(contextPath) + if err != nil { + return "", fmt.Errorf("context path does not exist: %w", err) + } + if !info.IsDir() { + return "", fmt.Errorf("context path must be a directory: %s", contextPath) + } + + absContextPath, err := filepath.Abs(contextPath) + if err != nil { + return "", fmt.Errorf("failed to get absolute context path: %w", err) + } + + sharedDir := "/tmp/ctrl" + if err := os.MkdirAll(sharedDir, 0o777); err != nil { + return "", fmt.Errorf("failed to create shared dir: %w", err) + } + + tmpFile, err := os.CreateTemp(sharedDir, "build-context-*.tar.gz") + if err != nil { + return "", fmt.Errorf("failed to create temp file: %w", err) + } + tmpFile.Close() + tarPath := tmpFile.Name() + + if err := os.Chmod(tarPath, 0o666); err != nil { + os.Remove(tarPath) + return "", fmt.Errorf("failed to set file permissions: %w", err) + } + + cmd := exec.Command("tar", "-czf", tarPath, "-C", absContextPath, ".") + output, err := cmd.CombinedOutput() + if err != nil { + os.Remove(tarPath) + return "", fmt.Errorf("tar command failed: %w\nOutput: %s", err, string(output)) + } + + return tarPath, nil +} + // CreateDeployment creates a new deployment in the control plane -func (c *ControlPlaneClient) CreateDeployment(ctx context.Context, dockerImage string) (string, error) { - // Get git commit information +// Pass either buildContextPath (for build from source) or dockerImage (for prebuilt image), not both +func (c *ControlPlaneClient) CreateDeployment(ctx context.Context, buildContextPath, dockerImage string) (string, error) { commitInfo := git.GetInfo() - createReq := connect.NewRequest(&ctrlv1.CreateDeploymentRequest{ - ProjectId: c.opts.ProjectID, - KeyspaceId: &c.opts.KeyspaceID, - Branch: c.opts.Branch, - SourceType: ctrlv1.SourceType_SOURCE_TYPE_CLI_UPLOAD, - EnvironmentSlug: c.opts.Environment, - DockerImage: dockerImage, - GitCommitSha: commitInfo.CommitSHA, - GitCommitMessage: commitInfo.Message, - GitCommitAuthorHandle: commitInfo.AuthorHandle, - GitCommitAuthorAvatarUrl: commitInfo.AuthorAvatarURL, - GitCommitTimestamp: commitInfo.CommitTimestamp, - }) - // Use API key for authentication if provided, fallback to auth token + dockerfilePath := c.opts.Dockerfile + if dockerfilePath == "" { + dockerfilePath = "Dockerfile" + } + + req := &ctrlv1.CreateDeploymentRequest{ + ProjectId: c.opts.ProjectID, + KeyspaceId: &c.opts.KeyspaceID, + Branch: c.opts.Branch, + EnvironmentSlug: c.opts.Environment, + GitCommit: &ctrlv1.GitCommitInfo{ + CommitSha: commitInfo.CommitSHA, + CommitMessage: commitInfo.Message, + AuthorHandle: commitInfo.AuthorHandle, + AuthorAvatarUrl: commitInfo.AuthorAvatarURL, + Timestamp: commitInfo.CommitTimestamp, + }, + } + + if buildContextPath != "" { + req.Source = &ctrlv1.CreateDeploymentRequest_BuildContext{ + BuildContext: &ctrlv1.BuildContext{ + BuildContextPath: buildContextPath, + DockerfilePath: ptr.P(dockerfilePath), + }, + } + } else if dockerImage != "" { + req.Source = &ctrlv1.CreateDeploymentRequest_DockerImage{ + DockerImage: dockerImage, + } + } else { + return "", fmt.Errorf("either buildContextPath or dockerImage must be provided") + } + + createReq := connect.NewRequest(req) + authHeader := c.opts.APIKey if authHeader == "" { authHeader = c.opts.AuthToken } createReq.Header().Set("Authorization", "Bearer "+authHeader) - createResp, err := c.client.CreateDeployment(ctx, createReq) + createResp, err := c.deploymentClient.CreateDeployment(ctx, createReq) if err != nil { return "", c.handleCreateDeploymentError(err) } @@ -88,18 +237,18 @@ func (c *ControlPlaneClient) CreateDeployment(ctx context.Context, dockerImage s } // GetDeployment retrieves deployment information from the control plane -func (c *ControlPlaneClient) GetDeployment(ctx context.Context, deploymentId string) (*ctrlv1.Deployment, error) { +func (c *ControlPlaneClient) GetDeployment(ctx context.Context, deploymentID string) (*ctrlv1.Deployment, error) { getReq := connect.NewRequest(&ctrlv1.GetDeploymentRequest{ - DeploymentId: deploymentId, + DeploymentId: deploymentID, }) - // Use API key for authentication if provided, fallback to auth token + authHeader := c.opts.APIKey if authHeader == "" { authHeader = c.opts.AuthToken } getReq.Header().Set("Authorization", "Bearer "+authHeader) - getResp, err := c.client.GetDeployment(ctx, getReq) + getResp, err := c.deploymentClient.GetDeployment(ctx, getReq) if err != nil { return nil, err } @@ -131,13 +280,14 @@ func (c *ControlPlaneClient) PollDeploymentStatus( case <-ticker.C: deployment, err := c.GetDeployment(ctx, deploymentID) if err != nil { - logger.Debug("Failed to get deployment status", "error", err, "deployment_id", deploymentID) + logger.Debug("Failed to get deployment status", + "error", err, + "deployment_id", deploymentID) continue } currentStatus := deployment.GetStatus() - // Handle deployment status changes if currentStatus != lastStatus { event := DeploymentStatusEvent{ DeploymentID: deploymentID, @@ -166,7 +316,6 @@ func (c *ControlPlaneClient) getFailureMessage(deployment *ctrlv1.Deployment) st return deployment.GetErrorMessage() } - // Check for error in steps for _, step := range deployment.GetSteps() { if step.GetErrorMessage() != "" { return step.GetErrorMessage() @@ -178,7 +327,6 @@ func (c *ControlPlaneClient) getFailureMessage(deployment *ctrlv1.Deployment) st // handleCreateDeploymentError provides specific error handling for deployment creation func (c *ControlPlaneClient) handleCreateDeploymentError(err error) error { - // Check if it's a connection error if strings.Contains(err.Error(), "connection refused") { return fault.Wrap(err, fault.Code(codes.UnkeyAppErrorsInternalServiceUnavailable), @@ -187,10 +335,8 @@ func (c *ControlPlaneClient) handleCreateDeploymentError(err error) error { ) } - // Check if it's an auth error if connectErr := new(connect.Error); errors.As(err, &connectErr) { if connectErr.Code() == connect.CodeUnauthenticated { - // Determine which auth method was used for better error message authMethod := "API key" if c.opts.APIKey == "" { authMethod = "auth token" @@ -203,7 +349,6 @@ func (c *ControlPlaneClient) handleCreateDeploymentError(err error) error { } } - // Generic API error return fault.Wrap(err, fault.Code(codes.UnkeyAppErrorsInternalUnexpectedError), fault.Internal(fmt.Sprintf("CreateDeployment API call failed: %v", err)), diff --git a/go/cmd/deploy/main.go b/go/cmd/deploy/main.go index cd919be2b8..0b0ff6c96b 100644 --- a/go/cmd/deploy/main.go +++ b/go/cmd/deploy/main.go @@ -37,14 +37,11 @@ const ( // Step messages MsgPreparingDeployment = "Preparing deployment" + MsgUploadingBuildContext = "Uploading build context" + MsgBuildContextUploaded = "Build context uploaded" MsgCreatingDeployment = "Creating deployment" - MsgSkippingRegistryPush = "Skipping registry push" - MsgUsingPreBuiltImage = "Using pre-built Docker image" - MsgPushingToRegistry = "Pushing to registry" - MsgImageBuiltSuccessfully = "Image built successfully" - MsgImagePushedSuccessfully = "Image pushed successfully" - MsgPushFailedContinuing = "Push failed but continuing deployment" - MsgDockerNotFound = "Docker not found - please install Docker" + MsgDeploymentCreated = "Deployment created" + MsgFailedToUploadContext = "Failed to upload build context" MsgFailedToCreateDeployment = "Failed to create deployment" MsgDeploymentFailed = "Deployment failed" MsgDeploymentCompleted = "Deployment completed successfully" @@ -69,24 +66,13 @@ const ( GitDirtyMarker = " (dirty)" ) -// Step predictor - maps current step message patterns to next expected steps -var stepSequence = map[string]string{ - "Version queued and ready to start": "Downloading Docker image:", - "Downloading Docker image:": "Building rootfs from Docker image:", - "Building rootfs from Docker image:": "Uploading rootfs image to storage", - "Uploading rootfs image to storage": "Creating VM for version:", - "Creating VM for deployment:": "VM booted successfully:", - "VM booted successfully:": "Assigned hostname:", - "Assigned hostname:": MsgDeploymentStepCompleted, -} - // DeployOptions contains all configuration for deployment type DeployOptions struct { ProjectID string KeyspaceID string Context string - Branch string DockerImage string + Branch string Dockerfile string Commit string Registry string @@ -194,8 +180,8 @@ func DeployAction(ctx context.Context, cmd *cli.Command) error { KeyspaceID: finalConfig.KeyspaceID, ProjectID: finalConfig.ProjectID, Context: finalConfig.Context, - Branch: cmd.String("branch"), DockerImage: cmd.String("docker-image"), + Branch: cmd.String("branch"), Dockerfile: cmd.String("dockerfile"), Commit: cmd.String("commit"), Registry: cmd.String("registry"), @@ -231,58 +217,42 @@ func executeDeploy(ctx context.Context, opts DeployOptions) error { ui.Print(MsgPreparingDeployment) - var dockerImage string - - // Build or use pre-built Docker image - if opts.DockerImage == "" { - // Check Docker availability using updated function - if err := isDockerAvailable(); err != nil { - ui.PrintError(MsgDockerNotFound) - return err - } - - // Generate image tag and full image name - imageTag := generateImageTag(opts, gitInfo) - dockerImage = fmt.Sprintf("%s:%s", opts.Registry, imageTag) + controlPlane := NewControlPlaneClient(opts) - ui.Print(fmt.Sprintf("Building image: %s", dockerImage)) + var deploymentID string + var err error - if err := buildImage(ctx, opts, dockerImage, ui); err != nil { - // Don't print additional error, buildImage already reported it with proper hierarchy + // Determine deployment source: prebuilt image or build from context + if opts.DockerImage != "" { + // Use prebuilt Docker image + ui.Print(MsgCreatingDeployment) + deploymentID, err = controlPlane.CreateDeployment(ctx, "", opts.DockerImage) + if err != nil { + ui.PrintError(MsgFailedToCreateDeployment) + ui.PrintErrorDetails(err.Error()) return err } - ui.PrintSuccess(MsgImageBuiltSuccessfully) + ui.PrintSuccess(fmt.Sprintf("%s: %s", MsgDeploymentCreated, deploymentID)) } else { - dockerImage = opts.DockerImage - ui.Print(MsgUsingPreBuiltImage) - } - - // Push to registry, unless skipped or using pre-built image - if !opts.SkipPush && opts.DockerImage == "" { - ui.Print(MsgPushingToRegistry) - if err := pushImage(ctx, dockerImage, opts.Registry); err != nil { - ui.PrintError(MsgPushFailedContinuing) + // Build from context + ui.Print(MsgUploadingBuildContext) + buildContextPath, err := controlPlane.UploadBuildContext(ctx, opts.Context) + if err != nil { + ui.PrintError(MsgFailedToUploadContext) ui.PrintErrorDetails(err.Error()) - // NOTE: Currently ignoring push failures for local development - // For production deployment, uncomment the line below: - // return err - } else { - ui.PrintSuccess(MsgImagePushedSuccessfully) + return err } - } else if opts.SkipPush { - ui.Print(MsgSkippingRegistryPush) - } + ui.PrintSuccess(fmt.Sprintf("%s: %s", MsgBuildContextUploaded, buildContextPath)) - // Create deployment - ui.Print(MsgCreatingDeployment) - controlPlane := NewControlPlaneClient(opts) - deploymentID, err := controlPlane.CreateDeployment(ctx, dockerImage) - if err != nil { - ui.PrintError(MsgFailedToCreateDeployment) - ui.PrintErrorDetails(err.Error()) - return err + ui.Print(MsgCreatingDeployment) + deploymentID, err = controlPlane.CreateDeployment(ctx, buildContextPath, "") + if err != nil { + ui.PrintError(MsgFailedToCreateDeployment) + ui.PrintErrorDetails(err.Error()) + return err + } + ui.PrintSuccess(fmt.Sprintf("%s: %s", MsgDeploymentCreated, deploymentID)) } - ui.PrintSuccess(fmt.Sprintf("Deployment created: %s", deploymentID)) // Track final deployment for completion info var finalDeployment *ctrlv1.Deployment @@ -318,16 +288,6 @@ func executeDeploy(ctx context.Context, opts DeployOptions) error { return nil } -func getNextStepMessage(currentMessage string) string { - // Check if current message starts with any known step pattern - for key, next := range stepSequence { - if len(currentMessage) >= len(key) && currentMessage[:len(key)] == key { - return next - } - } - return "" -} - func handleDeploymentFailure(controlPlane *ControlPlaneClient, deployment *ctrlv1.Deployment, ui *UI) error { errorMsg := controlPlane.getFailureMessage(deployment) ui.CompleteCurrentStep(MsgDeploymentFailed, false) @@ -348,10 +308,10 @@ func printSourceInfo(opts DeployOptions, gitInfo git.Info) { fmt.Printf(" %s: %s\n", LabelCommit, commitInfo) } - fmt.Printf(" %s: %s\n", LabelContext, opts.Context) - if opts.DockerImage != "" { fmt.Printf(" %s: %s\n", LabelImage, opts.DockerImage) + } else { + fmt.Printf(" %s: %s\n", LabelContext, opts.Context) } fmt.Printf("\n") diff --git a/go/cmd/krane/main.go b/go/cmd/krane/main.go index 0567ebdac4..a5fda59b58 100644 --- a/go/cmd/krane/main.go +++ b/go/cmd/krane/main.go @@ -13,12 +13,10 @@ var Cmd = &cli.Command{ Name: "krane", Usage: "Run the k8s management service", Description: `krane (/kreɪn/) is the kubernetes deployment service for Unkey infrastructure. - It manages the lifecycle of deployments in a kubernetes cluster: EXAMPLES: unkey run krane # Run with default configuration`, - Flags: []cli.Flag{ // Server Configuration cli.Int("http-port", "Port for the server to listen on. Default: 8080", @@ -34,10 +32,18 @@ unkey run krane # Run with default configurati cli.String("docker-socket", "Path to the docker socket. Only used if backend is docker. Default: /var/run/docker.sock", cli.Default("/var/run/docker.sock"), cli.EnvVar("UNKEY_DOCKER_SOCKET")), + cli.String("registry-url", "URL of the container registry for pulling images. Example: registry.depot.dev", + cli.EnvVar("UNKEY_REGISTRY_URL")), + + cli.String("registry-username", "Username for authenticating with the container registry.", + cli.EnvVar("UNKEY_REGISTRY_USERNAME")), + + cli.String("registry-password", "Password/token for authenticating with the container registry.", + cli.EnvVar("UNKEY_REGISTRY_PASSWORD")), + // This has no use outside of our demo cluster and will be removed soon cli.Duration("deployment-eviction-ttl", "Automatically delete deployments after some time. Use go duration formats such as 2h30m", cli.EnvVar("UNKEY_DEPLOYMENT_EVICTION_TTL")), }, - Action: action, } @@ -46,6 +52,7 @@ func action(ctx context.Context, cmd *cli.Command) error { if err != nil { return cli.Exit(err.Error(), 1) } + config := krane.Config{ HttpPort: cmd.Int("http-port"), Backend: backend, @@ -56,6 +63,9 @@ func action(ctx context.Context, cmd *cli.Command) error { OtelTraceSamplingRate: 1.0, InstanceID: cmd.String("instance-id"), DockerSocketPath: cmd.String("docker-socket"), + RegistryURL: cmd.String("registry-url"), + RegistryUsername: cmd.String("registry-username"), + RegistryPassword: cmd.String("registry-password"), DeploymentEvictionTTL: cmd.Duration("deployment-eviction-ttl"), } @@ -74,7 +84,6 @@ func parseBackend(s string) (krane.Backend, error) { case "docker": return krane.Docker, nil case "kubernetes": - return krane.Kubernetes, nil default: return "", fmt.Errorf("unknown backend type: %s", s) diff --git a/go/gen/proto/ctrl/v1/build.pb.go b/go/gen/proto/ctrl/v1/build.pb.go index a84d2ec329..bc6d73afe1 100644 --- a/go/gen/proto/ctrl/v1/build.pb.go +++ b/go/gen/proto/ctrl/v1/build.pb.go @@ -9,7 +9,6 @@ package ctrlv1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -22,73 +21,14 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// Build status enum -type BuildStatus int32 - -const ( - BuildStatus_BUILD_STATUS_UNSPECIFIED BuildStatus = 0 - BuildStatus_BUILD_STATUS_PENDING BuildStatus = 1 - BuildStatus_BUILD_STATUS_RUNNING BuildStatus = 2 - BuildStatus_BUILD_STATUS_SUCCEEDED BuildStatus = 3 - BuildStatus_BUILD_STATUS_FAILED BuildStatus = 4 - BuildStatus_BUILD_STATUS_CANCELLED BuildStatus = 5 -) - -// Enum value maps for BuildStatus. -var ( - BuildStatus_name = map[int32]string{ - 0: "BUILD_STATUS_UNSPECIFIED", - 1: "BUILD_STATUS_PENDING", - 2: "BUILD_STATUS_RUNNING", - 3: "BUILD_STATUS_SUCCEEDED", - 4: "BUILD_STATUS_FAILED", - 5: "BUILD_STATUS_CANCELLED", - } - BuildStatus_value = map[string]int32{ - "BUILD_STATUS_UNSPECIFIED": 0, - "BUILD_STATUS_PENDING": 1, - "BUILD_STATUS_RUNNING": 2, - "BUILD_STATUS_SUCCEEDED": 3, - "BUILD_STATUS_FAILED": 4, - "BUILD_STATUS_CANCELLED": 5, - } -) - -func (x BuildStatus) Enum() *BuildStatus { - p := new(BuildStatus) - *p = x - return p -} - -func (x BuildStatus) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (BuildStatus) Descriptor() protoreflect.EnumDescriptor { - return file_ctrl_v1_build_proto_enumTypes[0].Descriptor() -} - -func (BuildStatus) Type() protoreflect.EnumType { - return &file_ctrl_v1_build_proto_enumTypes[0] -} - -func (x BuildStatus) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use BuildStatus.Descriptor instead. -func (BuildStatus) EnumDescriptor() ([]byte, []int) { - return file_ctrl_v1_build_proto_rawDescGZIP(), []int{0} -} - type CreateBuildRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - WorkspaceId string `protobuf:"bytes,1,opt,name=workspace_id,json=workspaceId,proto3" json:"workspace_id,omitempty"` - ProjectId string `protobuf:"bytes,2,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` - VersionId string `protobuf:"bytes,3,opt,name=version_id,json=versionId,proto3" json:"version_id,omitempty"` - DockerImage string `protobuf:"bytes,4,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + BuildContextPath string `protobuf:"bytes,1,opt,name=build_context_path,json=buildContextPath,proto3" json:"build_context_path,omitempty"` // S3 key of the uploaded tar file + DockerfilePath *string `protobuf:"bytes,2,opt,name=dockerfile_path,json=dockerfilePath,proto3,oneof" json:"dockerfile_path,omitempty"` // Path to Dockerfile within the tar + UnkeyProjectId string `protobuf:"bytes,3,opt,name=unkey_project_id,json=unkeyProjectId,proto3" json:"unkey_project_id,omitempty"` // Your internal user/project ID + DeploymentId string `protobuf:"bytes,4,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateBuildRequest) Reset() { @@ -121,39 +61,41 @@ func (*CreateBuildRequest) Descriptor() ([]byte, []int) { return file_ctrl_v1_build_proto_rawDescGZIP(), []int{0} } -func (x *CreateBuildRequest) GetWorkspaceId() string { +func (x *CreateBuildRequest) GetBuildContextPath() string { if x != nil { - return x.WorkspaceId + return x.BuildContextPath } return "" } -func (x *CreateBuildRequest) GetProjectId() string { - if x != nil { - return x.ProjectId +func (x *CreateBuildRequest) GetDockerfilePath() string { + if x != nil && x.DockerfilePath != nil { + return *x.DockerfilePath } return "" } -func (x *CreateBuildRequest) GetVersionId() string { +func (x *CreateBuildRequest) GetUnkeyProjectId() string { if x != nil { - return x.VersionId + return x.UnkeyProjectId } return "" } -func (x *CreateBuildRequest) GetDockerImage() string { +func (x *CreateBuildRequest) GetDeploymentId() string { if x != nil { - return x.DockerImage + return x.DeploymentId } return "" } type CreateBuildResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ImageName string `protobuf:"bytes,1,opt,name=image_name,json=imageName,proto3" json:"image_name,omitempty"` // Full image tag (registry.depot.dev/project:tag) + BuildId string `protobuf:"bytes,2,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"` // Depot build ID for tracking + DepotProjectId string `protobuf:"bytes,3,opt,name=depot_project_id,json=depotProjectId,proto3" json:"depot_project_id,omitempty"` // Depot project ID + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateBuildResponse) Reset() { @@ -186,6 +128,13 @@ func (*CreateBuildResponse) Descriptor() ([]byte, []int) { return file_ctrl_v1_build_proto_rawDescGZIP(), []int{1} } +func (x *CreateBuildResponse) GetImageName() string { + if x != nil { + return x.ImageName + } + return "" +} + func (x *CreateBuildResponse) GetBuildId() string { if x != nil { return x.BuildId @@ -193,27 +142,34 @@ func (x *CreateBuildResponse) GetBuildId() string { return "" } -type GetBuildRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - BuildId string `protobuf:"bytes,1,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *CreateBuildResponse) GetDepotProjectId() string { + if x != nil { + return x.DepotProjectId + } + return "" +} + +type GenerateUploadURLRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UnkeyProjectId string `protobuf:"bytes,1,opt,name=unkey_project_id,json=unkeyProjectId,proto3" json:"unkey_project_id,omitempty"` // Your internal user/project ID + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetBuildRequest) Reset() { - *x = GetBuildRequest{} +func (x *GenerateUploadURLRequest) Reset() { + *x = GenerateUploadURLRequest{} mi := &file_ctrl_v1_build_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetBuildRequest) String() string { +func (x *GenerateUploadURLRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetBuildRequest) ProtoMessage() {} +func (*GenerateUploadURLRequest) ProtoMessage() {} -func (x *GetBuildRequest) ProtoReflect() protoreflect.Message { +func (x *GenerateUploadURLRequest) ProtoReflect() protoreflect.Message { mi := &file_ctrl_v1_build_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -225,39 +181,41 @@ func (x *GetBuildRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBuildRequest.ProtoReflect.Descriptor instead. -func (*GetBuildRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GenerateUploadURLRequest.ProtoReflect.Descriptor instead. +func (*GenerateUploadURLRequest) Descriptor() ([]byte, []int) { return file_ctrl_v1_build_proto_rawDescGZIP(), []int{2} } -func (x *GetBuildRequest) GetBuildId() string { +func (x *GenerateUploadURLRequest) GetUnkeyProjectId() string { if x != nil { - return x.BuildId + return x.UnkeyProjectId } return "" } -type GetBuildResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Build *Build `protobuf:"bytes,1,opt,name=build,proto3" json:"build,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type GenerateUploadURLResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + UploadUrl string `protobuf:"bytes,1,opt,name=upload_url,json=uploadUrl,proto3" json:"upload_url,omitempty"` // Presigned PUT URL + BuildContextPath string `protobuf:"bytes,2,opt,name=build_context_path,json=buildContextPath,proto3" json:"build_context_path,omitempty"` // S3 key to use in CreateBuild + ExpiresIn int64 `protobuf:"varint,3,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` // Seconds until URL expires + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *GetBuildResponse) Reset() { - *x = GetBuildResponse{} +func (x *GenerateUploadURLResponse) Reset() { + *x = GenerateUploadURLResponse{} mi := &file_ctrl_v1_build_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetBuildResponse) String() string { +func (x *GenerateUploadURLResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetBuildResponse) ProtoMessage() {} +func (*GenerateUploadURLResponse) ProtoMessage() {} -func (x *GetBuildResponse) ProtoReflect() protoreflect.Message { +func (x *GenerateUploadURLResponse) ProtoReflect() protoreflect.Message { mi := &file_ctrl_v1_build_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -269,191 +227,59 @@ func (x *GetBuildResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBuildResponse.ProtoReflect.Descriptor instead. -func (*GetBuildResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GenerateUploadURLResponse.ProtoReflect.Descriptor instead. +func (*GenerateUploadURLResponse) Descriptor() ([]byte, []int) { return file_ctrl_v1_build_proto_rawDescGZIP(), []int{3} } -func (x *GetBuildResponse) GetBuild() *Build { - if x != nil { - return x.Build - } - return nil -} - -type Build struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - WorkspaceId string `protobuf:"bytes,2,opt,name=workspace_id,json=workspaceId,proto3" json:"workspace_id,omitempty"` - ProjectId string `protobuf:"bytes,3,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` - VersionId string `protobuf:"bytes,4,opt,name=version_id,json=versionId,proto3" json:"version_id,omitempty"` - // Build details - Status BuildStatus `protobuf:"varint,5,opt,name=status,proto3,enum=ctrl.v1.BuildStatus" json:"status,omitempty"` - ErrorMessage string `protobuf:"bytes,6,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` // For failed builds - // Timestamps - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - StartedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` - CompletedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - // Build metadata - RootfsImageId string `protobuf:"bytes,11,opt,name=rootfs_image_id,json=rootfsImageId,proto3" json:"rootfs_image_id,omitempty"` // Output rootfs image - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Build) Reset() { - *x = Build{} - mi := &file_ctrl_v1_build_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Build) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Build) ProtoMessage() {} - -func (x *Build) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_build_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Build.ProtoReflect.Descriptor instead. -func (*Build) Descriptor() ([]byte, []int) { - return file_ctrl_v1_build_proto_rawDescGZIP(), []int{4} -} - -func (x *Build) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Build) GetWorkspaceId() string { - if x != nil { - return x.WorkspaceId - } - return "" -} - -func (x *Build) GetProjectId() string { +func (x *GenerateUploadURLResponse) GetUploadUrl() string { if x != nil { - return x.ProjectId + return x.UploadUrl } return "" } -func (x *Build) GetVersionId() string { - if x != nil { - return x.VersionId - } - return "" -} - -func (x *Build) GetStatus() BuildStatus { - if x != nil { - return x.Status - } - return BuildStatus_BUILD_STATUS_UNSPECIFIED -} - -func (x *Build) GetErrorMessage() string { +func (x *GenerateUploadURLResponse) GetBuildContextPath() string { if x != nil { - return x.ErrorMessage + return x.BuildContextPath } return "" } -func (x *Build) GetCreatedAt() *timestamppb.Timestamp { - if x != nil { - return x.CreatedAt - } - return nil -} - -func (x *Build) GetStartedAt() *timestamppb.Timestamp { - if x != nil { - return x.StartedAt - } - return nil -} - -func (x *Build) GetCompletedAt() *timestamppb.Timestamp { - if x != nil { - return x.CompletedAt - } - return nil -} - -func (x *Build) GetUpdatedAt() *timestamppb.Timestamp { - if x != nil { - return x.UpdatedAt - } - return nil -} - -func (x *Build) GetRootfsImageId() string { +func (x *GenerateUploadURLResponse) GetExpiresIn() int64 { if x != nil { - return x.RootfsImageId + return x.ExpiresIn } - return "" + return 0 } var File_ctrl_v1_build_proto protoreflect.FileDescriptor const file_ctrl_v1_build_proto_rawDesc = "" + "\n" + - "\x13ctrl/v1/build.proto\x12\actrl.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x98\x01\n" + - "\x12CreateBuildRequest\x12!\n" + - "\fworkspace_id\x18\x01 \x01(\tR\vworkspaceId\x12\x1d\n" + - "\n" + - "project_id\x18\x02 \x01(\tR\tprojectId\x12\x1d\n" + - "\n" + - "version_id\x18\x03 \x01(\tR\tversionId\x12!\n" + - "\fdocker_image\x18\x04 \x01(\tR\vdockerImage\"0\n" + - "\x13CreateBuildResponse\x12\x19\n" + - "\bbuild_id\x18\x01 \x01(\tR\abuildId\",\n" + - "\x0fGetBuildRequest\x12\x19\n" + - "\bbuild_id\x18\x01 \x01(\tR\abuildId\"8\n" + - "\x10GetBuildResponse\x12$\n" + - "\x05build\x18\x01 \x01(\v2\x0e.ctrl.v1.BuildR\x05build\"\xe3\x03\n" + - "\x05Build\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12!\n" + - "\fworkspace_id\x18\x02 \x01(\tR\vworkspaceId\x12\x1d\n" + - "\n" + - "project_id\x18\x03 \x01(\tR\tprojectId\x12\x1d\n" + - "\n" + - "version_id\x18\x04 \x01(\tR\tversionId\x12,\n" + - "\x06status\x18\x05 \x01(\x0e2\x14.ctrl.v1.BuildStatusR\x06status\x12#\n" + - "\rerror_message\x18\x06 \x01(\tR\ferrorMessage\x129\n" + + "\x13ctrl/v1/build.proto\x12\actrl.v1\"\xd3\x01\n" + + "\x12CreateBuildRequest\x12,\n" + + "\x12build_context_path\x18\x01 \x01(\tR\x10buildContextPath\x12,\n" + + "\x0fdockerfile_path\x18\x02 \x01(\tH\x00R\x0edockerfilePath\x88\x01\x01\x12(\n" + + "\x10unkey_project_id\x18\x03 \x01(\tR\x0eunkeyProjectId\x12#\n" + + "\rdeployment_id\x18\x04 \x01(\tR\fdeploymentIdB\x12\n" + + "\x10_dockerfile_path\"y\n" + + "\x13CreateBuildResponse\x12\x1d\n" + "\n" + - "created_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "image_name\x18\x01 \x01(\tR\timageName\x12\x19\n" + + "\bbuild_id\x18\x02 \x01(\tR\abuildId\x12(\n" + + "\x10depot_project_id\x18\x03 \x01(\tR\x0edepotProjectId\"D\n" + + "\x18GenerateUploadURLRequest\x12(\n" + + "\x10unkey_project_id\x18\x01 \x01(\tR\x0eunkeyProjectId\"\x87\x01\n" + + "\x19GenerateUploadURLResponse\x12\x1d\n" + "\n" + - "started_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" + - "\fcompleted_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\x129\n" + + "upload_url\x18\x01 \x01(\tR\tuploadUrl\x12,\n" + + "\x12build_context_path\x18\x02 \x01(\tR\x10buildContextPath\x12\x1d\n" + "\n" + - "updated_at\x18\n" + - " \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12&\n" + - "\x0frootfs_image_id\x18\v \x01(\tR\rrootfsImageId*\xb0\x01\n" + - "\vBuildStatus\x12\x1c\n" + - "\x18BUILD_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + - "\x14BUILD_STATUS_PENDING\x10\x01\x12\x18\n" + - "\x14BUILD_STATUS_RUNNING\x10\x02\x12\x1a\n" + - "\x16BUILD_STATUS_SUCCEEDED\x10\x03\x12\x17\n" + - "\x13BUILD_STATUS_FAILED\x10\x04\x12\x1a\n" + - "\x16BUILD_STATUS_CANCELLED\x10\x052\x9d\x01\n" + + "expires_in\x18\x03 \x01(\x03R\texpiresIn2\xb8\x01\n" + "\fBuildService\x12J\n" + - "\vCreateBuild\x12\x1b.ctrl.v1.CreateBuildRequest\x1a\x1c.ctrl.v1.CreateBuildResponse\"\x00\x12A\n" + - "\bGetBuild\x12\x18.ctrl.v1.GetBuildRequest\x1a\x19.ctrl.v1.GetBuildResponse\"\x00B\x8c\x01\n" + + "\vCreateBuild\x12\x1b.ctrl.v1.CreateBuildRequest\x1a\x1c.ctrl.v1.CreateBuildResponse\"\x00\x12\\\n" + + "\x11GenerateUploadURL\x12!.ctrl.v1.GenerateUploadURLRequest\x1a\".ctrl.v1.GenerateUploadURLResponse\"\x00B\x8c\x01\n" + "\vcom.ctrl.v1B\n" + "BuildProtoP\x01Z4github.com/unkeyed/unkey/go/gen/proto/ctrl/v1;ctrlv1\xa2\x02\x03CXX\xaa\x02\aCtrl.V1\xca\x02\aCtrl\\V1\xe2\x02\x13Ctrl\\V1\\GPBMetadata\xea\x02\bCtrl::V1b\x06proto3" @@ -469,33 +295,23 @@ func file_ctrl_v1_build_proto_rawDescGZIP() []byte { return file_ctrl_v1_build_proto_rawDescData } -var file_ctrl_v1_build_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_ctrl_v1_build_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_ctrl_v1_build_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_ctrl_v1_build_proto_goTypes = []any{ - (BuildStatus)(0), // 0: ctrl.v1.BuildStatus - (*CreateBuildRequest)(nil), // 1: ctrl.v1.CreateBuildRequest - (*CreateBuildResponse)(nil), // 2: ctrl.v1.CreateBuildResponse - (*GetBuildRequest)(nil), // 3: ctrl.v1.GetBuildRequest - (*GetBuildResponse)(nil), // 4: ctrl.v1.GetBuildResponse - (*Build)(nil), // 5: ctrl.v1.Build - (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*CreateBuildRequest)(nil), // 0: ctrl.v1.CreateBuildRequest + (*CreateBuildResponse)(nil), // 1: ctrl.v1.CreateBuildResponse + (*GenerateUploadURLRequest)(nil), // 2: ctrl.v1.GenerateUploadURLRequest + (*GenerateUploadURLResponse)(nil), // 3: ctrl.v1.GenerateUploadURLResponse } var file_ctrl_v1_build_proto_depIdxs = []int32{ - 5, // 0: ctrl.v1.GetBuildResponse.build:type_name -> ctrl.v1.Build - 0, // 1: ctrl.v1.Build.status:type_name -> ctrl.v1.BuildStatus - 6, // 2: ctrl.v1.Build.created_at:type_name -> google.protobuf.Timestamp - 6, // 3: ctrl.v1.Build.started_at:type_name -> google.protobuf.Timestamp - 6, // 4: ctrl.v1.Build.completed_at:type_name -> google.protobuf.Timestamp - 6, // 5: ctrl.v1.Build.updated_at:type_name -> google.protobuf.Timestamp - 1, // 6: ctrl.v1.BuildService.CreateBuild:input_type -> ctrl.v1.CreateBuildRequest - 3, // 7: ctrl.v1.BuildService.GetBuild:input_type -> ctrl.v1.GetBuildRequest - 2, // 8: ctrl.v1.BuildService.CreateBuild:output_type -> ctrl.v1.CreateBuildResponse - 4, // 9: ctrl.v1.BuildService.GetBuild:output_type -> ctrl.v1.GetBuildResponse - 8, // [8:10] is the sub-list for method output_type - 6, // [6:8] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 0, // 0: ctrl.v1.BuildService.CreateBuild:input_type -> ctrl.v1.CreateBuildRequest + 2, // 1: ctrl.v1.BuildService.GenerateUploadURL:input_type -> ctrl.v1.GenerateUploadURLRequest + 1, // 2: ctrl.v1.BuildService.CreateBuild:output_type -> ctrl.v1.CreateBuildResponse + 3, // 3: ctrl.v1.BuildService.GenerateUploadURL:output_type -> ctrl.v1.GenerateUploadURLResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_ctrl_v1_build_proto_init() } @@ -503,19 +319,19 @@ func file_ctrl_v1_build_proto_init() { if File_ctrl_v1_build_proto != nil { return } + file_ctrl_v1_build_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ctrl_v1_build_proto_rawDesc), len(file_ctrl_v1_build_proto_rawDesc)), - NumEnums: 1, - NumMessages: 5, + NumEnums: 0, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, GoTypes: file_ctrl_v1_build_proto_goTypes, DependencyIndexes: file_ctrl_v1_build_proto_depIdxs, - EnumInfos: file_ctrl_v1_build_proto_enumTypes, MessageInfos: file_ctrl_v1_build_proto_msgTypes, }.Build() File_ctrl_v1_build_proto = out.File diff --git a/go/gen/proto/ctrl/v1/ctrlv1connect/build.connect.go b/go/gen/proto/ctrl/v1/ctrlv1connect/build.connect.go index e4f3d5e321..2ace79e3f5 100644 --- a/go/gen/proto/ctrl/v1/ctrlv1connect/build.connect.go +++ b/go/gen/proto/ctrl/v1/ctrlv1connect/build.connect.go @@ -36,16 +36,15 @@ const ( // BuildServiceCreateBuildProcedure is the fully-qualified name of the BuildService's CreateBuild // RPC. BuildServiceCreateBuildProcedure = "/ctrl.v1.BuildService/CreateBuild" - // BuildServiceGetBuildProcedure is the fully-qualified name of the BuildService's GetBuild RPC. - BuildServiceGetBuildProcedure = "/ctrl.v1.BuildService/GetBuild" + // BuildServiceGenerateUploadURLProcedure is the fully-qualified name of the BuildService's + // GenerateUploadURL RPC. + BuildServiceGenerateUploadURLProcedure = "/ctrl.v1.BuildService/GenerateUploadURL" ) // BuildServiceClient is a client for the ctrl.v1.BuildService service. type BuildServiceClient interface { - // Create a new build CreateBuild(context.Context, *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error) - // Get build details - GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) + GenerateUploadURL(context.Context, *connect.Request[v1.GenerateUploadURLRequest]) (*connect.Response[v1.GenerateUploadURLResponse], error) } // NewBuildServiceClient constructs a client for the ctrl.v1.BuildService service. By default, it @@ -65,10 +64,10 @@ func NewBuildServiceClient(httpClient connect.HTTPClient, baseURL string, opts . connect.WithSchema(buildServiceMethods.ByName("CreateBuild")), connect.WithClientOptions(opts...), ), - getBuild: connect.NewClient[v1.GetBuildRequest, v1.GetBuildResponse]( + generateUploadURL: connect.NewClient[v1.GenerateUploadURLRequest, v1.GenerateUploadURLResponse]( httpClient, - baseURL+BuildServiceGetBuildProcedure, - connect.WithSchema(buildServiceMethods.ByName("GetBuild")), + baseURL+BuildServiceGenerateUploadURLProcedure, + connect.WithSchema(buildServiceMethods.ByName("GenerateUploadURL")), connect.WithClientOptions(opts...), ), } @@ -76,8 +75,8 @@ func NewBuildServiceClient(httpClient connect.HTTPClient, baseURL string, opts . // buildServiceClient implements BuildServiceClient. type buildServiceClient struct { - createBuild *connect.Client[v1.CreateBuildRequest, v1.CreateBuildResponse] - getBuild *connect.Client[v1.GetBuildRequest, v1.GetBuildResponse] + createBuild *connect.Client[v1.CreateBuildRequest, v1.CreateBuildResponse] + generateUploadURL *connect.Client[v1.GenerateUploadURLRequest, v1.GenerateUploadURLResponse] } // CreateBuild calls ctrl.v1.BuildService.CreateBuild. @@ -85,17 +84,15 @@ func (c *buildServiceClient) CreateBuild(ctx context.Context, req *connect.Reque return c.createBuild.CallUnary(ctx, req) } -// GetBuild calls ctrl.v1.BuildService.GetBuild. -func (c *buildServiceClient) GetBuild(ctx context.Context, req *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) { - return c.getBuild.CallUnary(ctx, req) +// GenerateUploadURL calls ctrl.v1.BuildService.GenerateUploadURL. +func (c *buildServiceClient) GenerateUploadURL(ctx context.Context, req *connect.Request[v1.GenerateUploadURLRequest]) (*connect.Response[v1.GenerateUploadURLResponse], error) { + return c.generateUploadURL.CallUnary(ctx, req) } // BuildServiceHandler is an implementation of the ctrl.v1.BuildService service. type BuildServiceHandler interface { - // Create a new build CreateBuild(context.Context, *connect.Request[v1.CreateBuildRequest]) (*connect.Response[v1.CreateBuildResponse], error) - // Get build details - GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) + GenerateUploadURL(context.Context, *connect.Request[v1.GenerateUploadURLRequest]) (*connect.Response[v1.GenerateUploadURLResponse], error) } // NewBuildServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -111,18 +108,18 @@ func NewBuildServiceHandler(svc BuildServiceHandler, opts ...connect.HandlerOpti connect.WithSchema(buildServiceMethods.ByName("CreateBuild")), connect.WithHandlerOptions(opts...), ) - buildServiceGetBuildHandler := connect.NewUnaryHandler( - BuildServiceGetBuildProcedure, - svc.GetBuild, - connect.WithSchema(buildServiceMethods.ByName("GetBuild")), + buildServiceGenerateUploadURLHandler := connect.NewUnaryHandler( + BuildServiceGenerateUploadURLProcedure, + svc.GenerateUploadURL, + connect.WithSchema(buildServiceMethods.ByName("GenerateUploadURL")), connect.WithHandlerOptions(opts...), ) return "/ctrl.v1.BuildService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case BuildServiceCreateBuildProcedure: buildServiceCreateBuildHandler.ServeHTTP(w, r) - case BuildServiceGetBuildProcedure: - buildServiceGetBuildHandler.ServeHTTP(w, r) + case BuildServiceGenerateUploadURLProcedure: + buildServiceGenerateUploadURLHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -136,6 +133,6 @@ func (UnimplementedBuildServiceHandler) CreateBuild(context.Context, *connect.Re return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ctrl.v1.BuildService.CreateBuild is not implemented")) } -func (UnimplementedBuildServiceHandler) GetBuild(context.Context, *connect.Request[v1.GetBuildRequest]) (*connect.Response[v1.GetBuildResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ctrl.v1.BuildService.GetBuild is not implemented")) +func (UnimplementedBuildServiceHandler) GenerateUploadURL(context.Context, *connect.Request[v1.GenerateUploadURLRequest]) (*connect.Response[v1.GenerateUploadURLResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ctrl.v1.BuildService.GenerateUploadURL is not implemented")) } diff --git a/go/gen/proto/ctrl/v1/deployment.pb.go b/go/gen/proto/ctrl/v1/deployment.pb.go index 514f444e0d..2240973b83 100644 --- a/go/gen/proto/ctrl/v1/deployment.pb.go +++ b/go/gen/proto/ctrl/v1/deployment.pb.go @@ -134,22 +134,21 @@ func (SourceType) EnumDescriptor() ([]byte, []int) { } type CreateDeploymentRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProjectId string `protobuf:"bytes,2,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` - Branch string `protobuf:"bytes,3,opt,name=branch,proto3" json:"branch,omitempty"` - // Source information - EnvironmentSlug string `protobuf:"bytes,4,opt,name=environment_slug,json=environmentSlug,proto3" json:"environment_slug,omitempty"` - SourceType SourceType `protobuf:"varint,5,opt,name=source_type,json=sourceType,proto3,enum=ctrl.v1.SourceType" json:"source_type,omitempty"` - DockerImage string `protobuf:"bytes,6,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"` - // Extended git information - GitCommitSha string `protobuf:"bytes,7,opt,name=git_commit_sha,json=gitCommitSha,proto3" json:"git_commit_sha,omitempty"` // For git sources - GitCommitMessage string `protobuf:"bytes,8,opt,name=git_commit_message,json=gitCommitMessage,proto3" json:"git_commit_message,omitempty"` - // TODO: Add GitHub API integration to lookup username/avatar from email - GitCommitAuthorHandle string `protobuf:"bytes,9,opt,name=git_commit_author_handle,json=gitCommitAuthorHandle,proto3" json:"git_commit_author_handle,omitempty"` - GitCommitAuthorAvatarUrl string `protobuf:"bytes,10,opt,name=git_commit_author_avatar_url,json=gitCommitAuthorAvatarUrl,proto3" json:"git_commit_author_avatar_url,omitempty"` - GitCommitTimestamp int64 `protobuf:"varint,11,opt,name=git_commit_timestamp,json=gitCommitTimestamp,proto3" json:"git_commit_timestamp,omitempty"` // Unix epoch milliseconds - // Keyspace ID for authentication - KeyspaceId *string `protobuf:"bytes,12,opt,name=keyspace_id,json=keyspaceId,proto3,oneof" json:"keyspace_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ProjectId string `protobuf:"bytes,2,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` + Branch string `protobuf:"bytes,3,opt,name=branch,proto3" json:"branch,omitempty"` + EnvironmentSlug string `protobuf:"bytes,4,opt,name=environment_slug,json=environmentSlug,proto3" json:"environment_slug,omitempty"` + // Build source, we can either build it from scratch or accept prebuilt image + // + // Types that are valid to be assigned to Source: + // + // *CreateDeploymentRequest_BuildContext + // *CreateDeploymentRequest_DockerImage + Source isCreateDeploymentRequest_Source `protobuf_oneof:"source"` + // Git information + GitCommit *GitCommitInfo `protobuf:"bytes,7,opt,name=git_commit,json=gitCommit,proto3,oneof" json:"git_commit,omitempty"` + // Authentication + KeyspaceId *string `protobuf:"bytes,8,opt,name=keyspace_id,json=keyspaceId,proto3,oneof" json:"keyspace_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -205,62 +204,189 @@ func (x *CreateDeploymentRequest) GetEnvironmentSlug() string { return "" } -func (x *CreateDeploymentRequest) GetSourceType() SourceType { +func (x *CreateDeploymentRequest) GetSource() isCreateDeploymentRequest_Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *CreateDeploymentRequest) GetBuildContext() *BuildContext { if x != nil { - return x.SourceType + if x, ok := x.Source.(*CreateDeploymentRequest_BuildContext); ok { + return x.BuildContext + } } - return SourceType_SOURCE_TYPE_UNSPECIFIED + return nil } func (x *CreateDeploymentRequest) GetDockerImage() string { if x != nil { - return x.DockerImage + if x, ok := x.Source.(*CreateDeploymentRequest_DockerImage); ok { + return x.DockerImage + } } return "" } -func (x *CreateDeploymentRequest) GetGitCommitSha() string { +func (x *CreateDeploymentRequest) GetGitCommit() *GitCommitInfo { if x != nil { - return x.GitCommitSha + return x.GitCommit + } + return nil +} + +func (x *CreateDeploymentRequest) GetKeyspaceId() string { + if x != nil && x.KeyspaceId != nil { + return *x.KeyspaceId } return "" } -func (x *CreateDeploymentRequest) GetGitCommitMessage() string { +type isCreateDeploymentRequest_Source interface { + isCreateDeploymentRequest_Source() +} + +type CreateDeploymentRequest_BuildContext struct { + BuildContext *BuildContext `protobuf:"bytes,5,opt,name=build_context,json=buildContext,proto3,oneof"` +} + +type CreateDeploymentRequest_DockerImage struct { + DockerImage string `protobuf:"bytes,6,opt,name=docker_image,json=dockerImage,proto3,oneof"` // Prebuilt image reference +} + +func (*CreateDeploymentRequest_BuildContext) isCreateDeploymentRequest_Source() {} + +func (*CreateDeploymentRequest_DockerImage) isCreateDeploymentRequest_Source() {} + +type BuildContext struct { + state protoimpl.MessageState `protogen:"open.v1"` + BuildContextPath string `protobuf:"bytes,1,opt,name=build_context_path,json=buildContextPath,proto3" json:"build_context_path,omitempty"` // S3 key for uploaded build context + DockerfilePath *string `protobuf:"bytes,2,opt,name=dockerfile_path,json=dockerfilePath,proto3,oneof" json:"dockerfile_path,omitempty"` // Path to Dockerfile within context (default: "Dockerfile") + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BuildContext) Reset() { + *x = BuildContext{} + mi := &file_ctrl_v1_deployment_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BuildContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BuildContext) ProtoMessage() {} + +func (x *BuildContext) ProtoReflect() protoreflect.Message { + mi := &file_ctrl_v1_deployment_proto_msgTypes[1] if x != nil { - return x.GitCommitMessage + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BuildContext.ProtoReflect.Descriptor instead. +func (*BuildContext) Descriptor() ([]byte, []int) { + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{1} +} + +func (x *BuildContext) GetBuildContextPath() string { + if x != nil { + return x.BuildContextPath + } + return "" +} + +func (x *BuildContext) GetDockerfilePath() string { + if x != nil && x.DockerfilePath != nil { + return *x.DockerfilePath } return "" } -func (x *CreateDeploymentRequest) GetGitCommitAuthorHandle() string { +type GitCommitInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + CommitSha string `protobuf:"bytes,1,opt,name=commit_sha,json=commitSha,proto3" json:"commit_sha,omitempty"` + CommitMessage string `protobuf:"bytes,2,opt,name=commit_message,json=commitMessage,proto3" json:"commit_message,omitempty"` + AuthorHandle string `protobuf:"bytes,3,opt,name=author_handle,json=authorHandle,proto3" json:"author_handle,omitempty"` + AuthorAvatarUrl string `protobuf:"bytes,4,opt,name=author_avatar_url,json=authorAvatarUrl,proto3" json:"author_avatar_url,omitempty"` + Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Unix epoch milliseconds + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GitCommitInfo) Reset() { + *x = GitCommitInfo{} + mi := &file_ctrl_v1_deployment_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GitCommitInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GitCommitInfo) ProtoMessage() {} + +func (x *GitCommitInfo) ProtoReflect() protoreflect.Message { + mi := &file_ctrl_v1_deployment_proto_msgTypes[2] if x != nil { - return x.GitCommitAuthorHandle + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GitCommitInfo.ProtoReflect.Descriptor instead. +func (*GitCommitInfo) Descriptor() ([]byte, []int) { + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{2} +} + +func (x *GitCommitInfo) GetCommitSha() string { + if x != nil { + return x.CommitSha } return "" } -func (x *CreateDeploymentRequest) GetGitCommitAuthorAvatarUrl() string { +func (x *GitCommitInfo) GetCommitMessage() string { if x != nil { - return x.GitCommitAuthorAvatarUrl + return x.CommitMessage } return "" } -func (x *CreateDeploymentRequest) GetGitCommitTimestamp() int64 { +func (x *GitCommitInfo) GetAuthorHandle() string { if x != nil { - return x.GitCommitTimestamp + return x.AuthorHandle } - return 0 + return "" } -func (x *CreateDeploymentRequest) GetKeyspaceId() string { - if x != nil && x.KeyspaceId != nil { - return *x.KeyspaceId +func (x *GitCommitInfo) GetAuthorAvatarUrl() string { + if x != nil { + return x.AuthorAvatarUrl } return "" } +func (x *GitCommitInfo) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + type CreateDeploymentResponse struct { state protoimpl.MessageState `protogen:"open.v1"` DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` @@ -271,7 +397,7 @@ type CreateDeploymentResponse struct { func (x *CreateDeploymentResponse) Reset() { *x = CreateDeploymentResponse{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[1] + mi := &file_ctrl_v1_deployment_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -283,7 +409,7 @@ func (x *CreateDeploymentResponse) String() string { func (*CreateDeploymentResponse) ProtoMessage() {} func (x *CreateDeploymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[1] + mi := &file_ctrl_v1_deployment_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -296,7 +422,7 @@ func (x *CreateDeploymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateDeploymentResponse.ProtoReflect.Descriptor instead. func (*CreateDeploymentResponse) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{1} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{3} } func (x *CreateDeploymentResponse) GetDeploymentId() string { @@ -322,7 +448,7 @@ type GetDeploymentRequest struct { func (x *GetDeploymentRequest) Reset() { *x = GetDeploymentRequest{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[2] + mi := &file_ctrl_v1_deployment_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -334,7 +460,7 @@ func (x *GetDeploymentRequest) String() string { func (*GetDeploymentRequest) ProtoMessage() {} func (x *GetDeploymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[2] + mi := &file_ctrl_v1_deployment_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -347,7 +473,7 @@ func (x *GetDeploymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDeploymentRequest.ProtoReflect.Descriptor instead. func (*GetDeploymentRequest) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{2} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{4} } func (x *GetDeploymentRequest) GetDeploymentId() string { @@ -366,7 +492,7 @@ type GetDeploymentResponse struct { func (x *GetDeploymentResponse) Reset() { *x = GetDeploymentResponse{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[3] + mi := &file_ctrl_v1_deployment_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -378,7 +504,7 @@ func (x *GetDeploymentResponse) String() string { func (*GetDeploymentResponse) ProtoMessage() {} func (x *GetDeploymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[3] + mi := &file_ctrl_v1_deployment_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -391,7 +517,7 @@ func (x *GetDeploymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDeploymentResponse.ProtoReflect.Descriptor instead. func (*GetDeploymentResponse) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{3} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{5} } func (x *GetDeploymentResponse) GetDeployment() *Deployment { @@ -439,7 +565,7 @@ type Deployment struct { func (x *Deployment) Reset() { *x = Deployment{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[4] + mi := &file_ctrl_v1_deployment_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -451,7 +577,7 @@ func (x *Deployment) String() string { func (*Deployment) ProtoMessage() {} func (x *Deployment) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[4] + mi := &file_ctrl_v1_deployment_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -464,7 +590,7 @@ func (x *Deployment) ProtoReflect() protoreflect.Message { // Deprecated: Use Deployment.ProtoReflect.Descriptor instead. func (*Deployment) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{4} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{6} } func (x *Deployment) GetId() string { @@ -619,7 +745,7 @@ type DeploymentStep struct { func (x *DeploymentStep) Reset() { *x = DeploymentStep{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[5] + mi := &file_ctrl_v1_deployment_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -631,7 +757,7 @@ func (x *DeploymentStep) String() string { func (*DeploymentStep) ProtoMessage() {} func (x *DeploymentStep) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[5] + mi := &file_ctrl_v1_deployment_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -644,7 +770,7 @@ func (x *DeploymentStep) ProtoReflect() protoreflect.Message { // Deprecated: Use DeploymentStep.ProtoReflect.Descriptor instead. func (*DeploymentStep) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{5} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{7} } func (x *DeploymentStep) GetStatus() string { @@ -691,7 +817,7 @@ type Topology struct { func (x *Topology) Reset() { *x = Topology{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[6] + mi := &file_ctrl_v1_deployment_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -703,7 +829,7 @@ func (x *Topology) String() string { func (*Topology) ProtoMessage() {} func (x *Topology) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[6] + mi := &file_ctrl_v1_deployment_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -716,7 +842,7 @@ func (x *Topology) ProtoReflect() protoreflect.Message { // Deprecated: Use Topology.ProtoReflect.Descriptor instead. func (*Topology) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{6} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{8} } func (x *Topology) GetCpuMillicores() int32 { @@ -772,7 +898,7 @@ type RegionalConfig struct { func (x *RegionalConfig) Reset() { *x = RegionalConfig{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[7] + mi := &file_ctrl_v1_deployment_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -784,7 +910,7 @@ func (x *RegionalConfig) String() string { func (*RegionalConfig) ProtoMessage() {} func (x *RegionalConfig) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[7] + mi := &file_ctrl_v1_deployment_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -797,7 +923,7 @@ func (x *RegionalConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RegionalConfig.ProtoReflect.Descriptor instead. func (*RegionalConfig) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{7} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{9} } func (x *RegionalConfig) GetRegion() string { @@ -831,7 +957,7 @@ type RollbackRequest struct { func (x *RollbackRequest) Reset() { *x = RollbackRequest{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[8] + mi := &file_ctrl_v1_deployment_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -843,7 +969,7 @@ func (x *RollbackRequest) String() string { func (*RollbackRequest) ProtoMessage() {} func (x *RollbackRequest) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[8] + mi := &file_ctrl_v1_deployment_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -856,7 +982,7 @@ func (x *RollbackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RollbackRequest.ProtoReflect.Descriptor instead. func (*RollbackRequest) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{8} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{10} } func (x *RollbackRequest) GetSourceDeploymentId() string { @@ -881,7 +1007,7 @@ type RollbackResponse struct { func (x *RollbackResponse) Reset() { *x = RollbackResponse{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[9] + mi := &file_ctrl_v1_deployment_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -893,7 +1019,7 @@ func (x *RollbackResponse) String() string { func (*RollbackResponse) ProtoMessage() {} func (x *RollbackResponse) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[9] + mi := &file_ctrl_v1_deployment_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -906,7 +1032,7 @@ func (x *RollbackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RollbackResponse.ProtoReflect.Descriptor instead. func (*RollbackResponse) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{9} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{11} } type PromoteRequest struct { @@ -918,7 +1044,7 @@ type PromoteRequest struct { func (x *PromoteRequest) Reset() { *x = PromoteRequest{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[10] + mi := &file_ctrl_v1_deployment_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -930,7 +1056,7 @@ func (x *PromoteRequest) String() string { func (*PromoteRequest) ProtoMessage() {} func (x *PromoteRequest) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[10] + mi := &file_ctrl_v1_deployment_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -943,7 +1069,7 @@ func (x *PromoteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteRequest.ProtoReflect.Descriptor instead. func (*PromoteRequest) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{10} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{12} } func (x *PromoteRequest) GetTargetDeploymentId() string { @@ -961,7 +1087,7 @@ type PromoteResponse struct { func (x *PromoteResponse) Reset() { *x = PromoteResponse{} - mi := &file_ctrl_v1_deployment_proto_msgTypes[11] + mi := &file_ctrl_v1_deployment_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -973,7 +1099,7 @@ func (x *PromoteResponse) String() string { func (*PromoteResponse) ProtoMessage() {} func (x *PromoteResponse) ProtoReflect() protoreflect.Message { - mi := &file_ctrl_v1_deployment_proto_msgTypes[11] + mi := &file_ctrl_v1_deployment_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -986,31 +1112,39 @@ func (x *PromoteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PromoteResponse.ProtoReflect.Descriptor instead. func (*PromoteResponse) Descriptor() ([]byte, []int) { - return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{11} + return file_ctrl_v1_deployment_proto_rawDescGZIP(), []int{13} } var File_ctrl_v1_deployment_proto protoreflect.FileDescriptor const file_ctrl_v1_deployment_proto_rawDesc = "" + "\n" + - "\x18ctrl/v1/deployment.proto\x12\actrl.v1\"\x8f\x04\n" + + "\x18ctrl/v1/deployment.proto\x12\actrl.v1\"\xef\x02\n" + "\x17CreateDeploymentRequest\x12\x1d\n" + "\n" + "project_id\x18\x02 \x01(\tR\tprojectId\x12\x16\n" + "\x06branch\x18\x03 \x01(\tR\x06branch\x12)\n" + - "\x10environment_slug\x18\x04 \x01(\tR\x0fenvironmentSlug\x124\n" + - "\vsource_type\x18\x05 \x01(\x0e2\x13.ctrl.v1.SourceTypeR\n" + - "sourceType\x12!\n" + - "\fdocker_image\x18\x06 \x01(\tR\vdockerImage\x12$\n" + - "\x0egit_commit_sha\x18\a \x01(\tR\fgitCommitSha\x12,\n" + - "\x12git_commit_message\x18\b \x01(\tR\x10gitCommitMessage\x127\n" + - "\x18git_commit_author_handle\x18\t \x01(\tR\x15gitCommitAuthorHandle\x12>\n" + - "\x1cgit_commit_author_avatar_url\x18\n" + - " \x01(\tR\x18gitCommitAuthorAvatarUrl\x120\n" + - "\x14git_commit_timestamp\x18\v \x01(\x03R\x12gitCommitTimestamp\x12$\n" + - "\vkeyspace_id\x18\f \x01(\tH\x00R\n" + - "keyspaceId\x88\x01\x01B\x0e\n" + - "\f_keyspace_idJ\x04\b\x01\x10\x02\"r\n" + + "\x10environment_slug\x18\x04 \x01(\tR\x0fenvironmentSlug\x12<\n" + + "\rbuild_context\x18\x05 \x01(\v2\x15.ctrl.v1.BuildContextH\x00R\fbuildContext\x12#\n" + + "\fdocker_image\x18\x06 \x01(\tH\x00R\vdockerImage\x12:\n" + + "\n" + + "git_commit\x18\a \x01(\v2\x16.ctrl.v1.GitCommitInfoH\x01R\tgitCommit\x88\x01\x01\x12$\n" + + "\vkeyspace_id\x18\b \x01(\tH\x02R\n" + + "keyspaceId\x88\x01\x01B\b\n" + + "\x06sourceB\r\n" + + "\v_git_commitB\x0e\n" + + "\f_keyspace_idJ\x04\b\x01\x10\x02\"~\n" + + "\fBuildContext\x12,\n" + + "\x12build_context_path\x18\x01 \x01(\tR\x10buildContextPath\x12,\n" + + "\x0fdockerfile_path\x18\x02 \x01(\tH\x00R\x0edockerfilePath\x88\x01\x01B\x12\n" + + "\x10_dockerfile_path\"\xc4\x01\n" + + "\rGitCommitInfo\x12\x1d\n" + + "\n" + + "commit_sha\x18\x01 \x01(\tR\tcommitSha\x12%\n" + + "\x0ecommit_message\x18\x02 \x01(\tR\rcommitMessage\x12#\n" + + "\rauthor_handle\x18\x03 \x01(\tR\fauthorHandle\x12*\n" + + "\x11author_avatar_url\x18\x04 \x01(\tR\x0fauthorAvatarUrl\x12\x1c\n" + + "\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\"r\n" + "\x18CreateDeploymentResponse\x12#\n" + "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\x121\n" + "\x06status\x18\x02 \x01(\x0e2\x19.ctrl.v1.DeploymentStatusR\x06status\";\n" + @@ -1107,46 +1241,49 @@ func file_ctrl_v1_deployment_proto_rawDescGZIP() []byte { } var file_ctrl_v1_deployment_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_ctrl_v1_deployment_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_ctrl_v1_deployment_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_ctrl_v1_deployment_proto_goTypes = []any{ (DeploymentStatus)(0), // 0: ctrl.v1.DeploymentStatus (SourceType)(0), // 1: ctrl.v1.SourceType (*CreateDeploymentRequest)(nil), // 2: ctrl.v1.CreateDeploymentRequest - (*CreateDeploymentResponse)(nil), // 3: ctrl.v1.CreateDeploymentResponse - (*GetDeploymentRequest)(nil), // 4: ctrl.v1.GetDeploymentRequest - (*GetDeploymentResponse)(nil), // 5: ctrl.v1.GetDeploymentResponse - (*Deployment)(nil), // 6: ctrl.v1.Deployment - (*DeploymentStep)(nil), // 7: ctrl.v1.DeploymentStep - (*Topology)(nil), // 8: ctrl.v1.Topology - (*RegionalConfig)(nil), // 9: ctrl.v1.RegionalConfig - (*RollbackRequest)(nil), // 10: ctrl.v1.RollbackRequest - (*RollbackResponse)(nil), // 11: ctrl.v1.RollbackResponse - (*PromoteRequest)(nil), // 12: ctrl.v1.PromoteRequest - (*PromoteResponse)(nil), // 13: ctrl.v1.PromoteResponse - nil, // 14: ctrl.v1.Deployment.EnvironmentVariablesEntry + (*BuildContext)(nil), // 3: ctrl.v1.BuildContext + (*GitCommitInfo)(nil), // 4: ctrl.v1.GitCommitInfo + (*CreateDeploymentResponse)(nil), // 5: ctrl.v1.CreateDeploymentResponse + (*GetDeploymentRequest)(nil), // 6: ctrl.v1.GetDeploymentRequest + (*GetDeploymentResponse)(nil), // 7: ctrl.v1.GetDeploymentResponse + (*Deployment)(nil), // 8: ctrl.v1.Deployment + (*DeploymentStep)(nil), // 9: ctrl.v1.DeploymentStep + (*Topology)(nil), // 10: ctrl.v1.Topology + (*RegionalConfig)(nil), // 11: ctrl.v1.RegionalConfig + (*RollbackRequest)(nil), // 12: ctrl.v1.RollbackRequest + (*RollbackResponse)(nil), // 13: ctrl.v1.RollbackResponse + (*PromoteRequest)(nil), // 14: ctrl.v1.PromoteRequest + (*PromoteResponse)(nil), // 15: ctrl.v1.PromoteResponse + nil, // 16: ctrl.v1.Deployment.EnvironmentVariablesEntry } var file_ctrl_v1_deployment_proto_depIdxs = []int32{ - 1, // 0: ctrl.v1.CreateDeploymentRequest.source_type:type_name -> ctrl.v1.SourceType - 0, // 1: ctrl.v1.CreateDeploymentResponse.status:type_name -> ctrl.v1.DeploymentStatus - 6, // 2: ctrl.v1.GetDeploymentResponse.deployment:type_name -> ctrl.v1.Deployment - 0, // 3: ctrl.v1.Deployment.status:type_name -> ctrl.v1.DeploymentStatus - 14, // 4: ctrl.v1.Deployment.environment_variables:type_name -> ctrl.v1.Deployment.EnvironmentVariablesEntry - 8, // 5: ctrl.v1.Deployment.topology:type_name -> ctrl.v1.Topology - 7, // 6: ctrl.v1.Deployment.steps:type_name -> ctrl.v1.DeploymentStep - 9, // 7: ctrl.v1.Topology.regions:type_name -> ctrl.v1.RegionalConfig - 2, // 8: ctrl.v1.DeploymentService.CreateDeployment:input_type -> ctrl.v1.CreateDeploymentRequest - 4, // 9: ctrl.v1.DeploymentService.GetDeployment:input_type -> ctrl.v1.GetDeploymentRequest - 10, // 10: ctrl.v1.DeploymentService.Rollback:input_type -> ctrl.v1.RollbackRequest - 12, // 11: ctrl.v1.DeploymentService.Promote:input_type -> ctrl.v1.PromoteRequest - 3, // 12: ctrl.v1.DeploymentService.CreateDeployment:output_type -> ctrl.v1.CreateDeploymentResponse - 5, // 13: ctrl.v1.DeploymentService.GetDeployment:output_type -> ctrl.v1.GetDeploymentResponse - 11, // 14: ctrl.v1.DeploymentService.Rollback:output_type -> ctrl.v1.RollbackResponse - 13, // 15: ctrl.v1.DeploymentService.Promote:output_type -> ctrl.v1.PromoteResponse - 12, // [12:16] is the sub-list for method output_type - 8, // [8:12] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 3, // 0: ctrl.v1.CreateDeploymentRequest.build_context:type_name -> ctrl.v1.BuildContext + 4, // 1: ctrl.v1.CreateDeploymentRequest.git_commit:type_name -> ctrl.v1.GitCommitInfo + 0, // 2: ctrl.v1.CreateDeploymentResponse.status:type_name -> ctrl.v1.DeploymentStatus + 8, // 3: ctrl.v1.GetDeploymentResponse.deployment:type_name -> ctrl.v1.Deployment + 0, // 4: ctrl.v1.Deployment.status:type_name -> ctrl.v1.DeploymentStatus + 16, // 5: ctrl.v1.Deployment.environment_variables:type_name -> ctrl.v1.Deployment.EnvironmentVariablesEntry + 10, // 6: ctrl.v1.Deployment.topology:type_name -> ctrl.v1.Topology + 9, // 7: ctrl.v1.Deployment.steps:type_name -> ctrl.v1.DeploymentStep + 11, // 8: ctrl.v1.Topology.regions:type_name -> ctrl.v1.RegionalConfig + 2, // 9: ctrl.v1.DeploymentService.CreateDeployment:input_type -> ctrl.v1.CreateDeploymentRequest + 6, // 10: ctrl.v1.DeploymentService.GetDeployment:input_type -> ctrl.v1.GetDeploymentRequest + 12, // 11: ctrl.v1.DeploymentService.Rollback:input_type -> ctrl.v1.RollbackRequest + 14, // 12: ctrl.v1.DeploymentService.Promote:input_type -> ctrl.v1.PromoteRequest + 5, // 13: ctrl.v1.DeploymentService.CreateDeployment:output_type -> ctrl.v1.CreateDeploymentResponse + 7, // 14: ctrl.v1.DeploymentService.GetDeployment:output_type -> ctrl.v1.GetDeploymentResponse + 13, // 15: ctrl.v1.DeploymentService.Rollback:output_type -> ctrl.v1.RollbackResponse + 15, // 16: ctrl.v1.DeploymentService.Promote:output_type -> ctrl.v1.PromoteResponse + 13, // [13:17] is the sub-list for method output_type + 9, // [9:13] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_ctrl_v1_deployment_proto_init() } @@ -1154,14 +1291,18 @@ func file_ctrl_v1_deployment_proto_init() { if File_ctrl_v1_deployment_proto != nil { return } - file_ctrl_v1_deployment_proto_msgTypes[0].OneofWrappers = []any{} + file_ctrl_v1_deployment_proto_msgTypes[0].OneofWrappers = []any{ + (*CreateDeploymentRequest_BuildContext)(nil), + (*CreateDeploymentRequest_DockerImage)(nil), + } + file_ctrl_v1_deployment_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ctrl_v1_deployment_proto_rawDesc), len(file_ctrl_v1_deployment_proto_rawDesc)), NumEnums: 2, - NumMessages: 13, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/go/gen/proto/hydra/v1/deployment.pb.go b/go/gen/proto/hydra/v1/deployment.pb.go index cd07d2c5b7..ffd58a9f91 100644 --- a/go/gen/proto/hydra/v1/deployment.pb.go +++ b/go/gen/proto/hydra/v1/deployment.pb.go @@ -23,12 +23,15 @@ const ( ) type DeployRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` - DockerImage string `protobuf:"bytes,2,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"` - KeyAuthId *string `protobuf:"bytes,3,opt,name=key_auth_id,json=keyAuthId,proto3,oneof" json:"key_auth_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` + KeyAuthId *string `protobuf:"bytes,2,opt,name=key_auth_id,json=keyAuthId,proto3,oneof" json:"key_auth_id,omitempty"` + // Build source fields, exactly one of (context_key, docker_image) must be set + BuildContextPath *string `protobuf:"bytes,3,opt,name=build_context_path,json=buildContextPath,proto3,oneof" json:"build_context_path,omitempty"` + DockerfilePath *string `protobuf:"bytes,4,opt,name=dockerfile_path,json=dockerfilePath,proto3,oneof" json:"dockerfile_path,omitempty"` + DockerImage *string `protobuf:"bytes,5,opt,name=docker_image,json=dockerImage,proto3,oneof" json:"docker_image,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeployRequest) Reset() { @@ -68,16 +71,30 @@ func (x *DeployRequest) GetDeploymentId() string { return "" } -func (x *DeployRequest) GetDockerImage() string { - if x != nil { - return x.DockerImage +func (x *DeployRequest) GetKeyAuthId() string { + if x != nil && x.KeyAuthId != nil { + return *x.KeyAuthId } return "" } -func (x *DeployRequest) GetKeyAuthId() string { - if x != nil && x.KeyAuthId != nil { - return *x.KeyAuthId +func (x *DeployRequest) GetBuildContextPath() string { + if x != nil && x.BuildContextPath != nil { + return *x.BuildContextPath + } + return "" +} + +func (x *DeployRequest) GetDockerfilePath() string { + if x != nil && x.DockerfilePath != nil { + return *x.DockerfilePath + } + return "" +} + +func (x *DeployRequest) GetDockerImage() string { + if x != nil && x.DockerImage != nil { + return *x.DockerImage } return "" } @@ -290,12 +307,17 @@ var File_hydra_v1_deployment_proto protoreflect.FileDescriptor const file_hydra_v1_deployment_proto_rawDesc = "" + "\n" + - "\x19hydra/v1/deployment.proto\x12\bhydra.v1\x1a\x18dev/restate/sdk/go.proto\"\x8c\x01\n" + + "\x19hydra/v1/deployment.proto\x12\bhydra.v1\x1a\x18dev/restate/sdk/go.proto\"\xae\x02\n" + "\rDeployRequest\x12#\n" + - "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\x12!\n" + - "\fdocker_image\x18\x02 \x01(\tR\vdockerImage\x12#\n" + - "\vkey_auth_id\x18\x03 \x01(\tH\x00R\tkeyAuthId\x88\x01\x01B\x0e\n" + - "\f_key_auth_id\"\x10\n" + + "\rdeployment_id\x18\x01 \x01(\tR\fdeploymentId\x12#\n" + + "\vkey_auth_id\x18\x02 \x01(\tH\x00R\tkeyAuthId\x88\x01\x01\x121\n" + + "\x12build_context_path\x18\x03 \x01(\tH\x01R\x10buildContextPath\x88\x01\x01\x12,\n" + + "\x0fdockerfile_path\x18\x04 \x01(\tH\x02R\x0edockerfilePath\x88\x01\x01\x12&\n" + + "\fdocker_image\x18\x05 \x01(\tH\x03R\vdockerImage\x88\x01\x01B\x0e\n" + + "\f_key_auth_idB\x15\n" + + "\x13_build_context_pathB\x12\n" + + "\x10_dockerfile_pathB\x0f\n" + + "\r_docker_image\"\x10\n" + "\x0eDeployResponse\"u\n" + "\x0fRollbackRequest\x120\n" + "\x14source_deployment_id\x18\x01 \x01(\tR\x12sourceDeploymentId\x120\n" + diff --git a/go/go.mod b/go/go.mod index 27e2cbe02b..90b12f5ea0 100644 --- a/go/go.mod +++ b/go/go.mod @@ -5,7 +5,9 @@ go 1.25 toolchain go1.25.1 require ( - connectrpc.com/connect v1.18.1 + buf.build/gen/go/depot/api/connectrpc/go v1.19.0-20250915125527-3af9e416de91.1 + buf.build/gen/go/depot/api/protocolbuffers/go v1.36.10-20250915125527-3af9e416de91.1 + connectrpc.com/connect v1.19.0 github.com/ClickHouse/clickhouse-go/v2 v2.40.1 github.com/aws/aws-sdk-go-v2 v1.36.6 github.com/aws/aws-sdk-go-v2/config v1.29.18 @@ -13,6 +15,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 github.com/btcsuite/btcutil v1.0.2 github.com/bufbuild/buf v1.57.0 + github.com/depot/depot-go v0.5.1 + github.com/docker/cli v28.4.0+incompatible github.com/docker/docker v28.4.0+incompatible github.com/docker/go-connections v0.6.0 github.com/getkin/kin-openapi v0.133.0 @@ -22,6 +26,7 @@ require ( github.com/golangci/golangci-lint/v2 v2.4.0 github.com/lmittmann/tint v1.1.1 github.com/maypok86/otter v1.2.4 + github.com/moby/buildkit v0.25.0 github.com/oapi-codegen/nullable v1.1.0 github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 github.com/oasdiff/oasdiff v1.11.4 @@ -145,9 +150,17 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect github.com/coder/websocket v1.8.12 // indirect + github.com/containerd/console v1.0.5 // indirect + github.com/containerd/containerd/api v1.9.0 // indirect + github.com/containerd/containerd/v2 v2.1.4 // indirect + github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cubicdaiya/gonp v1.0.4 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect @@ -158,7 +171,6 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/docker/cli v28.3.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -206,6 +218,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.3 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect @@ -219,6 +232,7 @@ require ( github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.20.6 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect @@ -226,10 +240,12 @@ require ( github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -279,6 +295,9 @@ require ( github.com/miekg/dns v1.1.67 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -308,6 +327,7 @@ require ( github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect @@ -335,10 +355,12 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect github.com/securego/gosec/v2 v2.22.7 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/encoding v0.5.3 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -370,6 +392,10 @@ require ( github.com/tklauser/numcpus v0.10.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect + github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect + github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect @@ -404,6 +430,8 @@ require ( go.lsp.dev/protocol v0.12.0 // indirect go.lsp.dev/uri v0.3.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect diff --git a/go/go.sum b/go/go.sum index 755beec890..8f7b1be8f5 100644 --- a/go/go.sum +++ b/go/go.sum @@ -10,6 +10,10 @@ buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250819211657-a3dd0d3e buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250819211657-a3dd0d3ea69b.1/go.mod h1:b3AXX8gmI0mFMkxKIFYXyga6bTno5bhiHsB559j1WGg= buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.8-20250819211657-a3dd0d3ea69b.1 h1:tcK+tM1vPYPhO+h20m0gO9mAyhxeKKvpLmFBKQExeEg= buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.8-20250819211657-a3dd0d3ea69b.1/go.mod h1:00R1Pr0ukHuneWSlz7I3GpenOl0hcLpj6AV8HMypndk= +buf.build/gen/go/depot/api/connectrpc/go v1.19.0-20250915125527-3af9e416de91.1 h1:pE/5MiKOvlpHdyutwgWboqHSQW4g/Qqo4hV6XhXujdc= +buf.build/gen/go/depot/api/connectrpc/go v1.19.0-20250915125527-3af9e416de91.1/go.mod h1:ojZkZqBkjf0NYURHq3MfoIwQCWRhP7fWJ4OYhsCEzqM= +buf.build/gen/go/depot/api/protocolbuffers/go v1.36.10-20250915125527-3af9e416de91.1 h1:CIV8+bpybDECz0wSEaWoeErOtQIlKsTbdG/AXAXbfD0= +buf.build/gen/go/depot/api/protocolbuffers/go v1.36.10-20250915125527-3af9e416de91.1/go.mod h1:uwFBBwta8N7weZkaVAdyWLFn0kX/vchbQZxVekqiUg4= buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.8-20241007202033-cf42259fcbfc.1 h1:KuP+b+in6LGh2ukof5KgDCD8hPXotEq6EVOo13Wg1pE= buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.8-20241007202033-cf42259fcbfc.1/go.mod h1:dV1Kz6zdmyXt7QWm5OXby44OFpyLemllUDBUG5HMLio= buf.build/go/app v0.1.0 h1:nlqD/h0rhIN73ZoiDElprrPiO2N6JV+RmNK34K29Ihg= @@ -34,8 +38,8 @@ cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= -connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= -connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= +connectrpc.com/connect v1.19.0 h1:LuqUbq01PqbtL0o7vn0WMRXzR2nNsiINe5zfcJ24pJM= +connectrpc.com/connect v1.19.0/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= connectrpc.com/otelconnect v0.7.2 h1:WlnwFzaW64dN06JXU+hREPUGeEzpz3Acz2ACOmN8cMI= connectrpc.com/otelconnect v0.7.2/go.mod h1:JS7XUKfuJs2adhCnXhNHPHLz6oAaZniCJdSF00OZSew= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= @@ -48,6 +52,8 @@ github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8 github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c= github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE= @@ -84,6 +90,8 @@ github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7r github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= @@ -107,6 +115,8 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -233,16 +243,40 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= +github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ= +github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg= +github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= +github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE= github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -266,14 +300,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/depot/depot-go v0.5.1 h1:Kdrsk8q7W2fQvoudWNjxsXG4ZbdlUAa6EV18udDnTFQ= +github.com/depot/depot-go v0.5.1/go.mod h1:QQtSqwRn0flx4KxrUVSJGlh0hTFeZ19MLYvOcJbxtP0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= -github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY= +github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= @@ -411,6 +447,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -471,6 +509,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -492,6 +532,8 @@ github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5uny github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= @@ -505,6 +547,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= +github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= @@ -628,12 +672,26 @@ github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/moby/buildkit v0.25.0 h1:cRgh74ymzyHxS5a/lsYT4OCyVU8iC3UgkwasIEUi0og= +github.com/moby/buildkit v0.25.0/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -702,6 +760,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= +github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -716,6 +778,8 @@ github.com/pb33f/libopenapi v0.22.2 h1:ChXG911vrr24KE7wzIib3eL8Td73ANFCNSpWf1C9h github.com/pb33f/libopenapi v0.22.2/go.mod h1:utT5sD2/mnN7YK68FfZT5yEPbI1wwRBpSS4Hi0oOrBU= github.com/pb33f/libopenapi-validator v0.4.6 h1:ESkSxqFnb3LwLyDShOYe0PlGEM+pXXMI0271+Ib/pFE= github.com/pb33f/libopenapi-validator v0.4.6/go.mod h1:NJaqqPxX2SX6kn+YTu+i588es/qIjP0vfGwK2NWg2Pw= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -738,6 +802,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -813,6 +879,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= +github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= +github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= github.com/securego/gosec/v2 v2.22.7 h1:8/9P+oTYI4yIpAzccQKVsg1/90Po+JzGtAhqoHImDeM= github.com/securego/gosec/v2 v2.22.7/go.mod h1:510TFNDMrIPytokyHQAVLvPeDr41Yihn2ak8P+XQfNE= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= @@ -824,6 +892,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -840,6 +910,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/speakeasy-api/jsonpath v0.6.2 h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoAtcEbqXQ= github.com/speakeasy-api/jsonpath v0.6.2/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= @@ -918,6 +990,14 @@ github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMS github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= @@ -1004,12 +1084,18 @@ go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDM go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 h1:EMIiYTms4Z4m3bBuKp1VmMNRLZcl6j4YbvOPL1IhlWo= go.opentelemetry.io/contrib/bridges/otelslog v0.11.0/go.mod h1:DIEZmUR7tzuOOVUTDKvkGWtYWSHFV18Qg8+GMb8wPJw= go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/processors/minsev v0.9.0 h1:eKlDcNp+GSygGk6PMJJyEdej+E1HteUy+KsY2YzaLbM= diff --git a/go/k8s/manifests/ctrl.yaml b/go/k8s/manifests/ctrl.yaml index 2a41d5e536..64ffffa419 100644 --- a/go/k8s/manifests/ctrl.yaml +++ b/go/k8s/manifests/ctrl.yaml @@ -67,7 +67,61 @@ spec: value: "minio_root_user" - name: UNKEY_VAULT_S3_ACCESS_KEY_SECRET value: "minio_root_password" - # Additional Configuration + # Build Configuration + - name: UNKEY_BUILD_BACKEND + value: "depot" + - name: UNKEY_BUILD_PLATFORM + value: "linux/amd64" + # Build S3 Storage (from depot-credentials secret) + #kubectl create secret generic depot-credentials \ + # --from-literal=token=xxxx \ + # --from-literal=s3-url=xxx \ + # --from-literal=s3-access-key-id=xxx \ + # --from-literal=s3-access-key-secret=xxx \ + # --namespace=unkey + - name: UNKEY_BUILD_S3_URL + valueFrom: + secretKeyRef: + name: depot-credentials + key: s3-url + - name: UNKEY_BUILD_S3_EXTERNAL_URL + valueFrom: + secretKeyRef: + name: depot-credentials + key: s3-url + - name: UNKEY_BUILD_S3_BUCKET + value: "build-contexts" + - name: UNKEY_BUILD_S3_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: depot-credentials + key: s3-access-key-id + - name: UNKEY_BUILD_S3_ACCESS_KEY_SECRET + valueFrom: + secretKeyRef: + name: depot-credentials + key: s3-access-key-secret + # Registry Configuration (used by both Docker and Depot backends) + #kubectl create secret docker-registry depot-registry \ + # --docker-server=registry.depot.dev \ + # --docker-username=x-token \ + # --docker-password=xxx \ + # --namespace=unkey + - name: UNKEY_REGISTRY_URL + value: "registry.depot.dev" + - name: UNKEY_REGISTRY_USERNAME + value: "x-token" + - name: UNKEY_REGISTRY_PASSWORD + valueFrom: + secretKeyRef: + name: depot-credentials + key: token + # Depot-Specific Configuration (only used when UNKEY_BUILD_BACKEND=depot) + - name: UNKEY_DEPOT_API_URL + value: "https://api.depot.dev" + - name: UNKEY_DEPOT_PROJECT_REGION + value: "us-east-1" + # ACME Configuration - name: UNKEY_ACME_ENABLED value: "false" @@ -84,7 +138,7 @@ spec: - name: UNKEY_RESTATE_REGISTER_AS value: "http://ctrl:9080" - # Additional Configuration + # API Key - name: UNKEY_API_KEY value: "your-local-dev-key" diff --git a/go/pkg/db/models_generated.go b/go/pkg/db/models_generated.go index ad35be5799..9d21ff614b 100644 --- a/go/pkg/db/models_generated.go +++ b/go/pkg/db/models_generated.go @@ -720,6 +720,7 @@ type Project struct { LiveDeploymentID sql.NullString `db:"live_deployment_id"` IsRolledBack bool `db:"is_rolled_back"` DefaultBranch sql.NullString `db:"default_branch"` + DepotProjectID sql.NullString `db:"depot_project_id"` DeleteProtection sql.NullBool `db:"delete_protection"` CreatedAt int64 `db:"created_at"` UpdatedAt sql.NullInt64 `db:"updated_at"` diff --git a/go/pkg/db/project_find_by_id.sql_generated.go b/go/pkg/db/project_find_by_id.sql_generated.go index 9fbea5e775..8ee74928a5 100644 --- a/go/pkg/db/project_find_by_id.sql_generated.go +++ b/go/pkg/db/project_find_by_id.sql_generated.go @@ -22,7 +22,8 @@ SELECT live_deployment_id, is_rolled_back, created_at, - updated_at + updated_at, + depot_project_id FROM projects WHERE id = ? ` @@ -39,6 +40,7 @@ type FindProjectByIdRow struct { IsRolledBack bool `db:"is_rolled_back"` CreatedAt int64 `db:"created_at"` UpdatedAt sql.NullInt64 `db:"updated_at"` + DepotProjectID sql.NullString `db:"depot_project_id"` } // FindProjectById @@ -54,7 +56,8 @@ type FindProjectByIdRow struct { // live_deployment_id, // is_rolled_back, // created_at, -// updated_at +// updated_at, +// depot_project_id // FROM projects // WHERE id = ? func (q *Queries) FindProjectById(ctx context.Context, db DBTX, id string) (FindProjectByIdRow, error) { @@ -72,6 +75,7 @@ func (q *Queries) FindProjectById(ctx context.Context, db DBTX, id string) (Find &i.IsRolledBack, &i.CreatedAt, &i.UpdatedAt, + &i.DepotProjectID, ) return i, err } diff --git a/go/pkg/db/project_update_depot_id.sql_generated.go b/go/pkg/db/project_update_depot_id.sql_generated.go new file mode 100644 index 0000000000..f422383145 --- /dev/null +++ b/go/pkg/db/project_update_depot_id.sql_generated.go @@ -0,0 +1,37 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: project_update_depot_id.sql + +package db + +import ( + "context" + "database/sql" +) + +const updateProjectDepotID = `-- name: UpdateProjectDepotID :exec +UPDATE projects +SET + depot_project_id = ?, + updated_at = ? +WHERE id = ? +` + +type UpdateProjectDepotIDParams struct { + DepotProjectID sql.NullString `db:"depot_project_id"` + UpdatedAt sql.NullInt64 `db:"updated_at"` + ID string `db:"id"` +} + +// UpdateProjectDepotID +// +// UPDATE projects +// SET +// depot_project_id = ?, +// updated_at = ? +// WHERE id = ? +func (q *Queries) UpdateProjectDepotID(ctx context.Context, db DBTX, arg UpdateProjectDepotIDParams) error { + _, err := db.ExecContext(ctx, updateProjectDepotID, arg.DepotProjectID, arg.UpdatedAt, arg.ID) + return err +} diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index c278403521..3ef3d85326 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -714,7 +714,8 @@ type Querier interface { // live_deployment_id, // is_rolled_back, // created_at, - // updated_at + // updated_at, + // depot_project_id // FROM projects // WHERE id = ? FindProjectById(ctx context.Context, db DBTX, id string) (FindProjectByIdRow, error) @@ -1818,6 +1819,14 @@ type Querier interface { // updated_at = ? // WHERE id = ? UpdateProjectDeployments(ctx context.Context, db DBTX, arg UpdateProjectDeploymentsParams) error + //UpdateProjectDepotID + // + // UPDATE projects + // SET + // depot_project_id = ?, + // updated_at = ? + // WHERE id = ? + UpdateProjectDepotID(ctx context.Context, db DBTX, arg UpdateProjectDepotIDParams) error //UpdateRatelimit // // UPDATE `ratelimits` diff --git a/go/pkg/db/queries/project_find_by_id.sql b/go/pkg/db/queries/project_find_by_id.sql index d857179ba0..83324c9b48 100644 --- a/go/pkg/db/queries/project_find_by_id.sql +++ b/go/pkg/db/queries/project_find_by_id.sql @@ -10,6 +10,7 @@ SELECT live_deployment_id, is_rolled_back, created_at, - updated_at + updated_at, + depot_project_id FROM projects WHERE id = ?; diff --git a/go/pkg/db/queries/project_update_depot_id.sql b/go/pkg/db/queries/project_update_depot_id.sql new file mode 100644 index 0000000000..3d8cf4e0c7 --- /dev/null +++ b/go/pkg/db/queries/project_update_depot_id.sql @@ -0,0 +1,6 @@ +-- name: UpdateProjectDepotID :exec +UPDATE projects +SET + depot_project_id = sqlc.arg(depot_project_id), + updated_at = sqlc.arg(updated_at) +WHERE id = sqlc.arg(id); diff --git a/go/pkg/db/schema.sql b/go/pkg/db/schema.sql index 62ef28717f..f433e5bffa 100644 --- a/go/pkg/db/schema.sql +++ b/go/pkg/db/schema.sql @@ -316,6 +316,7 @@ CREATE TABLE `projects` ( `live_deployment_id` varchar(256), `is_rolled_back` boolean NOT NULL DEFAULT false, `default_branch` varchar(256) DEFAULT 'main', + `depot_project_id` varchar(255), `delete_protection` boolean DEFAULT false, `created_at` bigint NOT NULL, `updated_at` bigint, diff --git a/go/pkg/vault/storage/interface.go b/go/pkg/vault/storage/interface.go index 68b21777bc..98cb6a2d61 100644 --- a/go/pkg/vault/storage/interface.go +++ b/go/pkg/vault/storage/interface.go @@ -6,22 +6,20 @@ import ( "time" ) -var ( - ErrObjectNotFound = errors.New("object not found") -) +var ErrObjectNotFound = errors.New("object not found") type GetObjectOptions struct { IfUnModifiedSince time.Time } type Storage interface { - // PutObject stores the object data for the given key + // PutObject stores the object data for the given key PutObject(ctx context.Context, key string, object []byte) error // GetObject returns the object data for the given key GetObject(ctx context.Context, key string) ([]byte, bool, error) - // ListObjects returns a list of object keys that match the given prefix + // ListObjectKeys returns a list of object keys that match the given prefix ListObjectKeys(ctx context.Context, prefix string) ([]string, error) // Key returns the object key for the given shard and version diff --git a/go/pkg/vault/storage/memory.go b/go/pkg/vault/storage/memory.go index a122037245..7262561e5b 100644 --- a/go/pkg/vault/storage/memory.go +++ b/go/pkg/vault/storage/memory.go @@ -41,7 +41,6 @@ func (s *memory) Latest(workspaceId string) string { } func (s *memory) PutObject(ctx context.Context, key string, b []byte) error { - s.mu.Lock() defer s.mu.Unlock() @@ -59,8 +58,8 @@ func (s *memory) GetObject(ctx context.Context, key string) ([]byte, bool, error } return b, true, nil - } + func (s *memory) ListObjectKeys(ctx context.Context, prefix string) ([]string, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -74,5 +73,4 @@ func (s *memory) ListObjectKeys(ctx context.Context, prefix string) ([]string, e } return keys, nil - } diff --git a/go/pkg/vault/storage/middleware/tracing.go b/go/pkg/vault/storage/middleware/tracing.go index 6253bf85fa..fbb36aa35e 100644 --- a/go/pkg/vault/storage/middleware/tracing.go +++ b/go/pkg/vault/storage/middleware/tracing.go @@ -31,8 +31,8 @@ func (tm *tracingMiddleware) PutObject(ctx context.Context, key string, object [ span.SetStatus(codes.Error, err.Error()) } return err - } + func (tm *tracingMiddleware) GetObject(ctx context.Context, key string) ([]byte, bool, error) { ctx, span := tracing.Start(ctx, fmt.Sprintf("storage.%s.GetObject", tm.name)) defer span.End() @@ -43,8 +43,8 @@ func (tm *tracingMiddleware) GetObject(ctx context.Context, key string) ([]byte, span.SetStatus(codes.Error, err.Error()) } return object, found, err - } + func (tm *tracingMiddleware) ListObjectKeys(ctx context.Context, prefix string) ([]string, error) { ctx, span := tracing.Start(ctx, fmt.Sprintf("storage.%s.ListObjectKeys", tm.name)) defer span.End() @@ -54,11 +54,12 @@ func (tm *tracingMiddleware) ListObjectKeys(ctx context.Context, prefix string) span.SetStatus(codes.Error, err.Error()) } return keys, err - } + func (tm *tracingMiddleware) Key(shard string, dekID string) string { return tm.next.Key(shard, dekID) } + func (tm *tracingMiddleware) Latest(shard string) string { return tm.next.Latest(shard) } diff --git a/go/pkg/vault/storage/s3.go b/go/pkg/vault/storage/s3.go index a8f8eb3003..597560e0fa 100644 --- a/go/pkg/vault/storage/s3.go +++ b/go/pkg/vault/storage/s3.go @@ -51,7 +51,6 @@ func NewS3(config S3Config) (Storage, error) { awsConfig.WithRetryMode(aws.RetryModeStandard), awsConfig.WithRetryMaxAttempts(3), ) - if err != nil { return nil, fault.Wrap(err, fault.Internal("failed to load aws config"), fault.Public("failed to load aws config")) } @@ -79,7 +78,6 @@ func (s *s3) Latest(workspaceId string) string { } func (s *s3) PutObject(ctx context.Context, key string, data []byte) error { - _, err := s.client.PutObject(ctx, &awsS3.PutObjectInput{ Bucket: aws.String(s.config.S3Bucket), Key: aws.String(key), @@ -92,7 +90,6 @@ func (s *s3) PutObject(ctx context.Context, key string, data []byte) error { } func (s *s3) GetObject(ctx context.Context, key string) ([]byte, bool, error) { - o, err := s.client.GetObject(ctx, &awsS3.GetObjectInput{ Bucket: aws.String(s.config.S3Bucket), Key: aws.String(key), @@ -113,7 +110,6 @@ func (s *s3) GetObject(ctx context.Context, key string) ([]byte, bool, error) { } func (s *s3) ListObjectKeys(ctx context.Context, prefix string) ([]string, error) { - input := &awsS3.ListObjectsV2Input{ Bucket: aws.String(s.config.S3Bucket), } @@ -122,7 +118,6 @@ func (s *s3) ListObjectKeys(ctx context.Context, prefix string) ([]string, error } o, err := s.client.ListObjectsV2(ctx, input) - if err != nil { return nil, fmt.Errorf("failed to list objects: %w", err) } diff --git a/go/proto/ctrl/v1/build.proto b/go/proto/ctrl/v1/build.proto index d75f83838d..d80985843d 100644 --- a/go/proto/ctrl/v1/build.proto +++ b/go/proto/ctrl/v1/build.proto @@ -1,64 +1,32 @@ syntax = "proto3"; - package ctrl.v1; -import "google/protobuf/timestamp.proto"; - option go_package = "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1;ctrlv1"; -// Build status enum -enum BuildStatus { - BUILD_STATUS_UNSPECIFIED = 0; - BUILD_STATUS_PENDING = 1; - BUILD_STATUS_RUNNING = 2; - BUILD_STATUS_SUCCEEDED = 3; - BUILD_STATUS_FAILED = 4; - BUILD_STATUS_CANCELLED = 5; +service BuildService { + rpc CreateBuild(CreateBuildRequest) returns (CreateBuildResponse) {} + rpc GenerateUploadURL(GenerateUploadURLRequest) returns (GenerateUploadURLResponse) {} } message CreateBuildRequest { - string workspace_id = 1; - string project_id = 2; - string version_id = 3; - string docker_image = 4; + string build_context_path = 1; // S3 key of the uploaded tar file + optional string dockerfile_path = 2; // Path to Dockerfile within the tar + string unkey_project_id = 3; // Your internal user/project ID + string deployment_id = 4; } message CreateBuildResponse { - string build_id = 1; -} - -message GetBuildRequest { - string build_id = 1; -} - -message GetBuildResponse { - Build build = 1; + string image_name = 1; // Full image tag (registry.depot.dev/project:tag) + string build_id = 2; // Depot build ID for tracking + string depot_project_id = 3; // Depot project ID } -message Build { - string id = 1; - string workspace_id = 2; - string project_id = 3; - string version_id = 4; - - // Build details - BuildStatus status = 5; - string error_message = 6; // For failed builds - - // Timestamps - google.protobuf.Timestamp created_at = 7; - google.protobuf.Timestamp started_at = 8; - google.protobuf.Timestamp completed_at = 9; - google.protobuf.Timestamp updated_at = 10; - - // Build metadata - string rootfs_image_id = 11; // Output rootfs image +message GenerateUploadURLRequest { + string unkey_project_id = 1; // Your internal user/project ID } -service BuildService { - // Create a new build - rpc CreateBuild(CreateBuildRequest) returns (CreateBuildResponse) {} - - // Get build details - rpc GetBuild(GetBuildRequest) returns (GetBuildResponse) {} +message GenerateUploadURLResponse { + string upload_url = 1; // Presigned PUT URL + string build_context_path = 2; // S3 key to use in CreateBuild + int64 expires_in = 3; // Seconds until URL expires } diff --git a/go/proto/ctrl/v1/deployment.proto b/go/proto/ctrl/v1/deployment.proto index 0230772af6..a5442e00c8 100644 --- a/go/proto/ctrl/v1/deployment.proto +++ b/go/proto/ctrl/v1/deployment.proto @@ -1,5 +1,4 @@ syntax = "proto3"; - package ctrl.v1; option go_package = "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1;ctrlv1"; @@ -26,23 +25,32 @@ message CreateDeploymentRequest { reserved 1; // was workspace_id, now inferred from project_id string project_id = 2; string branch = 3; - // Source information string environment_slug = 4; - SourceType source_type = 5; + // Build source, we can either build it from scratch or accept prebuilt image + oneof source { + BuildContext build_context = 5; + string docker_image = 6; // Prebuilt image reference + } - string docker_image = 6; + // Git information + optional GitCommitInfo git_commit = 7; - // Extended git information - string git_commit_sha = 7; // For git sources - string git_commit_message = 8; - // TODO: Add GitHub API integration to lookup username/avatar from email - string git_commit_author_handle = 9; - string git_commit_author_avatar_url = 10; - int64 git_commit_timestamp = 11; // Unix epoch milliseconds - - // Keyspace ID for authentication - optional string keyspace_id = 12; + // Authentication + optional string keyspace_id = 8; +} + +message BuildContext { + string build_context_path = 1; // S3 key for uploaded build context + optional string dockerfile_path = 2; // Path to Dockerfile within context (default: "Dockerfile") +} + +message GitCommitInfo { + string commit_sha = 1; + string commit_message = 2; + string author_handle = 3; + string author_avatar_url = 4; + int64 timestamp = 5; // Unix epoch milliseconds } message CreateDeploymentResponse { diff --git a/go/proto/hydra/v1/deployment.proto b/go/proto/hydra/v1/deployment.proto index e525e5b211..30e0577684 100644 --- a/go/proto/hydra/v1/deployment.proto +++ b/go/proto/hydra/v1/deployment.proto @@ -1,5 +1,4 @@ syntax = "proto3"; - package hydra.v1; import "dev/restate/sdk/go.proto"; @@ -15,8 +14,11 @@ service DeploymentService { message DeployRequest { string deployment_id = 1; - string docker_image = 2; - optional string key_auth_id = 3; + optional string key_auth_id = 2; + // Build source fields, exactly one of (context_key, docker_image) must be set + optional string build_context_path = 3; + optional string dockerfile_path = 4; + optional string docker_image = 5; } message DeployResponse {} diff --git a/internal/db/src/schema/projects.ts b/internal/db/src/schema/projects.ts index 092ac6564f..f213e9182c 100644 --- a/internal/db/src/schema/projects.ts +++ b/internal/db/src/schema/projects.ts @@ -21,6 +21,7 @@ export const projects = mysqlTable( liveDeploymentId: varchar("live_deployment_id", { length: 256 }), isRolledBack: boolean("is_rolled_back").notNull().default(false), defaultBranch: varchar("default_branch", { length: 256 }).default("main"), + depotProjectId: varchar("depot_project_id", { length: 255 }), ...deleteProtection, ...lifecycleDates, }, diff --git a/internal/proto/generated/ctrl/v1/build_pb.ts b/internal/proto/generated/ctrl/v1/build_pb.ts index 2f41138a1d..f86ec8ae0c 100644 --- a/internal/proto/generated/ctrl/v1/build_pb.ts +++ b/internal/proto/generated/ctrl/v1/build_pb.ts @@ -3,10 +3,8 @@ /* eslint-disable */ import type { Message } from "@bufbuild/protobuf"; -import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; -import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; -import type { Timestamp } from "@bufbuild/protobuf/wkt"; -import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; /** * Describes the file ctrl/v1/build.proto. @@ -14,8 +12,7 @@ import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; export const file_ctrl_v1_build: GenFile = /*@__PURE__*/ fileDesc( - "ChNjdHJsL3YxL2J1aWxkLnByb3RvEgdjdHJsLnYxImgKEkNyZWF0ZUJ1aWxkUmVxdWVzdBIUCgx3b3Jrc3BhY2VfaWQYASABKAkSEgoKcHJvamVjdF9pZBgCIAEoCRISCgp2ZXJzaW9uX2lkGAMgASgJEhQKDGRvY2tlcl9pbWFnZRgEIAEoCSInChNDcmVhdGVCdWlsZFJlc3BvbnNlEhAKCGJ1aWxkX2lkGAEgASgJIiMKD0dldEJ1aWxkUmVxdWVzdBIQCghidWlsZF9pZBgBIAEoCSIxChBHZXRCdWlsZFJlc3BvbnNlEh0KBWJ1aWxkGAEgASgLMg4uY3RybC52MS5CdWlsZCLpAgoFQnVpbGQSCgoCaWQYASABKAkSFAoMd29ya3NwYWNlX2lkGAIgASgJEhIKCnByb2plY3RfaWQYAyABKAkSEgoKdmVyc2lvbl9pZBgEIAEoCRIkCgZzdGF0dXMYBSABKA4yFC5jdHJsLnYxLkJ1aWxkU3RhdHVzEhUKDWVycm9yX21lc3NhZ2UYBiABKAkSLgoKY3JlYXRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKc3RhcnRlZF9hdBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMAoMY29tcGxldGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCg9yb290ZnNfaW1hZ2VfaWQYCyABKAkqsAEKC0J1aWxkU3RhdHVzEhwKGEJVSUxEX1NUQVRVU19VTlNQRUNJRklFRBAAEhgKFEJVSUxEX1NUQVRVU19QRU5ESU5HEAESGAoUQlVJTERfU1RBVFVTX1JVTk5JTkcQAhIaChZCVUlMRF9TVEFUVVNfU1VDQ0VFREVEEAMSFwoTQlVJTERfU1RBVFVTX0ZBSUxFRBAEEhoKFkJVSUxEX1NUQVRVU19DQU5DRUxMRUQQBTKdAQoMQnVpbGRTZXJ2aWNlEkoKC0NyZWF0ZUJ1aWxkEhsuY3RybC52MS5DcmVhdGVCdWlsZFJlcXVlc3QaHC5jdHJsLnYxLkNyZWF0ZUJ1aWxkUmVzcG9uc2UiABJBCghHZXRCdWlsZBIYLmN0cmwudjEuR2V0QnVpbGRSZXF1ZXN0GhkuY3RybC52MS5HZXRCdWlsZFJlc3BvbnNlIgBCjAEKC2NvbS5jdHJsLnYxQgpCdWlsZFByb3RvUAFaNGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vY3RybC92MTtjdHJsdjGiAgNDWFiqAgdDdHJsLlYxygIHQ3RybFxWMeICE0N0cmxcVjFcR1BCTWV0YWRhdGHqAghDdHJsOjpWMWIGcHJvdG8z", - [file_google_protobuf_timestamp], + "ChNjdHJsL3YxL2J1aWxkLnByb3RvEgdjdHJsLnYxIpMBChJDcmVhdGVCdWlsZFJlcXVlc3QSGgoSYnVpbGRfY29udGV4dF9wYXRoGAEgASgJEhwKD2RvY2tlcmZpbGVfcGF0aBgCIAEoCUgAiAEBEhgKEHVua2V5X3Byb2plY3RfaWQYAyABKAkSFQoNZGVwbG95bWVudF9pZBgEIAEoCUISChBfZG9ja2VyZmlsZV9wYXRoIlUKE0NyZWF0ZUJ1aWxkUmVzcG9uc2USEgoKaW1hZ2VfbmFtZRgBIAEoCRIQCghidWlsZF9pZBgCIAEoCRIYChBkZXBvdF9wcm9qZWN0X2lkGAMgASgJIjQKGEdlbmVyYXRlVXBsb2FkVVJMUmVxdWVzdBIYChB1bmtleV9wcm9qZWN0X2lkGAEgASgJIl8KGUdlbmVyYXRlVXBsb2FkVVJMUmVzcG9uc2USEgoKdXBsb2FkX3VybBgBIAEoCRIaChJidWlsZF9jb250ZXh0X3BhdGgYAiABKAkSEgoKZXhwaXJlc19pbhgDIAEoAzK4AQoMQnVpbGRTZXJ2aWNlEkoKC0NyZWF0ZUJ1aWxkEhsuY3RybC52MS5DcmVhdGVCdWlsZFJlcXVlc3QaHC5jdHJsLnYxLkNyZWF0ZUJ1aWxkUmVzcG9uc2UiABJcChFHZW5lcmF0ZVVwbG9hZFVSTBIhLmN0cmwudjEuR2VuZXJhdGVVcGxvYWRVUkxSZXF1ZXN0GiIuY3RybC52MS5HZW5lcmF0ZVVwbG9hZFVSTFJlc3BvbnNlIgBCjAEKC2NvbS5jdHJsLnYxQgpCdWlsZFByb3RvUAFaNGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vY3RybC92MTtjdHJsdjGiAgNDWFiqAgdDdHJsLlYxygIHQ3RybFxWMeICE0N0cmxcVjFcR1BCTWV0YWRhdGHqAghDdHJsOjpWMWIGcHJvdG8z", ); /** @@ -23,24 +20,30 @@ export const file_ctrl_v1_build: GenFile = */ export type CreateBuildRequest = Message<"ctrl.v1.CreateBuildRequest"> & { /** - * @generated from field: string workspace_id = 1; + * S3 key of the uploaded tar file + * + * @generated from field: string build_context_path = 1; */ - workspaceId: string; + buildContextPath: string; /** - * @generated from field: string project_id = 2; + * Path to Dockerfile within the tar + * + * @generated from field: optional string dockerfile_path = 2; */ - projectId: string; + dockerfilePath?: string; /** - * @generated from field: string version_id = 3; + * Your internal user/project ID + * + * @generated from field: string unkey_project_id = 3; */ - versionId: string; + unkeyProjectId: string; /** - * @generated from field: string docker_image = 4; + * @generated from field: string deployment_id = 4; */ - dockerImage: string; + deploymentId: string; }; /** @@ -56,9 +59,25 @@ export const CreateBuildRequestSchema: GenMessage = */ export type CreateBuildResponse = Message<"ctrl.v1.CreateBuildResponse"> & { /** - * @generated from field: string build_id = 1; + * Full image tag (registry.depot.dev/project:tag) + * + * @generated from field: string image_name = 1; + */ + imageName: string; + + /** + * Depot build ID for tracking + * + * @generated from field: string build_id = 2; */ buildId: string; + + /** + * Depot project ID + * + * @generated from field: string depot_project_id = 3; + */ + depotProjectId: string; }; /** @@ -70,168 +89,64 @@ export const CreateBuildResponseSchema: GenMessage = messageDesc(file_ctrl_v1_build, 1); /** - * @generated from message ctrl.v1.GetBuildRequest + * @generated from message ctrl.v1.GenerateUploadURLRequest */ -export type GetBuildRequest = Message<"ctrl.v1.GetBuildRequest"> & { +export type GenerateUploadURLRequest = Message<"ctrl.v1.GenerateUploadURLRequest"> & { /** - * @generated from field: string build_id = 1; + * Your internal user/project ID + * + * @generated from field: string unkey_project_id = 1; */ - buildId: string; + unkeyProjectId: string; }; /** - * Describes the message ctrl.v1.GetBuildRequest. - * Use `create(GetBuildRequestSchema)` to create a new message. + * Describes the message ctrl.v1.GenerateUploadURLRequest. + * Use `create(GenerateUploadURLRequestSchema)` to create a new message. */ -export const GetBuildRequestSchema: GenMessage = +export const GenerateUploadURLRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_ctrl_v1_build, 2); /** - * @generated from message ctrl.v1.GetBuildResponse + * @generated from message ctrl.v1.GenerateUploadURLResponse */ -export type GetBuildResponse = Message<"ctrl.v1.GetBuildResponse"> & { +export type GenerateUploadURLResponse = Message<"ctrl.v1.GenerateUploadURLResponse"> & { /** - * @generated from field: ctrl.v1.Build build = 1; - */ - build?: Build; -}; - -/** - * Describes the message ctrl.v1.GetBuildResponse. - * Use `create(GetBuildResponseSchema)` to create a new message. - */ -export const GetBuildResponseSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_ctrl_v1_build, 3); - -/** - * @generated from message ctrl.v1.Build - */ -export type Build = Message<"ctrl.v1.Build"> & { - /** - * @generated from field: string id = 1; - */ - id: string; - - /** - * @generated from field: string workspace_id = 2; - */ - workspaceId: string; - - /** - * @generated from field: string project_id = 3; - */ - projectId: string; - - /** - * @generated from field: string version_id = 4; - */ - versionId: string; - - /** - * Build details + * Presigned PUT URL * - * @generated from field: ctrl.v1.BuildStatus status = 5; + * @generated from field: string upload_url = 1; */ - status: BuildStatus; + uploadUrl: string; /** - * For failed builds + * S3 key to use in CreateBuild * - * @generated from field: string error_message = 6; + * @generated from field: string build_context_path = 2; */ - errorMessage: string; + buildContextPath: string; /** - * Timestamps + * Seconds until URL expires * - * @generated from field: google.protobuf.Timestamp created_at = 7; + * @generated from field: int64 expires_in = 3; */ - createdAt?: Timestamp; - - /** - * @generated from field: google.protobuf.Timestamp started_at = 8; - */ - startedAt?: Timestamp; - - /** - * @generated from field: google.protobuf.Timestamp completed_at = 9; - */ - completedAt?: Timestamp; - - /** - * @generated from field: google.protobuf.Timestamp updated_at = 10; - */ - updatedAt?: Timestamp; - - /** - * Build metadata - * - * Output rootfs image - * - * @generated from field: string rootfs_image_id = 11; - */ - rootfsImageId: string; + expiresIn: bigint; }; /** - * Describes the message ctrl.v1.Build. - * Use `create(BuildSchema)` to create a new message. - */ -export const BuildSchema: GenMessage = /*@__PURE__*/ messageDesc(file_ctrl_v1_build, 4); - -/** - * Build status enum - * - * @generated from enum ctrl.v1.BuildStatus - */ -export enum BuildStatus { - /** - * @generated from enum value: BUILD_STATUS_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: BUILD_STATUS_PENDING = 1; - */ - PENDING = 1, - - /** - * @generated from enum value: BUILD_STATUS_RUNNING = 2; - */ - RUNNING = 2, - - /** - * @generated from enum value: BUILD_STATUS_SUCCEEDED = 3; - */ - SUCCEEDED = 3, - - /** - * @generated from enum value: BUILD_STATUS_FAILED = 4; - */ - FAILED = 4, - - /** - * @generated from enum value: BUILD_STATUS_CANCELLED = 5; - */ - CANCELLED = 5, -} - -/** - * Describes the enum ctrl.v1.BuildStatus. + * Describes the message ctrl.v1.GenerateUploadURLResponse. + * Use `create(GenerateUploadURLResponseSchema)` to create a new message. */ -export const BuildStatusSchema: GenEnum = +export const GenerateUploadURLResponseSchema: GenMessage = /*@__PURE__*/ - enumDesc(file_ctrl_v1_build, 0); + messageDesc(file_ctrl_v1_build, 3); /** * @generated from service ctrl.v1.BuildService */ export const BuildService: GenService<{ /** - * Create a new build - * * @generated from rpc ctrl.v1.BuildService.CreateBuild */ createBuild: { @@ -240,13 +155,11 @@ export const BuildService: GenService<{ output: typeof CreateBuildResponseSchema; }; /** - * Get build details - * - * @generated from rpc ctrl.v1.BuildService.GetBuild + * @generated from rpc ctrl.v1.BuildService.GenerateUploadURL */ - getBuild: { + generateUploadURL: { methodKind: "unary"; - input: typeof GetBuildRequestSchema; - output: typeof GetBuildResponseSchema; + input: typeof GenerateUploadURLRequestSchema; + output: typeof GenerateUploadURLResponseSchema; }; }> = /*@__PURE__*/ serviceDesc(file_ctrl_v1_build, 0); diff --git a/internal/proto/generated/ctrl/v1/deployment_pb.ts b/internal/proto/generated/ctrl/v1/deployment_pb.ts index 069929c0af..2034cf6927 100644 --- a/internal/proto/generated/ctrl/v1/deployment_pb.ts +++ b/internal/proto/generated/ctrl/v1/deployment_pb.ts @@ -12,7 +12,7 @@ import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf export const file_ctrl_v1_deployment: GenFile = /*@__PURE__*/ fileDesc( - "ChhjdHJsL3YxL2RlcGxveW1lbnQucHJvdG8SB2N0cmwudjEi4QIKF0NyZWF0ZURlcGxveW1lbnRSZXF1ZXN0EhIKCnByb2plY3RfaWQYAiABKAkSDgoGYnJhbmNoGAMgASgJEhgKEGVudmlyb25tZW50X3NsdWcYBCABKAkSKAoLc291cmNlX3R5cGUYBSABKA4yEy5jdHJsLnYxLlNvdXJjZVR5cGUSFAoMZG9ja2VyX2ltYWdlGAYgASgJEhYKDmdpdF9jb21taXRfc2hhGAcgASgJEhoKEmdpdF9jb21taXRfbWVzc2FnZRgIIAEoCRIgChhnaXRfY29tbWl0X2F1dGhvcl9oYW5kbGUYCSABKAkSJAocZ2l0X2NvbW1pdF9hdXRob3JfYXZhdGFyX3VybBgKIAEoCRIcChRnaXRfY29tbWl0X3RpbWVzdGFtcBgLIAEoAxIYCgtrZXlzcGFjZV9pZBgMIAEoCUgAiAEBQg4KDF9rZXlzcGFjZV9pZEoECAEQAiJcChhDcmVhdGVEZXBsb3ltZW50UmVzcG9uc2USFQoNZGVwbG95bWVudF9pZBgBIAEoCRIpCgZzdGF0dXMYAiABKA4yGS5jdHJsLnYxLkRlcGxveW1lbnRTdGF0dXMiLQoUR2V0RGVwbG95bWVudFJlcXVlc3QSFQoNZGVwbG95bWVudF9pZBgBIAEoCSJAChVHZXREZXBsb3ltZW50UmVzcG9uc2USJwoKZGVwbG95bWVudBgBIAEoCzITLmN0cmwudjEuRGVwbG95bWVudCKIBQoKRGVwbG95bWVudBIKCgJpZBgBIAEoCRIUCgx3b3Jrc3BhY2VfaWQYAiABKAkSEgoKcHJvamVjdF9pZBgDIAEoCRIWCg5lbnZpcm9ubWVudF9pZBgEIAEoCRIWCg5naXRfY29tbWl0X3NoYRgFIAEoCRISCgpnaXRfYnJhbmNoGAYgASgJEikKBnN0YXR1cxgHIAEoDjIZLmN0cmwudjEuRGVwbG95bWVudFN0YXR1cxIVCg1lcnJvcl9tZXNzYWdlGAggASgJEkwKFWVudmlyb25tZW50X3ZhcmlhYmxlcxgJIAMoCzItLmN0cmwudjEuRGVwbG95bWVudC5FbnZpcm9ubWVudFZhcmlhYmxlc0VudHJ5EiMKCHRvcG9sb2d5GAogASgLMhEuY3RybC52MS5Ub3BvbG9neRISCgpjcmVhdGVkX2F0GAsgASgDEhIKCnVwZGF0ZWRfYXQYDCABKAMSEQoJaG9zdG5hbWVzGA0gAygJEhcKD3Jvb3Rmc19pbWFnZV9pZBgOIAEoCRIQCghidWlsZF9pZBgPIAEoCRImCgVzdGVwcxgQIAMoCzIXLmN0cmwudjEuRGVwbG95bWVudFN0ZXASGgoSZ2l0X2NvbW1pdF9tZXNzYWdlGBEgASgJEiAKGGdpdF9jb21taXRfYXV0aG9yX2hhbmRsZRgSIAEoCRIkChxnaXRfY29tbWl0X2F1dGhvcl9hdmF0YXJfdXJsGBMgASgJEhwKFGdpdF9jb21taXRfdGltZXN0YW1wGBQgASgDGjsKGUVudmlyb25tZW50VmFyaWFibGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJcCg5EZXBsb3ltZW50U3RlcBIOCgZzdGF0dXMYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIVCg1lcnJvcl9tZXNzYWdlGAMgASgJEhIKCmNyZWF0ZWRfYXQYBCABKAMipgEKCFRvcG9sb2d5EhYKDmNwdV9taWxsaWNvcmVzGAEgASgFEhEKCW1lbW9yeV9tYhgCIAEoBRIoCgdyZWdpb25zGAMgAygLMhcuY3RybC52MS5SZWdpb25hbENvbmZpZxIcChRpZGxlX3RpbWVvdXRfc2Vjb25kcxgEIAEoBRIZChFoZWFsdGhfY2hlY2tfcGF0aBgFIAEoCRIMCgRwb3J0GAYgASgFIk4KDlJlZ2lvbmFsQ29uZmlnEg4KBnJlZ2lvbhgBIAEoCRIVCg1taW5faW5zdGFuY2VzGAIgASgFEhUKDW1heF9pbnN0YW5jZXMYAyABKAUiTQoPUm9sbGJhY2tSZXF1ZXN0EhwKFHNvdXJjZV9kZXBsb3ltZW50X2lkGAEgASgJEhwKFHRhcmdldF9kZXBsb3ltZW50X2lkGAIgASgJIhIKEFJvbGxiYWNrUmVzcG9uc2UiLgoOUHJvbW90ZVJlcXVlc3QSHAoUdGFyZ2V0X2RlcGxveW1lbnRfaWQYASABKAkiEQoPUHJvbW90ZVJlc3BvbnNlKu8BChBEZXBsb3ltZW50U3RhdHVzEiEKHURFUExPWU1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHQoZREVQTE9ZTUVOVF9TVEFUVVNfUEVORElORxABEh4KGkRFUExPWU1FTlRfU1RBVFVTX0JVSUxESU5HEAISHwobREVQTE9ZTUVOVF9TVEFUVVNfREVQTE9ZSU5HEAMSHQoZREVQTE9ZTUVOVF9TVEFUVVNfTkVUV09SSxAEEhsKF0RFUExPWU1FTlRfU1RBVFVTX1JFQURZEAUSHAoYREVQTE9ZTUVOVF9TVEFUVVNfRkFJTEVEEAYqWgoKU291cmNlVHlwZRIbChdTT1VSQ0VfVFlQRV9VTlNQRUNJRklFRBAAEhMKD1NPVVJDRV9UWVBFX0dJVBABEhoKFlNPVVJDRV9UWVBFX0NMSV9VUExPQUQQAjLDAgoRRGVwbG95bWVudFNlcnZpY2USWQoQQ3JlYXRlRGVwbG95bWVudBIgLmN0cmwudjEuQ3JlYXRlRGVwbG95bWVudFJlcXVlc3QaIS5jdHJsLnYxLkNyZWF0ZURlcGxveW1lbnRSZXNwb25zZSIAElAKDUdldERlcGxveW1lbnQSHS5jdHJsLnYxLkdldERlcGxveW1lbnRSZXF1ZXN0Gh4uY3RybC52MS5HZXREZXBsb3ltZW50UmVzcG9uc2UiABJBCghSb2xsYmFjaxIYLmN0cmwudjEuUm9sbGJhY2tSZXF1ZXN0GhkuY3RybC52MS5Sb2xsYmFja1Jlc3BvbnNlIgASPgoHUHJvbW90ZRIXLmN0cmwudjEuUHJvbW90ZVJlcXVlc3QaGC5jdHJsLnYxLlByb21vdGVSZXNwb25zZSIAQpEBCgtjb20uY3RybC52MUIPRGVwbG95bWVudFByb3RvUAFaNGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vY3RybC92MTtjdHJsdjGiAgNDWFiqAgdDdHJsLlYxygIHQ3RybFxWMeICE0N0cmxcVjFcR1BCTWV0YWRhdGHqAghDdHJsOjpWMWIGcHJvdG8z", + "ChhjdHJsL3YxL2RlcGxveW1lbnQucHJvdG8SB2N0cmwudjEimQIKF0NyZWF0ZURlcGxveW1lbnRSZXF1ZXN0EhIKCnByb2plY3RfaWQYAiABKAkSDgoGYnJhbmNoGAMgASgJEhgKEGVudmlyb25tZW50X3NsdWcYBCABKAkSLgoNYnVpbGRfY29udGV4dBgFIAEoCzIVLmN0cmwudjEuQnVpbGRDb250ZXh0SAASFgoMZG9ja2VyX2ltYWdlGAYgASgJSAASLwoKZ2l0X2NvbW1pdBgHIAEoCzIWLmN0cmwudjEuR2l0Q29tbWl0SW5mb0gBiAEBEhgKC2tleXNwYWNlX2lkGAggASgJSAKIAQFCCAoGc291cmNlQg0KC19naXRfY29tbWl0Qg4KDF9rZXlzcGFjZV9pZEoECAEQAiJcCgxCdWlsZENvbnRleHQSGgoSYnVpbGRfY29udGV4dF9wYXRoGAEgASgJEhwKD2RvY2tlcmZpbGVfcGF0aBgCIAEoCUgAiAEBQhIKEF9kb2NrZXJmaWxlX3BhdGgigAEKDUdpdENvbW1pdEluZm8SEgoKY29tbWl0X3NoYRgBIAEoCRIWCg5jb21taXRfbWVzc2FnZRgCIAEoCRIVCg1hdXRob3JfaGFuZGxlGAMgASgJEhkKEWF1dGhvcl9hdmF0YXJfdXJsGAQgASgJEhEKCXRpbWVzdGFtcBgFIAEoAyJcChhDcmVhdGVEZXBsb3ltZW50UmVzcG9uc2USFQoNZGVwbG95bWVudF9pZBgBIAEoCRIpCgZzdGF0dXMYAiABKA4yGS5jdHJsLnYxLkRlcGxveW1lbnRTdGF0dXMiLQoUR2V0RGVwbG95bWVudFJlcXVlc3QSFQoNZGVwbG95bWVudF9pZBgBIAEoCSJAChVHZXREZXBsb3ltZW50UmVzcG9uc2USJwoKZGVwbG95bWVudBgBIAEoCzITLmN0cmwudjEuRGVwbG95bWVudCKIBQoKRGVwbG95bWVudBIKCgJpZBgBIAEoCRIUCgx3b3Jrc3BhY2VfaWQYAiABKAkSEgoKcHJvamVjdF9pZBgDIAEoCRIWCg5lbnZpcm9ubWVudF9pZBgEIAEoCRIWCg5naXRfY29tbWl0X3NoYRgFIAEoCRISCgpnaXRfYnJhbmNoGAYgASgJEikKBnN0YXR1cxgHIAEoDjIZLmN0cmwudjEuRGVwbG95bWVudFN0YXR1cxIVCg1lcnJvcl9tZXNzYWdlGAggASgJEkwKFWVudmlyb25tZW50X3ZhcmlhYmxlcxgJIAMoCzItLmN0cmwudjEuRGVwbG95bWVudC5FbnZpcm9ubWVudFZhcmlhYmxlc0VudHJ5EiMKCHRvcG9sb2d5GAogASgLMhEuY3RybC52MS5Ub3BvbG9neRISCgpjcmVhdGVkX2F0GAsgASgDEhIKCnVwZGF0ZWRfYXQYDCABKAMSEQoJaG9zdG5hbWVzGA0gAygJEhcKD3Jvb3Rmc19pbWFnZV9pZBgOIAEoCRIQCghidWlsZF9pZBgPIAEoCRImCgVzdGVwcxgQIAMoCzIXLmN0cmwudjEuRGVwbG95bWVudFN0ZXASGgoSZ2l0X2NvbW1pdF9tZXNzYWdlGBEgASgJEiAKGGdpdF9jb21taXRfYXV0aG9yX2hhbmRsZRgSIAEoCRIkChxnaXRfY29tbWl0X2F1dGhvcl9hdmF0YXJfdXJsGBMgASgJEhwKFGdpdF9jb21taXRfdGltZXN0YW1wGBQgASgDGjsKGUVudmlyb25tZW50VmFyaWFibGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJcCg5EZXBsb3ltZW50U3RlcBIOCgZzdGF0dXMYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIVCg1lcnJvcl9tZXNzYWdlGAMgASgJEhIKCmNyZWF0ZWRfYXQYBCABKAMipgEKCFRvcG9sb2d5EhYKDmNwdV9taWxsaWNvcmVzGAEgASgFEhEKCW1lbW9yeV9tYhgCIAEoBRIoCgdyZWdpb25zGAMgAygLMhcuY3RybC52MS5SZWdpb25hbENvbmZpZxIcChRpZGxlX3RpbWVvdXRfc2Vjb25kcxgEIAEoBRIZChFoZWFsdGhfY2hlY2tfcGF0aBgFIAEoCRIMCgRwb3J0GAYgASgFIk4KDlJlZ2lvbmFsQ29uZmlnEg4KBnJlZ2lvbhgBIAEoCRIVCg1taW5faW5zdGFuY2VzGAIgASgFEhUKDW1heF9pbnN0YW5jZXMYAyABKAUiTQoPUm9sbGJhY2tSZXF1ZXN0EhwKFHNvdXJjZV9kZXBsb3ltZW50X2lkGAEgASgJEhwKFHRhcmdldF9kZXBsb3ltZW50X2lkGAIgASgJIhIKEFJvbGxiYWNrUmVzcG9uc2UiLgoOUHJvbW90ZVJlcXVlc3QSHAoUdGFyZ2V0X2RlcGxveW1lbnRfaWQYASABKAkiEQoPUHJvbW90ZVJlc3BvbnNlKu8BChBEZXBsb3ltZW50U3RhdHVzEiEKHURFUExPWU1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHQoZREVQTE9ZTUVOVF9TVEFUVVNfUEVORElORxABEh4KGkRFUExPWU1FTlRfU1RBVFVTX0JVSUxESU5HEAISHwobREVQTE9ZTUVOVF9TVEFUVVNfREVQTE9ZSU5HEAMSHQoZREVQTE9ZTUVOVF9TVEFUVVNfTkVUV09SSxAEEhsKF0RFUExPWU1FTlRfU1RBVFVTX1JFQURZEAUSHAoYREVQTE9ZTUVOVF9TVEFUVVNfRkFJTEVEEAYqWgoKU291cmNlVHlwZRIbChdTT1VSQ0VfVFlQRV9VTlNQRUNJRklFRBAAEhMKD1NPVVJDRV9UWVBFX0dJVBABEhoKFlNPVVJDRV9UWVBFX0NMSV9VUExPQUQQAjLDAgoRRGVwbG95bWVudFNlcnZpY2USWQoQQ3JlYXRlRGVwbG95bWVudBIgLmN0cmwudjEuQ3JlYXRlRGVwbG95bWVudFJlcXVlc3QaIS5jdHJsLnYxLkNyZWF0ZURlcGxveW1lbnRSZXNwb25zZSIAElAKDUdldERlcGxveW1lbnQSHS5jdHJsLnYxLkdldERlcGxveW1lbnRSZXF1ZXN0Gh4uY3RybC52MS5HZXREZXBsb3ltZW50UmVzcG9uc2UiABJBCghSb2xsYmFjaxIYLmN0cmwudjEuUm9sbGJhY2tSZXF1ZXN0GhkuY3RybC52MS5Sb2xsYmFja1Jlc3BvbnNlIgASPgoHUHJvbW90ZRIXLmN0cmwudjEuUHJvbW90ZVJlcXVlc3QaGC5jdHJsLnYxLlByb21vdGVSZXNwb25zZSIAQpEBCgtjb20uY3RybC52MUIPRGVwbG95bWVudFByb3RvUAFaNGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vY3RybC92MTtjdHJsdjGiAgNDWFiqAgdDdHJsLlYxygIHQ3RybFxWMeICE0N0cmxcVjFcR1BCTWV0YWRhdGHqAghDdHJsOjpWMWIGcHJvdG8z", ); /** @@ -30,70 +30,123 @@ export type CreateDeploymentRequest = Message<"ctrl.v1.CreateDeploymentRequest"> branch: string; /** - * Source information - * * @generated from field: string environment_slug = 4; */ environmentSlug: string; /** - * @generated from field: ctrl.v1.SourceType source_type = 5; + * Build source, we can either build it from scratch or accept prebuilt image + * + * @generated from oneof ctrl.v1.CreateDeploymentRequest.source + */ + source: + | { + /** + * @generated from field: ctrl.v1.BuildContext build_context = 5; + */ + value: BuildContext; + case: "buildContext"; + } + | { + /** + * Prebuilt image reference + * + * @generated from field: string docker_image = 6; + */ + value: string; + case: "dockerImage"; + } + | { case: undefined; value?: undefined }; + + /** + * Git information + * + * @generated from field: optional ctrl.v1.GitCommitInfo git_commit = 7; */ - sourceType: SourceType; + gitCommit?: GitCommitInfo; /** - * @generated from field: string docker_image = 6; + * Authentication + * + * @generated from field: optional string keyspace_id = 8; */ - dockerImage: string; + keyspaceId?: string; +}; + +/** + * Describes the message ctrl.v1.CreateDeploymentRequest. + * Use `create(CreateDeploymentRequestSchema)` to create a new message. + */ +export const CreateDeploymentRequestSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_ctrl_v1_deployment, 0); +/** + * @generated from message ctrl.v1.BuildContext + */ +export type BuildContext = Message<"ctrl.v1.BuildContext"> & { /** - * Extended git information + * S3 key for uploaded build context * - * For git sources + * @generated from field: string build_context_path = 1; + */ + buildContextPath: string; + + /** + * Path to Dockerfile within context (default: "Dockerfile") * - * @generated from field: string git_commit_sha = 7; + * @generated from field: optional string dockerfile_path = 2; */ - gitCommitSha: string; + dockerfilePath?: string; +}; + +/** + * Describes the message ctrl.v1.BuildContext. + * Use `create(BuildContextSchema)` to create a new message. + */ +export const BuildContextSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_ctrl_v1_deployment, 1); +/** + * @generated from message ctrl.v1.GitCommitInfo + */ +export type GitCommitInfo = Message<"ctrl.v1.GitCommitInfo"> & { /** - * @generated from field: string git_commit_message = 8; + * @generated from field: string commit_sha = 1; */ - gitCommitMessage: string; + commitSha: string; /** - * TODO: Add GitHub API integration to lookup username/avatar from email - * - * @generated from field: string git_commit_author_handle = 9; + * @generated from field: string commit_message = 2; */ - gitCommitAuthorHandle: string; + commitMessage: string; /** - * @generated from field: string git_commit_author_avatar_url = 10; + * @generated from field: string author_handle = 3; */ - gitCommitAuthorAvatarUrl: string; + authorHandle: string; /** - * Unix epoch milliseconds - * - * @generated from field: int64 git_commit_timestamp = 11; + * @generated from field: string author_avatar_url = 4; */ - gitCommitTimestamp: bigint; + authorAvatarUrl: string; /** - * Keyspace ID for authentication + * Unix epoch milliseconds * - * @generated from field: optional string keyspace_id = 12; + * @generated from field: int64 timestamp = 5; */ - keyspaceId?: string; + timestamp: bigint; }; /** - * Describes the message ctrl.v1.CreateDeploymentRequest. - * Use `create(CreateDeploymentRequestSchema)` to create a new message. + * Describes the message ctrl.v1.GitCommitInfo. + * Use `create(GitCommitInfoSchema)` to create a new message. */ -export const CreateDeploymentRequestSchema: GenMessage = +export const GitCommitInfoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 0); + messageDesc(file_ctrl_v1_deployment, 2); /** * @generated from message ctrl.v1.CreateDeploymentResponse @@ -118,7 +171,7 @@ export type CreateDeploymentResponse = Message<"ctrl.v1.CreateDeploymentResponse */ export const CreateDeploymentResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 1); + messageDesc(file_ctrl_v1_deployment, 3); /** * @generated from message ctrl.v1.GetDeploymentRequest @@ -136,7 +189,7 @@ export type GetDeploymentRequest = Message<"ctrl.v1.GetDeploymentRequest"> & { */ export const GetDeploymentRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 2); + messageDesc(file_ctrl_v1_deployment, 4); /** * @generated from message ctrl.v1.GetDeploymentResponse @@ -154,7 +207,7 @@ export type GetDeploymentResponse = Message<"ctrl.v1.GetDeploymentResponse"> & { */ export const GetDeploymentResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 3); + messageDesc(file_ctrl_v1_deployment, 5); /** * @generated from message ctrl.v1.Deployment @@ -291,7 +344,7 @@ export type Deployment = Message<"ctrl.v1.Deployment"> & { */ export const DeploymentSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 4); + messageDesc(file_ctrl_v1_deployment, 6); /** * @generated from message ctrl.v1.DeploymentStep @@ -324,7 +377,7 @@ export type DeploymentStep = Message<"ctrl.v1.DeploymentStep"> & { */ export const DeploymentStepSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 5); + messageDesc(file_ctrl_v1_deployment, 7); /** * @generated from message ctrl.v1.Topology @@ -373,7 +426,7 @@ export type Topology = Message<"ctrl.v1.Topology"> & { */ export const TopologySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 6); + messageDesc(file_ctrl_v1_deployment, 8); /** * @generated from message ctrl.v1.RegionalConfig @@ -401,7 +454,7 @@ export type RegionalConfig = Message<"ctrl.v1.RegionalConfig"> & { */ export const RegionalConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 7); + messageDesc(file_ctrl_v1_deployment, 9); /** * @generated from message ctrl.v1.RollbackRequest @@ -424,7 +477,7 @@ export type RollbackRequest = Message<"ctrl.v1.RollbackRequest"> & { */ export const RollbackRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 8); + messageDesc(file_ctrl_v1_deployment, 10); /** * @generated from message ctrl.v1.RollbackResponse @@ -437,7 +490,7 @@ export type RollbackResponse = Message<"ctrl.v1.RollbackResponse"> & {}; */ export const RollbackResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 9); + messageDesc(file_ctrl_v1_deployment, 11); /** * @generated from message ctrl.v1.PromoteRequest @@ -455,7 +508,7 @@ export type PromoteRequest = Message<"ctrl.v1.PromoteRequest"> & { */ export const PromoteRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 10); + messageDesc(file_ctrl_v1_deployment, 12); /** * @generated from message ctrl.v1.PromoteResponse @@ -468,7 +521,7 @@ export type PromoteResponse = Message<"ctrl.v1.PromoteResponse"> & {}; */ export const PromoteResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_ctrl_v1_deployment, 11); + messageDesc(file_ctrl_v1_deployment, 13); /** * Deployment status enum