diff --git a/api/agent.go b/api/agent.go
index 5f4111a8..d086813e 100644
--- a/api/agent.go
+++ b/api/agent.go
@@ -29,6 +29,7 @@ type DockerAgentDeployment struct {
AgentDeployment
ComposeFile []byte `json:"composeFile"`
EnvFile []byte `json:"envFile"`
+ types.DockerType
}
type KubernetesAgentResource struct {
diff --git a/cmd/agent/docker/agent_deployment.go b/cmd/agent/docker/agent_deployment.go
index 1e8054df..ed4c26cb 100644
--- a/cmd/agent/docker/agent_deployment.go
+++ b/cmd/agent/docker/agent_deployment.go
@@ -8,13 +8,15 @@ import (
"path"
"github.com/glasskube/distr/api"
+ "github.com/glasskube/distr/internal/types"
"github.com/google/uuid"
)
type AgentDeployment struct {
- ID uuid.UUID `json:"id"`
- RevisionID uuid.UUID `json:"revisionId"`
- ProjectName string `json:"projectName"`
+ ID uuid.UUID `json:"id"`
+ RevisionID uuid.UUID `json:"revisionId"`
+ ProjectName string `json:"projectName"`
+ DockerType types.DockerType `json:"docker_type"`
}
func (d *AgentDeployment) FileName() string {
@@ -29,7 +31,7 @@ func NewAgentDeployment(deployment api.DockerAgentDeployment) (*AgentDeployment,
if name, err := getProjectName(deployment.ComposeFile); err != nil {
return nil, err
} else {
- return &AgentDeployment{ID: deployment.ID, RevisionID: deployment.RevisionID, ProjectName: name}, nil
+ return &AgentDeployment{ID: deployment.ID, RevisionID: deployment.RevisionID, ProjectName: name, DockerType: deployment.DockerType}, nil
}
}
@@ -42,8 +44,7 @@ func getProjectName(data []byte) (string, error) {
return name, nil
}
}
-
-func GetExistingDeployments() ([]AgentDeployment, error) {
+func GetExistingDeployments() (map[uuid.UUID]AgentDeployment, error) {
if entries, err := os.ReadDir(agentDeploymentDir()); err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
@@ -62,13 +63,13 @@ func GetExistingDeployments() ([]AgentDeployment, error) {
return &d, nil
}
}
- result := make([]AgentDeployment, 0, len(entries))
+ result := make(map[uuid.UUID]AgentDeployment, len(entries))
for _, entry := range entries {
if !entry.IsDir() {
if d, err := fn(entry.Name()); err != nil {
return nil, err
} else {
- result = append(result, *d)
+ result[d.RevisionID] = *d
}
}
}
diff --git a/cmd/agent/docker/docker_actions.go b/cmd/agent/docker/docker_actions.go
index 58fb3c41..123b3514 100644
--- a/cmd/agent/docker/docker_actions.go
+++ b/cmd/agent/docker/docker_actions.go
@@ -1,19 +1,68 @@
package main
import (
+ "bufio"
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
+ "strings"
"github.com/glasskube/distr/api"
"github.com/glasskube/distr/internal/agentauth"
+ "github.com/glasskube/distr/internal/types"
"go.uber.org/zap"
+ "gopkg.in/yaml.v3"
)
-func ApplyComposeFile(ctx context.Context, deployment api.DockerAgentDeployment) (*AgentDeployment, string, error) {
+func DockerEngineApply(
+ ctx context.Context,
+ deployment api.DockerAgentDeployment,
+) (*AgentDeployment, string, error) {
+
+ if deployment.DockerType == types.DockerTypeSwarm {
+
+ fmt.Println(deployment.RevisionID)
+
+ // Step 1 Ensure Docker Swarm is initialized
+ initCmd := exec.CommandContext(ctx, "docker", "info", "--format", "{{.Swarm.LocalNodeState}}")
+ initOutput, err := initCmd.CombinedOutput()
+ if err != nil {
+ logger.Error("Failed to check Docker Swarm state", zap.Error(err))
+ return nil, "", fmt.Errorf("failed to check Docker Swarm state: %w", err)
+ }
+
+ if !strings.Contains(strings.TrimSpace(string(initOutput)), "active") {
+ logger.Error("Docker Swarm not initialized", zap.String("output", string(initOutput)))
+ return nil, "", fmt.Errorf("docker Swarm not initialized: %s", string(initOutput))
+ }
+ // Step 2: Pull images before deployment
+ _, err = PullSwarmMode(ctx, deployment)
+ if err != nil {
+ logger.Error("Failed to Pull", zap.Error(err))
+ return nil, "", err
+ }
+ return ApplyComposeFileSwarm(ctx, deployment)
+
+ }
+ return ApplyComposeFile(ctx, deployment)
+
+}
+func DockerEngineUninstall(
+ ctx context.Context, deployment AgentDeployment,
+) error {
+ if deployment.DockerType == types.DockerTypeSwarm {
+ return UninstallDockerSwarm(ctx, deployment)
+ }
+ return UninstallDockerCompose(ctx, deployment)
+}
+func ApplyComposeFile(
+ ctx context.Context,
+ deployment api.DockerAgentDeployment,
+) (*AgentDeployment, string, error) {
+
agentDeploymet, err := NewAgentDeployment(deployment)
if err != nil {
return nil, "", err
@@ -60,7 +109,62 @@ func ApplyComposeFile(ctx context.Context, deployment api.DockerAgentDeployment)
}
}
-func UninstallDockerCompose(ctx context.Context, deployment AgentDeployment) error {
+func ApplyComposeFileSwarm(
+ ctx context.Context,
+ deployment api.DockerAgentDeployment,
+) (*AgentDeployment, string, error) {
+
+ agentDeployment, err := NewAgentDeployment(deployment)
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Read the Compose file without replacing environment variables
+ cleanedCompose := cleanComposeFile(deployment.ComposeFile)
+
+ // Construct environment variables
+ envVars := os.Environ()
+ envVars = append(envVars, agentauth.DockerConfigEnv(deployment.AgentDeployment)...)
+
+ // // If an env file is provided, load its values
+ if deployment.EnvFile != nil {
+ parsedEnv, err := parseEnvFile(deployment.EnvFile)
+ if err != nil {
+ logger.Error("Failed to parse env file", zap.Error(err))
+ return nil, "", fmt.Errorf("failed to parse env file: %w", err)
+ }
+ for key, value := range parsedEnv {
+ envVars = append(envVars, fmt.Sprintf("%s=%s", key, value))
+ }
+ }
+
+ // Deploy the stack
+ composeArgs := []string{
+ "stack", "deploy",
+ "--compose-file", "-",
+ "--with-registry-auth",
+ "--detach=true",
+ agentDeployment.ProjectName,
+ }
+ cmd := exec.CommandContext(ctx, "docker", composeArgs...)
+ cmd.Stdin = bytes.NewReader(cleanedCompose)
+ cmd.Env = envVars // Ensure the same env variables are used
+
+ // Execute the command and capture output
+ cmdOut, err := cmd.CombinedOutput()
+ statusStr := string(cmdOut)
+
+ if err != nil {
+ logger.Error("Docker stack deploy failed", zap.String("output", statusStr))
+ return nil, "", errors.New(statusStr)
+ }
+
+ return agentDeployment, statusStr, nil
+}
+
+func UninstallDockerCompose(
+ ctx context.Context, deployment AgentDeployment,
+) error {
cmd := exec.CommandContext(ctx, "docker", "compose", "--project-name", deployment.ProjectName, "down", "--volumes")
out, err := cmd.CombinedOutput()
if err != nil {
@@ -68,3 +172,110 @@ func UninstallDockerCompose(ctx context.Context, deployment AgentDeployment) err
}
return nil
}
+
+func UninstallDockerSwarm(
+ ctx context.Context, deployment AgentDeployment,
+) error {
+
+ cmd := exec.CommandContext(ctx, "docker", "stack", "rm", deployment.ProjectName)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("failed to remove Docker Swarm stack: %w: %v", err, string(out))
+ }
+
+ // Optional: Prune unused networks created by Swarm
+ pruneCmd := exec.CommandContext(ctx, "docker", "network", "prune", "-f")
+ pruneOut, pruneErr := pruneCmd.CombinedOutput()
+ if pruneErr != nil {
+ logger.Warn("Failed to prune networks", zap.String("output", string(pruneOut)), zap.Error(pruneErr))
+ }
+
+ return nil
+}
+func cleanComposeFile(composeData []byte) []byte {
+ lines := strings.Split(string(composeData), "\n")
+ cleanedLines := make([]string, 0, 50)
+
+ for _, line := range lines {
+ // Skip lines that define `name:`
+ if strings.HasPrefix(strings.TrimSpace(line), "name:") {
+ continue
+ }
+ cleanedLines = append(cleanedLines, line)
+ }
+ return []byte(strings.Join(cleanedLines, "\n"))
+}
+func parseEnvFile(envData []byte) (map[string]string, error) {
+ envVars := make(map[string]string)
+ scanner := bufio.NewScanner(bytes.NewReader(envData))
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") {
+ continue // Skip empty lines and comments
+ }
+ parts := strings.SplitN(line, "=", 2)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid environment variable: %s", line)
+ }
+ envVars[parts[0]] = parts[1]
+ }
+ return envVars, scanner.Err()
+}
+
+type ComposeService struct {
+ Image string `yaml:"image"`
+}
+
+// ComposeFile represents the structure of docker-compose.yml
+type ComposeFile struct {
+ Services map[string]ComposeService `yaml:"services"`
+}
+
+func PullSwarmMode(
+ ctx context.Context, deployment api.DockerAgentDeployment,
+) (string, error) {
+
+ // Parse the compose YAML file
+ var compose ComposeFile
+ err := yaml.Unmarshal(deployment.ComposeFile, &compose)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse docker-compose.yml: %w", err)
+ }
+
+ // Extract image names
+ var images []string
+ for _, service := range compose.Services {
+ if service.Image != "" {
+ images = append(images, service.Image)
+ }
+ }
+
+ if len(images) == 0 {
+ return "", fmt.Errorf("no images found in the compose file")
+ }
+
+ // Pull images using Docker CLI
+ var pullLogs bytes.Buffer
+ for _, image := range images {
+ fmt.Println("Pulling image:", image)
+ logger.Info("Pulling image:", zap.String("id", image))
+ // Run `docker pull IMAGE_NAME`
+ cmd := exec.CommandContext(ctx, "docker", "pull", image)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ cmd.Stderr = &out
+
+ err := cmd.Run()
+ if err != nil {
+ logger.Error("failed to pull image", zap.Error(err))
+ return "", fmt.Errorf("failed to pull image %s: %w\nOutput: %s", image, err, out.String())
+ }
+
+ // Append logs
+ pullLogs.WriteString(out.String() + "\n")
+ fmt.Println(out.String())
+ }
+
+ fmt.Println("Image pulling complete.")
+ return pullLogs.String(), nil
+}
diff --git a/cmd/agent/docker/main.go b/cmd/agent/docker/main.go
index ddcd30fe..8a7c284a 100644
--- a/cmd/agent/docker/main.go
+++ b/cmd/agent/docker/main.go
@@ -51,6 +51,7 @@ loop:
if resource, err := client.DockerResource(ctx); err != nil {
logger.Error("failed to get resource", zap.Error(err))
} else {
+
if agentVersionID != "" {
if agentVersionID != resource.Version.ID.String() {
logger.Info("agent version has changed. starting self-update")
@@ -71,13 +72,14 @@ loop:
}
}
- if deployments, err := GetExistingDeployments(); err != nil {
+ deployments, err := GetExistingDeployments()
+ if err != nil {
logger.Error("could not get existing deployments", zap.Error(err))
} else {
for _, deployment := range deployments {
if resource.Deployment == nil || resource.Deployment.ID != deployment.ID {
logger.Info("uninstalling old deployment", zap.String("id", deployment.ID.String()))
- if err := UninstallDockerCompose(ctx, deployment); err != nil {
+ if err := DockerEngineUninstall(ctx, deployment); err != nil {
logger.Error("could not uninstall deployment", zap.Error(err))
} else if err := DeleteDeployment(deployment); err != nil {
logger.Error("could not delete deployment", zap.Error(err))
@@ -96,8 +98,15 @@ loop:
_, err = agentauth.EnsureAuth(ctx, resource.Deployment.AgentDeployment)
if err != nil {
logger.Error("docker auth error", zap.Error(err))
- } else if agentDeployment, status, err = ApplyComposeFile(ctx, *resource.Deployment); err == nil {
- multierr.AppendInto(&err, SaveDeployment(*agentDeployment))
+ }
+ if _, exists := deployments[resource.Deployment.RevisionID]; !exists {
+
+ agentDeployment, status, err = DockerEngineApply(ctx, *resource.Deployment)
+
+ if err == nil {
+ multierr.AppendInto(&err, SaveDeployment(*agentDeployment))
+ }
+
}
if statusErr := client.Status(ctx, resource.Deployment.RevisionID, status, err); statusErr != nil {
diff --git a/frontend/ui/src/app/applications/application-detail.component.html b/frontend/ui/src/app/applications/application-detail.component.html
index 5e1b1a17..bef6f59b 100644
--- a/frontend/ui/src/app/applications/application-detail.component.html
+++ b/frontend/ui/src/app/applications/application-detail.component.html
@@ -244,6 +244,24 @@
New Ver
+
+
+
+ @if (
+ newVersionForm.controls.docker.controls.dockerType.invalid &&
+ newVersionForm.controls.docker.controls.dockerType.touched
+ ) {
+
Field is required.
+ }
+
@if (application.type === 'kubernetes') {
diff --git a/frontend/ui/src/app/applications/application-detail.component.ts b/frontend/ui/src/app/applications/application-detail.component.ts
index e4013fcd..2efe5a89 100644
--- a/frontend/ui/src/app/applications/application-detail.component.ts
+++ b/frontend/ui/src/app/applications/application-detail.component.ts
@@ -20,7 +20,7 @@ import {
} from 'rxjs';
import {ApplicationsService} from '../services/applications.service';
import {AsyncPipe, DatePipe, NgOptimizedImage} from '@angular/common';
-import {Application, ApplicationVersion, HelmChartType} from '@glasskube/distr-sdk';
+import {Application, ApplicationVersion, DockerType, HelmChartType} from '@glasskube/distr-sdk';
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
import {
faArchive,
@@ -111,7 +111,12 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy {
template: new FormControl(''),
}),
docker: new FormGroup({
- compose: new FormControl('', Validators.required),
+ dockerType: new FormControl
('compose', {
+ nonNullable: true,
+ validators: Validators.required,
+ }),
+ compose: new FormControl(''),
+ swarm: new FormControl(''),
template: new FormControl(''),
}),
});
@@ -137,6 +142,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy {
@ViewChild('nameInput') nameInputElem?: ElementRef;
ngOnInit() {
this.route.url.subscribe(() => this.breadcrumbDropdown.set(false));
+
this.newVersionForm.controls.kubernetes.controls.chartType.valueChanges
.pipe(takeUntil(this.destroyed$))
.subscribe((type) => {
@@ -146,8 +152,19 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy {
this.newVersionForm.controls.kubernetes.controls.chartName.disable();
}
});
- }
+ this.newVersionForm.controls.docker.controls.dockerType.valueChanges
+ .pipe(takeUntil(this.destroyed$))
+ .subscribe((type) => {
+ if (type === 'compose') {
+ this.newVersionForm.controls.docker.controls.compose.enable();
+
+ } else {
+ this.newVersionForm.controls.docker.controls.compose.disable();
+
+ }
+ });
+ }
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
@@ -201,9 +218,11 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy {
application,
{
name: this.newVersionForm.controls.versionName.value!,
+ dockerType: this.newVersionForm.controls.docker.controls.dockerType.value!,
},
this.newVersionForm.controls.docker.controls.compose.value!,
- this.newVersionForm.controls.docker.controls.template.value!
+ this.newVersionForm.controls.docker.controls.template.value!,
+
);
} else {
const versionFormVal = this.newVersionForm.controls.kubernetes.value;
diff --git a/frontend/ui/src/app/services/applications.service.ts b/frontend/ui/src/app/services/applications.service.ts
index dc27ffc1..b025b804 100644
--- a/frontend/ui/src/app/services/applications.service.ts
+++ b/frontend/ui/src/app/services/applications.service.ts
@@ -73,6 +73,7 @@ export class ApplicationsService implements CrudService {
if (template) {
formData.append('templatefile', new Blob([template], {type: 'application/yaml'}));
}
+
return this.doCreateVersion(application, applicationVersion, formData);
}
diff --git a/internal/db/applications.go b/internal/db/applications.go
index 268b2c54..a21411f5 100644
--- a/internal/db/applications.go
+++ b/internal/db/applications.go
@@ -21,7 +21,7 @@ const (
applicationWithVersionsOutputExpr = applicationOutputExpr + `,
coalesce((
SELECT array_agg(row(av.id, av.created_at, av.archived_at, av.name, av.application_id,
- av.chart_type, av.chart_name, av.chart_url, av.chart_version) ORDER BY av.created_at ASC)
+ av.chart_type, av.chart_name, av.chart_url, av.chart_version, av.docker_type) ORDER BY av.created_at ASC)
FROM ApplicationVersion av
WHERE av.application_id = a.id
), array[]::record[]) AS versions `
@@ -29,7 +29,7 @@ const (
applicationWithLicensedVersionsOutputExpr = applicationOutputExpr + `,
coalesce((
SELECT array_agg(row(av.id, av.created_at, av.archived_at, av.name, av.application_id,
- av.chart_type, av.chart_name, av.chart_url, av.chart_version) ORDER BY av.created_at ASC)
+ av.chart_type, av.chart_name, av.chart_url, av.chart_version, av.docker_type) ORDER BY av.created_at ASC)
FROM ApplicationVersion av
WHERE av.application_id = a.id and
((av.id IN
@@ -210,7 +210,12 @@ func GetApplicationForApplicationVersionID(ctx context.Context, id, orgID uuid.U
}
func CreateApplicationVersion(ctx context.Context, applicationVersion *types.ApplicationVersion) error {
+ // defaultDockerType := DockerTypeCompose
+ // av.Docker_Type = &defaultDockerType
+ fmt.Println("----------------->>>>>>>>>>>>", applicationVersion.Docker_Type)
+
db := internalctx.GetDb(ctx)
+
args := pgx.NamedArgs{
"name": applicationVersion.Name,
"applicationId": applicationVersion.ApplicationID,
@@ -218,6 +223,7 @@ func CreateApplicationVersion(ctx context.Context, applicationVersion *types.App
"chartName": applicationVersion.ChartName,
"chartUrl": applicationVersion.ChartUrl,
"chartVersion": applicationVersion.ChartVersion,
+ "dockerType": applicationVersion.Docker_Type,
}
if applicationVersion.ComposeFileData != nil {
args["composeFileData"] = applicationVersion.ComposeFileData
@@ -231,11 +237,11 @@ func CreateApplicationVersion(ctx context.Context, applicationVersion *types.App
row, err := db.Query(ctx,
`INSERT INTO ApplicationVersion AS av (name, application_id, chart_type, chart_name, chart_url, chart_version,
- compose_file_data, values_file_data, template_file_data)
- VALUES (@name, @applicationId, @chartType, @chartName, @chartUrl, @chartVersion, @composeFileData::bytea,
+ docker_type, compose_file_data, values_file_data, template_file_data)
+ VALUES (@name, @applicationId, @chartType, @chartName, @chartUrl, @chartVersion, @dockerType, @composeFileData::bytea,
@valuesFileData::bytea, @templateFileData::bytea)
RETURNING av.id, av.created_at, av.archived_at, av.name, av.chart_type, av.chart_name, av.chart_url,
- av.chart_version, av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id`,
+ av.chart_version, av.docker_type, av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id`,
args)
if err != nil {
return fmt.Errorf("can not create ApplicationVersion: %w", err)
@@ -255,13 +261,15 @@ func CreateApplicationVersion(ctx context.Context, applicationVersion *types.App
func UpdateApplicationVersion(ctx context.Context, applicationVersion *types.ApplicationVersion) error {
db := internalctx.GetDb(ctx)
rows, err := db.Query(ctx,
- `UPDATE ApplicationVersion AS av SET name = @name, archived_at = @archivedAt WHERE id = @id
+ `UPDATE ApplicationVersion AS av SET name = @name, archived_at = @archivedAt, docker_type = @dockerType
+ WHERE id = @id
RETURNING av.id, av.created_at, av.archived_at, av.name, av.chart_type, av.chart_name, av.chart_url, av.chart_version,
- av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id`,
+ av.docker_type, av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id`,
pgx.NamedArgs{
"id": applicationVersion.ID,
"name": applicationVersion.Name,
"archivedAt": applicationVersion.ArchivedAt,
+ "dockerType": applicationVersion.Docker_Type,
})
if err != nil {
if pgerr := (*pgconn.PgError)(nil); errors.As(err, &pgerr) && pgerr.Code == pgerrcode.UniqueViolation {
@@ -284,7 +292,7 @@ func GetApplicationVersion(ctx context.Context, applicationVersionID uuid.UUID)
rows, err := db.Query(
ctx,
`SELECT av.id, av.created_at, av.archived_at, av.name, av.chart_type, av.chart_name, av.chart_url, av.chart_version,
- av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id
+ av.docker_type, av.values_file_data, av.template_file_data, av.compose_file_data, av.application_id
FROM ApplicationVersion av
WHERE id = @id`,
pgx.NamedArgs{"id": applicationVersionID},
diff --git a/internal/handlers/agent.go b/internal/handlers/agent.go
index caac8084..0ea548aa 100644
--- a/internal/handlers/agent.go
+++ b/internal/handlers/agent.go
@@ -183,6 +183,7 @@ func agentResourcesHandler(w http.ResponseWriter, r *http.Request) {
AgentDeployment: baseDeployment,
ComposeFile: patchedComposeFile,
EnvFile: deployment.EnvFileData,
+ DockerType: *appVersion.Docker_Type,
}
}
} else {
diff --git a/internal/migrations/sql/20_add_docker_type_to_application_version.sql.down.sql b/internal/migrations/sql/20_add_docker_type_to_application_version.sql.down.sql
new file mode 100644
index 00000000..f72dd28a
--- /dev/null
+++ b/internal/migrations/sql/20_add_docker_type_to_application_version.sql.down.sql
@@ -0,0 +1,9 @@
+ALTER TABLE ApplicationVersion
+ DROP COLUMN IF EXISTS docker_type;
+
+DO $$
+BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'docker_type') THEN
+ DROP TYPE DOCKER_TYPE;
+ END IF;
+END $$;
diff --git a/internal/migrations/sql/20_add_docker_type_to_application_version.sql.up.sql b/internal/migrations/sql/20_add_docker_type_to_application_version.sql.up.sql
new file mode 100644
index 00000000..4a34349c
--- /dev/null
+++ b/internal/migrations/sql/20_add_docker_type_to_application_version.sql.up.sql
@@ -0,0 +1,10 @@
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'docker_type') THEN
+ CREATE TYPE DOCKER_TYPE AS ENUM ('compose', 'swarm');
+ END IF;
+END $$;
+
+ALTER TABLE ApplicationVersion
+ ADD COLUMN IF NOT EXISTS docker_type DOCKER_TYPE DEFAULT 'compose';
+
diff --git a/internal/types/application_version.go b/internal/types/application_version.go
index 1f0ec259..1746421f 100644
--- a/internal/types/application_version.go
+++ b/internal/types/application_version.go
@@ -11,16 +11,16 @@ import (
type ApplicationVersion struct {
// unfortunately Base nested type doesn't work when ApplicationVersion is a nested row in an SQL query
- ID uuid.UUID `db:"id" json:"id"`
- CreatedAt time.Time `db:"created_at" json:"createdAt"`
- ArchivedAt *time.Time `db:"archived_at" json:"archivedAt,omitempty"`
- Name string `db:"name" json:"name"`
- ApplicationID uuid.UUID `db:"application_id" json:"applicationId"`
-
- ChartType *HelmChartType `db:"chart_type" json:"chartType,omitempty"`
- ChartName *string `db:"chart_name" json:"chartName,omitempty"`
- ChartUrl *string `db:"chart_url" json:"chartUrl,omitempty"`
- ChartVersion *string `db:"chart_version" json:"chartVersion,omitempty"`
+ ID uuid.UUID `db:"id" json:"id"`
+ CreatedAt time.Time `db:"created_at" json:"createdAt"`
+ ArchivedAt *time.Time `db:"archived_at" json:"archivedAt,omitempty"`
+ Name string `db:"name" json:"name"`
+ ApplicationID uuid.UUID `db:"application_id" json:"applicationId"`
+ Docker_Type *DockerType `db:"docker_type" json:"dockerType,omitempty"`
+ ChartType *HelmChartType `db:"chart_type" json:"chartType,omitempty"`
+ ChartName *string `db:"chart_name" json:"chartName,omitempty"`
+ ChartUrl *string `db:"chart_url" json:"chartUrl,omitempty"`
+ ChartVersion *string `db:"chart_version" json:"chartVersion,omitempty"`
// awful but relevant: the following must be defined after the ChartType, because somehow order matters
// for pgx at collecting the subrows (relevant at getting application + list of its versions with these
@@ -60,8 +60,10 @@ func (av ApplicationVersion) ParsedComposeFile() (result map[string]any, err err
func (av ApplicationVersion) Validate(deplType DeploymentType) error {
if deplType == DeploymentTypeDocker {
- if av.ComposeFileData == nil {
- return errors.New("missing compose file")
+ if av.Docker_Type == nil || *av.Docker_Type == "" {
+ return errors.New("missing docker type")
+ } else if *av.Docker_Type == DockerTypeCompose && av.ComposeFileData == nil {
+ return errors.New("missing compose file for compose deployment")
} else if av.ChartType != nil || av.ChartName != nil || av.ChartUrl != nil || av.ChartVersion != nil ||
av.ValuesFileData != nil {
return errors.New("unexpected kubernetes specifics in docker application")
diff --git a/internal/types/types.go b/internal/types/types.go
index dd371493..fb765ea6 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -12,6 +12,7 @@ type HelmChartType string
type DeploymentStatusType string
type DeploymentTargetScope string
type Feature string
+type DockerType string
const (
DeploymentTypeDocker DeploymentType = "docker"
@@ -23,6 +24,9 @@ const (
HelmChartTypeRepository HelmChartType = "repository"
HelmChartTypeOCI HelmChartType = "oci"
+ DockerTypeCompose DockerType = "compose"
+ DockerTypeSwarm DockerType = "swarm"
+
DeploymentStatusTypeOK DeploymentStatusType = "ok"
DeploymentStatusTypeError DeploymentStatusType = "error"
diff --git a/sdk/js/src/types/application.ts b/sdk/js/src/types/application.ts
index f0c96203..d5a1c1bb 100644
--- a/sdk/js/src/types/application.ts
+++ b/sdk/js/src/types/application.ts
@@ -1,5 +1,5 @@
import {BaseModel, Named} from './base';
-import {DeploymentType, HelmChartType} from './deployment';
+import {DeploymentType, HelmChartType, DockerType} from './deployment';
export interface Application extends BaseModel, Named {
type: DeploymentType;
@@ -13,6 +13,7 @@ export interface ApplicationVersion {
archivedAt?: string;
applicationId?: string;
chartType?: HelmChartType;
+ dockerType?: DockerType;
chartName?: string;
chartUrl?: string;
chartVersion?: string;
diff --git a/sdk/js/src/types/deployment.ts b/sdk/js/src/types/deployment.ts
index 7ef270fa..4346b0b7 100644
--- a/sdk/js/src/types/deployment.ts
+++ b/sdk/js/src/types/deployment.ts
@@ -37,6 +37,8 @@ export type DeploymentType = 'docker' | 'kubernetes';
export type HelmChartType = 'repository' | 'oci';
+export type DockerType = 'compose' | 'swarm';
+
export type DeploymentStatusType = 'ok' | 'error';
export type DeploymentTargetScope = 'cluster' | 'namespace';