mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19e096f8ae |
No files matched your search
@@ -2,12 +2,14 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"containr/internal/database"
|
"containr/internal/database"
|
||||||
|
"containr/internal/database/sqlcdb"
|
||||||
"containr/internal/deployment"
|
"containr/internal/deployment"
|
||||||
"containr/internal/deployqueue"
|
"containr/internal/deployqueue"
|
||||||
"containr/internal/docker"
|
"containr/internal/docker"
|
||||||
"containr/internal/source"
|
"containr/internal/source"
|
||||||
"containr/internal/types"
|
"containr/internal/types"
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -37,6 +39,72 @@ type DeploymentModel struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deploymentRowShape carries the deployment columns shared by the
|
||||||
|
// generated list/detail/rollback row types.
|
||||||
|
type deploymentRowShape struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
ServiceID uuid.UUID
|
||||||
|
CommitHash sql.NullString
|
||||||
|
Status sql.NullString
|
||||||
|
ImageName sql.NullString
|
||||||
|
ImageTag sql.NullString
|
||||||
|
BuildLog sql.NullString
|
||||||
|
RuntimeLog sql.NullString
|
||||||
|
Error sql.NullString
|
||||||
|
StartedAt sql.NullTime
|
||||||
|
CompletedAt sql.NullTime
|
||||||
|
CreatedAt sql.NullTime
|
||||||
|
UpdatedAt sql.NullTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtrNS(v sql.NullString) *string {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := v.String
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrStr(s *string) string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func timePtrNT(v sql.NullTime) *time.Time {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := v.Time
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntPtr(t *time.Time) sql.NullTime {
|
||||||
|
if t == nil {
|
||||||
|
return sql.NullTime{}
|
||||||
|
}
|
||||||
|
return sql.NullTime{Time: *t, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deploymentModelFrom(r deploymentRowShape) DeploymentModel {
|
||||||
|
return DeploymentModel{
|
||||||
|
ID: r.ID,
|
||||||
|
ServiceID: r.ServiceID,
|
||||||
|
CommitHash: strPtrNS(r.CommitHash),
|
||||||
|
Status: r.Status.String,
|
||||||
|
ImageName: r.ImageName.String,
|
||||||
|
ImageTag: r.ImageTag.String,
|
||||||
|
BuildLog: r.BuildLog.String,
|
||||||
|
RuntimeLog: r.RuntimeLog.String,
|
||||||
|
Error: strPtrNS(r.Error),
|
||||||
|
StartedAt: timePtrNT(r.StartedAt),
|
||||||
|
CompletedAt: timePtrNT(r.CompletedAt),
|
||||||
|
CreatedAt: r.CreatedAt.Time,
|
||||||
|
UpdatedAt: r.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type CreateDeploymentRequest struct {
|
type CreateDeploymentRequest struct {
|
||||||
CommitHash string `json:"commit_hash"`
|
CommitHash string `json:"commit_hash"`
|
||||||
Branch string `json:"branch"`
|
Branch string `json:"branch"`
|
||||||
@@ -84,44 +152,29 @@ func handleGetDeployments(c *gin.Context) {
|
|||||||
|
|
||||||
offset, limit := pageWindow(c, 50, 100)
|
offset, limit := pageWindow(c, 50, 100)
|
||||||
|
|
||||||
var total int
|
q := sqlcdb.New(db.(*database.DB).DB)
|
||||||
_ = db.(*database.DB).QueryRow(
|
total, _ := q.CountDeploymentsForService(c.Request.Context(), serviceID)
|
||||||
`SELECT COUNT(*) FROM deployments WHERE service_id = $1`, serviceID,
|
|
||||||
).Scan(&total)
|
|
||||||
|
|
||||||
rows, err := db.(*database.DB).Query(
|
rows, err := q.ListDeploymentsForService(c.Request.Context(), sqlcdb.ListDeploymentsForServiceParams{
|
||||||
`SELECT id, service_id, commit_hash, status, image_name, image_tag,
|
ServiceID: serviceID, Limit: int32(limit), Offset: int32(offset)})
|
||||||
build_log, runtime_log, error, started_at, completed_at, created_at, updated_at
|
|
||||||
FROM deployments
|
|
||||||
WHERE service_id = $1
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT $2 OFFSET $3`,
|
|
||||||
serviceID, limit, offset,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve deployments"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve deployments"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var deployments []DeploymentModel
|
deployments := make([]DeploymentModel, 0, len(rows))
|
||||||
for rows.Next() {
|
for _, r := range rows {
|
||||||
var d DeploymentModel
|
deployments = append(deployments, deploymentModelFrom(deploymentRowShape{
|
||||||
err := rows.Scan(
|
ID: r.ID, ServiceID: r.ServiceID, CommitHash: r.CommitHash, Status: r.Status,
|
||||||
&d.ID, &d.ServiceID, &d.CommitHash, &d.Status, &d.ImageName, &d.ImageTag,
|
ImageName: r.ImageName, ImageTag: r.ImageTag, BuildLog: r.BuildLog,
|
||||||
&d.BuildLog, &d.RuntimeLog, &d.Error, &d.StartedAt, &d.CompletedAt,
|
RuntimeLog: r.RuntimeLog, Error: r.Error, StartedAt: r.StartedAt,
|
||||||
&d.CreatedAt, &d.UpdatedAt,
|
CompletedAt: r.CompletedAt, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
|
||||||
)
|
}))
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan deployment"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
deployments = append(deployments, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"deployments": deployments,
|
"deployments": deployments,
|
||||||
"pagination": paginationMeta(offset, limit, total),
|
"pagination": paginationMeta(offset, limit, int(total)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,51 +205,33 @@ func handleGetRecentDeployments(c *gin.Context) {
|
|||||||
|
|
||||||
offset, limit := pageWindow(c, 10, 50)
|
offset, limit := pageWindow(c, 10, 50)
|
||||||
|
|
||||||
var total int
|
q := sqlcdb.New(db.(*database.DB).DB)
|
||||||
if err := db.(*database.DB).QueryRow(
|
total, err := q.CountAccessibleDeployments(c.Request.Context(), sqlcdb.CountAccessibleDeploymentsParams{
|
||||||
`SELECT COUNT(*)
|
OwnerID: userID, Column2: isAdmin})
|
||||||
FROM deployments d
|
if err != nil {
|
||||||
JOIN services s ON s.id = d.service_id
|
|
||||||
JOIN projects p ON p.id = s.project_id
|
|
||||||
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
|
||||||
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1)`,
|
|
||||||
userID, isAdmin,
|
|
||||||
).Scan(&total); err != nil {
|
|
||||||
total = 0
|
total = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := db.(*database.DB).Query(
|
rows, err := q.ListRecentAccessibleDeployments(c.Request.Context(), sqlcdb.ListRecentAccessibleDeploymentsParams{
|
||||||
`SELECT d.id, d.service_id, s.name, p.name, d.status,
|
OwnerID: userID, Column2: isAdmin, Limit: int32(limit), Offset: int32(offset)})
|
||||||
COALESCE(d.image_name, ''), d.started_at, d.completed_at, d.created_at
|
|
||||||
FROM deployments d
|
|
||||||
JOIN services s ON s.id = d.service_id
|
|
||||||
JOIN projects p ON p.id = s.project_id
|
|
||||||
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
|
||||||
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1)
|
|
||||||
ORDER BY d.created_at DESC
|
|
||||||
LIMIT $3 OFFSET $4`,
|
|
||||||
userID, isAdmin, limit, offset,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve deployments"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve deployments"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
deployments := []RecentDeployment{}
|
deployments := make([]RecentDeployment, 0, len(rows))
|
||||||
for rows.Next() {
|
for _, r := range rows {
|
||||||
var d RecentDeployment
|
deployments = append(deployments, RecentDeployment{
|
||||||
if err := rows.Scan(&d.ID, &d.ServiceID, &d.ServiceName, &d.ProjectName,
|
ID: r.ID, ServiceID: r.ServiceID, ServiceName: r.ServiceName,
|
||||||
&d.Status, &d.ImageName, &d.StartedAt, &d.CompletedAt, &d.CreatedAt); err != nil {
|
ProjectName: r.ProjectName, Status: r.Status.String, ImageName: r.ImageName,
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan deployment"})
|
StartedAt: timePtrNT(r.StartedAt), CompletedAt: timePtrNT(r.CompletedAt),
|
||||||
return
|
CreatedAt: r.CreatedAt.Time,
|
||||||
}
|
})
|
||||||
deployments = append(deployments, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"deployments": deployments,
|
"deployments": deployments,
|
||||||
"pagination": paginationMeta(offset, limit, total),
|
"pagination": paginationMeta(offset, limit, int(total)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,38 +265,27 @@ func handleCreateDeployment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var service Service
|
ctx := c.Request.Context()
|
||||||
var projectOwner string
|
q := sqlcdb.New(db.(*database.DB).DB)
|
||||||
err = db.(*database.DB).QueryRow(
|
sr, err := q.GetServiceForDeployWithOwner(ctx, serviceID)
|
||||||
`SELECT s.id, s.project_id, s.name, s.type, s.status, s.image, s.command,
|
|
||||||
s.environment, s.git_repo, s.git_branch, s.build_path, s.cpu, s.memory,
|
|
||||||
COALESCE(s.replicas, 1), COALESCE(s.port, 0),
|
|
||||||
COALESCE(s.domain, ''), COALESCE(s.healthcheck_path, ''),
|
|
||||||
COALESCE(s.restart_policy, 'unless-stopped'),
|
|
||||||
COALESCE(s.builder, 'auto'), COALESCE(s.cpu_reserve, ''),
|
|
||||||
COALESCE(s.memory_reserve, ''), COALESCE(s.static_build_cmd, ''),
|
|
||||||
COALESCE(s.static_dir, ''),
|
|
||||||
s.created_at, s.updated_at, p.owner_id
|
|
||||||
FROM services s
|
|
||||||
JOIN projects p ON s.project_id = p.id
|
|
||||||
WHERE s.id = $1`,
|
|
||||||
serviceID,
|
|
||||||
).Scan(
|
|
||||||
&service.ID, &service.ProjectID, &service.Name, &service.Type, &service.Status,
|
|
||||||
&service.Image, &service.Command, &service.Environment, &service.GitRepo,
|
|
||||||
&service.GitBranch, &service.BuildPath, &service.CPU, &service.Memory,
|
|
||||||
&service.Replicas, &service.Port, &service.Domain, &service.HealthCheckPath,
|
|
||||||
&service.RestartPolicy, &service.Builder, &service.CPUReserve,
|
|
||||||
&service.MemoryReserve, &service.StaticBuildCmd, &service.StaticDir,
|
|
||||||
&service.CreatedAt, &service.UpdatedAt, &projectOwner,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Service not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Service not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
service := serviceForDeployFromRow(sqlcdb.GetServiceForDeployRow{
|
||||||
|
ID: sr.ID, ProjectID: sr.ProjectID, Name: sr.Name, Type: sr.Type,
|
||||||
|
Status: sr.Status, Image: sr.Image, Command: sr.Command,
|
||||||
|
Environment: sr.Environment, GitRepo: sr.GitRepo, GitBranch: sr.GitBranch,
|
||||||
|
BuildPath: sr.BuildPath, Cpu: sr.Cpu, Memory: sr.Memory,
|
||||||
|
Replicas: sr.Replicas, Port: sr.Port, Domain: sr.Domain,
|
||||||
|
HealthcheckPath: sr.HealthcheckPath, RestartPolicy: sr.RestartPolicy,
|
||||||
|
Builder: sr.Builder, CpuReserve: sr.CpuReserve, MemoryReserve: sr.MemoryReserve,
|
||||||
|
StaticBuildCmd: sr.StaticBuildCmd, StaticDir: sr.StaticDir,
|
||||||
|
CreatedAt: sr.CreatedAt, UpdatedAt: sr.UpdatedAt,
|
||||||
|
})
|
||||||
|
projectOwner := sr.OwnerID
|
||||||
|
|
||||||
if projectOwner != userID.(string) {
|
if projectOwner.String() != userID.(string) {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -287,12 +311,17 @@ func handleCreateDeployment(c *gin.Context) {
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.(*database.DB).Exec(
|
err = q.InsertDeployment(ctx, sqlcdb.InsertDeploymentParams{
|
||||||
`INSERT INTO deployments
|
ID: d.ID,
|
||||||
(id, service_id, version, commit_hash, status, image_name, image_tag, created_at, updated_at)
|
ServiceID: d.ServiceID,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
Version: fmt.Sprintf("v%d", now.Unix()),
|
||||||
d.ID, d.ServiceID, fmt.Sprintf("v%d", now.Unix()), d.CommitHash, d.Status, d.ImageName, d.ImageTag, d.CreatedAt, d.UpdatedAt,
|
CommitHash: sql.NullString{String: ptrStr(d.CommitHash), Valid: d.CommitHash != nil},
|
||||||
)
|
Status: sql.NullString{String: d.Status, Valid: true},
|
||||||
|
ImageName: sql.NullString{String: d.ImageName, Valid: true},
|
||||||
|
ImageTag: sql.NullString{String: d.ImageTag, Valid: true},
|
||||||
|
CreatedAt: sql.NullTime{Time: d.CreatedAt, Valid: true},
|
||||||
|
UpdatedAt: sql.NullTime{Time: d.UpdatedAt, Valid: true},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create deployment"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create deployment"})
|
||||||
@@ -303,20 +332,20 @@ func handleCreateDeployment(c *gin.Context) {
|
|||||||
if !exists || engine == nil {
|
if !exists || engine == nil {
|
||||||
unavailableErr := "Deployment engine unavailable. Docker may not be configured on this server."
|
unavailableErr := "Deployment engine unavailable. Docker may not be configured on this server."
|
||||||
completedAt := time.Now()
|
completedAt := time.Now()
|
||||||
_, _ = db.(*database.DB).Exec(
|
_ = q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
`UPDATE deployments
|
Error: sql.NullString{String: unavailableErr, Valid: true},
|
||||||
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
CompletedAt: sql.NullTime{Time: completedAt, Valid: true},
|
||||||
WHERE id = $3`,
|
ID: d.ID,
|
||||||
unavailableErr, completedAt, d.ID,
|
})
|
||||||
)
|
|
||||||
d.Status = "failed"
|
d.Status = "failed"
|
||||||
d.Error = &unavailableErr
|
d.Error = &unavailableErr
|
||||||
d.CompletedAt = &completedAt
|
d.CompletedAt = &completedAt
|
||||||
} else {
|
} else {
|
||||||
_, err = db.(*database.DB).Exec(
|
err = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
`UPDATE services SET status = 'building', updated_at = $1 WHERE id = $2`,
|
Status: sql.NullString{String: "building", Valid: true},
|
||||||
time.Now(), serviceID,
|
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
)
|
ID: serviceID,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update service status"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update service status"})
|
||||||
return
|
return
|
||||||
@@ -331,14 +360,17 @@ func handleCreateDeployment(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
if pos > 0 {
|
if pos > 0 {
|
||||||
d.Status = "queued"
|
d.Status = "queued"
|
||||||
_, _ = db.(*database.DB).Exec(
|
now := time.Now()
|
||||||
`UPDATE deployments SET status = 'queued', updated_at = $1 WHERE id = $2`,
|
_ = q.SetDeploymentStatus(ctx, sqlcdb.SetDeploymentStatusParams{
|
||||||
time.Now(), d.ID,
|
Status: sql.NullString{String: "queued", Valid: true},
|
||||||
)
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
_, _ = db.(*database.DB).Exec(
|
ID: d.ID,
|
||||||
`UPDATE services SET status = 'queued', updated_at = $1 WHERE id = $2`,
|
})
|
||||||
time.Now(), serviceID,
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
)
|
Status: sql.NullString{String: "queued", Valid: true},
|
||||||
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
|
ID: serviceID,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +412,7 @@ func runDeploymentAndSyncWithImage(
|
|||||||
) {
|
) {
|
||||||
ctx, cancel := context.WithTimeout(parentCtx, 30*time.Minute)
|
ctx, cancel := context.WithTimeout(parentCtx, 30*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
q := sqlcdb.New(db.DB)
|
||||||
|
|
||||||
// The checkout and any shipped artifacts die with this deployment.
|
// The checkout and any shipped artifacts die with this deployment.
|
||||||
var artifactIDs []string
|
var artifactIDs []string
|
||||||
@@ -392,10 +425,11 @@ func runDeploymentAndSyncWithImage(
|
|||||||
}()
|
}()
|
||||||
failDeploy := func(msg string) {
|
failDeploy := func(msg string) {
|
||||||
failedAt := time.Now()
|
failedAt := time.Now()
|
||||||
_, _ = db.Exec(
|
_ = q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
`UPDATE deployments SET status = 'failed', error = $1, completed_at = $2, updated_at = $2 WHERE id = $3`,
|
Error: sql.NullString{String: msg, Valid: true},
|
||||||
msg, failedAt, dbDeployment.ID,
|
CompletedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
)
|
ID: dbDeployment.ID,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePath := strings.TrimSpace(service.BuildPath)
|
sourcePath := strings.TrimSpace(service.BuildPath)
|
||||||
@@ -423,11 +457,10 @@ func runDeploymentAndSyncWithImage(
|
|||||||
replicas = 1
|
replicas = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var publishedPort int32
|
|
||||||
// Best effort: reuse the host port from the last live deployment so public
|
// Best effort: reuse the host port from the last live deployment so public
|
||||||
// URLs survive redeploys. Column exists post-migration; older DBs get
|
// URLs survive redeploys. Column exists post-migration; older DBs get
|
||||||
// ephemeral ports.
|
// ephemeral ports.
|
||||||
_ = db.QueryRow(`SELECT published_port FROM services WHERE id = $1`, service.ID).Scan(&publishedPort)
|
publishedPort, _ := q.GetServicePublishedPort(ctx, service.ID)
|
||||||
|
|
||||||
publicPort := int32(service.Port)
|
publicPort := int32(service.Port)
|
||||||
if service.Builder == "static" && publicPort == 0 {
|
if service.Builder == "static" && publicPort == 0 {
|
||||||
@@ -451,7 +484,9 @@ func runDeploymentAndSyncWithImage(
|
|||||||
// remote builds package it into a context artifact the node builds.
|
// remote builds package it into a context artifact the node builds.
|
||||||
if service.GitRepo != "" && imageOverride == "" {
|
if service.GitRepo != "" && imageOverride == "" {
|
||||||
var ownerID string
|
var ownerID string
|
||||||
_ = db.QueryRow(`SELECT owner_id FROM projects WHERE id = $1`, service.ProjectID).Scan(&ownerID)
|
if o, err := q.GetProjectOwner(ctx, service.ProjectID); err == nil {
|
||||||
|
ownerID = o.String()
|
||||||
|
}
|
||||||
branch := req.Branch
|
branch := req.Branch
|
||||||
if branch == "" {
|
if branch == "" {
|
||||||
branch = service.GitBranch
|
branch = service.GitBranch
|
||||||
@@ -535,7 +570,9 @@ func runDeploymentAndSyncWithImage(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var ownerID string
|
var ownerID string
|
||||||
_ = db.QueryRow(`SELECT owner_id FROM projects WHERE id = $1`, service.ProjectID).Scan(&ownerID)
|
if o, err := q.GetProjectOwner(ctx, service.ProjectID); err == nil {
|
||||||
|
ownerID = o.String()
|
||||||
|
}
|
||||||
auth := registryAuthFor(db, ownerID, image)
|
auth := registryAuthFor(db, ownerID, image)
|
||||||
deployReq.BuildConfig = &deployment.BuildConfig{
|
deployReq.BuildConfig = &deployment.BuildConfig{
|
||||||
BuildType: "prebuilt",
|
BuildType: "prebuilt",
|
||||||
@@ -605,10 +642,11 @@ func runDeploymentAndSyncWithImage(
|
|||||||
|
|
||||||
if msg := capacityCheck(parentCtx, db, engine.DockerClient(), service, replicas); msg != "" {
|
if msg := capacityCheck(parentCtx, db, engine.DockerClient(), service, replicas); msg != "" {
|
||||||
failedAt := time.Now()
|
failedAt := time.Now()
|
||||||
_, _ = db.Exec(
|
_ = q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
`UPDATE deployments SET status = 'failed', error = $1, completed_at = $2, updated_at = $2 WHERE id = $3`,
|
Error: sql.NullString{String: msg, Valid: true},
|
||||||
msg, failedAt, dbDeployment.ID,
|
CompletedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
)
|
ID: dbDeployment.ID,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,16 +654,16 @@ func runDeploymentAndSyncWithImage(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
failedAt := time.Now()
|
failedAt := time.Now()
|
||||||
failure := "Failed to start deployment engine: " + err.Error()
|
failure := "Failed to start deployment engine: " + err.Error()
|
||||||
_, _ = db.Exec(
|
_ = q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
`UPDATE deployments
|
Error: sql.NullString{String: failure, Valid: true},
|
||||||
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
CompletedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
WHERE id = $3`,
|
ID: dbDeployment.ID,
|
||||||
failure, failedAt, dbDeployment.ID,
|
})
|
||||||
)
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
_, _ = db.Exec(
|
Status: sql.NullString{String: "failed", Valid: true},
|
||||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
UpdatedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
failedAt, service.ID,
|
ID: service.ID,
|
||||||
)
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,16 +678,17 @@ func runDeploymentAndSyncWithImage(
|
|||||||
if errors.Is(ctx.Err(), context.Canceled) {
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
finalStatus, finalErr = "cancelled", "Deployment cancelled"
|
finalStatus, finalErr = "cancelled", "Deployment cancelled"
|
||||||
}
|
}
|
||||||
_, _ = db.Exec(
|
_ = q.CompleteDeployment(ctx, sqlcdb.CompleteDeploymentParams{
|
||||||
`UPDATE deployments
|
Status: sql.NullString{String: finalStatus, Valid: true},
|
||||||
SET status = $1, error = $2, completed_at = $3, updated_at = $3
|
Error: sql.NullString{String: finalErr, Valid: true},
|
||||||
WHERE id = $4`,
|
CompletedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
finalStatus, finalErr, failedAt, dbDeployment.ID,
|
ID: dbDeployment.ID,
|
||||||
)
|
})
|
||||||
_, _ = db.Exec(
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
`UPDATE services SET status = $1, updated_at = $2 WHERE id = $3`,
|
Status: sql.NullString{String: finalStatus, Valid: true},
|
||||||
finalStatus, failedAt, service.ID,
|
UpdatedAt: sql.NullTime{Time: failedAt, Valid: true},
|
||||||
)
|
ID: service.ID,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
case <-syncTicker.C:
|
case <-syncTicker.C:
|
||||||
current, getErr := engine.GetDeployment(engineDeployment.ID)
|
current, getErr := engine.GetDeployment(engineDeployment.ID)
|
||||||
@@ -660,49 +699,36 @@ func runDeploymentAndSyncWithImage(
|
|||||||
dbStatus := mapEngineStatusToDBStatus(current.Status)
|
dbStatus := mapEngineStatusToDBStatus(current.Status)
|
||||||
imageName, imageTag := splitImageReference(current.ImageName, dbDeployment.ImageTag)
|
imageName, imageTag := splitImageReference(current.ImageName, dbDeployment.ImageTag)
|
||||||
|
|
||||||
var dbError interface{}
|
now := time.Now()
|
||||||
if current.Error != "" {
|
_ = q.SyncDeploymentProgress(ctx, sqlcdb.SyncDeploymentProgressParams{
|
||||||
dbError = current.Error
|
Status: sql.NullString{String: dbStatus, Valid: true},
|
||||||
}
|
ImageName: sql.NullString{String: imageName, Valid: true},
|
||||||
|
ImageTag: sql.NullString{String: imageTag, Valid: true},
|
||||||
_, _ = db.Exec(
|
BuildLog: sql.NullString{String: current.BuildLog, Valid: true},
|
||||||
`UPDATE deployments
|
RuntimeLog: sql.NullString{String: current.DeployLog, Valid: true},
|
||||||
SET status = $1,
|
Error: sql.NullString{String: current.Error, Valid: current.Error != ""},
|
||||||
image_name = $2,
|
StartedAt: ntPtr(current.StartedAt),
|
||||||
image_tag = $3,
|
CompletedAt: ntPtr(current.CompletedAt),
|
||||||
build_log = $4,
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
runtime_log = $5,
|
ID: dbDeployment.ID,
|
||||||
error = $6,
|
})
|
||||||
started_at = $7,
|
|
||||||
completed_at = $8,
|
|
||||||
updated_at = $9
|
|
||||||
WHERE id = $10`,
|
|
||||||
dbStatus,
|
|
||||||
imageName,
|
|
||||||
imageTag,
|
|
||||||
current.BuildLog,
|
|
||||||
current.DeployLog,
|
|
||||||
dbError,
|
|
||||||
current.StartedAt,
|
|
||||||
current.CompletedAt,
|
|
||||||
time.Now(),
|
|
||||||
dbDeployment.ID,
|
|
||||||
)
|
|
||||||
|
|
||||||
switch dbStatus {
|
switch dbStatus {
|
||||||
case "deployed":
|
case "deployed":
|
||||||
_, _ = db.Exec(
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
`UPDATE services SET status = 'running', updated_at = $1 WHERE id = $2`,
|
Status: sql.NullString{String: "running", Valid: true},
|
||||||
time.Now(), service.ID,
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
)
|
ID: service.ID,
|
||||||
|
})
|
||||||
insertUserNotification(db, userID, "deployment", "Deployment succeeded",
|
insertUserNotification(db, userID, "deployment", "Deployment succeeded",
|
||||||
fmt.Sprintf("Service %s is now running.", service.Name), "service", service.ID.String())
|
fmt.Sprintf("Service %s is now running.", service.Name), "service", service.ID.String())
|
||||||
return
|
return
|
||||||
case "failed":
|
case "failed":
|
||||||
_, _ = db.Exec(
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
Status: sql.NullString{String: "failed", Valid: true},
|
||||||
time.Now(), service.ID,
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
)
|
ID: service.ID,
|
||||||
|
})
|
||||||
body := fmt.Sprintf("Service %s failed to deploy.", service.Name)
|
body := fmt.Sprintf("Service %s failed to deploy.", service.Name)
|
||||||
if current.Error != "" {
|
if current.Error != "" {
|
||||||
body = fmt.Sprintf("Service %s failed to deploy: %s", service.Name, current.Error)
|
body = fmt.Sprintf("Service %s failed to deploy: %s", service.Name, current.Error)
|
||||||
@@ -754,21 +780,16 @@ func handleCancelDeployment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var status, owner string
|
ctx := c.Request.Context()
|
||||||
var serviceID uuid.UUID
|
q := sqlcdb.New(db.(*database.DB).DB)
|
||||||
err = db.(*database.DB).QueryRow(
|
access, err := q.GetDeploymentAccess(ctx, deploymentID)
|
||||||
`SELECT d.status, d.service_id, p.owner_id
|
|
||||||
FROM deployments d
|
|
||||||
JOIN services s ON d.service_id = s.id
|
|
||||||
JOIN projects p ON s.project_id = p.id
|
|
||||||
WHERE d.id = $1`,
|
|
||||||
deploymentID,
|
|
||||||
).Scan(&status, &serviceID, &owner)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found", "code": "NOT_FOUND"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found", "code": "NOT_FOUND"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if owner != userID.(string) && !contextIsAdmin(c) {
|
serviceID := access.ServiceID
|
||||||
|
status := access.Status.String
|
||||||
|
if access.OwnerID.String() != userID.(string) && !contextIsAdmin(c) {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied", "code": "FORBIDDEN"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied", "code": "FORBIDDEN"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -782,14 +803,17 @@ func handleCancelDeployment(c *gin.Context) {
|
|||||||
switch getDeployQueue(c).Cancel(serviceID, deploymentID) {
|
switch getDeployQueue(c).Cancel(serviceID, deploymentID) {
|
||||||
case deployqueue.WasQueued:
|
case deployqueue.WasQueued:
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
_, _ = db.(*database.DB).Exec(
|
_ = q.CompleteDeployment(ctx, sqlcdb.CompleteDeploymentParams{
|
||||||
`UPDATE deployments SET status = 'cancelled', error = 'Deployment cancelled', completed_at = $1, updated_at = $1 WHERE id = $2`,
|
Status: sql.NullString{String: "cancelled", Valid: true},
|
||||||
now, deploymentID,
|
Error: sql.NullString{String: "Deployment cancelled", Valid: true},
|
||||||
)
|
CompletedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
_, _ = db.(*database.DB).Exec(
|
ID: deploymentID,
|
||||||
`UPDATE services SET status = 'cancelled', updated_at = $1 WHERE id = $2`,
|
})
|
||||||
now, serviceID,
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
)
|
Status: sql.NullString{String: "cancelled", Valid: true},
|
||||||
|
UpdatedAt: sql.NullTime{Time: now, Valid: true},
|
||||||
|
ID: serviceID,
|
||||||
|
})
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "cancelled"})
|
c.JSON(http.StatusOK, gin.H{"status": "cancelled"})
|
||||||
case deployqueue.WasActive:
|
case deployqueue.WasActive:
|
||||||
c.JSON(http.StatusAccepted, gin.H{"status": "cancelling"})
|
c.JSON(http.StatusAccepted, gin.H{"status": "cancelling"})
|
||||||
@@ -832,27 +856,18 @@ func handleGetDeployment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var d DeploymentModel
|
r, err := sqlcdb.New(db.(*database.DB).DB).GetDeploymentWithProject(c.Request.Context(), deploymentID)
|
||||||
var projectID uuid.UUID
|
|
||||||
err = db.(*database.DB).QueryRow(
|
|
||||||
`SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
|
||||||
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
|
||||||
d.created_at, d.updated_at, p.id
|
|
||||||
FROM deployments d
|
|
||||||
JOIN services s ON d.service_id = s.id
|
|
||||||
JOIN projects p ON s.project_id = p.id
|
|
||||||
WHERE d.id = $1`,
|
|
||||||
deploymentID,
|
|
||||||
).Scan(
|
|
||||||
&d.ID, &d.ServiceID, &d.CommitHash, &d.Status, &d.ImageName, &d.ImageTag,
|
|
||||||
&d.BuildLog, &d.RuntimeLog, &d.Error, &d.StartedAt, &d.CompletedAt,
|
|
||||||
&d.CreatedAt, &d.UpdatedAt, &projectID,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
projectID := r.ProjectID
|
||||||
|
d := deploymentModelFrom(deploymentRowShape{
|
||||||
|
ID: r.ID, ServiceID: r.ServiceID, CommitHash: r.CommitHash, Status: r.Status,
|
||||||
|
ImageName: r.ImageName, ImageTag: r.ImageTag, BuildLog: r.BuildLog,
|
||||||
|
RuntimeLog: r.RuntimeLog, Error: r.Error, StartedAt: r.StartedAt,
|
||||||
|
CompletedAt: r.CompletedAt, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
|
||||||
|
})
|
||||||
|
|
||||||
if _, allowed := projectReadAccess(c, db.(*database.DB), projectID); !allowed {
|
if _, allowed := projectReadAccess(c, db.(*database.DB), projectID); !allowed {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
||||||
@@ -882,33 +897,23 @@ func handleRollbackDeployment(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetDeployment DeploymentModel
|
ctx := c.Request.Context()
|
||||||
var serviceID uuid.UUID
|
q := sqlcdb.New(db.(*database.DB).DB)
|
||||||
var ownerCheck string
|
r, err := q.GetDeploymentForRollback(ctx, deploymentID)
|
||||||
|
|
||||||
err = db.(*database.DB).QueryRow(
|
|
||||||
`SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
|
||||||
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
|
||||||
d.created_at, d.updated_at, p.owner_id
|
|
||||||
FROM deployments d
|
|
||||||
JOIN services s ON d.service_id = s.id
|
|
||||||
JOIN projects p ON s.project_id = p.id
|
|
||||||
WHERE d.id = $1`,
|
|
||||||
deploymentID,
|
|
||||||
).Scan(
|
|
||||||
&targetDeployment.ID, &serviceID, &targetDeployment.CommitHash, &targetDeployment.Status,
|
|
||||||
&targetDeployment.ImageName, &targetDeployment.ImageTag, &targetDeployment.BuildLog,
|
|
||||||
&targetDeployment.RuntimeLog, &targetDeployment.Error, &targetDeployment.StartedAt,
|
|
||||||
&targetDeployment.CompletedAt, &targetDeployment.CreatedAt, &targetDeployment.UpdatedAt,
|
|
||||||
&ownerCheck,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
serviceID := r.ServiceID
|
||||||
|
targetDeployment := deploymentModelFrom(deploymentRowShape{
|
||||||
|
ID: r.ID, ServiceID: r.ServiceID, CommitHash: r.CommitHash, Status: r.Status,
|
||||||
|
ImageName: r.ImageName, ImageTag: r.ImageTag, BuildLog: r.BuildLog,
|
||||||
|
RuntimeLog: r.RuntimeLog, Error: r.Error, StartedAt: r.StartedAt,
|
||||||
|
CompletedAt: r.CompletedAt, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
|
||||||
|
})
|
||||||
|
ownerCheck := r.OwnerID
|
||||||
|
|
||||||
if ownerCheck != userID.(string) && !contextIsAdmin(c) {
|
if ownerCheck.String() != userID.(string) && !contextIsAdmin(c) {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -931,23 +936,28 @@ func handleRollbackDeployment(c *gin.Context) {
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.(*database.DB).Exec(
|
err = q.InsertDeployment(ctx, sqlcdb.InsertDeploymentParams{
|
||||||
`INSERT INTO deployments
|
ID: rollback.ID,
|
||||||
(id, service_id, version, commit_hash, status, image_name, image_tag, created_at, updated_at)
|
ServiceID: rollback.ServiceID,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
Version: fmt.Sprintf("rollback-%d", now.Unix()),
|
||||||
rollback.ID, rollback.ServiceID, fmt.Sprintf("rollback-%d", now.Unix()), rollback.CommitHash, rollback.Status,
|
CommitHash: sql.NullString{String: ptrStr(rollback.CommitHash), Valid: rollback.CommitHash != nil},
|
||||||
rollback.ImageName, rollback.ImageTag, rollback.CreatedAt, rollback.UpdatedAt,
|
Status: sql.NullString{String: rollback.Status, Valid: true},
|
||||||
)
|
ImageName: sql.NullString{String: rollback.ImageName, Valid: true},
|
||||||
|
ImageTag: sql.NullString{String: rollback.ImageTag, Valid: true},
|
||||||
|
CreatedAt: sql.NullTime{Time: rollback.CreatedAt, Valid: true},
|
||||||
|
UpdatedAt: sql.NullTime{Time: rollback.UpdatedAt, Valid: true},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create rollback deployment"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create rollback deployment"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.(*database.DB).Exec(
|
err = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
`UPDATE services SET status = 'building', updated_at = $1 WHERE id = $2`,
|
Status: sql.NullString{String: "building", Valid: true},
|
||||||
time.Now(), serviceID,
|
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
)
|
ID: serviceID,
|
||||||
|
})
|
||||||
|
|
||||||
// Real rollback: redeploy the target deployment's image.
|
// Real rollback: redeploy the target deployment's image.
|
||||||
engineValue, _ := c.Get("deployment_engine")
|
engineValue, _ := c.Get("deployment_engine")
|
||||||
@@ -963,36 +973,18 @@ func handleRollbackDeployment(c *gin.Context) {
|
|||||||
if engine == nil {
|
if engine == nil {
|
||||||
reason = "Deployment engine unavailable. Docker may not be configured on this server."
|
reason = "Deployment engine unavailable. Docker may not be configured on this server."
|
||||||
}
|
}
|
||||||
_, _ = db.(*database.DB).Exec(
|
_ = q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
`UPDATE deployments SET status = 'failed', error = $1, completed_at = $2, updated_at = $2 WHERE id = $3`,
|
Error: sql.NullString{String: reason, Valid: true},
|
||||||
reason, completedAt, rollbackID,
|
CompletedAt: sql.NullTime{Time: completedAt, Valid: true},
|
||||||
)
|
ID: rollbackID,
|
||||||
_, _ = db.(*database.DB).Exec(
|
})
|
||||||
`UPDATE services SET status = 'failed', updated_at = $1 WHERE id = $2`,
|
_ = q.SetServiceStatus(ctx, sqlcdb.SetServiceStatusParams{
|
||||||
completedAt, serviceID,
|
Status: sql.NullString{String: "failed", Valid: true},
|
||||||
)
|
UpdatedAt: sql.NullTime{Time: completedAt, Valid: true},
|
||||||
|
ID: serviceID,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
var service Service
|
service, _ := loadServiceForDeploy(db.(*database.DB), serviceID)
|
||||||
_ = db.(*database.DB).QueryRow(
|
|
||||||
`SELECT s.id, s.project_id, s.name, s.type, s.status, s.image, s.command,
|
|
||||||
s.environment, s.git_repo, s.git_branch, s.build_path, s.cpu, s.memory,
|
|
||||||
COALESCE(s.replicas, 1), COALESCE(s.port, 0),
|
|
||||||
COALESCE(s.domain, ''), COALESCE(s.healthcheck_path, ''),
|
|
||||||
COALESCE(s.restart_policy, 'unless-stopped'),
|
|
||||||
COALESCE(s.builder, 'auto'), COALESCE(s.cpu_reserve, ''),
|
|
||||||
COALESCE(s.memory_reserve, ''), COALESCE(s.static_build_cmd, ''),
|
|
||||||
COALESCE(s.static_dir, ''),
|
|
||||||
s.created_at, s.updated_at
|
|
||||||
FROM services s WHERE s.id = $1`, serviceID,
|
|
||||||
).Scan(
|
|
||||||
&service.ID, &service.ProjectID, &service.Name, &service.Type, &service.Status,
|
|
||||||
&service.Image, &service.Command, &service.Environment, &service.GitRepo,
|
|
||||||
&service.GitBranch, &service.BuildPath, &service.CPU, &service.Memory,
|
|
||||||
&service.Replicas, &service.Port, &service.Domain, &service.HealthCheckPath,
|
|
||||||
&service.RestartPolicy, &service.Builder, &service.CPUReserve,
|
|
||||||
&service.MemoryReserve, &service.StaticBuildCmd, &service.StaticDir,
|
|
||||||
&service.CreatedAt, &service.UpdatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
rollbackReq := CreateDeploymentRequest{
|
rollbackReq := CreateDeploymentRequest{
|
||||||
CommitHash: func() string {
|
CommitHash: func() string {
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"containr/internal/database"
|
"containr/internal/database"
|
||||||
"containr/internal/database/sqlcdb"
|
"containr/internal/database/sqlcdb"
|
||||||
@@ -338,3 +339,108 @@ func TestPreviewEnvironmentsLiveInvariants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
must(q.DeletePreviewEnvironment(ctx, previewID))
|
must(q.DeletePreviewEnvironment(ctx, previewID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Live deployment lifecycle: generated queries preserve list ordering,
|
||||||
|
// the access join, and the status-transition writes.
|
||||||
|
func TestDeploymentsLiveInvariants(t *testing.T) {
|
||||||
|
dsn := testDSNString(t)
|
||||||
|
db, err := database.NewConnection(dsn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("connect: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
ctx := context.Background()
|
||||||
|
q := sqlcdb.New(db.DB)
|
||||||
|
|
||||||
|
userID := uuid.New()
|
||||||
|
projectID := uuid.New()
|
||||||
|
envID := uuid.New()
|
||||||
|
serviceID := uuid.New()
|
||||||
|
deployID := uuid.New()
|
||||||
|
suffix := strings.ReplaceAll(deployID.String()[:8], "-", "")
|
||||||
|
must := func(err error) {
|
||||||
|
t.Helper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setup: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = db.Exec(`INSERT INTO users (id, email, password_hash, name) VALUES ($1, $2, 'x', 'test')`,
|
||||||
|
userID, "dep-"+suffix+"@test.local")
|
||||||
|
must(err)
|
||||||
|
_, err = db.Exec(`INSERT INTO projects (id, name, owner_id, is_approved) VALUES ($1, $2, $3, true)`,
|
||||||
|
projectID, "dep-"+suffix, userID)
|
||||||
|
must(err)
|
||||||
|
_, err = db.Exec(`INSERT INTO environments (id, name, project_id) VALUES ($1, 'production', $2)`,
|
||||||
|
envID, projectID)
|
||||||
|
must(err)
|
||||||
|
_, err = db.Exec(`INSERT INTO services (id, name, project_id, environment_id, service_type, source_type, status)
|
||||||
|
VALUES ($1, $2, $3, $4, 'web', 'image', 'running')`,
|
||||||
|
serviceID, "dep-"+suffix, projectID, envID)
|
||||||
|
must(err)
|
||||||
|
defer func() { _, _ = db.Exec(`DELETE FROM users WHERE id = $1`, userID) }()
|
||||||
|
|
||||||
|
must(q.InsertDeployment(ctx, sqlcdb.InsertDeploymentParams{
|
||||||
|
ID: deployID,
|
||||||
|
ServiceID: serviceID,
|
||||||
|
Version: "v1-" + suffix,
|
||||||
|
Status: sql.NullString{String: "pending", Valid: true},
|
||||||
|
}))
|
||||||
|
total, err := q.CountDeploymentsForService(ctx, serviceID)
|
||||||
|
if err != nil || total != 1 {
|
||||||
|
t.Fatalf("count: %v %d", err, total)
|
||||||
|
}
|
||||||
|
rows, err := q.ListDeploymentsForService(ctx, sqlcdb.ListDeploymentsForServiceParams{
|
||||||
|
ServiceID: serviceID, Limit: 10, Offset: 0})
|
||||||
|
if err != nil || len(rows) != 1 || rows[0].ID != deployID {
|
||||||
|
t.Fatalf("list: %v %+v", err, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access join returns owner.
|
||||||
|
access, err := q.GetDeploymentAccess(ctx, deployID)
|
||||||
|
if err != nil || access.OwnerID != userID || access.ServiceID != serviceID {
|
||||||
|
t.Fatalf("access: %v %+v", err, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent feed honors the access predicate.
|
||||||
|
feed, err := q.ListRecentAccessibleDeployments(ctx, sqlcdb.ListRecentAccessibleDeploymentsParams{
|
||||||
|
OwnerID: userID, Limit: 10, Offset: 0})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("feed: %v", err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, d := range feed {
|
||||||
|
if d.ID == deployID && d.ServiceName == "dep-"+suffix {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("feed missing deployment: %+v", feed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status transitions.
|
||||||
|
must(q.SetDeploymentStatus(ctx, sqlcdb.SetDeploymentStatusParams{
|
||||||
|
Status: sql.NullString{String: "queued", Valid: true}, UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true}, ID: deployID}))
|
||||||
|
must(q.SyncDeploymentProgress(ctx, sqlcdb.SyncDeploymentProgressParams{
|
||||||
|
Status: sql.NullString{String: "deployed", Valid: true},
|
||||||
|
ImageName: sql.NullString{String: "nginx", Valid: true},
|
||||||
|
ImageTag: sql.NullString{String: "alpine", Valid: true},
|
||||||
|
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
|
ID: deployID,
|
||||||
|
}))
|
||||||
|
r, err := q.GetDeploymentWithProject(ctx, deployID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get: %v", err)
|
||||||
|
}
|
||||||
|
if r.Status.String != "deployed" || r.ImageName.String != "nginx" || r.ProjectID != projectID {
|
||||||
|
t.Fatalf("post-sync row: %+v", r)
|
||||||
|
}
|
||||||
|
must(q.FailDeployment(ctx, sqlcdb.FailDeploymentParams{
|
||||||
|
Error: sql.NullString{String: "boom", Valid: true},
|
||||||
|
CompletedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||||
|
ID: deployID,
|
||||||
|
}))
|
||||||
|
r, _ = q.GetDeploymentWithProject(ctx, deployID)
|
||||||
|
if r.Status.String != "failed" || r.Error.String != "boom" {
|
||||||
|
t.Fatalf("post-fail row: %+v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -775,11 +775,7 @@ func resolvePreviewHost(db *database.DB, cloneID uuid.UUID, envName string) stri
|
|||||||
|
|
||||||
// loadServiceForDeploy hydrates the Service fields runDeploymentAndSync
|
// loadServiceForDeploy hydrates the Service fields runDeploymentAndSync
|
||||||
// needs — mirrors the loader in handleDeployService.
|
// needs — mirrors the loader in handleDeployService.
|
||||||
func loadServiceForDeploy(db *database.DB, serviceID uuid.UUID) (Service, error) {
|
func serviceForDeployFromRow(r sqlcdb.GetServiceForDeployRow) Service {
|
||||||
r, err := sqlcdb.New(db.DB).GetServiceForDeploy(context.Background(), serviceID)
|
|
||||||
if err != nil {
|
|
||||||
return Service{}, err
|
|
||||||
}
|
|
||||||
return Service{
|
return Service{
|
||||||
ID: r.ID, ProjectID: r.ProjectID, Name: r.Name, Type: r.Type.String,
|
ID: r.ID, ProjectID: r.ProjectID, Name: r.Name, Type: r.Type.String,
|
||||||
Status: r.Status.String, Image: r.Image.String, Command: r.Command.String,
|
Status: r.Status.String, Image: r.Image.String, Command: r.Command.String,
|
||||||
@@ -791,7 +787,15 @@ func loadServiceForDeploy(db *database.DB, serviceID uuid.UUID) (Service, error)
|
|||||||
Builder: r.Builder, CPUReserve: r.CpuReserve, MemoryReserve: r.MemoryReserve,
|
Builder: r.Builder, CPUReserve: r.CpuReserve, MemoryReserve: r.MemoryReserve,
|
||||||
StaticBuildCmd: r.StaticBuildCmd, StaticDir: r.StaticDir,
|
StaticBuildCmd: r.StaticBuildCmd, StaticDir: r.StaticDir,
|
||||||
CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time,
|
CreatedAt: r.CreatedAt.Time, UpdatedAt: r.UpdatedAt.Time,
|
||||||
}, nil
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadServiceForDeploy(db *database.DB, serviceID uuid.UUID) (Service, error) {
|
||||||
|
r, err := sqlcdb.New(db.DB).GetServiceForDeploy(context.Background(), serviceID)
|
||||||
|
if err != nil {
|
||||||
|
return Service{}, err
|
||||||
|
}
|
||||||
|
return serviceForDeployFromRow(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartPreviewSweeper expires preview environments past their TTL — tears
|
// StartPreviewSweeper expires preview environments past their TTL — tears
|
||||||
|
|||||||
@@ -0,0 +1,536 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.31.1
|
||||||
|
// source: deployments.sql
|
||||||
|
|
||||||
|
package sqlcdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const completeDeployment = `-- name: CompleteDeployment :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = $1, error = $2, completed_at = $3, updated_at = $3
|
||||||
|
WHERE id = $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type CompleteDeploymentParams struct {
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CompleteDeployment(ctx context.Context, arg CompleteDeploymentParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, completeDeployment,
|
||||||
|
arg.Status,
|
||||||
|
arg.Error,
|
||||||
|
arg.CompletedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const countAccessibleDeployments = `-- name: CountAccessibleDeployments :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON s.id = d.service_id
|
||||||
|
JOIN projects p ON p.id = s.project_id
|
||||||
|
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
||||||
|
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CountAccessibleDeploymentsParams struct {
|
||||||
|
OwnerID uuid.UUID `json:"owner_id"`
|
||||||
|
Column2 bool `json:"column_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CountAccessibleDeployments(ctx context.Context, arg CountAccessibleDeploymentsParams) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, countAccessibleDeployments, arg.OwnerID, arg.Column2)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const countDeploymentsForService = `-- name: CountDeploymentsForService :one
|
||||||
|
SELECT COUNT(*) FROM deployments WHERE service_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CountDeploymentsForService(ctx context.Context, serviceID uuid.UUID) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, countDeploymentsForService, serviceID)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const failDeployment = `-- name: FailDeployment :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
||||||
|
WHERE id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type FailDeploymentParams struct {
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) FailDeployment(ctx context.Context, arg FailDeploymentParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, failDeployment, arg.Error, arg.CompletedAt, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDeploymentAccess = `-- name: GetDeploymentAccess :one
|
||||||
|
SELECT d.status, d.service_id, p.owner_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDeploymentAccessRow struct {
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
OwnerID uuid.UUID `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDeploymentAccess(ctx context.Context, id uuid.UUID) (GetDeploymentAccessRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getDeploymentAccess, id)
|
||||||
|
var i GetDeploymentAccessRow
|
||||||
|
err := row.Scan(&i.Status, &i.ServiceID, &i.OwnerID)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDeploymentForRollback = `-- name: GetDeploymentForRollback :one
|
||||||
|
SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
||||||
|
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
||||||
|
d.created_at, d.updated_at, p.owner_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDeploymentForRollbackRow struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
CommitHash sql.NullString `json:"commit_hash"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName sql.NullString `json:"image_name"`
|
||||||
|
ImageTag sql.NullString `json:"image_tag"`
|
||||||
|
BuildLog sql.NullString `json:"build_log"`
|
||||||
|
RuntimeLog sql.NullString `json:"runtime_log"`
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
StartedAt sql.NullTime `json:"started_at"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
OwnerID uuid.UUID `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDeploymentForRollback(ctx context.Context, id uuid.UUID) (GetDeploymentForRollbackRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getDeploymentForRollback, id)
|
||||||
|
var i GetDeploymentForRollbackRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ServiceID,
|
||||||
|
&i.CommitHash,
|
||||||
|
&i.Status,
|
||||||
|
&i.ImageName,
|
||||||
|
&i.ImageTag,
|
||||||
|
&i.BuildLog,
|
||||||
|
&i.RuntimeLog,
|
||||||
|
&i.Error,
|
||||||
|
&i.StartedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.OwnerID,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDeploymentWithProject = `-- name: GetDeploymentWithProject :one
|
||||||
|
SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
||||||
|
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
||||||
|
d.created_at, d.updated_at, p.id AS project_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDeploymentWithProjectRow struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
CommitHash sql.NullString `json:"commit_hash"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName sql.NullString `json:"image_name"`
|
||||||
|
ImageTag sql.NullString `json:"image_tag"`
|
||||||
|
BuildLog sql.NullString `json:"build_log"`
|
||||||
|
RuntimeLog sql.NullString `json:"runtime_log"`
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
StartedAt sql.NullTime `json:"started_at"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
ProjectID uuid.UUID `json:"project_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDeploymentWithProject(ctx context.Context, id uuid.UUID) (GetDeploymentWithProjectRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getDeploymentWithProject, id)
|
||||||
|
var i GetDeploymentWithProjectRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ServiceID,
|
||||||
|
&i.CommitHash,
|
||||||
|
&i.Status,
|
||||||
|
&i.ImageName,
|
||||||
|
&i.ImageTag,
|
||||||
|
&i.BuildLog,
|
||||||
|
&i.RuntimeLog,
|
||||||
|
&i.Error,
|
||||||
|
&i.StartedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.ProjectID,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getServiceForDeployWithOwner = `-- name: GetServiceForDeployWithOwner :one
|
||||||
|
SELECT s.id, s.project_id, s.name, s.type, s.status, s.image, s.command,
|
||||||
|
s.environment, s.git_repo, s.git_branch, s.build_path, s.cpu, s.memory,
|
||||||
|
COALESCE(s.replicas, 1) AS replicas, COALESCE(s.port, 0) AS port,
|
||||||
|
COALESCE(s.domain, '') AS domain, COALESCE(s.healthcheck_path, '') AS healthcheck_path,
|
||||||
|
COALESCE(s.restart_policy, 'unless-stopped') AS restart_policy,
|
||||||
|
COALESCE(s.builder, 'auto') AS builder, COALESCE(s.cpu_reserve, '') AS cpu_reserve,
|
||||||
|
COALESCE(s.memory_reserve, '') AS memory_reserve, COALESCE(s.static_build_cmd, '') AS static_build_cmd,
|
||||||
|
COALESCE(s.static_dir, '') AS static_dir,
|
||||||
|
s.created_at, s.updated_at, p.owner_id
|
||||||
|
FROM services s
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE s.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetServiceForDeployWithOwnerRow struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ProjectID uuid.UUID `json:"project_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type sql.NullString `json:"type"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
Image sql.NullString `json:"image"`
|
||||||
|
Command sql.NullString `json:"command"`
|
||||||
|
Environment sql.NullString `json:"environment"`
|
||||||
|
GitRepo sql.NullString `json:"git_repo"`
|
||||||
|
GitBranch sql.NullString `json:"git_branch"`
|
||||||
|
BuildPath sql.NullString `json:"build_path"`
|
||||||
|
Cpu sql.NullString `json:"cpu"`
|
||||||
|
Memory sql.NullString `json:"memory"`
|
||||||
|
Replicas int32 `json:"replicas"`
|
||||||
|
Port int32 `json:"port"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
HealthcheckPath string `json:"healthcheck_path"`
|
||||||
|
RestartPolicy string `json:"restart_policy"`
|
||||||
|
Builder string `json:"builder"`
|
||||||
|
CpuReserve string `json:"cpu_reserve"`
|
||||||
|
MemoryReserve string `json:"memory_reserve"`
|
||||||
|
StaticBuildCmd string `json:"static_build_cmd"`
|
||||||
|
StaticDir string `json:"static_dir"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
OwnerID uuid.UUID `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetServiceForDeployWithOwner(ctx context.Context, id uuid.UUID) (GetServiceForDeployWithOwnerRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getServiceForDeployWithOwner, id)
|
||||||
|
var i GetServiceForDeployWithOwnerRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProjectID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Type,
|
||||||
|
&i.Status,
|
||||||
|
&i.Image,
|
||||||
|
&i.Command,
|
||||||
|
&i.Environment,
|
||||||
|
&i.GitRepo,
|
||||||
|
&i.GitBranch,
|
||||||
|
&i.BuildPath,
|
||||||
|
&i.Cpu,
|
||||||
|
&i.Memory,
|
||||||
|
&i.Replicas,
|
||||||
|
&i.Port,
|
||||||
|
&i.Domain,
|
||||||
|
&i.HealthcheckPath,
|
||||||
|
&i.RestartPolicy,
|
||||||
|
&i.Builder,
|
||||||
|
&i.CpuReserve,
|
||||||
|
&i.MemoryReserve,
|
||||||
|
&i.StaticBuildCmd,
|
||||||
|
&i.StaticDir,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.OwnerID,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getServicePublishedPort = `-- name: GetServicePublishedPort :one
|
||||||
|
SELECT published_port FROM services WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetServicePublishedPort(ctx context.Context, id uuid.UUID) (int32, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getServicePublishedPort, id)
|
||||||
|
var published_port int32
|
||||||
|
err := row.Scan(&published_port)
|
||||||
|
return published_port, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertDeployment = `-- name: InsertDeployment :exec
|
||||||
|
INSERT INTO deployments
|
||||||
|
(id, service_id, version, commit_hash, status, image_name, image_tag, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertDeploymentParams struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
CommitHash sql.NullString `json:"commit_hash"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName sql.NullString `json:"image_name"`
|
||||||
|
ImageTag sql.NullString `json:"image_tag"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertDeployment(ctx context.Context, arg InsertDeploymentParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, insertDeployment,
|
||||||
|
arg.ID,
|
||||||
|
arg.ServiceID,
|
||||||
|
arg.Version,
|
||||||
|
arg.CommitHash,
|
||||||
|
arg.Status,
|
||||||
|
arg.ImageName,
|
||||||
|
arg.ImageTag,
|
||||||
|
arg.CreatedAt,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const listDeploymentsForService = `-- name: ListDeploymentsForService :many
|
||||||
|
SELECT id, service_id, commit_hash, status, image_name, image_tag,
|
||||||
|
build_log, runtime_log, error, started_at, completed_at, created_at, updated_at
|
||||||
|
FROM deployments
|
||||||
|
WHERE service_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $2 OFFSET $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListDeploymentsForServiceParams struct {
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDeploymentsForServiceRow struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
CommitHash sql.NullString `json:"commit_hash"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName sql.NullString `json:"image_name"`
|
||||||
|
ImageTag sql.NullString `json:"image_tag"`
|
||||||
|
BuildLog sql.NullString `json:"build_log"`
|
||||||
|
RuntimeLog sql.NullString `json:"runtime_log"`
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
StartedAt sql.NullTime `json:"started_at"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListDeploymentsForService(ctx context.Context, arg ListDeploymentsForServiceParams) ([]ListDeploymentsForServiceRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, listDeploymentsForService, arg.ServiceID, arg.Limit, arg.Offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []ListDeploymentsForServiceRow{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i ListDeploymentsForServiceRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ServiceID,
|
||||||
|
&i.CommitHash,
|
||||||
|
&i.Status,
|
||||||
|
&i.ImageName,
|
||||||
|
&i.ImageTag,
|
||||||
|
&i.BuildLog,
|
||||||
|
&i.RuntimeLog,
|
||||||
|
&i.Error,
|
||||||
|
&i.StartedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const listRecentAccessibleDeployments = `-- name: ListRecentAccessibleDeployments :many
|
||||||
|
SELECT d.id, d.service_id, s.name AS service_name, p.name AS project_name, d.status,
|
||||||
|
COALESCE(d.image_name, '') AS image_name, d.started_at, d.completed_at, d.created_at
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON s.id = d.service_id
|
||||||
|
JOIN projects p ON p.id = s.project_id
|
||||||
|
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
||||||
|
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1)
|
||||||
|
ORDER BY d.created_at DESC
|
||||||
|
LIMIT $3 OFFSET $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListRecentAccessibleDeploymentsParams struct {
|
||||||
|
OwnerID uuid.UUID `json:"owner_id"`
|
||||||
|
Column2 bool `json:"column_2"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListRecentAccessibleDeploymentsRow struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
ServiceID uuid.UUID `json:"service_id"`
|
||||||
|
ServiceName string `json:"service_name"`
|
||||||
|
ProjectName string `json:"project_name"`
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName string `json:"image_name"`
|
||||||
|
StartedAt sql.NullTime `json:"started_at"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
CreatedAt sql.NullTime `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListRecentAccessibleDeployments(ctx context.Context, arg ListRecentAccessibleDeploymentsParams) ([]ListRecentAccessibleDeploymentsRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, listRecentAccessibleDeployments,
|
||||||
|
arg.OwnerID,
|
||||||
|
arg.Column2,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []ListRecentAccessibleDeploymentsRow{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i ListRecentAccessibleDeploymentsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ServiceID,
|
||||||
|
&i.ServiceName,
|
||||||
|
&i.ProjectName,
|
||||||
|
&i.Status,
|
||||||
|
&i.ImageName,
|
||||||
|
&i.StartedAt,
|
||||||
|
&i.CompletedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDeploymentStatus = `-- name: SetDeploymentStatus :exec
|
||||||
|
UPDATE deployments SET status = $1, updated_at = $2 WHERE id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type SetDeploymentStatusParams struct {
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SetDeploymentStatus(ctx context.Context, arg SetDeploymentStatusParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setDeploymentStatus, arg.Status, arg.UpdatedAt, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const setServiceStatus = `-- name: SetServiceStatus :exec
|
||||||
|
UPDATE services SET status = $1, updated_at = $2 WHERE id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type SetServiceStatusParams struct {
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SetServiceStatus(ctx context.Context, arg SetServiceStatusParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setServiceStatus, arg.Status, arg.UpdatedAt, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncDeploymentProgress = `-- name: SyncDeploymentProgress :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = $1,
|
||||||
|
image_name = $2,
|
||||||
|
image_tag = $3,
|
||||||
|
build_log = $4,
|
||||||
|
runtime_log = $5,
|
||||||
|
error = $6,
|
||||||
|
started_at = $7,
|
||||||
|
completed_at = $8,
|
||||||
|
updated_at = $9
|
||||||
|
WHERE id = $10
|
||||||
|
`
|
||||||
|
|
||||||
|
type SyncDeploymentProgressParams struct {
|
||||||
|
Status sql.NullString `json:"status"`
|
||||||
|
ImageName sql.NullString `json:"image_name"`
|
||||||
|
ImageTag sql.NullString `json:"image_tag"`
|
||||||
|
BuildLog sql.NullString `json:"build_log"`
|
||||||
|
RuntimeLog sql.NullString `json:"runtime_log"`
|
||||||
|
Error sql.NullString `json:"error"`
|
||||||
|
StartedAt sql.NullTime `json:"started_at"`
|
||||||
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SyncDeploymentProgress(ctx context.Context, arg SyncDeploymentProgressParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, syncDeploymentProgress,
|
||||||
|
arg.Status,
|
||||||
|
arg.ImageName,
|
||||||
|
arg.ImageTag,
|
||||||
|
arg.BuildLog,
|
||||||
|
arg.RuntimeLog,
|
||||||
|
arg.Error,
|
||||||
|
arg.StartedAt,
|
||||||
|
arg.CompletedAt,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -22,9 +22,12 @@ type Querier interface {
|
|||||||
CloneServiceRow(ctx context.Context, arg CloneServiceRowParams) error
|
CloneServiceRow(ctx context.Context, arg CloneServiceRowParams) error
|
||||||
CloneServiceVariables(ctx context.Context, arg CloneServiceVariablesParams) error
|
CloneServiceVariables(ctx context.Context, arg CloneServiceVariablesParams) error
|
||||||
CompleteCommand(ctx context.Context, arg CompleteCommandParams) (AgentCommand, error)
|
CompleteCommand(ctx context.Context, arg CompleteCommandParams) (AgentCommand, error)
|
||||||
|
CompleteDeployment(ctx context.Context, arg CompleteDeploymentParams) error
|
||||||
|
CountAccessibleDeployments(ctx context.Context, arg CountAccessibleDeploymentsParams) (int64, error)
|
||||||
CountActivePreviewsForBranch(ctx context.Context, arg CountActivePreviewsForBranchParams) (int64, error)
|
CountActivePreviewsForBranch(ctx context.Context, arg CountActivePreviewsForBranchParams) (int64, error)
|
||||||
CountDatabaseServicesByUserAndName(ctx context.Context, arg CountDatabaseServicesByUserAndNameParams) (int64, error)
|
CountDatabaseServicesByUserAndName(ctx context.Context, arg CountDatabaseServicesByUserAndNameParams) (int64, error)
|
||||||
CountDatabasesUsingBackupTarget(ctx context.Context, targetID sql.NullString) (int64, error)
|
CountDatabasesUsingBackupTarget(ctx context.Context, targetID sql.NullString) (int64, error)
|
||||||
|
CountDeploymentsForService(ctx context.Context, serviceID uuid.UUID) (int64, error)
|
||||||
CountEnvironmentServices(ctx context.Context, environmentID uuid.UUID) (int64, error)
|
CountEnvironmentServices(ctx context.Context, environmentID uuid.UUID) (int64, error)
|
||||||
CountProjectsByUser(ctx context.Context, arg CountProjectsByUserParams) (int64, error)
|
CountProjectsByUser(ctx context.Context, arg CountProjectsByUserParams) (int64, error)
|
||||||
CountServiceDomains(ctx context.Context, serviceID uuid.UUID) (int64, error)
|
CountServiceDomains(ctx context.Context, serviceID uuid.UUID) (int64, error)
|
||||||
@@ -68,6 +71,7 @@ type Querier interface {
|
|||||||
DeleteServiceDomain(ctx context.Context, arg DeleteServiceDomainParams) (int64, error)
|
DeleteServiceDomain(ctx context.Context, arg DeleteServiceDomainParams) (int64, error)
|
||||||
DeleteUserInvite(ctx context.Context, id uuid.UUID) error
|
DeleteUserInvite(ctx context.Context, id uuid.UUID) error
|
||||||
DeleteUserTemplate(ctx context.Context, arg DeleteUserTemplateParams) (int64, error)
|
DeleteUserTemplate(ctx context.Context, arg DeleteUserTemplateParams) (int64, error)
|
||||||
|
FailDeployment(ctx context.Context, arg FailDeploymentParams) error
|
||||||
GetActiveAgentAuthTokenByHash(ctx context.Context, tokenHash string) (AgentAuthToken, error)
|
GetActiveAgentAuthTokenByHash(ctx context.Context, tokenHash string) (AgentAuthToken, error)
|
||||||
GetAgent(ctx context.Context, id string) (NodeAgent, error)
|
GetAgent(ctx context.Context, id string) (NodeAgent, error)
|
||||||
GetAgentByHostAndIP(ctx context.Context, arg GetAgentByHostAndIPParams) (NodeAgent, error)
|
GetAgentByHostAndIP(ctx context.Context, arg GetAgentByHostAndIPParams) (NodeAgent, error)
|
||||||
@@ -81,6 +85,9 @@ type Querier interface {
|
|||||||
GetDatabaseServiceByID(ctx context.Context, id string) (DatabaseService, error)
|
GetDatabaseServiceByID(ctx context.Context, id string) (DatabaseService, error)
|
||||||
GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (DatabaseService, error)
|
GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (DatabaseService, error)
|
||||||
GetDatabaseServiceOwnerID(ctx context.Context, id string) (string, error)
|
GetDatabaseServiceOwnerID(ctx context.Context, id string) (string, error)
|
||||||
|
GetDeploymentAccess(ctx context.Context, id uuid.UUID) (GetDeploymentAccessRow, error)
|
||||||
|
GetDeploymentForRollback(ctx context.Context, id uuid.UUID) (GetDeploymentForRollbackRow, error)
|
||||||
|
GetDeploymentWithProject(ctx context.Context, id uuid.UUID) (GetDeploymentWithProjectRow, error)
|
||||||
GetLastAgentCommandByType(ctx context.Context, arg GetLastAgentCommandByTypeParams) (AgentCommand, error)
|
GetLastAgentCommandByType(ctx context.Context, arg GetLastAgentCommandByTypeParams) (AgentCommand, error)
|
||||||
GetOutboundWebhookByID(ctx context.Context, id uuid.UUID) (OutboundWebhook, error)
|
GetOutboundWebhookByID(ctx context.Context, id uuid.UUID) (OutboundWebhook, error)
|
||||||
GetOutboundWebhookByIDAndUser(ctx context.Context, arg GetOutboundWebhookByIDAndUserParams) (OutboundWebhook, error)
|
GetOutboundWebhookByIDAndUser(ctx context.Context, arg GetOutboundWebhookByIDAndUserParams) (OutboundWebhook, error)
|
||||||
@@ -101,12 +108,15 @@ type Querier interface {
|
|||||||
GetServiceDomainFirst(ctx context.Context, serviceID uuid.UUID) (string, error)
|
GetServiceDomainFirst(ctx context.Context, serviceID uuid.UUID) (string, error)
|
||||||
GetServiceEnvironment(ctx context.Context, id uuid.UUID) (string, error)
|
GetServiceEnvironment(ctx context.Context, id uuid.UUID) (string, error)
|
||||||
GetServiceForDeploy(ctx context.Context, id uuid.UUID) (GetServiceForDeployRow, error)
|
GetServiceForDeploy(ctx context.Context, id uuid.UUID) (GetServiceForDeployRow, error)
|
||||||
|
GetServiceForDeployWithOwner(ctx context.Context, id uuid.UUID) (GetServiceForDeployWithOwnerRow, error)
|
||||||
GetServicePlacementBrief(ctx context.Context, id uuid.UUID) (GetServicePlacementBriefRow, error)
|
GetServicePlacementBrief(ctx context.Context, id uuid.UUID) (GetServicePlacementBriefRow, error)
|
||||||
|
GetServicePublishedPort(ctx context.Context, id uuid.UUID) (int32, error)
|
||||||
GetServiceTemplateByID(ctx context.Context, id string) (ServiceTemplate, error)
|
GetServiceTemplateByID(ctx context.Context, id string) (ServiceTemplate, error)
|
||||||
GetServiceTypeBrief(ctx context.Context, arg GetServiceTypeBriefParams) (GetServiceTypeBriefRow, error)
|
GetServiceTypeBrief(ctx context.Context, arg GetServiceTypeBriefParams) (GetServiceTypeBriefRow, error)
|
||||||
GetUserInviteByTokenHash(ctx context.Context, tokenHash string) (UserInvite, error)
|
GetUserInviteByTokenHash(ctx context.Context, tokenHash string) (UserInvite, error)
|
||||||
GetUserTokenByHash(ctx context.Context, tokenHash string) (UserToken, error)
|
GetUserTokenByHash(ctx context.Context, tokenHash string) (UserToken, error)
|
||||||
InsertAgentHeartbeat(ctx context.Context, arg InsertAgentHeartbeatParams) error
|
InsertAgentHeartbeat(ctx context.Context, arg InsertAgentHeartbeatParams) error
|
||||||
|
InsertDeployment(ctx context.Context, arg InsertDeploymentParams) error
|
||||||
InsertNotificationChannel(ctx context.Context, arg InsertNotificationChannelParams) (NotificationChannel, error)
|
InsertNotificationChannel(ctx context.Context, arg InsertNotificationChannelParams) (NotificationChannel, error)
|
||||||
InsertPendingDeployment(ctx context.Context, arg InsertPendingDeploymentParams) error
|
InsertPendingDeployment(ctx context.Context, arg InsertPendingDeploymentParams) error
|
||||||
InsertProjectEnvironment(ctx context.Context, arg InsertProjectEnvironmentParams) error
|
InsertProjectEnvironment(ctx context.Context, arg InsertProjectEnvironmentParams) error
|
||||||
@@ -123,6 +133,7 @@ type Querier interface {
|
|||||||
ListDatabaseBackupsByDatabaseAndUser(ctx context.Context, arg ListDatabaseBackupsByDatabaseAndUserParams) ([]DatabaseBackup, error)
|
ListDatabaseBackupsByDatabaseAndUser(ctx context.Context, arg ListDatabaseBackupsByDatabaseAndUserParams) ([]DatabaseBackup, error)
|
||||||
ListDatabaseServicesByProject(ctx context.Context, projectID uuid.NullUUID) ([]DatabaseService, error)
|
ListDatabaseServicesByProject(ctx context.Context, projectID uuid.NullUUID) ([]DatabaseService, error)
|
||||||
ListDatabaseServicesByUser(ctx context.Context, userID string) ([]DatabaseService, error)
|
ListDatabaseServicesByUser(ctx context.Context, userID string) ([]DatabaseService, error)
|
||||||
|
ListDeploymentsForService(ctx context.Context, arg ListDeploymentsForServiceParams) ([]ListDeploymentsForServiceRow, error)
|
||||||
ListDueDatabaseBackups(ctx context.Context) ([]ListDueDatabaseBackupsRow, error)
|
ListDueDatabaseBackups(ctx context.Context) ([]ListDueDatabaseBackupsRow, error)
|
||||||
ListEnabledOutboundWebhooks(ctx context.Context) ([]OutboundWebhook, error)
|
ListEnabledOutboundWebhooks(ctx context.Context) ([]OutboundWebhook, error)
|
||||||
ListExpiredPreviewsForUser(ctx context.Context, arg ListExpiredPreviewsForUserParams) ([]ListExpiredPreviewsForUserRow, error)
|
ListExpiredPreviewsForUser(ctx context.Context, arg ListExpiredPreviewsForUserParams) ([]ListExpiredPreviewsForUserRow, error)
|
||||||
@@ -139,6 +150,7 @@ type Querier interface {
|
|||||||
// display, when the caller owns or is a member of it, or when the caller is a
|
// display, when the caller owns or is a member of it, or when the caller is a
|
||||||
// platform admin. Anonymous callers pass user_id = uuid.Nil, is_admin = false.
|
// platform admin. Anonymous callers pass user_id = uuid.Nil, is_admin = false.
|
||||||
ListProjectsWithStatsByUser(ctx context.Context, arg ListProjectsWithStatsByUserParams) ([]ListProjectsWithStatsByUserRow, error)
|
ListProjectsWithStatsByUser(ctx context.Context, arg ListProjectsWithStatsByUserParams) ([]ListProjectsWithStatsByUserRow, error)
|
||||||
|
ListRecentAccessibleDeployments(ctx context.Context, arg ListRecentAccessibleDeploymentsParams) ([]ListRecentAccessibleDeploymentsRow, error)
|
||||||
// Base domains of every online, schedulable agent carrying the required
|
// Base domains of every online, schedulable agent carrying the required
|
||||||
// placement tags — auto-domain candidates for spread services.
|
// placement tags — auto-domain candidates for spread services.
|
||||||
ListSchedulableAgentDomains(ctx context.Context, dollar_1 json.RawMessage) ([]string, error)
|
ListSchedulableAgentDomains(ctx context.Context, dollar_1 json.RawMessage) ([]string, error)
|
||||||
@@ -183,9 +195,12 @@ type Querier interface {
|
|||||||
SetDatabaseServiceStatusAndConnectionByID(ctx context.Context, arg SetDatabaseServiceStatusAndConnectionByIDParams) error
|
SetDatabaseServiceStatusAndConnectionByID(ctx context.Context, arg SetDatabaseServiceStatusAndConnectionByIDParams) error
|
||||||
SetDatabaseServiceStatusByID(ctx context.Context, arg SetDatabaseServiceStatusByIDParams) error
|
SetDatabaseServiceStatusByID(ctx context.Context, arg SetDatabaseServiceStatusByIDParams) error
|
||||||
SetDatabaseServiceStatusByIDAndUser(ctx context.Context, arg SetDatabaseServiceStatusByIDAndUserParams) error
|
SetDatabaseServiceStatusByIDAndUser(ctx context.Context, arg SetDatabaseServiceStatusByIDAndUserParams) error
|
||||||
|
SetDeploymentStatus(ctx context.Context, arg SetDeploymentStatusParams) error
|
||||||
SetProjectApproved(ctx context.Context, arg SetProjectApprovedParams) (int64, error)
|
SetProjectApproved(ctx context.Context, arg SetProjectApprovedParams) (int64, error)
|
||||||
SetServiceDomainDefault(ctx context.Context, arg SetServiceDomainDefaultParams) (int64, error)
|
SetServiceDomainDefault(ctx context.Context, arg SetServiceDomainDefaultParams) (int64, error)
|
||||||
SetServiceLegacyDomain(ctx context.Context, arg SetServiceLegacyDomainParams) error
|
SetServiceLegacyDomain(ctx context.Context, arg SetServiceLegacyDomainParams) error
|
||||||
|
SetServiceStatus(ctx context.Context, arg SetServiceStatusParams) error
|
||||||
|
SyncDeploymentProgress(ctx context.Context, arg SyncDeploymentProgressParams) error
|
||||||
SyncPreviewStatuses(ctx context.Context) error
|
SyncPreviewStatuses(ctx context.Context) error
|
||||||
TouchAgentAuthToken(ctx context.Context, tokenHash string) error
|
TouchAgentAuthToken(ctx context.Context, tokenHash string) error
|
||||||
TouchServiceDomainChecked(ctx context.Context, id uuid.UUID) error
|
TouchServiceDomainChecked(ctx context.Context, id uuid.UUID) error
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
-- name: CountDeploymentsForService :one
|
||||||
|
SELECT COUNT(*) FROM deployments WHERE service_id = $1;
|
||||||
|
|
||||||
|
-- name: ListDeploymentsForService :many
|
||||||
|
SELECT id, service_id, commit_hash, status, image_name, image_tag,
|
||||||
|
build_log, runtime_log, error, started_at, completed_at, created_at, updated_at
|
||||||
|
FROM deployments
|
||||||
|
WHERE service_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
-- name: CountAccessibleDeployments :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON s.id = d.service_id
|
||||||
|
JOIN projects p ON p.id = s.project_id
|
||||||
|
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
||||||
|
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1);
|
||||||
|
|
||||||
|
-- name: ListRecentAccessibleDeployments :many
|
||||||
|
SELECT d.id, d.service_id, s.name AS service_name, p.name AS project_name, d.status,
|
||||||
|
COALESCE(d.image_name, '') AS image_name, d.started_at, d.completed_at, d.created_at
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON s.id = d.service_id
|
||||||
|
JOIN projects p ON p.id = s.project_id
|
||||||
|
WHERE p.is_approved OR p.owner_id = $1 OR $2::bool
|
||||||
|
OR EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = $1)
|
||||||
|
ORDER BY d.created_at DESC
|
||||||
|
LIMIT $3 OFFSET $4;
|
||||||
|
|
||||||
|
-- name: GetServiceForDeployWithOwner :one
|
||||||
|
SELECT s.id, s.project_id, s.name, s.type, s.status, s.image, s.command,
|
||||||
|
s.environment, s.git_repo, s.git_branch, s.build_path, s.cpu, s.memory,
|
||||||
|
COALESCE(s.replicas, 1) AS replicas, COALESCE(s.port, 0) AS port,
|
||||||
|
COALESCE(s.domain, '') AS domain, COALESCE(s.healthcheck_path, '') AS healthcheck_path,
|
||||||
|
COALESCE(s.restart_policy, 'unless-stopped') AS restart_policy,
|
||||||
|
COALESCE(s.builder, 'auto') AS builder, COALESCE(s.cpu_reserve, '') AS cpu_reserve,
|
||||||
|
COALESCE(s.memory_reserve, '') AS memory_reserve, COALESCE(s.static_build_cmd, '') AS static_build_cmd,
|
||||||
|
COALESCE(s.static_dir, '') AS static_dir,
|
||||||
|
s.created_at, s.updated_at, p.owner_id
|
||||||
|
FROM services s
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE s.id = $1;
|
||||||
|
|
||||||
|
-- name: InsertDeployment :exec
|
||||||
|
INSERT INTO deployments
|
||||||
|
(id, service_id, version, commit_hash, status, image_name, image_tag, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
||||||
|
|
||||||
|
-- name: FailDeployment :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = 'failed', error = $1, completed_at = $2, updated_at = $2
|
||||||
|
WHERE id = $3;
|
||||||
|
|
||||||
|
-- name: CompleteDeployment :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = $1, error = $2, completed_at = $3, updated_at = $3
|
||||||
|
WHERE id = $4;
|
||||||
|
|
||||||
|
-- name: SetDeploymentStatus :exec
|
||||||
|
UPDATE deployments SET status = $1, updated_at = $2 WHERE id = $3;
|
||||||
|
|
||||||
|
-- name: SetServiceStatus :exec
|
||||||
|
UPDATE services SET status = $1, updated_at = $2 WHERE id = $3;
|
||||||
|
|
||||||
|
-- name: SyncDeploymentProgress :exec
|
||||||
|
UPDATE deployments
|
||||||
|
SET status = $1,
|
||||||
|
image_name = $2,
|
||||||
|
image_tag = $3,
|
||||||
|
build_log = $4,
|
||||||
|
runtime_log = $5,
|
||||||
|
error = $6,
|
||||||
|
started_at = $7,
|
||||||
|
completed_at = $8,
|
||||||
|
updated_at = $9
|
||||||
|
WHERE id = $10;
|
||||||
|
|
||||||
|
-- name: GetDeploymentAccess :one
|
||||||
|
SELECT d.status, d.service_id, p.owner_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1;
|
||||||
|
|
||||||
|
-- name: GetDeploymentWithProject :one
|
||||||
|
SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
||||||
|
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
||||||
|
d.created_at, d.updated_at, p.id AS project_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1;
|
||||||
|
|
||||||
|
-- name: GetDeploymentForRollback :one
|
||||||
|
SELECT d.id, d.service_id, d.commit_hash, d.status, d.image_name, d.image_tag,
|
||||||
|
d.build_log, d.runtime_log, d.error, d.started_at, d.completed_at,
|
||||||
|
d.created_at, d.updated_at, p.owner_id
|
||||||
|
FROM deployments d
|
||||||
|
JOIN services s ON d.service_id = s.id
|
||||||
|
JOIN projects p ON s.project_id = p.id
|
||||||
|
WHERE d.id = $1;
|
||||||
|
|
||||||
|
-- name: GetServicePublishedPort :one
|
||||||
|
SELECT published_port FROM services WHERE id = $1;
|
||||||
Reference in new issue
Block a user