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
14 changes: 12 additions & 2 deletions .github/workflows/components-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
- 'components/frontend/**'
- 'components/public-api/**'
- 'components/ambient-api-server/**'
- 'components/ambient-control-plane/**'
- 'components/ambient-mcp/**'
pull_request:
branches: [main, alpha]
paths:
Expand All @@ -23,10 +25,12 @@ on:
- 'components/frontend/**'
- 'components/public-api/**'
- 'components/ambient-api-server/**'
- 'components/ambient-control-plane/**'
- 'components/ambient-mcp/**'
workflow_dispatch:
inputs:
components:
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server) - leave empty for all'
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server,ambient-control-plane,ambient-mcp) - leave empty for all'
required: false
type: string
default: ''
Expand Down Expand Up @@ -54,7 +58,9 @@ jobs:
{"name":"ambient-runner","context":"./components/runners/ambient-runner","image":"quay.io/ambient_code/vteam_claude_runner","dockerfile":"./components/runners/ambient-runner/Dockerfile"},
{"name":"state-sync","context":"./components/runners/state-sync","image":"quay.io/ambient_code/vteam_state_sync","dockerfile":"./components/runners/state-sync/Dockerfile"},
{"name":"public-api","context":"./components/public-api","image":"quay.io/ambient_code/vteam_public_api","dockerfile":"./components/public-api/Dockerfile"},
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"}
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"},
{"name":"ambient-control-plane","context":"./components/ambient-control-plane","image":"quay.io/ambient_code/vteam_control_plane","dockerfile":"./components/ambient-control-plane/Dockerfile"},
{"name":"ambient-mcp","context":"./components/ambient-mcp","image":"quay.io/ambient_code/vteam_mcp","dockerfile":"./components/ambient-mcp/Dockerfile"}
]'

SELECTED="${{ github.event.inputs.components }}"
Expand Down Expand Up @@ -376,6 +382,8 @@ jobs:
kustomize edit set image quay.io/ambient_code/vteam_state_sync:latest=quay.io/ambient_code/vteam_state_sync:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_api_server:latest=quay.io/ambient_code/vteam_api_server:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_public_api:latest=quay.io/ambient_code/vteam_public_api:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_control_plane:latest=quay.io/ambient_code/vteam_control_plane:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_mcp:latest=quay.io/ambient_code/vteam_mcp:${{ github.sha }}

- name: Validate kustomization
working-directory: components/manifests/overlays/production
Expand Down Expand Up @@ -451,6 +459,8 @@ jobs:
kustomize edit set image quay.io/ambient_code/vteam_state_sync:latest=quay.io/ambient_code/vteam_state_sync:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_api_server:latest=quay.io/ambient_code/vteam_api_server:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_public_api:latest=quay.io/ambient_code/vteam_public_api:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_control_plane:latest=quay.io/ambient_code/vteam_control_plane:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_mcp:latest=quay.io/ambient_code/vteam_mcp:${{ github.sha }}

- name: Validate kustomization
working-directory: components/manifests/overlays/production
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/prod-release-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
type: boolean
default: true
components:
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server) - leave empty for all'
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server,ambient-control-plane,ambient-mcp) - leave empty for all'
required: false
type: string
default: ''
Expand Down Expand Up @@ -236,7 +236,9 @@ jobs:
{"name":"ambient-runner","context":"./components/runners/ambient-runner","image":"quay.io/ambient_code/vteam_claude_runner","dockerfile":"./components/runners/ambient-runner/Dockerfile"},
{"name":"state-sync","context":"./components/runners/state-sync","image":"quay.io/ambient_code/vteam_state_sync","dockerfile":"./components/runners/state-sync/Dockerfile"},
{"name":"public-api","context":"./components/public-api","image":"quay.io/ambient_code/vteam_public_api","dockerfile":"./components/public-api/Dockerfile"},
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"}
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"},
{"name":"ambient-control-plane","context":"./components/ambient-control-plane","image":"quay.io/ambient_code/vteam_control_plane","dockerfile":"./components/ambient-control-plane/Dockerfile"},
{"name":"ambient-mcp","context":"./components/ambient-mcp","image":"quay.io/ambient_code/vteam_mcp","dockerfile":"./components/ambient-mcp/Dockerfile"}
]'

FORCE_ALL="${{ github.event.inputs.force_build_all }}"
Expand Down Expand Up @@ -621,16 +623,20 @@ jobs:
["operator"]="agentic-operator:agentic-operator"
["public-api"]="public-api:public-api"
["ambient-api-server"]="ambient-api-server:ambient-api-server"
["ambient-control-plane"]="ambient-control-plane:ambient-control-plane"
)


for comp_image in \
"frontend:quay.io/ambient_code/vteam_frontend" \
"backend:quay.io/ambient_code/vteam_backend" \
"operator:quay.io/ambient_code/vteam_operator" \
"ambient-runner:quay.io/ambient_code/vteam_claude_runner" \
"state-sync:quay.io/ambient_code/vteam_state_sync" \
"public-api:quay.io/ambient_code/vteam_public_api" \
"ambient-api-server:quay.io/ambient_code/vteam_api_server"; do
"ambient-api-server:quay.io/ambient_code/vteam_api_server" \
"ambient-control-plane:quay.io/ambient_code/vteam_control_plane" \
"ambient-mcp:quay.io/ambient_code/vteam_mcp"; do
COMP="${comp_image%%:*}"
IMAGE="${comp_image#*:}"

Expand Down
17 changes: 9 additions & 8 deletions components/ambient-api-server/plugins/credentials/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,17 @@ func rolesMigration() *gormigrate.Migration {
if err != nil {
return err
}
row := roleRow{
ID: api.NewID(),
Name: r.name,
DisplayName: r.displayName,
Description: r.description,
Permissions: string(permsJSON),
BuiltIn: true,
}
var row roleRow
if err := tx.Table("roles").
Where("name = ?", r.name).
Attrs(roleRow{
ID: api.NewID(),
Name: r.name,
DisplayName: r.displayName,
Description: r.description,
Permissions: string(permsJSON),
BuiltIn: true,
}).
FirstOrCreate(&row).Error; err != nil {
return err
}
Expand Down
17 changes: 9 additions & 8 deletions components/ambient-api-server/plugins/roles/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,17 @@ func seedBuiltInRoles(tx *gorm.DB) error {
if err != nil {
return err
}
row := roleRow{
ID: api.NewID(),
Name: r.name,
DisplayName: r.displayName,
Description: r.description,
Permissions: string(permsJSON),
BuiltIn: true,
}
var row roleRow
if err := tx.Table("roles").
Where("name = ?", r.name).
Attrs(roleRow{
ID: api.NewID(),
Name: r.name,
DisplayName: r.displayName,
Description: r.description,
Permissions: string(permsJSON),
BuiltIn: true,
}).
FirstOrCreate(&row).Error; err != nil {
return err
}
Expand Down
33 changes: 33 additions & 0 deletions components/ambient-mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM registry.access.redhat.com/ubi9/go-toolset:1.25 AS builder

ARG GIT_COMMIT=unknown
ARG GIT_BRANCH=unknown
ARG GIT_REPO=unknown
ARG GIT_VERSION=unknown
ARG BUILD_DATE=unknown
ARG BUILD_USER=unknown

USER 0
WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o ambient-mcp .

FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

WORKDIR /app

RUN microdnf install -y procps && microdnf clean all

COPY --from=builder /app/ambient-mcp .

RUN chmod +x ./ambient-mcp && chmod 775 /app

USER 1001

ENTRYPOINT ["./ambient-mcp"]
CMD []
116 changes: 116 additions & 0 deletions components/ambient-mcp/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
)

type Client struct {
httpClient *http.Client
baseURL string
mu sync.RWMutex
token string
}

func New(baseURL, token string) *Client {
return &Client{
httpClient: &http.Client{Timeout: 30 * time.Second},
baseURL: strings.TrimSuffix(baseURL, "/"),
token: token,
}
}

func (c *Client) BaseURL() string { return c.baseURL }

func (c *Client) Token() string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.token
}

func (c *Client) SetToken(token string) {
c.mu.Lock()
defer c.mu.Unlock()
c.token = token
}

func (c *Client) do(ctx context.Context, method, path string, body []byte, result interface{}, expectedStatuses ...int) error {
reqURL := c.baseURL + "/api/ambient/v1" + path
var bodyReader io.Reader
if body != nil {
bodyReader = bytes.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Authorization", "Bearer "+c.Token())
req.Header.Set("Accept", "application/json")

resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response: %w", err)
}

ok := false
for _, s := range expectedStatuses {
if resp.StatusCode == s {
ok = true
break
}
}
if !ok {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
}

if result != nil && len(respBody) > 0 {
if err := json.Unmarshal(respBody, result); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
}
}
return nil
}

func (c *Client) Get(ctx context.Context, path string, result interface{}) error {
return c.do(ctx, http.MethodGet, path, nil, result, http.StatusOK)
}

func (c *Client) GetWithQuery(ctx context.Context, path string, params url.Values, result interface{}) error {
if len(params) > 0 {
path = path + "?" + params.Encode()
}
return c.Get(ctx, path, result)
}

func (c *Client) Post(ctx context.Context, path string, body interface{}, result interface{}, expectedStatus int) error {
b, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshal body: %w", err)
}
return c.do(ctx, http.MethodPost, path, b, result, expectedStatus)
}

func (c *Client) Patch(ctx context.Context, path string, body interface{}, result interface{}) error {
b, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshal body: %w", err)
}
return c.do(ctx, http.MethodPatch, path, b, result, http.StatusOK)
}
Loading
Loading