Compare commits

...

4 Commits

7 changed files with 309 additions and 169 deletions
+8 -1
View File
@@ -1510,7 +1510,14 @@ func (s *Store) ListProjectIDsForUser(userID string) ([]string, error) {
func (s *Store) UpdateRepo(repo models.Repo) error {
var err error
_, err = s.Exec(`UPDATE repos SET name = ?, description = ?, path = ? WHERE public_id = ?`, repo.Name, repo.Description, repo.Path, repo.ID)
var result sql.Result
var rowsAffected int64
result, err = s.Exec(`UPDATE repos SET name = ?, description = ?, path = ? WHERE public_id = ?`, repo.Name, repo.Description, repo.Path, repo.ID)
if err != nil { return err }
rowsAffected, err = result.RowsAffected()
if err != nil { return err }
if rowsAffected <= 0 { return sql.ErrNoRows }
return err
}
+114 -46
View File
@@ -363,45 +363,44 @@ func (api *API) createACMEOrder(ctx context.Context, profileID string, commonNam
var certKeyPEM string
var csrPEM string
var err error
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order create requested profile=%s cn=%s san_count=%d", strings.TrimSpace(profileID), strings.TrimSpace(commonName), len(sanDNS))
if strings.TrimSpace(profileID) == "" {
return order, errors.New("profile_id is required")
}
if strings.TrimSpace(commonName) == "" {
return order, errors.New("common_name is required")
}
if strings.TrimSpace(profileID) == "" { return order, errors.New("profile_id is required") }
if strings.TrimSpace(commonName) == "" { return order, errors.New("common_name is required") }
profile, err = api.storeContext(ctx).GetACMEProfile(strings.TrimSpace(profileID))
if err != nil {
return order, err
}
if !profile.Enabled {
return order, errors.New("ACME profile is disabled")
}
if err != nil { return order, err }
if !profile.Enabled { return order, errors.New("ACME profile is disabled") }
domains = normalizeACMEDomains(commonName, sanDNS)
if len(domains) == 0 {
return order, errors.New("at least one domain is required")
}
if len(domains) == 0 { return order, errors.New("at least one domain is required") }
for i = 0; i < len(domains); i++ {
domainIDs = append(domainIDs, acme.AuthzID{Type: "dns", Value: domains[i]})
}
certKeyPEM, csrPEM, _, err = generateACMECSR(domains[0], domains)
if err != nil {
return order, err
}
if err != nil { return order, err }
// attemp to ensure the acme profiel h as a usable acme account registered with the ca
// it makes http/https calls to the acme server(profile.DirectoryURL). so it must not
// be held in a transaction.
client, accountKey, profile, err = api.ensureACMEAccount(ctx, profile)
if err != nil {
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order create failed profile=%s step=account err=%v", profile.ID, err)
_ = api.storeContext(ctx).UpdateACMEProfileLastError(profile.ID, err.Error())
_ = api.persistACMEProfileLastError(profile.ID, err.Error())
return order, err
}
_ = accountKey
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order create account ready profile=%s account=%s", profile.ID, profile.AccountURL)
rawOrder, err = client.AuthorizeOrder(ctx, domainIDs)
if err != nil {
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order create failed profile=%s step=authorize_order err=%v", profile.ID, err)
_ = api.storeContext(ctx).UpdateACMEProfileLastError(profile.ID, err.Error())
_ = api.persistACMEProfileLastError(profile.ID, err.Error())
return order, err
}
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order created upstream profile=%s order_url=%s authz_count=%d", profile.ID, rawOrder.URI, len(rawOrder.AuthzURLs))
for i = 0; i < len(rawOrder.AuthzURLs); i++ {
authz, err = client.GetAuthorization(ctx, rawOrder.AuthzURLs[i])
@@ -415,13 +414,11 @@ func (api *API) createACMEOrder(ctx context.Context, profileID string, commonNam
break
}
}
if ch == nil {
return order, errors.New("dns-01 challenge not offered by ACME CA")
}
if ch == nil { return order, errors.New("dns-01 challenge not offered by ACME CA") }
txt, err = client.DNS01ChallengeRecord(ch.Token)
if err != nil {
return order, err
}
if err != nil { return order, err }
challenges = append(challenges, models.ACMEDNSChallenge{
AuthorizationURL: rawOrder.AuthzURLs[i],
Identifier: authz.Identifier.Value,
@@ -446,13 +443,12 @@ func (api *API) createACMEOrder(ctx context.Context, profileID string, commonNam
CSRPEM: csrPEM,
KeyPEM: certKeyPEM,
}
order, err = api.storeContext(ctx).CreateACMEOrder(order)
order, err = api.persistACMEOrderCreate(order, profile.ID)
if err != nil {
api.Logger.Write(acmeLogID, codit_logger.LOG_WARN, "order create persist failed profile=%s order_url=%s err=%v", profile.ID, rawOrder.URI, err)
return order, err
}
api.Logger.Write(acmeLogID, codit_logger.LOG_INFO, "order create success id=%s profile=%s cn=%s challenge_count=%d", order.ID, order.ProfileID, order.CommonName, len(order.Challenges))
_ = api.storeContext(ctx).UpdateACMEProfileLastError(profile.ID, "")
return order, nil
}
@@ -670,7 +666,7 @@ func (api *API) persistACMEFinalizeFailure(order models.ACMEOrder, profileID str
var store *db.Store
var err error
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
store, err = api.Store.BeginImmediateStore(ctx)
@@ -707,7 +703,7 @@ func (api *API) persistACMEFinalizeSuccess(order models.ACMEOrder, profileID str
var existing models.PKICert
var err error
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
store, err = api.Store.BeginImmediateStore(ctx)
@@ -752,6 +748,83 @@ func (api *API) persistACMEFinalizeSuccess(order models.ACMEOrder, profileID str
return order, cert, nil
}
func (api *API) persistACMEProfileAccount(profile models.ACMEProfile) error {
var ctx context.Context
var cancel context.CancelFunc
var store *db.Store
var err error
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
store, err = api.Store.BeginImmediateStore(ctx)
if err != nil { return err }
err = store.UpdateACMEProfileAccount(profile.ID, profile.AccountURL, profile.AccountKeyPEM)
if err != nil {
_ = store.Rollback()
return err
}
err = store.Commit()
if err != nil {
_ = store.Rollback()
return err
}
return nil
}
func (api *API) persistACMEProfileLastError(profileID string, msg string) error {
var ctx context.Context
var cancel context.CancelFunc
var store *db.Store
var err error
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
store, err = api.Store.BeginImmediateStore(ctx)
if err != nil { return err }
err = store.UpdateACMEProfileLastError(profileID, msg)
if err != nil {
_ = store.Rollback()
return err
}
err = store.Commit()
if err != nil {
_ = store.Rollback()
return err
}
return nil
}
func (api *API) persistACMEOrderCreate(order models.ACMEOrder, profileID string) (models.ACMEOrder, error) {
var ctx context.Context
var cancel context.CancelFunc
var store *db.Store
var err error
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
store, err = api.Store.BeginImmediateStore(ctx)
if err != nil { return order, err }
order, err = store.CreateACMEOrder(order)
if err != nil {
_ = store.Rollback()
return order, err
}
err = store.UpdateACMEProfileLastError(profileID, "")
if err != nil {
_ = store.Rollback()
return order, err
}
err = store.Commit()
if err != nil {
_ = store.Rollback()
return order, err
}
return order, nil
}
func (api *API) DeleteACMEOrder(w http.ResponseWriter, r *http.Request, params map[string]string) {
var err error
if !api.requireAdmin(w, r) {
@@ -774,17 +847,15 @@ func (api *API) ensureACMEAccount(ctx context.Context, profile models.ACMEProfil
var contact []string
var keyPEM string
var err error
if strings.TrimSpace(profile.AccountKeyPEM) == "" {
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, profile, err
}
if err != nil { return nil, nil, profile, err }
} else {
key, err = parseECPrivateKeyPEM(profile.AccountKeyPEM)
if err != nil {
return nil, nil, profile, err
}
if err != nil { return nil, nil, profile, err }
}
client = &acme.Client{Key: key, DirectoryURL: strings.TrimSpace(profile.DirectoryURL), UserAgent: api.serverId()+"-acme"}
if strings.TrimSpace(profile.AccountURL) != "" {
client.KID = acme.KeyID(strings.TrimSpace(profile.AccountURL))
@@ -805,18 +876,15 @@ func (api *API) ensureACMEAccount(ctx context.Context, profile models.ACMEProfil
return nil, nil, profile, err
}
}
if account != nil {
profile.AccountURL = account.URI
}
if account != nil { profile.AccountURL = account.URI }
keyPEM, err = encodeECPrivateKeyPEM(key)
if err != nil {
return nil, nil, profile, err
}
if err != nil { return nil, nil, profile, err }
profile.AccountKeyPEM = keyPEM
err = api.storeContext(ctx).UpdateACMEProfileAccount(profile.ID, profile.AccountURL, profile.AccountKeyPEM)
if err != nil {
return nil, nil, profile, err
}
err = api.persistACMEProfileAccount(profile)
if err != nil { return nil, nil, profile, err }
client.KID = acme.KeyID(strings.TrimSpace(profile.AccountURL))
return client, key, profile, nil
}
+148 -87
View File
@@ -2207,24 +2207,37 @@ func (api *API) DeleteProject(w http.ResponseWriter, r *http.Request, params map
var err error
var repos []models.Repo
var currentRepos []models.Repo
var running bool
var i int
var tempPaths []string
var sourcePaths []string
var temp string
var txStore *db.Store
if !api.requireProjectRole(w, r, params["id"], models.RoleAdmin) {
return
}
if !api.requireProjectRole(w, r, params["id"], models.RoleAdmin) { return }
repos, err = api.store(r).ListReposOwned(params["id"])
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
// Prevent deletion if a repo row is still in the provisioning window.
// This can happen due to race condition with CreateRepo.
// CreateRepo has two transactions:
// 1. Short DB transaction creates repo row with path="".
// 2. Transaction commits.
// 3. Handler initializes filesystem/git storage.
// 4. Second short DB transaction updates the repo row with the final path.
// If DeleteProject comes while 3 is ongoing, there is a risk.
for i = 0; i < len(repos); i++ {
if repos[i].Type != models.RepoTypeRPM {
continue
if repos[i].Path == "" {
WriteJSON(w, http.StatusConflict, map[string]string{"error": "repository storage is not ready", "repo_id": repos[i].ID, "repo_name": repos[i].Name})
return
}
if repos[i].Type == models.RepoTypeRPM {
var running bool
running, err = api.store(r).HasRunningRPMMirrorTask(repos[i].ID)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
@@ -2235,12 +2248,12 @@ func (api *API) DeleteProject(w http.ResponseWriter, r *http.Request, params map
return
}
}
}
tempPaths = make([]string, 0, len(repos))
sourcePaths = make([]string, 0, len(repos))
for i = 0; i < len(repos); i++ {
if repos[i].Path == "" {
continue
}
if repos[i].Path == "" { continue }
temp, err = moveToTrash(repos[i].Path)
if err != nil {
restoreFromTrash(tempPaths, sourcePaths)
@@ -2250,6 +2263,7 @@ func (api *API) DeleteProject(w http.ResponseWriter, r *http.Request, params map
tempPaths = append(tempPaths, temp)
sourcePaths = append(sourcePaths, repos[i].Path)
}
txStore, err = api.Store.BeginImmediateStore(r.Context())
if err != nil {
restoreFromTrash(tempPaths, sourcePaths)
@@ -2269,10 +2283,16 @@ func (api *API) DeleteProject(w http.ResponseWriter, r *http.Request, params map
WriteJSONWithErrorReason(w, r, http.StatusConflict, "project repositories changed while deletion was in progress")
return
}
for i = 0; i < len(currentRepos); i++ {
if currentRepos[i].Type != models.RepoTypeRPM {
continue
if currentRepos[i].Path == "" {
_ = txStore.Rollback()
restoreFromTrash(tempPaths, sourcePaths)
WriteJSON(w, http.StatusConflict, map[string]string{"error": "repository storage is not ready", "repo_id": currentRepos[i].ID, "repo_name": currentRepos[i].Name})
return
}
if currentRepos[i].Type == models.RepoTypeRPM {
var running bool
running, err = txStore.HasRunningRPMMirrorTask(currentRepos[i].ID)
if err != nil {
_ = txStore.Rollback()
@@ -2287,6 +2307,7 @@ func (api *API) DeleteProject(w http.ResponseWriter, r *http.Request, params map
return
}
}
}
err = txStore.DeleteProject(params["id"])
if err != nil {
_ = txStore.Rollback()
@@ -2673,7 +2694,6 @@ func (api *API) GetRepo(w http.ResponseWriter, r *http.Request, params map[strin
func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[string]string) {
var req createRepoRequest
var err error
var user models.User
var repoID string
var project models.Project
@@ -2683,13 +2703,16 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
var repoType string
var projectStorageID int64
var repoStorageID int64
var ok bool
var exists bool
var txStore *db.Store
var cloneURL string
var rpmURL string
var dockerURL string
var ok bool
var err error
if !api.requireProjectRole(w, r, params["projectId"], models.RoleWriter) { return }
if !api.requireProjectRole(w, r, params["projectId"], models.RoleWriter) {
return
}
err = DecodeJSON(r, &req)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid json")
@@ -2708,12 +2731,14 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "unsupported repo type")
return
}
user, _ = middleware.UserFromContext(r.Context())
repoID, err = util.NewID()
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, "failed to create repo")
return
}
repo = models.Repo{
ID: repoID,
ProjectID: params["projectId"],
@@ -2723,6 +2748,7 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
Path: "",
CreatedBy: user.ID,
}
txStore, err = api.Store.BeginImmediateStore(r.Context())
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
@@ -2739,30 +2765,38 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
WriteJSONWithErrorReason(w, r, http.StatusConflict, "repository name already exists")
return
}
project, err = txStore.GetProject(params["projectId"])
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "project not found")
return
}
created, err = txStore.CreateRepo(repo)
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return
}
projectStorageID, repoStorageID, err = txStore.GetRepoStorageIDs(created.ID)
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
err = txStore.Commit()
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
// we need to settle the file system parts. this is outside the transaction
// upon failure, deleting the row created above is important.
// api.deleteRepoRecord() has its own write transaction.
repoPath = api.repoStoragePathByType(created.Type, projectStorageID, repoStorageID)
if created.Type == models.RepoTypeGit {
err = os.MkdirAll(filepath.Dir(repoPath), 0o755)
@@ -2773,23 +2807,25 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
err = os.MkdirAll(repoPath, 0o755)
}
if err != nil {
_ = api.deleteRepoRecord(r.Context(), created.ID)
_ = api.deleteRepoRecord(created.ID)
_ = os.RemoveAll(repoPath)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
created.Path = repoPath
txStore, err = api.Store.BeginImmediateStore(r.Context())
if err != nil {
_ = api.deleteRepoRecord(r.Context(), created.ID)
_ = api.deleteRepoRecord(created.ID)
_ = os.RemoveAll(repoPath)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
err = txStore.UpdateRepo(created)
if err != nil {
_ = txStore.Rollback()
_ = api.deleteRepoRecord(r.Context(), created.ID)
_ = api.deleteRepoRecord(created.ID)
_ = os.RemoveAll(repoPath)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
@@ -2797,23 +2833,16 @@ func (api *API) CreateRepo(w http.ResponseWriter, r *http.Request, params map[st
err = txStore.Commit()
if err != nil {
_ = txStore.Rollback()
_ = api.deleteRepoRecord(r.Context(), created.ID)
_ = api.deleteRepoRecord(created.ID)
_ = os.RemoveAll(repoPath)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
var cloneURL string
var rpmURL string
var dockerURL string
if created.Type == models.RepoTypeGit {
cloneURL = api.cloneURL(project.Slug, created.Name)
}
if created.Type == models.RepoTypeRPM {
rpmURL = api.rpmURL(project.Slug, created.Name)
}
if created.Type == models.RepoTypeDocker {
dockerURL = api.dockerURL(project.Slug, created.Name)
}
if created.Type == models.RepoTypeGit { cloneURL = api.cloneURL(project.Slug, created.Name) }
if created.Type == models.RepoTypeRPM { rpmURL = api.rpmURL(project.Slug, created.Name) }
if created.Type == models.RepoTypeDocker { dockerURL = api.dockerURL(project.Slug, created.Name) }
WriteJSON(w, http.StatusCreated, repoResponse{Repo: created, CloneURL: cloneURL, RPMURL: rpmURL, DockerURL: dockerURL})
}
@@ -3004,6 +3033,10 @@ func (api *API) UpdateRepo(w http.ResponseWriter, r *http.Request, params map[st
repo.Path = newPath
err = api.store(r).UpdateRepo(repo)
if err != nil {
if err == sql.ErrNoRows {
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "repo not found")
return
}
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
@@ -3024,109 +3057,137 @@ func (api *API) UpdateRepo(w http.ResponseWriter, r *http.Request, params map[st
func (api *API) DeleteRepo(w http.ResponseWriter, r *http.Request, params map[string]string) {
var repo models.Repo
var current models.Repo
var project models.Project
var running bool
var err error
var temp string
var txStore *db.Store
var cloneURL string
var rpmURL string
var dockerURL string
var err error
repo, err = api.getRepoResolved(api.store(r), params["id"])
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "repo not found")
return
}
if !api.requireProjectRole(w, r, repo.ProjectID, models.RoleWriter) {
return
}
if repo.Type == models.RepoTypeRPM {
running, err = api.store(r).HasRunningRPMMirrorTask(repo.ID)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
if running {
WriteJSONWithErrorReason(w, r, http.StatusConflict, "cannot delete repository while rpm mirror sync is running")
return
}
}
project, err = api.store(r).GetProject(repo.ProjectID)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "project not found")
return
}
if repo.Path != "" {
temp, err = moveToTrash(repo.Path)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
}
if !api.requireProjectRole(w, r, repo.ProjectID, models.RoleWriter) { return }
txStore, err = api.Store.BeginImmediateStore(r.Context())
if err != nil {
if temp != "" && repo.Path != "" {
_ = os.Rename(temp, repo.Path)
}
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
// re-read to avoid race condition with CreateRepo which has uses two transactions
repo, err = txStore.GetRepo(params["id"])
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "repo not found")
return
}
if repo.Path == "" {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusConflict, "repository storage is not ready")
return
}
if repo.Type == models.RepoTypeRPM {
running, err = txStore.HasRunningRPMMirrorTask(repo.ID)
if err != nil {
_ = txStore.Rollback()
if temp != "" && repo.Path != "" {
_ = os.Rename(temp, repo.Path)
}
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
if running {
_ = txStore.Rollback()
if temp != "" && repo.Path != "" {
_ = os.Rename(temp, repo.Path)
}
WriteJSONWithErrorReason(w, r, http.StatusConflict, "cannot delete repository while rpm mirror sync is running")
return
}
}
project, err = txStore.GetProject(repo.ProjectID)
if err != nil {
_ = txStore.Rollback()
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "project not found")
return
}
_ = txStore.Rollback()
temp, err = moveToTrash(repo.Path)
if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
txStore, err = api.Store.BeginImmediateStore(r.Context())
if err != nil {
_ = os.Rename(temp, repo.Path)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
current, err = txStore.GetRepo(params["id"])
if err != nil {
_ = txStore.Rollback()
_ = os.Rename(temp, repo.Path)
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "repo not found")
return
}
if current.ProjectID != repo.ProjectID || current.Path != repo.Path {
_ = txStore.Rollback()
_ = os.Rename(temp, repo.Path)
WriteJSONWithErrorReason(w, r, http.StatusConflict, "repository changed while deletion was in progress")
return
}
if repo.Type == models.RepoTypeRPM {
running, err = txStore.HasRunningRPMMirrorTask(repo.ID)
if err != nil {
_ = txStore.Rollback()
_ = os.Rename(temp, repo.Path)
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
if running {
_ = txStore.Rollback()
_ = os.Rename(temp, repo.Path)
WriteJSONWithErrorReason(w, r, http.StatusConflict, "cannot delete repository while rpm mirror sync is running")
return
}
}
err = txStore.DeleteRepo(repo.ID)
if err != nil {
_ = txStore.Rollback()
if temp != "" && repo.Path != "" {
_ = os.Rename(temp, repo.Path)
}
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
err = txStore.Commit()
if err != nil {
_ = txStore.Rollback()
if temp != "" && repo.Path != "" {
_ = os.Rename(temp, repo.Path)
}
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
return
}
if temp != "" {
_ = os.RemoveAll(temp)
}
var cloneURL string
var rpmURL string
var dockerURL string
if repo.Type == models.RepoTypeGit {
cloneURL = api.cloneURL(project.Slug, repo.Name)
}
if repo.Type == models.RepoTypeRPM {
rpmURL = api.rpmURL(project.Slug, repo.Name)
}
if repo.Type == models.RepoTypeDocker {
dockerURL = api.dockerURL(project.Slug, repo.Name)
}
if repo.Type == models.RepoTypeGit { cloneURL = api.cloneURL(project.Slug, repo.Name) }
if repo.Type == models.RepoTypeRPM { rpmURL = api.rpmURL(project.Slug, repo.Name) }
if repo.Type == models.RepoTypeDocker { dockerURL = api.dockerURL(project.Slug, repo.Name) }
WriteJSON(w, http.StatusOK, repoResponse{Repo: repo, CloneURL: cloneURL, RPMURL: rpmURL, DockerURL: dockerURL})
}
func (api *API) deleteRepoRecord(ctx context.Context, repoID string) error {
func (api *API) deleteRepoRecord(repoID string) error {
var ctx context.Context
var cancel context.CancelFunc
var txStore *db.Store
var err error
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
txStore, err = api.Store.BeginImmediateStore(ctx)
if err != nil {
return err
@@ -167,7 +167,7 @@ func (api *API) finishSSHFileTransferAudit(audit *sshFileTransferAudit, status s
audit.Finished = true
// Use an independent bounded context so client cancellation does not skip the final audit update.
ctx, cancel = context.WithTimeout(context.Background(), 10 * time.Second)
ctx, cancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
defer cancel()
err = audit.Store.FinishSSHFileTransfer(ctx, audit.Item.ID, status, audit.Paths, audit.Bytes, audit.ErrorText)
+5 -3
View File
@@ -24,6 +24,8 @@ import "codit/internal/models"
const logIDSSHBroker string = "ssh-broker"
const sshWebsocketWriteTimeout time.Duration = 5 * time.Second
const sshHostKeyDiscoveryTimeout time.Duration = 10 * time.Second
const sshSessionDialTimeout time.Duration = 15 * time.Second
const sshSessionInputBufferSize int = 64
const sshPrepareStageLoadProfile string = "load_profile"
const sshPrepareStageLoadServer string = "load_server"
@@ -2536,9 +2538,9 @@ func discoverSSHServerHostKey(host string, port int, defaultUser string) (models
discovered = key
return nil
},
Timeout: 10 * time.Second,
Timeout: sshHostKeyDiscoveryTimeout,
}
conn, err = net.DialTimeout("tcp", addr, 10*time.Second)
conn, err = net.DialTimeout("tcp", addr, sshHostKeyDiscoveryTimeout)
if err != nil { return item, err }
defer conn.Close()
@@ -3072,7 +3074,7 @@ func (api *API) runSSHSessionStream(ctx context.Context, store *db.Store, user *
Auth: authMethods,
HostKeyCallback: sshHostKeyCallback(hostKeys, &connectedFingerprint, &connectedHostKey, prepared.PinHostKeyOnFirstUse),
}
dialCtx, dialCancel = context.WithTimeout(ctx, 15*time.Second)
dialCtx, dialCancel = context.WithTimeout(ctx, sshSessionDialTimeout)
runtimeSession.SetDialCancel(dialCancel)
defer dialCancel()
dialConn, err = (&net.Dialer{}).DialContext(dialCtx, "tcp", dialAddr)
+2 -2
View File
@@ -200,7 +200,7 @@ func (m *MirrorManager) runDue(ctx context.Context) {
for i = 0; i < len(tasks); i++ {
// use an independent bounded context so task cancellation does not skip the state transition.
writeCtx, writeCancel = context.WithTimeout(context.Background(), 10 * time.Second)
writeCtx, writeCancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout)
runID, started, err = m.store.TryStartRPMMirrorRun(writeCtx, tasks[i].RepoID, tasks[i].MirrorPath, now)
writeCancel()
if err != nil {
@@ -358,7 +358,7 @@ func (m *MirrorManager) finishTaskRun(task models.RPMMirrorTask, runID string, s
finishedAt = time.Now().UTC().Unix()
// use a child of context.Background() to write the status regardless of task cancellation
writeCtx, writeCancel = context.WithTimeout(context.Background(), 10 * time.Second); defer writeCancel()
writeCtx, writeCancel = context.WithTimeout(context.Background(), db.IndependentDBOperationTimeout); defer writeCancel()
err = m.store.FinishRPMMirrorTaskRun(writeCtx, task.RepoID, task.MirrorPath, runID, success, finishedAt, step, total, done, failed, deleted, revision, errMsg)
if err != nil && m.logger != nil {
m.logger.Write(logIDRPMMirror, codit_logger.LOG_ERROR, "finish sync state failed repo=%s path=%s run=%s err=%v", task.RepoID, task.MirrorPath, runID, err)
+12 -10
View File
@@ -40,6 +40,8 @@ import "golang.org/x/text/transform"
import _ "modernc.org/sqlite"
const logIDAPI string = "api"
const serverShutdownTimeout time.Duration = 10 * time.Second
const httpServerShutdownTimeout time.Duration = 5 * time.Second
type server_http_log_writer struct {
l Logger
@@ -653,7 +655,7 @@ func (app *Server) ServeContext(ctx context.Context) error {
err = extraListenerManager.Start()
if err != nil {
app.logger.Write("", LOG_ERROR, "additional listener manager error - %v", err)
shutdownCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), serverShutdownTimeout)
extraListenerManager.Stop(shutdownCtx)
app.rpm_mirror.Stop()
app.rpm_mirror.Wait()
@@ -664,7 +666,7 @@ func (app *Server) ServeContext(ctx context.Context) error {
mainEndpoints, err = buildListenerEndpoints("main", "main", "", util.TLSSettingsFromConfig(*app.cfg), app.store)
if err != nil {
app.logger.Write("", LOG_ERROR, "main listener config error - %v", err)
shutdownCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), serverShutdownTimeout)
extraListenerManager.Stop(shutdownCtx)
app.rpm_mirror.Stop()
app.rpm_mirror.Wait()
@@ -674,7 +676,7 @@ func (app *Server) ServeContext(ctx context.Context) error {
if len(mainEndpoints) == 0 && extraListenerManager.TotalEndpointCount() == 0 {
err = errors.New("at least one main or additional listener is required")
app.logger.Write("", LOG_ERROR, "listener config error - %v", err)
shutdownCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), serverShutdownTimeout)
extraListenerManager.Stop(shutdownCtx)
app.rpm_mirror.Stop()
app.rpm_mirror.Wait()
@@ -683,7 +685,7 @@ func (app *Server) ServeContext(ctx context.Context) error {
}
if len(mainEndpoints) == 0 {
<-ctx.Done()
shutdownCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), serverShutdownTimeout)
extraListenerManager.Stop(shutdownCtx)
if servicesStarted && app.rpm_mirror != nil {
app.rpm_mirror.Stop()
@@ -693,7 +695,7 @@ func (app *Server) ServeContext(ctx context.Context) error {
return nil
}
err = serveListeners(ctx, mainEndpoints, mux, app.logger, app.serverId)
shutdownCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), serverShutdownTimeout)
if extraListenerManager != nil {
extraListenerManager.Stop(shutdownCtx)
}
@@ -826,7 +828,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
router.Handle("GET", "/api/admin/pki/certs", txRead(api.ListPKICerts))
router.Handle("GET", "/api/admin/pki/certs/:id", txRead(api.GetPKICert))
router.Handle("GET", "/api/admin/pki/certs/:id/inspect", txRead(api.GetPKICertInspect))
router.Handle("POST", "/api/admin/pki/certs/:id/renew-acme", txWrite(api.RenewPKICertWithACME))
router.Handle("POST", "/api/admin/pki/certs/:id/renew-acme", api.RenewPKICertWithACME)
router.Handle("GET", "/api/admin/pki/certs/:id/bundle", api.DownloadPKICertBundle)
router.Handle("POST", "/api/admin/pki/certs", txWrite(api.IssuePKICert))
router.Handle("POST", "/api/admin/pki/certs/import", txWrite(api.ImportPKICert))
@@ -841,7 +843,7 @@ func (app *Server) initHandlers() (*handlers.API, *http.ServeMux, error) {
router.Handle("PATCH", "/api/admin/pki/acme/profiles/:id", txWrite(api.UpdateACMEProfile))
router.Handle("DELETE", "/api/admin/pki/acme/profiles/:id", txWrite(api.DeleteACMEProfile))
router.Handle("GET", "/api/admin/pki/acme/orders", txRead(api.ListACMEOrders))
router.Handle("POST", "/api/admin/pki/acme/orders", txWrite(api.CreateACMEOrder))
router.Handle("POST", "/api/admin/pki/acme/orders", api.CreateACMEOrder)
router.Handle("POST", "/api/admin/pki/acme/orders/:id/finalize", api.FinalizeACMEOrder)
router.Handle("DELETE", "/api/admin/pki/acme/orders/:id", txWrite(api.DeleteACMEOrder))
router.Handle("GET", "/api/admin/rpm/mirrors", txRead(api.ListAdminRPMMirrors))
@@ -1253,7 +1255,7 @@ func (m *additionalListenerManager) Stop(ctx context.Context) {
m.mu.Lock()
for key, running = range m.Running {
shutdownCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), httpServerShutdownTimeout)
_ = running.Server.Shutdown(shutdownCtx)
cancel()
delete(m.Running, key)
@@ -1340,7 +1342,7 @@ func (m *additionalListenerManager) reconcile() error {
if exists {
continue
} // found in configuration
shutdownCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
shutdownCtx, cancel = context.WithTimeout(context.Background(), httpServerShutdownTimeout)
_ = running.Server.Shutdown(shutdownCtx)
cancel()
delete(m.Running, key)
@@ -2136,7 +2138,7 @@ func serveListeners(ctx context.Context, endpoints []listenerEndpoint, handler h
copied = append(copied, servers...)
serversMu.Unlock()
for _, server = range copied {
shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), 5*time.Second)
shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), httpServerShutdownTimeout)
_ = server.Shutdown(shutdownCtx)
shutdownCancel()
}