mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
ai_gateway_coderd_key -> ai_gateway_keys table rename
This commit is contained in:
@@ -1,22 +0,0 @@
|
|||||||
package aigatewaycoderdkey_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/aigatewaycoderdkey"
|
|
||||||
"github.com/coder/coder/v2/coderd/apikey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
params, key, err := aigatewaycoderdkey.New("test-key")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, key, aigatewaycoderdkey.KeyLength)
|
|
||||||
require.Len(t, params.SecretPrefix, aigatewaycoderdkey.KeyPrefixLength)
|
|
||||||
require.Equal(t, key[:aigatewaycoderdkey.KeyPrefixLength], params.SecretPrefix)
|
|
||||||
require.True(t, apikey.ValidateHash(params.HashedSecret, key))
|
|
||||||
require.False(t, apikey.ValidateHash(params.HashedSecret, key[aigatewaycoderdkey.KeyPrefixLength:]))
|
|
||||||
}
|
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package aigatewaycoderdkey
|
package aigatewaykey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -21,14 +21,14 @@ const (
|
|||||||
|
|
||||||
// New generates an AI Gateway Coderd key. Returns InsertParams ready
|
// New generates an AI Gateway Coderd key. Returns InsertParams ready
|
||||||
// for the database query.
|
// for the database query.
|
||||||
func New(name string) (database.InsertAIGatewayCoderdKeyParams, string, error) {
|
func New(name string) (database.InsertAIGatewayKeyParams, string, error) {
|
||||||
secret, hashed, err := apikey.GenerateSecret(KeyLength)
|
secret, hashed, err := apikey.GenerateSecret(KeyLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return database.InsertAIGatewayCoderdKeyParams{}, "", xerrors.Errorf("generate secret: %w", err)
|
return database.InsertAIGatewayKeyParams{}, "", xerrors.Errorf("generate secret: %w", err)
|
||||||
}
|
}
|
||||||
visiblePrefix := secret[:KeyPrefixLength]
|
visiblePrefix := secret[:KeyPrefixLength]
|
||||||
|
|
||||||
return database.InsertAIGatewayCoderdKeyParams{
|
return database.InsertAIGatewayKeyParams{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
Name: name,
|
Name: name,
|
||||||
SecretPrefix: visiblePrefix,
|
SecretPrefix: visiblePrefix,
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package aigatewaykey_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/aigatewaykey"
|
||||||
|
"github.com/coder/coder/v2/coderd/apikey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
params, key, err := aigatewaykey.New("test-key")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, key, aigatewaykey.KeyLength)
|
||||||
|
require.Len(t, params.SecretPrefix, aigatewaykey.KeyPrefixLength)
|
||||||
|
require.Equal(t, key[:aigatewaykey.KeyPrefixLength], params.SecretPrefix)
|
||||||
|
require.True(t, apikey.ValidateHash(params.HashedSecret, key))
|
||||||
|
require.False(t, apikey.ValidateHash(params.HashedSecret, key[aigatewaykey.KeyPrefixLength:]))
|
||||||
|
}
|
||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AIGatewayCoderdKey is a shared secret used by a standalone AI Gateway
|
// AIGatewayKey is a shared secret used by a standalone AI Gateway
|
||||||
// to authenticate into coderd.
|
// to authenticate into coderd.
|
||||||
type AIGatewayCoderdKey struct {
|
type AIGatewayKey struct {
|
||||||
ID uuid.UUID `json:"id" format:"uuid"`
|
ID uuid.UUID `json:"id" format:"uuid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
KeyPrefix string `json:"key_prefix"`
|
KeyPrefix string `json:"key_prefix"`
|
||||||
@@ -21,14 +21,14 @@ type AIGatewayCoderdKey struct {
|
|||||||
LastUsedAt *time.Time `json:"last_used_at,omitempty" format:"date-time"`
|
LastUsedAt *time.Time `json:"last_used_at,omitempty" format:"date-time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAIGatewayCoderdKeyRequest requests a new AI Gateway coderd key.
|
// CreateAIGatewayKeyRequest requests a new AI Gateway key.
|
||||||
type CreateAIGatewayCoderdKeyRequest struct {
|
type CreateAIGatewayKeyRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAIGatewayCoderdKeyResponse returns all key information.
|
// CreateAIGatewayKeyResponse returns all key information.
|
||||||
// Key value is only returned here and cannot be recovered afterwards.
|
// Key value is only returned here and cannot be recovered afterwards.
|
||||||
type CreateAIGatewayCoderdKeyResponse struct {
|
type CreateAIGatewayKeyResponse struct {
|
||||||
ID uuid.UUID `json:"id" format:"uuid"`
|
ID uuid.UUID `json:"id" format:"uuid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
@@ -36,24 +36,24 @@ type CreateAIGatewayCoderdKeyResponse struct {
|
|||||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAIGatewayCoderdKey creates a new AI Gateway coderd key.
|
// CreateAIGatewayKey creates a new AI Gateway key.
|
||||||
func (c *Client) CreateAIGatewayCoderdKey(ctx context.Context, req CreateAIGatewayCoderdKeyRequest) (CreateAIGatewayCoderdKeyResponse, error) {
|
func (c *Client) CreateAIGatewayKey(ctx context.Context, req CreateAIGatewayKeyRequest) (CreateAIGatewayKeyResponse, error) {
|
||||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/aibridge/coderd-keys", req)
|
res, err := c.Request(ctx, http.MethodPost, "/api/v2/aibridge/keys", req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateAIGatewayCoderdKeyResponse{}, xerrors.Errorf("make request: %w", err)
|
return CreateAIGatewayKeyResponse{}, xerrors.Errorf("make request: %w", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != http.StatusCreated {
|
if res.StatusCode != http.StatusCreated {
|
||||||
return CreateAIGatewayCoderdKeyResponse{}, ReadBodyAsError(res)
|
return CreateAIGatewayKeyResponse{}, ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
var resp CreateAIGatewayCoderdKeyResponse
|
var resp CreateAIGatewayKeyResponse
|
||||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAIGatewayCoderdKeys lists all AI Gateway coderd keys.
|
// ListAIGatewayKeys lists all AI Gateway keys.
|
||||||
func (c *Client) ListAIGatewayCoderdKeys(ctx context.Context) ([]AIGatewayCoderdKey, error) {
|
func (c *Client) ListAIGatewayKeys(ctx context.Context) ([]AIGatewayKey, error) {
|
||||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/aibridge/coderd-keys", nil)
|
res, err := c.Request(ctx, http.MethodGet, "/api/v2/aibridge/keys", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("make request: %w", err)
|
return nil, xerrors.Errorf("make request: %w", err)
|
||||||
}
|
}
|
||||||
@@ -62,14 +62,14 @@ func (c *Client) ListAIGatewayCoderdKeys(ctx context.Context) ([]AIGatewayCoderd
|
|||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, ReadBodyAsError(res)
|
return nil, ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
var resp []AIGatewayCoderdKey
|
var resp []AIGatewayKey
|
||||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAIGatewayCoderdKey deletes an AI Gateway coderd key by ID.
|
// DeleteAIGatewayKey deletes an AI Gateway key by ID.
|
||||||
func (c *Client) DeleteAIGatewayCoderdKey(ctx context.Context, id uuid.UUID) error {
|
func (c *Client) DeleteAIGatewayKey(ctx context.Context, id uuid.UUID) error {
|
||||||
res, err := c.Request(ctx, http.MethodDelete,
|
res, err := c.Request(ctx, http.MethodDelete,
|
||||||
fmt.Sprintf("/api/v2/aibridge/coderd-keys/%s", id.String()), nil)
|
fmt.Sprintf("/api/v2/aibridge/keys/%s", id.String()), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("make request: %w", err)
|
return xerrors.Errorf("make request: %w", err)
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/aigatewaycoderdkey"
|
"github.com/coder/coder/v2/coderd/aigatewaykey"
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
@@ -24,20 +24,20 @@ const maxKeyInsertAttempts = 7
|
|||||||
// nameFormatDetail is the human-readable description of valid key names.
|
// nameFormatDetail is the human-readable description of valid key names.
|
||||||
const nameFormatDetail = "Must be 64 characters or fewer, lowercase letters, numbers, and non-consecutive hyphens, cannot start or end with a hyphen."
|
const nameFormatDetail = "Must be 64 characters or fewer, lowercase letters, numbers, and non-consecutive hyphens, cannot start or end with a hyphen."
|
||||||
|
|
||||||
// @Summary Create AI Gateway coderd key
|
// @Summary Create AI Gateway key
|
||||||
// @ID create-ai-gateway-coderd-key
|
// @ID create-ai-gateway-key
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Enterprise
|
// @Tags Enterprise
|
||||||
// @Param request body codersdk.CreateAIGatewayCoderdKeyRequest true "Create AI Gateway coderd key request"
|
// @Param request body codersdk.CreateAIGatewayKeyRequest true "Create AI Gateway key request"
|
||||||
// @Success 201 {object} codersdk.CreateAIGatewayCoderdKeyResponse
|
// @Success 201 {object} codersdk.CreateAIGatewayKeyResponse
|
||||||
// @Router /api/v2/aibridge/coderd-keys [post]
|
// @Router /api/v2/aibridge/keys [post]
|
||||||
func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) postAIGatewayKey(rw http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
ctx = r.Context()
|
ctx = r.Context()
|
||||||
auditor = api.AGPL.Auditor.Load()
|
auditor = api.AGPL.Auditor.Load()
|
||||||
aReq, commitAudit = audit.InitRequest[database.AiGatewayCoderdKey](rw, &audit.RequestParams{
|
aReq, commitAudit = audit.InitRequest[database.AIGatewayKey](rw, &audit.RequestParams{
|
||||||
Audit: *auditor,
|
Audit: *auditor,
|
||||||
Log: api.Logger,
|
Log: api.Logger,
|
||||||
Request: r,
|
Request: r,
|
||||||
@@ -46,7 +46,7 @@ func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request)
|
|||||||
)
|
)
|
||||||
defer commitAudit()
|
defer commitAudit()
|
||||||
|
|
||||||
var req codersdk.CreateAIGatewayCoderdKeyRequest
|
var req codersdk.CreateAIGatewayKeyRequest
|
||||||
if !httpapi.Read(ctx, rw, r, &req) {
|
if !httpapi.Read(ctx, rw, r, &req) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -60,14 +60,14 @@ func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aReq.New = database.AiGatewayCoderdKey{
|
aReq.New = database.AIGatewayKey{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
SecretPrefix: row.SecretPrefix,
|
SecretPrefix: row.SecretPrefix,
|
||||||
CreatedAt: row.CreatedAt,
|
CreatedAt: row.CreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateAIGatewayCoderdKeyResponse{
|
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateAIGatewayKeyResponse{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
KeyPrefix: row.SecretPrefix,
|
KeyPrefix: row.SecretPrefix,
|
||||||
@@ -77,14 +77,14 @@ func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateAndInsertKey creates fresh key material and attempts an insert.
|
// generateAndInsertKey creates fresh key material and attempts an insert.
|
||||||
func (api *API) generateAndInsertKey(ctx context.Context, name string) (database.InsertAIGatewayCoderdKeyRow, string, error) {
|
func (api *API) generateAndInsertKey(ctx context.Context, name string) (database.InsertAIGatewayKeyRow, string, error) {
|
||||||
params, key, err := aigatewaycoderdkey.New(name)
|
params, key, err := aigatewaykey.New(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return database.InsertAIGatewayCoderdKeyRow{}, "", err
|
return database.InsertAIGatewayKeyRow{}, "", err
|
||||||
}
|
}
|
||||||
row, err := api.Database.InsertAIGatewayCoderdKey(ctx, params)
|
row, err := api.Database.InsertAIGatewayKey(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return database.InsertAIGatewayCoderdKeyRow{}, "", err
|
return database.InsertAIGatewayKeyRow{}, "", err
|
||||||
}
|
}
|
||||||
return row, key, nil
|
return row, key, nil
|
||||||
}
|
}
|
||||||
@@ -92,8 +92,8 @@ func (api *API) generateAndInsertKey(ctx context.Context, name string) (database
|
|||||||
// isRetryableKeyInsertErr returns true for generated-secret collisions.
|
// isRetryableKeyInsertErr returns true for generated-secret collisions.
|
||||||
func isRetryableKeyInsertErr(err error) bool {
|
func isRetryableKeyInsertErr(err error) bool {
|
||||||
return database.IsUniqueViolation(err,
|
return database.IsUniqueViolation(err,
|
||||||
database.UniqueAiGatewayCoderdKeysSecretPrefixIndex,
|
database.UniqueAiGatewayKeysSecretPrefixIndex,
|
||||||
database.UniqueAiGatewayCoderdKeysHashedSecretIndex,
|
database.UniqueAiGatewayKeysHashedSecretIndex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,14 +102,14 @@ func writeKeyInsertError(ctx context.Context, rw http.ResponseWriter, err error)
|
|||||||
switch {
|
switch {
|
||||||
case httpapi.IsUnauthorizedError(err):
|
case httpapi.IsUnauthorizedError(err):
|
||||||
httpapi.Forbidden(rw)
|
httpapi.Forbidden(rw)
|
||||||
case database.IsCheckViolation(err, database.CheckAiGatewayCoderdKeysNameCheck):
|
case database.IsCheckViolation(err, database.CheckAiGatewayKeysNameCheck):
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
Message: "Invalid key name.",
|
Message: "Invalid key name.",
|
||||||
Validations: []codersdk.ValidationError{
|
Validations: []codersdk.ValidationError{
|
||||||
{Field: "name", Detail: nameFormatDetail},
|
{Field: "name", Detail: nameFormatDetail},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
case database.IsUniqueViolation(err, database.UniqueAiGatewayCoderdKeysNameIndex):
|
case database.IsUniqueViolation(err, database.UniqueAiGatewayKeysNameIndex):
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
Message: "Key name must be unique.",
|
Message: "Key name must be unique.",
|
||||||
Validations: []codersdk.ValidationError{
|
Validations: []codersdk.ValidationError{
|
||||||
@@ -121,17 +121,17 @@ func writeKeyInsertError(ctx context.Context, rw http.ResponseWriter, err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary List AI Gateway coderd keys
|
// @Summary List AI Gateway keys
|
||||||
// @ID list-ai-gateway-coderd-keys
|
// @ID list-ai-gatewaykeys
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Enterprise
|
// @Tags Enterprise
|
||||||
// @Success 200 {array} codersdk.AIGatewayCoderdKey
|
// @Success 200 {array} codersdk.AIGatewayKey
|
||||||
// @Router /api/v2/aibridge/coderd-keys [get]
|
// @Router /api/v2/aibridge/keys [get]
|
||||||
func (api *API) aiGatewayCoderdKeys(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) aiGatewayKeys(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
rows, err := api.Database.ListAIGatewayCoderdKeys(ctx)
|
rows, err := api.Database.ListAIGatewayKeys(ctx)
|
||||||
if httpapi.IsUnauthorizedError(err) {
|
if httpapi.IsUnauthorizedError(err) {
|
||||||
httpapi.Forbidden(rw)
|
httpapi.Forbidden(rw)
|
||||||
return
|
return
|
||||||
@@ -141,26 +141,26 @@ func (api *API) aiGatewayCoderdKeys(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]codersdk.AIGatewayCoderdKey, 0, len(rows))
|
out := make([]codersdk.AIGatewayKey, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
out = append(out, convertAIGatewayCoderdKey(row))
|
out = append(out, convertAIGatewayKey(row))
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, out)
|
httpapi.Write(ctx, rw, http.StatusOK, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Delete AI Gateway coderd key
|
// @Summary Delete AI Gateway key
|
||||||
// @ID delete-ai-gateway-coderd-key
|
// @ID delete-ai-gateway-key
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Tags Enterprise
|
// @Tags Enterprise
|
||||||
// @Param key path string true "Key ID" format(uuid)
|
// @Param key path string true "Key ID" format(uuid)
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /api/v2/aibridge/coderd-keys/{key} [delete]
|
// @Router /api/v2/aibridge/keys/{key} [delete]
|
||||||
func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) deleteAIGatewayKey(rw http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
ctx = r.Context()
|
ctx = r.Context()
|
||||||
auditor = api.AGPL.Auditor.Load()
|
auditor = api.AGPL.Auditor.Load()
|
||||||
aReq, commitAudit = audit.InitRequest[database.AiGatewayCoderdKey](rw, &audit.RequestParams{
|
aReq, commitAudit = audit.InitRequest[database.AIGatewayKey](rw, &audit.RequestParams{
|
||||||
Audit: *auditor,
|
Audit: *auditor,
|
||||||
Log: api.Logger,
|
Log: api.Logger,
|
||||||
Request: r,
|
Request: r,
|
||||||
@@ -178,7 +178,7 @@ func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deleted, err := api.Database.DeleteAIGatewayCoderdKey(ctx, id)
|
deleted, err := api.Database.DeleteAIGatewayKey(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if httpapi.IsUnauthorizedError(err) {
|
if httpapi.IsUnauthorizedError(err) {
|
||||||
httpapi.Forbidden(rw)
|
httpapi.Forbidden(rw)
|
||||||
@@ -192,7 +192,7 @@ func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aReq.Old = database.AiGatewayCoderdKey{
|
aReq.Old = database.AIGatewayKey{
|
||||||
ID: deleted.ID,
|
ID: deleted.ID,
|
||||||
Name: deleted.Name,
|
Name: deleted.Name,
|
||||||
SecretPrefix: deleted.SecretPrefix,
|
SecretPrefix: deleted.SecretPrefix,
|
||||||
@@ -203,13 +203,13 @@ func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request
|
|||||||
rw.WriteHeader(http.StatusNoContent)
|
rw.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertAIGatewayCoderdKey(row database.ListAIGatewayCoderdKeysRow) codersdk.AIGatewayCoderdKey {
|
func convertAIGatewayKey(row database.ListAIGatewayKeysRow) codersdk.AIGatewayKey {
|
||||||
var lastUsed *time.Time
|
var lastUsed *time.Time
|
||||||
if row.LastUsedAt.Valid {
|
if row.LastUsedAt.Valid {
|
||||||
t := row.LastUsedAt.Time
|
t := row.LastUsedAt.Time
|
||||||
lastUsed = &t
|
lastUsed = &t
|
||||||
}
|
}
|
||||||
return codersdk.AIGatewayCoderdKey{
|
return codersdk.AIGatewayKey{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
KeyPrefix: row.SecretPrefix,
|
KeyPrefix: row.SecretPrefix,
|
||||||
+31
-31
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/aigatewaycoderdkey"
|
"github.com/coder/coder/v2/coderd/aigatewaykey"
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAIGatewayCoderdKeys(t *testing.T) {
|
func TestAIGatewayKeys(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Single instance shared by all subtests (except FeatureGate).
|
// Single instance shared by all subtests (except FeatureGate).
|
||||||
@@ -36,22 +36,22 @@ func TestAIGatewayCoderdKeys(t *testing.T) {
|
|||||||
|
|
||||||
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
||||||
t.Run("CRUD", func(t *testing.T) {
|
t.Run("CRUD", func(t *testing.T) {
|
||||||
keys, err := ownerClient.ListAIGatewayCoderdKeys(ctx)
|
keys, err := ownerClient.ListAIGatewayKeys(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, keys)
|
require.Empty(t, keys)
|
||||||
|
|
||||||
name := uniqueName(t, "happy")
|
name := uniqueName(t, "happy")
|
||||||
|
|
||||||
created, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: name})
|
created, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: name})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, uuid.Nil, created.ID)
|
require.NotEqual(t, uuid.Nil, created.ID)
|
||||||
require.Equal(t, name, created.Name)
|
require.Equal(t, name, created.Name)
|
||||||
require.Len(t, created.KeyPrefix, aigatewaycoderdkey.KeyPrefixLength)
|
require.Len(t, created.KeyPrefix, aigatewaykey.KeyPrefixLength)
|
||||||
require.Len(t, created.Key, aigatewaycoderdkey.KeyLength)
|
require.Len(t, created.Key, aigatewaykey.KeyLength)
|
||||||
require.True(t, strings.HasPrefix(created.Key, created.KeyPrefix), "key must begin with key_prefix")
|
require.True(t, strings.HasPrefix(created.Key, created.KeyPrefix), "key must begin with key_prefix")
|
||||||
require.WithinDuration(t, time.Now(), created.CreatedAt, time.Minute)
|
require.WithinDuration(t, time.Now(), created.CreatedAt, time.Minute)
|
||||||
|
|
||||||
keys, err = ownerClient.ListAIGatewayCoderdKeys(ctx)
|
keys, err = ownerClient.ListAIGatewayKeys(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, keys, 1)
|
require.Len(t, keys, 1)
|
||||||
require.Equal(t, created.ID, keys[0].ID)
|
require.Equal(t, created.ID, keys[0].ID)
|
||||||
@@ -59,25 +59,25 @@ func TestAIGatewayCoderdKeys(t *testing.T) {
|
|||||||
require.Equal(t, created.KeyPrefix, keys[0].KeyPrefix)
|
require.Equal(t, created.KeyPrefix, keys[0].KeyPrefix)
|
||||||
require.Nil(t, keys[0].LastUsedAt)
|
require.Nil(t, keys[0].LastUsedAt)
|
||||||
|
|
||||||
require.NoError(t, ownerClient.DeleteAIGatewayCoderdKey(ctx, created.ID))
|
require.NoError(t, ownerClient.DeleteAIGatewayKey(ctx, created.ID))
|
||||||
|
|
||||||
keys, err = ownerClient.ListAIGatewayCoderdKeys(ctx)
|
keys, err = ownerClient.ListAIGatewayKeys(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, keys)
|
require.Empty(t, keys)
|
||||||
})
|
})
|
||||||
|
|
||||||
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
||||||
t.Run("ListResponseDoesNotLeakSecrets", func(t *testing.T) {
|
t.Run("ListResponseDoesNotLeakSecrets", func(t *testing.T) {
|
||||||
created, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{
|
created, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{
|
||||||
Name: uniqueName(t, "leak"),
|
Name: uniqueName(t, "leak"),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = ownerClient.DeleteAIGatewayCoderdKey(ctx, created.ID)
|
_ = ownerClient.DeleteAIGatewayKey(ctx, created.ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Raw HTTP read of LIST to confirm the JSON shape.
|
// Raw HTTP read of LIST to confirm the JSON shape.
|
||||||
resp, err := ownerClient.Request(ctx, http.MethodGet, "/api/v2/aibridge/coderd-keys", nil)
|
resp, err := ownerClient.Request(ctx, http.MethodGet, "/api/v2/aibridge/keys", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { _ = resp.Body.Close() })
|
t.Cleanup(func() { _ = resp.Body.Close() })
|
||||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
@@ -94,46 +94,46 @@ func TestAIGatewayCoderdKeys(t *testing.T) {
|
|||||||
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
||||||
t.Run("CreateValidation", func(t *testing.T) {
|
t.Run("CreateValidation", func(t *testing.T) {
|
||||||
// Empty name -> 400 (validate:"required" on request struct).
|
// Empty name -> 400 (validate:"required" on request struct).
|
||||||
_, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: ""})
|
_, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: ""})
|
||||||
require.ErrorContains(t, err, "Validation failed")
|
require.ErrorContains(t, err, "Validation failed")
|
||||||
|
|
||||||
// >64 char name -> 400 (DB check constraint).
|
// >64 char name -> 400 (DB check constraint).
|
||||||
longName := strings.Repeat("a", 65)
|
longName := strings.Repeat("a", 65)
|
||||||
_, err = ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: longName})
|
_, err = ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: longName})
|
||||||
require.ErrorContains(t, err, "Invalid key name")
|
require.ErrorContains(t, err, "Invalid key name")
|
||||||
|
|
||||||
// Uppercase name -> 400 (DB check constraint rejects non-lowercase).
|
// Uppercase name -> 400 (DB check constraint rejects non-lowercase).
|
||||||
_, err = ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: "UPPER-CASE"})
|
_, err = ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: "UPPER-CASE"})
|
||||||
require.ErrorContains(t, err, "Invalid key name")
|
require.ErrorContains(t, err, "Invalid key name")
|
||||||
|
|
||||||
// Duplicate name -> 400.
|
// Duplicate name -> 400.
|
||||||
name := uniqueName(t, "dup")
|
name := uniqueName(t, "dup")
|
||||||
created, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: name})
|
created, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: name})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = ownerClient.DeleteAIGatewayCoderdKey(ctx, created.ID)
|
_ = ownerClient.DeleteAIGatewayKey(ctx, created.ID)
|
||||||
})
|
})
|
||||||
_, err = ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: name})
|
_, err = ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: name})
|
||||||
require.ErrorContains(t, err, "must be unique")
|
require.ErrorContains(t, err, "must be unique")
|
||||||
})
|
})
|
||||||
|
|
||||||
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
//nolint:paralleltest // Subtests share a single coderdenttest instance.
|
||||||
t.Run("DeleteValidation", func(t *testing.T) {
|
t.Run("DeleteValidation", func(t *testing.T) {
|
||||||
// Invalid UUID -> 400 (raw request; SDK method accepts uuid.UUID).
|
// Invalid UUID -> 400 (raw request; SDK method accepts uuid.UUID).
|
||||||
resp, err := ownerClient.Request(ctx, http.MethodDelete, "/api/v2/aibridge/coderd-keys/not-a-uuid", nil)
|
resp, err := ownerClient.Request(ctx, http.MethodDelete, "/api/v2/aibridge/keys/not-a-uuid", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { _ = resp.Body.Close() })
|
t.Cleanup(func() { _ = resp.Body.Close() })
|
||||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
|
||||||
// Delete existing key -> 204 (SDK returns nil error on 204).
|
// Delete existing key -> 204 (SDK returns nil error on 204).
|
||||||
created, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{
|
created, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{
|
||||||
Name: uniqueName(t, "del"),
|
Name: uniqueName(t, "del"),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, ownerClient.DeleteAIGatewayCoderdKey(ctx, created.ID))
|
require.NoError(t, ownerClient.DeleteAIGatewayKey(ctx, created.ID))
|
||||||
|
|
||||||
// Unknown UUID -> 404.
|
// Unknown UUID -> 404.
|
||||||
err = ownerClient.DeleteAIGatewayCoderdKey(ctx, uuid.New())
|
err = ownerClient.DeleteAIGatewayKey(ctx, uuid.New())
|
||||||
var sdkErr *codersdk.Error
|
var sdkErr *codersdk.Error
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
require.ErrorAs(t, err, &sdkErr)
|
||||||
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
|
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
|
||||||
@@ -143,18 +143,18 @@ func TestAIGatewayCoderdKeys(t *testing.T) {
|
|||||||
t.Run("ReturnsForbiddenForNonOwners", func(t *testing.T) {
|
t.Run("ReturnsForbiddenForNonOwners", func(t *testing.T) {
|
||||||
member, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
member, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
|
||||||
|
|
||||||
_, err := member.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{
|
_, err := member.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{
|
||||||
Name: uniqueName(t, "denied"),
|
Name: uniqueName(t, "denied"),
|
||||||
})
|
})
|
||||||
var sdkErr *codersdk.Error
|
var sdkErr *codersdk.Error
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
require.ErrorAs(t, err, &sdkErr)
|
||||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||||
|
|
||||||
_, err = member.ListAIGatewayCoderdKeys(ctx)
|
_, err = member.ListAIGatewayKeys(ctx)
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
require.ErrorAs(t, err, &sdkErr)
|
||||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||||
|
|
||||||
err = member.DeleteAIGatewayCoderdKey(ctx, uuid.New())
|
err = member.DeleteAIGatewayKey(ctx, uuid.New())
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
require.ErrorAs(t, err, &sdkErr)
|
||||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||||
})
|
})
|
||||||
@@ -171,14 +171,14 @@ func TestAIGatewayCoderdKeys(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
||||||
_, err := ownerClient.ListAIGatewayCoderdKeys(ctx)
|
_, err := ownerClient.ListAIGatewayKeys(ctx)
|
||||||
var sdkErr *codersdk.Error
|
var sdkErr *codersdk.Error
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
require.ErrorAs(t, err, &sdkErr)
|
||||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAIGatewayCoderdKeyAudit(t *testing.T) {
|
func TestAIGatewayKeyAudit(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
db, ps := dbtestutil.NewDB(t)
|
db, ps := dbtestutil.NewDB(t)
|
||||||
@@ -200,15 +200,15 @@ func TestAIGatewayCoderdKeyAudit(t *testing.T) {
|
|||||||
|
|
||||||
name := uniqueName(t, "audit")
|
name := uniqueName(t, "audit")
|
||||||
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
||||||
created, err := ownerClient.CreateAIGatewayCoderdKey(ctx, codersdk.CreateAIGatewayCoderdKeyRequest{Name: name})
|
created, err := ownerClient.CreateAIGatewayKey(ctx, codersdk.CreateAIGatewayKeyRequest{Name: name})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
//nolint:gocritic // Managing AI Gateway coderd keys is owner-only.
|
||||||
require.NoError(t, ownerClient.DeleteAIGatewayCoderdKey(ctx, created.ID))
|
require.NoError(t, ownerClient.DeleteAIGatewayKey(ctx, created.ID))
|
||||||
|
|
||||||
rows, err := db.GetAuditLogsOffset(
|
rows, err := db.GetAuditLogsOffset(
|
||||||
dbauthz.AsSystemRestricted(ctx),
|
dbauthz.AsSystemRestricted(ctx),
|
||||||
database.GetAuditLogsOffsetParams{
|
database.GetAuditLogsOffsetParams{
|
||||||
ResourceType: string(database.ResourceTypeAiGatewayCoderdKey),
|
ResourceType: string(database.ResourceTypeAIGatewayKey),
|
||||||
LimitOpt: 10,
|
LimitOpt: 10,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -233,7 +233,7 @@ func TestAIGatewayCoderdKeyAudit(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusNoContent, int(deleteLog.StatusCode))
|
require.Equal(t, http.StatusNoContent, int(deleteLog.StatusCode))
|
||||||
|
|
||||||
for _, log := range []database.AuditLog{createLog, deleteLog} {
|
for _, log := range []database.AuditLog{createLog, deleteLog} {
|
||||||
require.Equal(t, database.ResourceTypeAiGatewayCoderdKey, log.ResourceType)
|
require.Equal(t, database.ResourceTypeAIGatewayKey, log.ResourceType)
|
||||||
require.Equal(t, created.ID, log.ResourceID)
|
require.Equal(t, created.ID, log.ResourceID)
|
||||||
require.Equal(t, name, log.ResourceTarget)
|
require.Equal(t, name, log.ResourceTarget)
|
||||||
}
|
}
|
||||||
@@ -299,14 +299,14 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
api.AGPL.APIHandler.Group(func(r chi.Router) {
|
api.AGPL.APIHandler.Group(func(r chi.Router) {
|
||||||
r.Route("/aibridge/coderd-keys", func(r chi.Router) {
|
r.Route("/aibridge/keys", func(r chi.Router) {
|
||||||
r.Use(
|
r.Use(
|
||||||
apiKeyMiddleware,
|
apiKeyMiddleware,
|
||||||
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
api.RequireFeatureMW(codersdk.FeatureAIBridge),
|
||||||
)
|
)
|
||||||
r.Get("/", api.aiGatewayCoderdKeys)
|
r.Get("/", api.aiGatewayKeys)
|
||||||
r.Post("/", api.postAIGatewayCoderdKey)
|
r.Post("/", api.postAIGatewayKey)
|
||||||
r.Delete("/{key}", api.deleteAIGatewayCoderdKey)
|
r.Delete("/{key}", api.deleteAIGatewayKey)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Generated
+9
-9
@@ -304,12 +304,12 @@ export interface AIConfig {
|
|||||||
readonly chat?: ChatConfig;
|
readonly chat?: ChatConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/aigatewaycoderdkeys.go
|
// From codersdk/aigatewaykeys.go
|
||||||
/**
|
/**
|
||||||
* AIGatewayCoderdKey is a shared secret used by a standalone AI Gateway
|
* AIGatewayKey is a shared secret used by a standalone AI Gateway
|
||||||
* to authenticate into coderd.
|
* to authenticate into coderd.
|
||||||
*/
|
*/
|
||||||
export interface AIGatewayCoderdKey {
|
export interface AIGatewayKey {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly key_prefix: string;
|
readonly key_prefix: string;
|
||||||
@@ -3257,20 +3257,20 @@ export interface ConvertLoginRequest {
|
|||||||
readonly password: string;
|
readonly password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/aigatewaycoderdkeys.go
|
// From codersdk/aigatewaykeys.go
|
||||||
/**
|
/**
|
||||||
* CreateAIGatewayCoderdKeyRequest requests a new AI Gateway coderd key.
|
* CreateAIGatewayKeyRequest requests a new AI Gateway key.
|
||||||
*/
|
*/
|
||||||
export interface CreateAIGatewayCoderdKeyRequest {
|
export interface CreateAIGatewayKeyRequest {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/aigatewaycoderdkeys.go
|
// From codersdk/aigatewaykeys.go
|
||||||
/**
|
/**
|
||||||
* CreateAIGatewayCoderdKeyResponse returns all key information.
|
* CreateAIGatewayKeyResponse returns all key information.
|
||||||
* Key value is only returned here and cannot be recovered afterwards.
|
* Key value is only returned here and cannot be recovered afterwards.
|
||||||
*/
|
*/
|
||||||
export interface CreateAIGatewayCoderdKeyResponse {
|
export interface CreateAIGatewayKeyResponse {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user