Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions svc/api/internal/ctrlclient/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@rules_go//go:def.bzl", "go_library")

go_library(
name = "ctrlclient",
srcs = ["errors.go"],
importpath = "github.com/unkeyed/unkey/svc/api/internal/ctrlclient",
visibility = ["//svc/api:__subpackages__"],
deps = [
"//pkg/codes",
"//pkg/fault",
"@com_connectrpc_connect//:connect",
],
)
52 changes: 52 additions & 0 deletions svc/api/internal/ctrlclient/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ctrlclient

import (
"errors"
"fmt"

"connectrpc.com/connect"
"github.com/unkeyed/unkey/pkg/codes"
"github.com/unkeyed/unkey/pkg/fault"
)

// HandleError converts Connect RPC errors from ctrl services to fault errors
// with appropriate error codes and user-facing messages.
//
// The context parameter should describe the operation being performed (e.g., "create deployment",
// "generate upload URL") and will be used to generate user-facing error messages.
func HandleError(err error, context string) error {
// Convert Connect errors to fault errors
var connectErr *connect.Error
if !errors.As(err, &connectErr) {
// Non-Connect errors
return fault.Wrap(err,
fault.Code(codes.App.Internal.ServiceUnavailable.URN()),
fault.Public(fmt.Sprintf("Failed to %s.", context)),
)
}

//nolint:exhaustive // Default case handles all other Connect error codes
switch connectErr.Code() {
case connect.CodeNotFound:
return fault.Wrap(err,
fault.Code(codes.Data.Project.NotFound.URN()),
fault.Public("Project not found."),
)
case connect.CodeInvalidArgument:
return fault.Wrap(err,
fault.Code(codes.App.Validation.InvalidInput.URN()),
fault.Public(fmt.Sprintf("Invalid request for %s.", context)),
)
case connect.CodeUnauthenticated:
return fault.Wrap(err,
fault.Code(codes.App.Internal.ServiceUnavailable.URN()),
fault.Public("Failed to authenticate with service."),
)
default:
// All other Connect errors (Internal, Unavailable, etc.)
return fault.Wrap(err,
fault.Code(codes.App.Internal.ServiceUnavailable.URN()),
fault.Public(fmt.Sprintf("Failed to %s.", context)),
)
}
}
30 changes: 28 additions & 2 deletions svc/api/openapi/gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 85 additions & 1 deletion svc/api/openapi/openapi-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,26 @@ components:
$ref: "#/components/schemas/Meta"
data:
$ref: "#/components/schemas/V2DeployCreateDeploymentResponseData"
V2DeployGenerateUploadUrlRequestBody:
type: object
required:
- projectId
properties:
projectId:
type: string
minLength: 1
description: Unkey project ID for which to generate the upload URL
example: "proj_123abc"
V2DeployGenerateUploadUrlResponseBody:
type: object
required:
- meta
- data
properties:
meta:
$ref: "#/components/schemas/Meta"
data:
$ref: "#/components/schemas/V2DeployGenerateUploadUrlResponseData"
V2IdentitiesCreateIdentityRequestBody:
type: object
required:
Expand Down Expand Up @@ -2611,6 +2631,20 @@ components:
type: string
description: Unique deployment identifier
example: "dep_abc123xyz"
V2DeployGenerateUploadUrlResponseData:
type: object
required:
- uploadUrl
- context
properties:
uploadUrl:
type: string
description: Presigned PUT URL for uploading the build context tar file
example: "https://s3.amazonaws.com/bucket/path?signature=..."
context:
type: string
description: S3 path to use in the createDeployment request when building from source
example: "proj_123abc/ctx_456def.tar.gz"
RatelimitRequest:
type: object
required:
Expand Down Expand Up @@ -4160,7 +4194,7 @@ paths:
Creates a new deployment for a project using either a pre-built Docker image or build context.

**Authentication**: Requires a valid root key with appropriate permissions.
operationId: createDeployment
operationId: deploy.createDeployment
requestBody:
content:
application/json:
Expand Down Expand Up @@ -4204,6 +4238,56 @@ paths:
tags:
- deploy
x-speakeasy-name-override: createDeployment
/v2/deploy.generateUploadUrl:
post:
description: |
Generates a presigned S3 URL for uploading build context archives.

**Authentication**: Requires a valid root key with appropriate permissions.
operationId: deploy.generateUploadUrl
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/V2DeployGenerateUploadUrlRequestBody'
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/V2DeployGenerateUploadUrlResponseBody'
description: Upload URL generated successfully
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequestErrorResponse'
description: Bad request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/UnauthorizedErrorResponse'
description: Unauthorized
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/NotFoundErrorResponse'
description: Not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/InternalServerErrorResponse'
description: Internal server error
security:
- rootKey: []
summary: Generate upload URL
tags:
- deploy
x-speakeasy-name-override: generateUploadUrl
/v2/identities.createIdentity:
post:
description: |
Expand Down
2 changes: 2 additions & 0 deletions svc/api/openapi/openapi-split.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ paths:
# Deploy Endpoints
/v2/deploy.createDeployment:
$ref: "./spec/paths/v2/deploy/createDeployment/index.yaml"
/v2/deploy.generateUploadUrl:
$ref: "./spec/paths/v2/deploy/generateUploadUrl/index.yaml"

# Identity Endpoints
/v2/identities.createIdentity:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ post:
Creates a new deployment for a project using either a pre-built Docker image or build context.

**Authentication**: Requires a valid root key with appropriate permissions.
operationId: createDeployment
operationId: deploy.createDeployment
x-speakeasy-name-override: createDeployment
security:
- rootKey: []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type: object
required:
- projectId
properties:
projectId:
type: string
minLength: 1
description: Unkey project ID for which to generate the upload URL
example: "proj_123abc"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type: object
required:
- meta
- data
properties:
meta:
$ref: "../../../../common/Meta.yaml"
data:
$ref: "./V2DeployGenerateUploadUrlResponseData.yaml"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: object
required:
- uploadUrl
- context
properties:
uploadUrl:
type: string
description: Presigned PUT URL for uploading the build context tar file
example: "https://s3.amazonaws.com/bucket/path?signature=..."
context:
type: string
description: S3 path to use in the createDeployment request when building from source
example: "proj_123abc/ctx_456def.tar.gz"
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
post:
tags:
- deploy
summary: Generate upload URL
description: |
Generates a presigned S3 URL for uploading build context archives.

**Authentication**: Requires a valid root key with appropriate permissions.
operationId: deploy.generateUploadUrl
x-speakeasy-name-override: generateUploadUrl
security:
- rootKey: []
requestBody:
required: true
content:
application/json:
schema:
"$ref": "./V2DeployGenerateUploadUrlRequestBody.yaml"
responses:
"200":
description: Upload URL generated successfully
content:
application/json:
schema:
"$ref": "./V2DeployGenerateUploadUrlResponseBody.yaml"
"400":
description: Bad request
content:
application/json:
schema:
"$ref": "../../../../error/BadRequestErrorResponse.yaml"
"401":
description: Unauthorized
content:
application/json:
schema:
"$ref": "../../../../error/UnauthorizedErrorResponse.yaml"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "../../../../error/NotFoundErrorResponse.yaml"
"500":
description: Internal server error
content:
application/json:
schema:
"$ref": "../../../../error/InternalServerErrorResponse.yaml"
1 change: 1 addition & 0 deletions svc/api/routes/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ go_library(
"//svc/api/routes/v2_apis_get_api",
"//svc/api/routes/v2_apis_list_keys",
"//svc/api/routes/v2_deploy_create_deployment",
"//svc/api/routes/v2_deploy_generate_upload_url",
"//svc/api/routes/v2_identities_create_identity",
"//svc/api/routes/v2_identities_delete_identity",
"//svc/api/routes/v2_identities_get_identity",
Expand Down
16 changes: 14 additions & 2 deletions svc/api/routes/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
v2ApisListKeys "github.com/unkeyed/unkey/svc/api/routes/v2_apis_list_keys"

v2DeployCreateDeployment "github.com/unkeyed/unkey/svc/api/routes/v2_deploy_create_deployment"
v2DeployGenerateUploadUrl "github.com/unkeyed/unkey/svc/api/routes/v2_deploy_generate_upload_url"

v2IdentitiesCreateIdentity "github.com/unkeyed/unkey/svc/api/routes/v2_identities_create_identity"
v2IdentitiesDeleteIdentity "github.com/unkeyed/unkey/svc/api/routes/v2_identities_delete_identity"
Expand Down Expand Up @@ -324,7 +325,7 @@ func Register(srv *zen.Server, svc *Services, info zen.InstanceInfo) {
// ---------------------------------------------------------------------------
// v2/deploy

if svc.CtrlDeploymentClient != nil {
if svc.CtrlBuildClient != nil {
// v2/deploy.createDeployment
srv.RegisterRoute(
defaultMiddlewares,
Expand All @@ -335,6 +336,17 @@ func Register(srv *zen.Server, svc *Services, info zen.InstanceInfo) {
CtrlClient: svc.CtrlDeploymentClient,
},
)

// v2/deploy.generateUploadUrl
srv.RegisterRoute(
defaultMiddlewares,
&v2DeployGenerateUploadUrl.Handler{
Logger: svc.Logger,
DB: svc.Database,
Keys: svc.Keys,
CtrlClient: svc.CtrlBuildClient,
},
)
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -628,7 +640,7 @@ func Register(srv *zen.Server, svc *Services, info zen.InstanceInfo) {
// ---------------------------------------------------------------------------
// misc

var miscMiddlewares = []zen.Middleware{
miscMiddlewares := []zen.Middleware{
withObservability,
withMetrics,
withLogging,
Expand Down
1 change: 1 addition & 0 deletions svc/api/routes/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Services struct {
Vault *vault.Service
ChproxyToken string
CtrlDeploymentClient ctrlv1connect.DeploymentServiceClient
CtrlBuildClient ctrlv1connect.BuildServiceClient
PprofEnabled bool
PprofUsername string
PprofPassword string
Expand Down
Loading