Skip to content
This repository was archived by the owner on Jun 27, 2025. It is now read-only.

Commit cff0cc0

Browse files
authored
feature: "tagless import" feature mode (#4)
In order to build from lookahead sources, srpmproc must support the new gitlab address for centos 9 stream as well as manipulating the sources to expected format. Resolves #2. - Options for tagless import and new stream lookaside format added - Git Mode now supports scanning for branch head in addition to the "normal" pulling of specific version tags - Alternate ProcessRPM added for Tagless mode in process.go (still a WIP) - Tagless mode converts a repo to the "traditional" format (SPECS/ + SOURCES/ + <pkg>.metadata ) - Tagless mode will build a dummy srpm to determine NVR information (rpmbuild + rpm shell commands) (will use this to tag imports in the target git) - Limitation: Tagless imports only pull the latest head from a branch - CentOS-Stream import branches are converted from stream-<MODULE_VERSION>-<RHEL_VERSION> to the more familiar r9s-stream-<VERSION> - stream-style YAML is detected and converted for modules, similar to the older modulemd.src.txt files - This new pattern is for "tagless mode" only, previous tagged imports (from git.centos.org) should not be affected
1 parent 9b44bc6 commit cff0cc0

File tree

6 files changed

+856
-54
lines changed

6 files changed

+856
-54
lines changed

cmd/srpmproc/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ var (
5656
basicPassword string
5757
packageVersion string
5858
packageRelease string
59+
taglessMode bool
60+
altLookAside bool
5961
)
6062

6163
var root = &cobra.Command{
@@ -92,7 +94,10 @@ func mn(_ *cobra.Command, _ []string) {
9294
HttpPassword: basicPassword,
9395
PackageVersion: packageVersion,
9496
PackageRelease: packageRelease,
97+
TaglessMode: taglessMode,
98+
AltLookAside: altLookAside,
9599
})
100+
96101
if err != nil {
97102
log.Fatal(err)
98103
}
@@ -142,6 +147,8 @@ func main() {
142147
root.Flags().StringVar(&basicPassword, "basic-password", "", "Basic auth password")
143148
root.Flags().StringVar(&packageVersion, "package-version", "", "Package version to fetch")
144149
root.Flags().StringVar(&packageRelease, "package-release", "", "Package release to fetch")
150+
root.Flags().BoolVar(&taglessMode, "taglessmode", false, "Tagless mode: If set, pull the latest commit from a branch, and determine version info from spec file (aka upstream versions aren't tagged)")
151+
root.Flags().BoolVar(&altLookAside, "altlookaside", false, "If set, uses the new CentOS Stream lookaside pattern (https://<SITE_PREFIX>/<RPM_NAME>/<FILE_NAME>/<SHA_VERSION>/<SHA_SUM>/<FILE_NAME>)")
145152

146153
if err := root.Execute(); err != nil {
147154
log.Fatal(err)

pkg/data/process.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@ type ProcessData struct {
5757
Log *log.Logger
5858
PackageVersion string
5959
PackageRelease string
60+
TaglessMode bool
61+
AltLookAside bool
6062
}

pkg/misc/regex.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/rocky-linux/srpmproc/pkg/data"
66
"path/filepath"
77
"regexp"
8+
"strings"
89
)
910

1011
func GetTagImportRegex(pd *data.ProcessData) *regexp.Regexp {
@@ -31,3 +32,22 @@ func GetTagImportRegex(pd *data.ProcessData) *regexp.Regexp {
3132

3233
return regexp.MustCompile(regex)
3334
}
35+
36+
// Given a git reference in tagless mode (like "refs/heads/c9s", or "refs/heads/stream-httpd-2.4-rhel-9.1.0"), determine
37+
// if we are ok with importing that reference. We are looking for the traditional <prefix><version><suffix> pattern, like "c9s", and also the
38+
// modular "stream-<NAME>-<VERSION>-rhel-<VERSION> branch pattern as well
39+
func TaglessRefOk(tag string, pd *data.ProcessData) bool {
40+
41+
// First case is very easy: if we are "refs/heads/<prefix><version><suffix>" , then this is def. a branch we should import
42+
if strings.HasPrefix(tag, fmt.Sprintf("refs/heads/%s%d%s", pd.ImportBranchPrefix, pd.Version, pd.BranchSuffix)) {
43+
return true
44+
}
45+
46+
// Less easy: if a modular branch is present (starts w/ "stream-"), we need to check if it's part of our major version, and return true if it is
47+
// (major version means we look for the text "rhel-X." in the branch name, like "rhel-9.1.0")
48+
if strings.HasPrefix(tag, "refs/heads/stream-") && strings.Contains(tag, fmt.Sprintf("rhel-%d.", pd.Version)) {
49+
return true
50+
}
51+
52+
return false
53+
}

pkg/modes/git.go

Lines changed: 116 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ func (g *GitMode) RetrieveSource(pd *data.ProcessData) (*data.ModeData, error) {
116116
if exists != nil && exists.when.After(tag.Tagger.When) {
117117
return nil
118118
}
119-
120119
latestTags[match[2]] = &remoteTarget{
121120
remote: refSpec,
122121
when: tag.Tagger.When,
@@ -126,11 +125,39 @@ func (g *GitMode) RetrieveSource(pd *data.ProcessData) (*data.ModeData, error) {
126125
return nil
127126
}
128127

128+
// In case of "tagless mode", we need to get the head ref of the branch instead
129+
// This is a kind of alternative implementation of the above tagAdd assignment
130+
refAdd := func(tag *object.Tag) error {
131+
if misc.TaglessRefOk(tag.Name, pd) {
132+
pd.Log.Printf("Tagless mode: Identified tagless commit for import: %s\n", tag.Name)
133+
refSpec := fmt.Sprintf(tag.Name)
134+
135+
// We split the string by "/", the branch name we're looking for to pass to latestTags is always last
136+
// (ex: "refs/heads/c9s" ---> we want latestTags[c9s]
137+
tmpRef := strings.Split(refSpec, "/")
138+
tmpBranchName := tmpRef[(len(tmpRef) - 1)]
139+
140+
latestTags[tmpBranchName] = &remoteTarget{
141+
remote: refSpec,
142+
when: tag.Tagger.When,
143+
}
144+
}
145+
return nil
146+
}
147+
129148
tagIter, err := repo.TagObjects()
149+
130150
if err != nil {
131151
return nil, fmt.Errorf("could not get tag objects: %v", err)
132152
}
133-
_ = tagIter.ForEach(tagAdd)
153+
154+
// tagless mode means we use "refAdd" (add commit by reference)
155+
// normal mode means we can rely on "tagAdd" (the tag should be present for us in the source repo)
156+
if pd.TaglessMode {
157+
_ = tagIter.ForEach(refAdd)
158+
} else {
159+
_ = tagIter.ForEach(tagAdd)
160+
}
134161

135162
listOpts := &git.ListOptions{
136163
Auth: pd.Authenticator,
@@ -157,17 +184,26 @@ func (g *GitMode) RetrieveSource(pd *data.ProcessData) (*data.ModeData, error) {
157184
if err != nil {
158185
continue
159186
}
160-
_ = tagAdd(&object.Tag{
161-
Name: strings.TrimPrefix(string(ref.Name()), "refs/tags/"),
162-
Tagger: commit.Committer,
163-
})
187+
188+
// Call refAdd instead of tagAdd in the case of TaglessMode enabled
189+
if pd.TaglessMode {
190+
_ = refAdd(&object.Tag{
191+
Name: string(ref.Name()),
192+
Tagger: commit.Committer,
193+
})
194+
} else {
195+
_ = tagAdd(&object.Tag{
196+
Name: strings.TrimPrefix(string(ref.Name()), "refs/tags/"),
197+
Tagger: commit.Committer,
198+
})
199+
}
200+
164201
}
165202

166203
for _, branch := range latestTags {
167204
pd.Log.Printf("tag: %s", strings.TrimPrefix(branch.remote, "refs/tags/"))
168205
branches = append(branches, *branch)
169206
}
170-
171207
sort.Sort(branches)
172208

173209
var sortedBranches []string
@@ -185,54 +221,66 @@ func (g *GitMode) RetrieveSource(pd *data.ProcessData) (*data.ModeData, error) {
185221
}
186222

187223
func (g *GitMode) WriteSource(pd *data.ProcessData, md *data.ModeData) error {
224+
188225
remote, err := md.Repo.Remote("upstream")
189-
if err != nil {
226+
227+
if err != nil && !pd.TaglessMode {
190228
return fmt.Errorf("could not get upstream remote: %v", err)
191229
}
192230

193231
var refspec config.RefSpec
194232
var branchName string
195233

196-
if strings.HasPrefix(md.TagBranch, "refs/heads") {
197-
refspec = config.RefSpec(fmt.Sprintf("+%s:%s", md.TagBranch, md.TagBranch))
198-
branchName = strings.TrimPrefix(md.TagBranch, "refs/heads/")
199-
} else {
200-
match := misc.GetTagImportRegex(pd).FindStringSubmatch(md.TagBranch)
201-
branchName = match[2]
202-
refspec = config.RefSpec(fmt.Sprintf("+refs/heads/%s:%s", branchName, md.TagBranch))
203-
}
204-
pd.Log.Printf("checking out upstream refspec %s", refspec)
205-
fetchOpts := &git.FetchOptions{
206-
Auth: pd.Authenticator,
207-
RemoteName: "upstream",
208-
RefSpecs: []config.RefSpec{refspec},
209-
Tags: git.AllTags,
210-
Force: true,
211-
}
212-
err = remote.Fetch(fetchOpts)
213-
if err != nil && err != git.NoErrAlreadyUpToDate {
214-
if err == transport.ErrInvalidAuthMethod || err == transport.ErrAuthenticationRequired {
215-
fetchOpts.Auth = nil
216-
err = remote.Fetch(fetchOpts)
217-
if err != nil && err != git.NoErrAlreadyUpToDate {
234+
// In the case of tagless mode, we already have the transformed repo sitting in the worktree,
235+
// and don't need to perform any checkout or fetch operations
236+
if !pd.TaglessMode {
237+
if strings.HasPrefix(md.TagBranch, "refs/heads") {
238+
refspec = config.RefSpec(fmt.Sprintf("+%s:%s", md.TagBranch, md.TagBranch))
239+
branchName = strings.TrimPrefix(md.TagBranch, "refs/heads/")
240+
} else {
241+
match := misc.GetTagImportRegex(pd).FindStringSubmatch(md.TagBranch)
242+
branchName = match[2]
243+
refspec = config.RefSpec(fmt.Sprintf("+refs/heads/%s:%s", branchName, md.TagBranch))
244+
fmt.Println("Found branchname that does not start w/ refs/heads :: ", branchName)
245+
}
246+
pd.Log.Printf("checking out upstream refspec %s", refspec)
247+
248+
fetchOpts := &git.FetchOptions{
249+
Auth: pd.Authenticator,
250+
RemoteName: "upstream",
251+
RefSpecs: []config.RefSpec{refspec},
252+
Tags: git.AllTags,
253+
Force: true,
254+
}
255+
err = remote.Fetch(fetchOpts)
256+
if err != nil && err != git.NoErrAlreadyUpToDate {
257+
if err == transport.ErrInvalidAuthMethod || err == transport.ErrAuthenticationRequired {
258+
fetchOpts.Auth = nil
259+
err = remote.Fetch(fetchOpts)
260+
if err != nil && err != git.NoErrAlreadyUpToDate {
261+
return fmt.Errorf("could not fetch upstream: %v", err)
262+
}
263+
} else {
218264
return fmt.Errorf("could not fetch upstream: %v", err)
219265
}
220-
} else {
221-
return fmt.Errorf("could not fetch upstream: %v", err)
222266
}
223-
}
224267

225-
err = md.Worktree.Checkout(&git.CheckoutOptions{
226-
Branch: plumbing.ReferenceName(md.TagBranch),
227-
Force: true,
228-
})
229-
if err != nil {
230-
return fmt.Errorf("could not checkout source from git: %v", err)
268+
err = md.Worktree.Checkout(&git.CheckoutOptions{
269+
Branch: plumbing.ReferenceName(md.TagBranch),
270+
Force: true,
271+
})
272+
if err != nil {
273+
return fmt.Errorf("could not checkout source from git: %v", err)
274+
}
275+
276+
_, err = md.Worktree.Add(".")
277+
if err != nil {
278+
return fmt.Errorf("could not add Worktree: %v", err)
279+
}
231280
}
232281

233-
_, err = md.Worktree.Add(".")
234-
if err != nil {
235-
return fmt.Errorf("could not add Worktree: %v", err)
282+
if pd.TaglessMode {
283+
branchName = fmt.Sprintf("%s%d%s", pd.ImportBranchPrefix, pd.Version, pd.BranchSuffix)
236284
}
237285

238286
metadataPath := ""
@@ -292,7 +340,32 @@ func (g *GitMode) WriteSource(pd *data.ProcessData, md *data.ModeData) error {
292340
body = fromBlobStorage
293341
pd.Log.Printf("downloading %s from blob storage", hash)
294342
} else {
295-
url := fmt.Sprintf("%s/%s/%s/%s", pd.CdnUrl, md.Name, branchName, hash)
343+
344+
url := ""
345+
// Alternate lookaside logic: if enabled, we pull from a new URL pattern
346+
if !pd.AltLookAside {
347+
url = fmt.Sprintf("%s/%s/%s/%s", pd.CdnUrl, md.Name, branchName, hash)
348+
} else {
349+
// We first need the hash algorithm based on length of hash:
350+
hashType := "sha512"
351+
switch len(hash) {
352+
case 128:
353+
hashType = "sha512"
354+
case 64:
355+
hashType = "sha256"
356+
case 40:
357+
hashType = "sha1"
358+
case 32:
359+
hashType = "md5"
360+
}
361+
362+
// need the name of the file without "SOURCES/":
363+
fileName := strings.Split(path, "/")[1]
364+
365+
// Alt. lookaside url is of the form: <cdn> / <name> / <filename> / <hashtype> / <hash> / <filename>
366+
url = fmt.Sprintf("%s/%s/%s/%s/%s/%s", pd.CdnUrl, md.Name, fileName, hashType, hash, fileName)
367+
}
368+
296369
pd.Log.Printf("downloading %s", url)
297370

298371
req, err := http.NewRequest("GET", url, nil)

pkg/srpmproc/patch.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"strings"
3333
"time"
3434

35+
"github.com/go-git/go-billy/v5"
3536
"github.com/go-git/go-billy/v5/memfs"
3637
"github.com/go-git/go-git/v5"
3738
"github.com/go-git/go-git/v5/config"
@@ -286,13 +287,30 @@ func patchModuleYaml(pd *data.ProcessData, md *data.ModeData) error {
286287
return nil
287288
}
288289

289-
mdTxtPath := "SOURCES/modulemd.src.txt"
290-
f, err := md.Worktree.Filesystem.Open(mdTxtPath)
291-
if err != nil {
292-
mdTxtPath = "SOURCES/modulemd.txt"
290+
mdTxtPath := ""
291+
var f billy.File
292+
293+
// tagless mode implies we're looking for CentOS Stream modules, which are generally "SOURCES/NAME.yaml" (copied to SOURCES/ from import)
294+
// if not tagless mode, proceed as usual with SOURCES/modulemd.*.txt
295+
if pd.TaglessMode {
296+
mdTxtPath = fmt.Sprintf("SOURCES/%s.yaml", md.Name)
297+
f, err = md.Worktree.Filesystem.Open(mdTxtPath)
298+
if err != nil {
299+
mdTxtPath = fmt.Sprintf("SOURCES/%s.yml", md.Name)
300+
f, err = md.Worktree.Filesystem.Open(mdTxtPath)
301+
if err != nil {
302+
return fmt.Errorf("could not open modulemd file: %v", err)
303+
}
304+
}
305+
} else {
306+
mdTxtPath = "SOURCES/modulemd.src.txt"
293307
f, err = md.Worktree.Filesystem.Open(mdTxtPath)
294308
if err != nil {
295-
return fmt.Errorf("could not open modulemd file: %v", err)
309+
mdTxtPath = "SOURCES/modulemd.txt"
310+
f, err = md.Worktree.Filesystem.Open(mdTxtPath)
311+
if err != nil {
312+
return fmt.Errorf("could not open modulemd file: %v", err)
313+
}
296314
}
297315
}
298316

@@ -307,11 +325,13 @@ func patchModuleYaml(pd *data.ProcessData, md *data.ModeData) error {
307325
}
308326

309327
// Get stream branch from tag
310-
match := misc.GetTagImportRegex(pd).FindStringSubmatch(md.TagBranch)
311-
streamBranch := strings.Split(match[2], "-")
312-
// Force stream to be the same as stream name in branch
313-
module.Data.Stream = streamBranch[len(streamBranch)-1]
314-
328+
// (in tagless mode we are trusting the "Stream: <VERSION>" text in the source YAML to be accurate)
329+
if !pd.TaglessMode {
330+
match := misc.GetTagImportRegex(pd).FindStringSubmatch(md.TagBranch)
331+
streamBranch := strings.Split(match[2], "-")
332+
// Force stream to be the same as stream name in branch
333+
module.Data.Stream = streamBranch[len(streamBranch)-1]
334+
}
315335
log.Println("This module contains the following rpms:")
316336
for name := range module.Data.Components.Rpms {
317337
pd.Log.Printf("\t- %s", name)

0 commit comments

Comments
 (0)