diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index a78848be69..37499a8900 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -164,7 +164,7 @@ services: networks: - default container_name: s3 - image: bitnami/minio:2025.4.3 + image: bitnamilegacy/minio:2025.7.23 ports: - 3902:3902 - 2903:2903 diff --git a/go/apps/ctrl/services/deployment/deploy_workflow.go b/go/apps/ctrl/services/deployment/deploy_workflow.go index f54b056ba4..1cffb0ce2f 100644 --- a/go/apps/ctrl/services/deployment/deploy_workflow.go +++ b/go/apps/ctrl/services/deployment/deploy_workflow.go @@ -11,7 +11,6 @@ import ( "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect" partitionv1 "github.com/unkeyed/unkey/go/gen/proto/partition/v1" "github.com/unkeyed/unkey/go/pkg/db" - "github.com/unkeyed/unkey/go/pkg/git" "github.com/unkeyed/unkey/go/pkg/hydra" "github.com/unkeyed/unkey/go/pkg/otel/logging" partitiondb "github.com/unkeyed/unkey/go/pkg/partition/db" @@ -40,6 +39,12 @@ type DeployWorkflowConfig struct { // NewDeployWorkflow creates a new deploy workflow instance func NewDeployWorkflow(cfg DeployWorkflowConfig) *DeployWorkflow { + + cfg.Logger.Info("Initializing deploy workflow", + "metald_backend", cfg.MetaldBackend, + "default_domain", cfg.DefaultDomain, + "is_running_docker", cfg.IsRunningDocker, + ) // Create the appropriate deployment backend deploymentBackend, err := NewDeploymentBackend(cfg.MetalD, cfg.MetaldBackend, cfg.Logger, cfg.IsRunningDocker) if err != nil { @@ -88,8 +93,34 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro "workspace_id", req.WorkspaceID, "project_id", req.ProjectID, ) + + workspace, err := hydra.Step(ctx, "get-workspace", func(stepCtx context.Context) (db.Workspace, error) { + return db.Query.FindWorkspaceByID(stepCtx, w.db.RW(), req.WorkspaceID) + }) + if err != nil { + return err + } + project, err := hydra.Step(ctx, "get-project", func(stepCtx context.Context) (db.FindProjectByIdRow, error) { + return db.Query.FindProjectById(stepCtx, w.db.RW(), req.ProjectID) + }) + if err != nil { + return err + } + environment, err := hydra.Step(ctx, "get-environment", func(stepCtx context.Context) (db.FindEnvironmentByIdRow, error) { + return db.Query.FindEnvironmentById(stepCtx, w.db.RW(), req.EnvironmentID) + }) + if err != nil { + return err + } + deployment, err := hydra.Step(ctx, "get-deployment", func(stepCtx context.Context) (db.FindDeploymentByIdRow, error) { + return db.Query.FindDeploymentById(stepCtx, w.db.RW(), req.DeploymentID) + }) + if err != nil { + return err + } + // Log deployment pending - err := hydra.StepVoid(ctx, "log-deployment-pending", func(stepCtx context.Context) error { + err = hydra.StepVoid(ctx, "log-deployment-pending", func(stepCtx context.Context) error { return db.Query.InsertDeploymentStep(stepCtx, w.db.RW(), db.InsertDeploymentStepParams{ WorkspaceID: req.WorkspaceID, ProjectID: req.ProjectID, @@ -119,7 +150,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro return err } - deployment, err := hydra.Step(ctx, "create-deployment", func(stepCtx context.Context) (*metaldv1.CreateDeploymentResponse, error) { + metaldDeployment, err := hydra.Step(ctx, "create-deployment", func(stepCtx context.Context) (*metaldv1.CreateDeploymentResponse, error) { if w.deploymentBackend == nil { return nil, fmt.Errorf("deployment backend not initialized") } @@ -146,7 +177,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro return err } - w.logger.Info("deployment created", "deployment_id", req.DeploymentID, "vm_count", len(deployment.GetVmIds())) + w.logger.Info("deployment created", "deployment_id", req.DeploymentID, "vm_count", len(metaldDeployment.GetVmIds())) // Update version status to deploying _, err = hydra.Step(ctx, "update-version-deploying", func(stepCtx context.Context) (*struct{}, error) { @@ -243,46 +274,19 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro if err != nil { return err } - - allDomains, err := hydra.Step(ctx, "generate-all-domains", func(stepCtx context.Context) ([]string, error) { - var domains []string - - // Generate auto-generated hostname for this deployment - gitInfo := git.GetInfo() - branch := "main" // Default branch - identifier := req.DeploymentID // Use full version ID as identifier - - if gitInfo.IsRepo { - if gitInfo.Branch != "" { - branch = gitInfo.Branch - } - if gitInfo.CommitSHA != "" { - identifier = gitInfo.CommitSHA - } - } - - // Generate primary hostname: branch-identifier-workspace.domain - cleanIdentifier := strings.ToLower(strings.ReplaceAll(identifier, "_", "-")) - cleanBranch := strings.ToLower(strings.ReplaceAll(branch, "/", "-")) - cleanWorkspaceID := strings.ToLower(req.WorkspaceID) - autoGeneratedHostname := fmt.Sprintf("%s-%s-%s.%s", cleanBranch, cleanIdentifier, cleanWorkspaceID, w.defaultDomain) - domains = append(domains, autoGeneratedHostname) - - w.logger.Info("generated all domains", - "deployment_id", req.DeploymentID, - "total_domains", len(domains), - "domains", domains, - ) - - return domains, nil - }) - if err != nil { - return err - } + allDomains := buildDomains( + workspace.Slug, + project.Slug, + environment.Slug, + deployment.GitCommitSha.String, + deployment.GitBranch.String, + w.defaultDomain, + ) // Create database entries for all domains err = hydra.StepVoid(ctx, "create-domain-entries", func(stepCtx context.Context) error { // Prepare bulk insert parameters + // domainParams := make([]db.InsertDomainParams, 0, len(allDomains)) currentTime := time.Now().UnixMilli() @@ -296,7 +300,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro DeploymentID: sql.NullString{Valid: true, String: req.DeploymentID}, CreatedAt: currentTime, UpdatedAt: sql.NullInt64{Valid: true, Int64: currentTime}, - Type: db.DomainsTypeCustom, + Type: db.DomainsTypeWildcard, }) } diff --git a/go/apps/ctrl/services/deployment/domains.go b/go/apps/ctrl/services/deployment/domains.go new file mode 100644 index 0000000000..8879ae54ab --- /dev/null +++ b/go/apps/ctrl/services/deployment/domains.go @@ -0,0 +1,75 @@ +package deployment + +import ( + "fmt" + "regexp" + "strings" +) + +// buildDomains looks at the deployment and returns a list of domains +// that should be assigned to the deployment. +// +// We want these domains per deployment +// - `-git--.unkey.app` (this never gets reassigned) +// - `-git--.unkey.app` (this needs to point to the latest deployment of that branch, sluggify the branch name ) +// - `--.unkey.app` (this needs to point to the latest deployment of that environment and be rolled back) +func buildDomains(workspaceSlug, projectSlug, environmentSlug, gitSha, branchName, apex string) []string { + + var domains []string + + if gitSha != "" { + short := gitSha + if len(short) > 7 { + short = short[:7] + } + domains = append(domains, + fmt.Sprintf("%s-git-%s-%s.%s", projectSlug, short, workspaceSlug, apex), + ) + } + + if branchName != "" { + domains = append( + domains, + fmt.Sprintf("%s-git-%s-%s.%s", projectSlug, sluggify(branchName), workspaceSlug, apex), + ) + } + + domains = append( + domains, + fmt.Sprintf("%s-%s-%s.%s", projectSlug, environmentSlug, workspaceSlug, apex), + ) + return domains + +} + +var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9\s]`) +var multipleSpacesRegex = regexp.MustCompile(`\s+`) + +func sluggify(s string) string { + // Trim whitespace + s = strings.TrimSpace(s) + + // Remove all non-alphanumeric characters except spaces + s = nonAlphanumericRegex.ReplaceAllString(s, " ") + s = strings.ReplaceAll(s, "-", " ") + s = strings.ReplaceAll(s, "_", " ") + + // Replace multiple spaces with single space + s = multipleSpacesRegex.ReplaceAllString(s, " ") + + // Replace spaces with hyphens + s = strings.ReplaceAll(s, " ", "-") + + // Convert to lowercase + s = strings.ToLower(s) + + // Limit to 80 characters + if len(s) > 80 { + s = s[:80] + } + + // Remove trailing hyphen if present + s = strings.TrimSuffix(s, "-") + + return s +} diff --git a/go/k8s/manifests/ctrl.yaml b/go/k8s/manifests/ctrl.yaml index f0d9a6e5e9..ca0c6215b8 100644 --- a/go/k8s/manifests/ctrl.yaml +++ b/go/k8s/manifests/ctrl.yaml @@ -71,6 +71,8 @@ spec: value: "false" - name: UNKEY_METALD_BACKEND value: "k8s" + - name: UNKEY_DEFAULT_DOMAIN + value: "unkey.local" command: ["/unkey", "run", "ctrl"] initContainers: - name: wait-for-dependencies diff --git a/go/k8s/manifests/s3.yaml b/go/k8s/manifests/s3.yaml index 96314dad6c..106b7f94ef 100644 --- a/go/k8s/manifests/s3.yaml +++ b/go/k8s/manifests/s3.yaml @@ -31,7 +31,7 @@ spec: spec: containers: - name: minio - image: bitnami/minio:2025.7.23 + image: bitnamilegacy/minio:2025.7.23 ports: - containerPort: 9000 name: api diff --git a/go/pkg/db/environment_find_by_id.sql_generated.go b/go/pkg/db/environment_find_by_id.sql_generated.go new file mode 100644 index 0000000000..7f16aa6d61 --- /dev/null +++ b/go/pkg/db/environment_find_by_id.sql_generated.go @@ -0,0 +1,43 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: environment_find_by_id.sql + +package db + +import ( + "context" + "database/sql" +) + +const findEnvironmentById = `-- name: FindEnvironmentById :one +SELECT id, workspace_id, project_id, slug, description +FROM environments +WHERE id = ? +` + +type FindEnvironmentByIdRow struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Slug string `db:"slug"` + Description sql.NullString `db:"description"` +} + +// FindEnvironmentById +// +// SELECT id, workspace_id, project_id, slug, description +// FROM environments +// WHERE id = ? +func (q *Queries) FindEnvironmentById(ctx context.Context, db DBTX, id string) (FindEnvironmentByIdRow, error) { + row := db.QueryRowContext(ctx, findEnvironmentById, id) + var i FindEnvironmentByIdRow + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.Slug, + &i.Description, + ) + return i, err +} diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index a622be962e..748e612955 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -189,6 +189,12 @@ type Querier interface { // WHERE deployment_id = ? // ORDER BY created_at ASC FindDomainsByDeploymentId(ctx context.Context, db DBTX, deploymentID sql.NullString) ([]FindDomainsByDeploymentIdRow, error) + //FindEnvironmentById + // + // SELECT id, workspace_id, project_id, slug, description + // FROM environments + // WHERE id = ? + FindEnvironmentById(ctx context.Context, db DBTX, id string) (FindEnvironmentByIdRow, error) //FindEnvironmentByProjectIdAndSlug // // SELECT id, workspace_id, project_id, slug, description diff --git a/go/pkg/db/queries/environment_find_by_id.sql b/go/pkg/db/queries/environment_find_by_id.sql new file mode 100644 index 0000000000..84440d2871 --- /dev/null +++ b/go/pkg/db/queries/environment_find_by_id.sql @@ -0,0 +1,4 @@ +-- name: FindEnvironmentById :one +SELECT id, workspace_id, project_id, slug, description +FROM environments +WHERE id = sqlc.arg(id);