retry repo create

This commit is contained in:
pavel 2026-05-14 20:09:30 +02:00
commit 515d90cc9e
7 changed files with 88 additions and 19 deletions

View file

@ -9,7 +9,7 @@ import (
)
func (a *App) listProjects(ctx context.Context, userID string) ([]Project, error) {
rows, err := a.db.QueryContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.service_name, p.route_host, p.target_port, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.created_at
rows, err := a.db.QueryContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.target_port, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.created_at
FROM projects p
JOIN workspaces w ON w.id = p.workspace_id
WHERE p.user_id=$1 ORDER BY p.created_at DESC`, userID)
@ -20,7 +20,7 @@ func (a *App) listProjects(ctx context.Context, userID string) ([]Project, error
var out []Project
for rows.Next() {
var p Project
if err := rows.Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt); err != nil {
if err := rows.Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt); err != nil {
return nil, err
}
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
@ -54,9 +54,9 @@ func (a *App) createProject(ctx context.Context, user User, workspaceID int64, n
}
defer tx.Rollback()
err = tx.QueryRowContext(ctx, `INSERT INTO projects (user_id, workspace_id, name, slug, description, repo_url, service_name, route_host, target_port, deploy_token, provision_state, provision_error) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,'pending','') RETURNING id, user_id, name, slug, description, repo_url, service_name, route_host, target_port, workspace_id, deploy_token, provision_state, provision_error, created_at`,
user.Username, workspaceID, name, slug, description, repoURL, serviceName, routeHost, targetPort, deployToken,
).Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt)
err = tx.QueryRowContext(ctx, `INSERT INTO projects (user_id, workspace_id, name, slug, description, repo_url, repo_private, service_name, route_host, target_port, deploy_token, provision_state, provision_error) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,'pending','') RETURNING id, user_id, name, slug, description, repo_url, repo_private, service_name, route_host, target_port, workspace_id, deploy_token, provision_state, provision_error, created_at`,
user.Username, workspaceID, name, slug, description, repoURL, private, serviceName, routeHost, targetPort, deployToken,
).Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
return Project{}, errors.New("project with this name already exists")
@ -70,57 +70,67 @@ func (a *App) createProject(ctx context.Context, user User, workspaceID int64, n
p.Workspace = workspace.Name
p.UnixUser = workspace.UnixUser
p.WebhookURL = a.deployWebhookURL(p.ID, p.DeployToken)
return p, a.provisionProject(ctx, &p)
}
func (a *App) provisionProject(ctx context.Context, p *Project) error {
if p.RepoURL == "" {
repoURL, err = a.createForgejoRepo(ctx, slug, description, private)
if a.cfg.ForgejoBaseURL == "" || a.cfg.ForgejoToken == "" {
err := errors.New("repo_url is empty and Forgejo is not configured; set FORGEJO_BASE_URL/FORGEJO_TOKEN or provide repo_url")
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return err
}
repoURL, err := a.createForgejoRepo(ctx, p.Slug, p.Description, p.RepoPrivate)
if err != nil {
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
p.RepoURL = repoURL
if _, err := a.db.ExecContext(ctx, `UPDATE projects SET repo_url=$1 WHERE id=$2`, repoURL, p.ID); err != nil {
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
}
if err := a.ensureForgejoDeployWebhook(ctx, p); err != nil {
if err := a.ensureForgejoDeployWebhook(ctx, *p); err != nil {
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
if err := a.renderSystemdForProject(ctx, p); err != nil {
if err := a.renderSystemdForProject(ctx, *p); err != nil {
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
if err := a.ensureCaddyRoute(ctx, p); err != nil {
if err := a.ensureCaddyRoute(ctx, *p); err != nil {
_ = a.updateProvisioningState(ctx, p.ID, "failed", err.Error())
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
if err := a.updateProvisioningState(ctx, p.ID, "provisioned", ""); err != nil {
p.ProvisionState = "failed"
p.ProvisionError = err.Error()
return p, err
return err
}
p.ProvisionState = "provisioned"
p.ProvisionError = ""
return p, nil
return nil
}
func (a *App) getProject(ctx context.Context, userID string, projectID int64) (Project, error) {
var p Project
err := a.db.QueryRowContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.service_name, p.route_host, p.target_port, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.created_at
err := a.db.QueryRowContext(ctx, `SELECT p.id, p.user_id, p.name, p.slug, p.description, p.repo_url, p.repo_private, p.service_name, p.route_host, p.target_port, p.workspace_id, w.name, w.unix_user, p.deploy_token, p.provision_state, p.provision_error, p.created_at
FROM projects p JOIN workspaces w ON w.id=p.workspace_id WHERE p.user_id=$1 AND p.id=$2`, userID, projectID).
Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt)
Scan(&p.ID, &p.UserID, &p.Name, &p.Slug, &p.Description, &p.RepoURL, &p.RepoPrivate, &p.ServiceName, &p.RouteHost, &p.TargetPort, &p.WorkspaceID, &p.Workspace, &p.UnixUser, &p.DeployToken, &p.ProvisionState, &p.ProvisionError, &p.CreatedAt)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return Project{}, errors.New("project not found")
@ -136,6 +146,22 @@ func (a *App) updateProvisioningState(ctx context.Context, projectID int64, stat
return err
}
func (a *App) reprovisionProject(ctx context.Context, userID string, projectID int64) (Project, error) {
p, err := a.getProject(ctx, userID, projectID)
if err != nil {
return Project{}, err
}
if err := a.updateProvisioningState(ctx, p.ID, "pending", ""); err != nil {
return Project{}, err
}
p.ProvisionState = "pending"
p.ProvisionError = ""
if err := a.provisionProject(ctx, &p); err != nil {
return p, err
}
return p, nil
}
func (a *App) listEnvVars(ctx context.Context, userID string, projectID int64) ([]EnvVar, error) {
if _, err := a.getProject(ctx, userID, projectID); err != nil {
return nil, err