Compare commits

...

10 Commits

Author SHA256 Message Date
4da7a62404 staging: Support multiple Labels per QA project
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 13s
Integration tests / t (pull_request) Successful in 9m4s
This is required to handle base projects for binary snapshotting
2026-03-09 11:47:49 +01:00
1f6ec2a1c3 Use our IBS branch for packaging
Need to use it from internal resource policy wise, but will be mirrored outside.
2026-03-09 11:42:24 +01:00
e43833c2ed staging: Hardcode currently used user of IBS
We should consolidate to a nicer user name...
2026-03-09 11:42:24 +01:00
98d701bfe8 staging: Fix service file to work with obs ssh connections
We need a fixed user for that
2026-03-09 11:42:24 +01:00
1c63899e49 Add an "lfs fsck" check after submodule update
to avoid merging pull requests where lfs objects are not correctly
registered. Can happen when user has not installed lfs for example.
2026-03-09 11:42:24 +01:00
2c891ded6a Disable temporary comment adding in case of lacking permissions 2026-03-09 11:42:23 +01:00
ef61736d70 pr: only update PR if elided title not changed
Gitea trims long titles so we need to compare if the trimmed length
is same, not entire string that will always differ.
2026-03-09 11:40:29 +01:00
57a4860d67 workflow-direct: use relative path when adding a submodule
This solves the issue of using the right credentials based on the
main repo.
Also it allows to rename the organisation.
2026-03-09 11:40:28 +01:00
94d83ccf80 Fix build status check when package source is in subdir
We must not use the upper directory name as part of the package name.
2026-03-09 11:40:28 +01:00
Andrii Nikitin
5440476d10 staging: Add config for poll interval
Some checks failed
go-generate-check / go-generate-check (pull_request) Successful in 13s
Integration tests / t (pull_request) Has been cancelled
go-generate-check / go-generate-check (push) Successful in 22s
Integration tests / t (push) Has been cancelled
Needed for testing
2026-03-09 09:29:48 +01:00
6 changed files with 77 additions and 12 deletions

View File

@@ -22,6 +22,8 @@ Release: 0
Summary: GitWorkflow utilities
License: GPL-2.0-or-later
URL: https://src.opensuse.org/adamm/autogits
#!RemoteAsset: git+https://src.suse.de/adrianSuSE/autogits#ibs_state
Source0: %name-%version.tar.xz
BuildRequires: git
BuildRequires: systemd-rpm-macros
BuildRequires: go

View File

@@ -59,6 +59,7 @@ type QAConfig struct {
Name string
Origin string
Label string // requires this gitea lable to be set or skipped
Labels []string // requires any of the lables to be set
BuildDisableRepos []string // which repos to build disable in the new project
}
@@ -342,6 +343,13 @@ func ParseStagingConfig(data []byte) (*StagingConfig, error) {
if err := json.Unmarshal(data, &staging); err != nil {
return nil, err
}
// backward compability, transfer legacy Label to new Labels array
for _, setup := range staging.QA {
if len(setup.Labels) == 0 && len(setup.Label) > 0 {
setup.Labels = []string{setup.Label}
setup.Label = ""
}
}
return &staging, nil
}

View File

@@ -27,6 +27,7 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"runtime/debug"
"slices"
@@ -733,8 +734,16 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
var qa_projects []string
for _, setup := range stagingConfig.QA {
QAproject := stagingProject + ":" + setup.Name
if len(setup.Label) > 0 {
if _, ok := prLabelNames[setup.Label]; !ok {
if len(setup.Labels) > 0 {
matchedLabel := false
for labelName := range prLabelNames {
if slices.Contains(setup.Labels, labelName) {
matchedLabel = true
break
}
}
if matchedLabel == false {
if !IsDryRun {
// blindly remove, will fail when not existing
obs.DeleteProject(QAproject)
@@ -910,10 +919,13 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
if !stagingConfig.RebuildAll {
for pkg, headOid := range headSubmodules {
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
if pkg != "rpms" && pkg != "dependencies" {
_, spkg := filepath.Split(pkg)
if exists {
modifiedPackages = append(modifiedPackages, pkg)
modifiedPackages = append(modifiedPackages, spkg)
} else {
newPackages = append(newPackages, pkg)
newPackages = append(newPackages, spkg)
}
}
common.LogDebug(pkg, ":", baseOid, "->", headOid)
}
@@ -1023,6 +1035,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
done := false
overallBuildStatus := ProcessBuildStatus(stagingResult)
commentSuffix := ""
common.LogDebug("Number of QA Projects: ", len(qaProjects))
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
seperator := " in "
for _, qaProject := range qaProjects {
@@ -1171,6 +1184,7 @@ var IsDryRun bool
var ProcessPROnly string
var ObsClient common.ObsClientInterface
var BotUser string
var PollInterval = 5 * time.Minute
func ObsWebHostFromApiHost(apihost string) string {
u, err := url.Parse(apihost)
@@ -1193,9 +1207,18 @@ func main() {
flag.StringVar(&ObsApiHost, "obs", "", "API for OBS instance")
flag.StringVar(&ObsWebHost, "obs-web", "", "Web OBS instance, if not derived from the obs config")
flag.BoolVar(&IsDryRun, "dry", false, "Dry-run, don't actually create any build projects or review changes")
pollIntervalStr := flag.String("poll-interval", common.GetEnvOverrideString(os.Getenv("AUTOGITS_STAGING_BOT_POLL_INTERVAL"), ""), "Polling interval for notifications (e.g. 5m, 10s)")
debug := flag.Bool("debug", false, "Turns on debug logging")
flag.Parse()
if len(*pollIntervalStr) > 0 {
if d, err := time.ParseDuration(*pollIntervalStr); err == nil {
PollInterval = d
} else {
common.LogError("Invalid poll interval:", err)
}
}
if *debug {
common.SetLoggingLevel(common.LogLevelDebug)
} else {
@@ -1264,6 +1287,6 @@ func main() {
for {
PollWorkNotifications(ObsClient, gitea)
common.LogInfo("Poll cycle finished")
time.Sleep(5 * time.Minute)
time.Sleep(PollInterval)
}
}

View File

@@ -6,9 +6,13 @@ After=network-online.target
Type=exec
ExecStart=/usr/bin/obs-staging-bot
EnvironmentFile=-/etc/default/obs-staging-bot.env
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
User=autogits_obs_staging_bot
Group=users
# This may work when not using ssh api connections:
#DynamicUser=yes
#NoNewPrivileges=yes
#ProtectSystem=strict
[Install]
WantedBy=multi-user.target

View File

@@ -123,7 +123,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
return
}
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name))
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
@@ -215,7 +215,7 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
}
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name)
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
} else if !stat.IsDir() {
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
@@ -420,7 +420,7 @@ next_repo:
}
// add repository to git project
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + r.Name, r.Name))
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
if branch != curBranch {

View File

@@ -180,6 +180,33 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
}
updateSubmoduleInPR(submodulePath, prHead, git)
err := git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fetch")
common.LogError("lfs fetch err: ", err)
if err = git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fsck"); err != nil {
found_comment := false
timeline, terr := common.FetchTimelineSinceLastPush(Gitea, prHead, org, repo, idx)
if terr != nil {
common.LogError("lfs fsck error, but timeline fetch failed")
break
}
msgPrefix := "The LFS objects are broken!"
for _, t := range timeline {
if t.Type == common.TimelineCommentType_Comment && strings.HasPrefix(t.Body, msgPrefix) {
found_comment = true
common.LogError("lfs fsck Comment already found")
break
}
}
if !found_comment && !common.IsDryRun {
Gitea.AddComment(pr.PR, msgPrefix + " Please verify with 'git lfs fsck'")
}
common.LogError("lfs fsck failed with: ", err.Error())
return err
}
status, err := git.GitStatus(common.DefaultGitPrj)
common.LogDebug("status:", status)
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
@@ -314,6 +341,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
// well, wrong place...
// common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
// Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
return nil
}
}
@@ -384,7 +412,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
}
return CurrentTitle == NewTitle
}
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
if !pr.config.NoProjectGitPR && PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
RemoveDeadline: true,
Title: PrjGitTitle,