"tools/git@developer.sourcefind.cn:OpenDAS/openpcdet.git" did not exist on "ed2bb815f2531ef7c5fbfd2e2a182ce7aa04481a"
Commit 5ddc7f7d authored by Maksim Khadkevich's avatar Maksim Khadkevich Committed by GitHub
Browse files

feat: moved compoundAI operator, APIserver, and examples (#10)

parent 14ce7e03
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
VERSION 0.8
############### ARTIFACTS TARGETS ##############################
# These targets are invoked in child Earthfiles to pass top-level files that are out of their build context
# https://docs.earthly.dev/earthly-0.6/best-practices#copying-files-from-outside-the-build-context
############### SHARED LIBRARY TARGETS ##############################
golang-base:
FROM golang:1.23
RUN apt-get update && apt-get install -y git && apt-get clean && rm -rf /var/lib/apt/lists/* && curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz | tar -xzv && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/
############### ALL TARGETS ##############################
all-test:
BUILD ./deploy/compoundai/operator+test
# BUILD ./deploy/compoundai/api-server+test #TODO: mkhadkevich earthly tests fail https://gitlab-master.nvidia.com/aire/microservices/compoundai/-/jobs/144475821
all-docker:
ARG CI_REGISTRY_IMAGE=my-registry
ARG CI_COMMIT_SHA=latest
BUILD ./deploy/compoundai/operator+docker --CI_REGISTRY_IMAGE=$CI_REGISTRY_IMAGE --CI_COMMIT_SHA=$CI_COMMIT_SHA
BUILD ./deploy/compoundai/api-server+docker --CI_REGISTRY_IMAGE=$CI_REGISTRY_IMAGE --CI_COMMIT_SHA=$CI_COMMIT_SHA
all-lint:
BUILD ./deploy/compoundai/operator+lint
all:
BUILD +all-test
BUILD +all-docker
BUILD +all-lint
# For testing
custom:
ARG CI_REGISTRY_IMAGE=my-registry
ARG CI_COMMIT_SHA=latest
BUILD +all-test
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./api"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true
# Local development env
DB_USER="postgres"
DB_PASSWORD="pgadmin"
DB_HOST="localhost"
DB_PORT=5432
DB_NAME="postgres"
DMS_HOST="localhost"
DMS_PORT=8080
NDS_HOST="localhost"
NDS_PORT=8001
DEFAULT_KUBE_NAMESPACE="compoundai"
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/*
Dockerfile.cross
# Test binary, built with `go test -c`
*.test
# Temporary folder used by Air
tmp
\ No newline at end of file
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Build the manager binary
FROM golang:1.23 AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY api/ api/
COPY .env .env
# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o server api/main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/server .
COPY --from=builder /workspace/.env .
USER 65532:65532
ENTRYPOINT ["/server"]
VERSION 0.8
build:
FROM golang:1.23
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY api/ api/
COPY .env .env
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o server api/main.go
SAVE ARTIFACT /workspace/server
SAVE ARTIFACT /workspace/.env
#TODO: mkhadkevich earthly tests fail https://gitlab-master.nvidia.com/aire/microservices/compoundai/-/jobs/144475821
#test:
# FROM +build
# # copy test files
# COPY tests/ tests/
# RUN go test ./...
docker:
ARG CI_REGISTRY_IMAGE=my-registry
ARG CI_COMMIT_SHA=latest
ARG IMAGE=compound-api-server
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY +build/server .
COPY +build/.env .
USER 65532:65532
ENTRYPOINT ["/server"]
SAVE IMAGE --push $CI_REGISTRY_IMAGE/$IMAGE:$CI_COMMIT_SHA
\ No newline at end of file
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func SendRequestJSON(url string, method string, body *any) (*http.Response, []byte, error) {
var req *http.Request
var err error
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal request: %v", err)
}
req, err = http.NewRequest(method, url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, nil, fmt.Errorf("failed to create request: %v", err)
}
} else {
req, err = http.NewRequest(method, url, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create request: %v", err)
}
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("received non-OK response: %v, %s", resp.Status, respBody)
}
return resp, respBody, nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package consts
import (
"errors"
"gorm.io/gorm"
)
var (
ErrNotFound = gorm.ErrRecordNotFound
ErrNoPermission = errors.New("no permission")
ErrEmptyData = errors.New("data is nil")
ErrNoImplemented = errors.New("no implemented")
ErrTimeout = errors.New("timeout")
YataiOrganizationHeaderName = "X-Yatai-Organization"
NgcOrganizationHeaderName = "Nv-Ngc-Org"
NgcUserHeaderName = "Nv-Actor-Id"
CompoundNimContainerPortName = "http"
)
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package consts
const (
KubeLabelCompoundNim = "yatai.ai/bento-repository"
KubeLabelCompoundNimVersion = "yatai.ai/bento"
KubeLabelCompoundNimVersionDeployment = "yatai.ai/bento-deployment"
KubeImageBuilderMainContainer = "builder"
)
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package env
import (
"github.com/joho/godotenv"
"github.com/rs/zerolog/log"
)
func SetupEnv() {
err := godotenv.Load()
if err != nil {
log.Fatal().Msgf("Failed to load env during setup %s", err.Error())
}
_, err = SetResourceScope()
if err != nil {
log.Fatal().Msgf("Failed to set resource scope during env setup %s", err.Error())
}
_, err = SetNdsHost()
if err != nil {
log.Fatal().Msgf("Failed to set nds urls during env setup %s", err.Error())
}
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package env
import (
"fmt"
"sync"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/common/utils"
)
var (
NdsHostBase string
once sync.Once
)
func GetNdsUrl() string {
baseUrl := GetNdsHost()
return fmt.Sprintf("http://%s", baseUrl)
}
func GetNdsHost() string {
return NdsHostBase
}
func SetNdsHost() (string, error) {
var err error
once.Do(func() { // We cache and reuse the same NDS host
NDS_HOST, syncErr := utils.MustGetEnv("NDS_HOST")
if syncErr != nil {
err = syncErr
return
}
NDS_PORT, syncErr := utils.MustGetEnv("NDS_PORT")
if syncErr != nil {
err = syncErr
return
}
NdsHostBase = fmt.Sprintf("%s:%s", NDS_HOST, NDS_PORT)
})
if err != nil {
return "", err
}
return NdsHostBase, nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package env
import (
"fmt"
"os"
"sync"
)
type ResourceScope string
const (
OrganizationScope ResourceScope = "organization"
UserScope ResourceScope = "user"
)
var (
ApplicationScope ResourceScope
getScopeOnce sync.Once
)
func SetResourceScope() (ResourceScope, error) {
var err error
getScopeOnce.Do(func() {
scope := os.Getenv("RESOURCE_SCOPE")
if scope == "" {
scope = string(UserScope)
}
switch ResourceScope(scope) {
case OrganizationScope, UserScope:
ApplicationScope = ResourceScope(scope)
default:
err = fmt.Errorf("invalid scope value: %s", scope)
return
}
})
if err != nil {
return "", err
}
return ApplicationScope, nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package utils
import (
"fmt"
"os"
)
func MustGetEnv(key string) (string, error) {
value := os.Getenv(key)
if value == "" {
return "", fmt.Errorf("environment variable %s is not set", key)
}
return value, nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/converters"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/models"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/schemas"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/services"
)
type clusterController struct{}
var ClusterController = clusterController{}
func (s *clusterController) GetCluster(ctx *gin.Context, clusterName string) (*models.Cluster, error) {
ownership, err := GetOwnershipInfo(ctx)
if err != nil {
return nil, err
}
cluster, err := services.ClusterService.GetByName(ctx, ownership.OrganizationId, clusterName)
if err != nil {
return nil, err
}
return cluster, nil
}
func (c *clusterController) Create(ctx *gin.Context) {
var schema schemas.CreateClusterSchema
if err := ctx.ShouldBindJSON(&schema); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ownership, err := GetOwnershipInfo(ctx)
if err != nil {
ctx.JSON(400, err)
}
cluster, err := services.ClusterService.Create(ctx, services.CreateClusterOption{
Name: schema.Name,
OrganizationId: ownership.OrganizationId,
CreatorId: ownership.UserId,
Description: schema.Description,
KubeConfig: schema.KubeConfig,
})
if err != nil {
log.Info().Msgf("Failed to create cluster: %s", err.Error())
ctx.JSON(500, gin.Error{Err: err})
return
}
ctx.JSON(200, converters.ToClusterFullSchema(cluster))
}
func (c *clusterController) Update(ctx *gin.Context) {
var schema schemas.UpdateClusterSchema
clusterName := ctx.Param("clusterName")
if err := ctx.ShouldBindJSON(&schema); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
cluster, err := c.GetCluster(ctx, clusterName)
if err != nil {
ctx.JSON(404, gin.H{"error": fmt.Sprintf("Could not find cluster with the name %s", clusterName)})
return
}
cluster, err = services.ClusterService.Update(ctx, cluster, services.UpdateClusterOption{
Description: schema.Description,
KubeConfig: schema.KubeConfig,
})
if err != nil {
log.Info().Msgf("Failed to update cluster: %s", err.Error())
ctx.JSON(500, gin.H{"error": fmt.Sprintf("Error updating cluster %s", err.Error())})
return
}
ctx.JSON(200, converters.ToClusterFullSchema(cluster))
}
func (c *clusterController) Get(ctx *gin.Context) {
clusterName := ctx.Param("clusterName")
cluster, err := c.GetCluster(ctx, clusterName)
if err != nil {
ctx.JSON(404, gin.H{"error": fmt.Sprintf("Could not find cluster with the name %s", clusterName)})
return
}
ctx.JSON(200, converters.ToClusterFullSchema(cluster))
}
func (c *clusterController) List(ctx *gin.Context) {
var schema schemas.ListQuerySchema
if err := ctx.ShouldBindQuery(&schema); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ownership, err := GetOwnershipInfo(ctx)
if err != nil {
ctx.JSON(500, gin.H{"error": err.Error()})
return
}
clusters, total, err := services.ClusterService.List(ctx, services.ListClusterOption{
BaseListOption: services.BaseListOption{
Start: &schema.Start,
Count: &schema.Count,
Search: schema.Search,
},
OrganizationId: &ownership.OrganizationId,
})
if err != nil {
log.Info().Msgf("Failed to list clusters: %s", err.Error())
ctx.JSON(400, gin.H{"Error": fmt.Sprintf("List clusters %s", err.Error())})
return
}
clusterList := schemas.ClusterListSchema{
BaseListSchema: schemas.BaseListSchema{
Start: schema.Start,
Count: schema.Count,
Total: total,
},
Items: converters.ToClusterSchemaList(clusters),
}
ctx.JSON(200, clusterList)
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"errors"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/common/consts"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/schemas"
"github.com/gin-gonic/gin"
)
const OwnershipInfoKey = "_ownershipInfoKey"
func GetOwnershipInfo(ctx *gin.Context) (*schemas.OwnershipSchema, error) {
ownership_ := ctx.Value(OwnershipInfoKey)
if ownership_ == nil {
return nil, consts.ErrNotFound
}
ownership, ok := ownership_.(*schemas.OwnershipSchema)
if !ok {
return nil, errors.New("current ownership is not an ownership struct")
}
return ownership, nil
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"errors"
"fmt"
"strings"
"time"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/common/consts"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/converters"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/database"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/models"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/schemas"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/services"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
type compoundComponentController struct{}
var CompoundComponentController = compoundComponentController{}
func (c *compoundComponentController) Register(ctx *gin.Context) {
var getCluster schemas.GetClusterSchema
var registerCompoundComponentSchema schemas.RegisterCompoundComponentSchema
if err := ctx.ShouldBindUri(&getCluster); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
if err := ctx.ShouldBindJSON(&registerCompoundComponentSchema); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
names := []string{getCluster.ClusterName}
clusters, _, err := services.ClusterService.List(ctx, services.ListClusterOption{
Names: &names,
})
if err != nil {
errMsg := fmt.Sprintf("Failed to get clusters %s when registering Compound Component: %s", getCluster.ClusterName, err.Error())
log.Error().Msg(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
kubeNamespace := strings.TrimSpace(registerCompoundComponentSchema.KubeNamespace)
// nolint: ineffassign, staticcheck
tx, ctx_, df, err := database.DatabaseUtil.StartTransaction(ctx)
defer func() { df(err) }()
log.Info().Msgf("Registering compound component for %d clusters", len(clusters))
var compoundComponent *models.CompoundComponent
for _, cluster := range clusters {
compoundComponent, err = services.CompoundComponentService.GetByName(ctx_, cluster.ID, string(registerCompoundComponentSchema.Name))
isNotFound := errors.Is(err, consts.ErrNotFound)
if err != nil && !isNotFound {
log.Error().Msgf("Failed to get compoundComponent: %s", err.Error())
ctx.JSON(500, gin.H{"error": "failed to get compoundComponent"})
return
}
manifest := &schemas.CompoundComponentManifestSchema{
SelectorLabels: registerCompoundComponentSchema.SelectorLabels,
}
if registerCompoundComponentSchema.Manifest != nil {
manifest = registerCompoundComponentSchema.Manifest
}
if isNotFound {
compoundComponent, err = services.CompoundComponentService.Create(ctx_, services.CreateCompoundComponentOption{
ClusterId: cluster.ID,
Name: string(registerCompoundComponentSchema.Name),
KubeNamespace: kubeNamespace,
Version: registerCompoundComponentSchema.Version,
Manifest: manifest,
})
} else {
now := time.Now()
now_ := &now
opt := services.UpdateCompoundComponentOption{
LatestHeartbeatAt: &now_,
Version: &registerCompoundComponentSchema.Version,
Manifest: &manifest,
}
if compoundComponent.Version != registerCompoundComponentSchema.Version {
opt.LatestInstalledAt = &now_
}
compoundComponent, err = services.CompoundComponentService.Update(ctx_, compoundComponent, opt)
}
if err != nil {
log.Error().Msgf("Failed to register compoundComponent: %s", err.Error())
ctx.JSON(500, gin.H{"error": "failed to register compoundComponent"})
return
}
}
tx.Commit()
compoundComponentSchema, err := converters.ToCompoundComponentSchema(ctx, compoundComponent)
if err != nil {
log.Error().Msgf("Failed to convert compound component model to schema: %s", err.Error())
ctx.JSON(500, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, compoundComponentSchema)
}
func (c *compoundComponentController) ListAll(ctx *gin.Context) {
compoundComponents, err := services.CompoundComponentService.List(ctx, services.ListCompoundComponentOption{})
if err != nil {
errMsg := fmt.Sprintf("Failed to get all compoundComponents: %s", err.Error())
log.Error().Msg(errMsg)
ctx.JSON(400, gin.H{"error": errMsg})
return
}
compoundComponentSchema, err := converters.ToCompoundComponentSchemas(ctx, compoundComponents)
if err != nil {
log.Error().Msgf("Failed to convert compound component model to schema: %s", err.Error())
ctx.JSON(500, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, compoundComponentSchema)
}
func (c *compoundComponentController) List(ctx *gin.Context) {
var getCluster schemas.GetClusterSchema
if err := ctx.ShouldBindUri(&getCluster); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
names := []string{getCluster.ClusterName}
clusters, _, err := services.ClusterService.List(ctx, services.ListClusterOption{
Names: &names,
})
if err != nil {
errMsg := fmt.Sprintf("Failed to get clusters %s when registering Compound Component: %s", getCluster.ClusterName, err.Error())
log.Error().Msg(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
clusterIds := []uint{}
for _, cluster := range clusters {
clusterIds = append(clusterIds, cluster.ID)
}
compoundComponents, err := services.CompoundComponentService.List(ctx, services.ListCompoundComponentOption{
ClusterIds: &clusterIds,
})
if err != nil {
errMsg := fmt.Sprintf("Failed to get compoundComponents for the cluster %s: %s", getCluster.ClusterName, err.Error())
log.Error().Msg(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
compoundComponentSchema, err := converters.ToCompoundComponentSchemas(ctx, compoundComponents)
if err != nil {
log.Error().Msgf("Failed to convert compound component model to schema: %s", err.Error())
ctx.JSON(500, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, compoundComponentSchema)
}
This diff is collapsed.
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"fmt"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/converters"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/schemas"
"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/services"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
type deploymentRevisionController struct{}
var DeploymentRevisionController = deploymentRevisionController{}
func (c *deploymentRevisionController) List(ctx *gin.Context) {
var schema schemas.ListQuerySchema
var getSchema schemas.GetDeploymentSchema
if err := ctx.ShouldBindUri(&getSchema); err != nil {
log.Error().Msgf("Error binding: %s", err.Error())
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
if err := ctx.ShouldBindQuery(&schema); err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
deployment, err := getDeployment(ctx, &getSchema)
if err != nil {
log.Error().Msgf("Could not find deployment with the name %s: %s", getSchema.DeploymentName, err.Error())
ctx.JSON(404, gin.H{"error": fmt.Sprintf("Could not find deployment with the name %s", getSchema.DeploymentName)})
return
}
deploymentRevisions, total, err := services.DeploymentRevisionService.List(ctx, services.ListDeploymentRevisionOption{
BaseListOption: services.BaseListOption{
Start: &schema.Start,
Count: &schema.Count,
Search: schema.Search,
},
DeploymentId: &deployment.ID,
})
if err != nil {
errMsg := fmt.Sprintf("Failed to get deployment revisions %s", err.Error())
log.Error().Msgf(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
deploymentRevisionSchemas, err := converters.ToDeploymentRevisionSchemas(ctx, deploymentRevisions)
if err != nil {
errMsg := fmt.Sprintf("Failed to convert models to deployment revision schemas %s", err.Error())
log.Error().Msgf(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
log.Info().Msgf("Got %d deployment revisions", len(deploymentRevisionSchemas))
deploymentRevisionListSchema := schemas.DeploymentRevisionListSchema{
BaseListSchema: schemas.BaseListSchema{
Total: total,
Start: schema.Start,
Count: schema.Count,
},
Items: deploymentRevisionSchemas,
}
ctx.JSON(200, deploymentRevisionListSchema)
}
func (c *deploymentRevisionController) Get(ctx *gin.Context) {
var schema schemas.GetDeploymentRevisionSchema
if err := ctx.ShouldBindUri(&schema); err != nil {
log.Error().Msgf("Error binding: %s", err.Error())
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
_, err := getDeployment(ctx, &schema.GetDeploymentSchema)
if err != nil {
log.Error().Msgf("Could not find deployment with the name %s: %s", schema.DeploymentName, err.Error())
ctx.JSON(404, gin.H{"error": fmt.Sprintf("Could not find deployment with the name %s", schema.DeploymentName)})
return
}
deploymentRevision, err := services.DeploymentRevisionService.GetByUid(ctx, schema.RevisionUid)
if err != nil {
errMsg := fmt.Sprintf("Failed to get deployment revisions %s for %s", schema.DeploymentName, err.Error())
log.Error().Msgf(errMsg)
ctx.JSON(404, gin.H{"error": errMsg})
return
}
deploymentRevisionSchema, err := converters.ToDeploymentRevisionSchema(ctx, deploymentRevision)
if err != nil {
errMsg := fmt.Sprintf("Failed to convert model to deployment revision schema %s", err.Error())
log.Error().Msgf(errMsg)
ctx.JSON(500, gin.H{"error": errMsg})
return
}
ctx.JSON(200, deploymentRevisionSchema)
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"net/http"
"github.com/gin-gonic/gin"
)
type healthController struct{}
var HealthController = healthController{}
func (h *healthController) Get(gin *gin.Context) {
gin.JSON(http.StatusOK, "ok")
}
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"github.com/gin-gonic/gin"
)
type infoController struct{}
var InfoController = infoController{}
type InfoSchema struct {
IsSaas bool `json:"is_saas"`
SaasDomainSuffix string `json:"saas_domain_suffix"`
}
func (c *infoController) GetInfo(ctx *gin.Context) {
schema := InfoSchema{
IsSaas: true,
SaasDomainSuffix: "",
}
ctx.JSON(200, schema)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment