Compare commits
4 Commits
37dfa6edc4
...
a9c7fddfaf
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c7fddfaf | |||
| 7f418927e7 | |||
| 90328001e5 | |||
| c4a1f5575c |
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+167
-106
@@ -2207,40 +2207,53 @@ 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
|
||||
}
|
||||
running, err = api.store(r).HasRunningRPMMirrorTask(repos[i].ID)
|
||||
if err != nil {
|
||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||
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 running {
|
||||
WriteJSON(w, http.StatusConflict, map[string]string{"error": "cannot delete project while rpm mirror sync is running", "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())
|
||||
return
|
||||
}
|
||||
if running {
|
||||
WriteJSON(w, http.StatusConflict, map[string]string{"error": "cannot delete project while rpm mirror sync is running", "repo_id": repos[i].ID, "repo_name": repos[i].Name})
|
||||
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,22 +2283,29 @@ 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
|
||||
}
|
||||
running, err = txStore.HasRunningRPMMirrorTask(currentRepos[i].ID)
|
||||
if err != nil {
|
||||
if currentRepos[i].Path == "" {
|
||||
_ = txStore.Rollback()
|
||||
restoreFromTrash(tempPaths, sourcePaths)
|
||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||
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 running {
|
||||
_ = txStore.Rollback()
|
||||
restoreFromTrash(tempPaths, sourcePaths)
|
||||
WriteJSON(w, http.StatusConflict, map[string]string{"error": "cannot delete project while rpm mirror sync is running", "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()
|
||||
restoreFromTrash(tempPaths, sourcePaths)
|
||||
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if running {
|
||||
_ = txStore.Rollback()
|
||||
restoreFromTrash(tempPaths, sourcePaths)
|
||||
WriteJSON(w, http.StatusConflict, map[string]string{"error": "cannot delete project while rpm mirror sync is running", "repo_id": currentRepos[i].ID, "repo_name": currentRepos[i].Name})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = txStore.DeleteProject(params["id"])
|
||||
@@ -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)
|
||||
}
|
||||
_ = 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)
|
||||
}
|
||||
_ = 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)
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(temp)
|
||||
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user