Skip to content

Commit 28e49ba

Browse files
committed
internal/v1: use latest snapshots for CDN repos by default
Automatically transform CDN repository URLs to use latest snapshots from content sources API when no explicit SnapshotDate is provided. Non-CDN repos pass through unchanged. If a snapshot is not available, falls back to CDN Switching to latest snapshot aligns the method of authentication with the one used for other snapshots and templates, reducing some complexity
1 parent c631b47 commit 28e49ba

File tree

3 files changed

+345
-46
lines changed

3 files changed

+345
-46
lines changed

internal/v1/handler_compose_image.go

Lines changed: 121 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io"
99
"log/slog"
1010
"net/http"
11+
"net/url"
1112
"slices"
1213
"strings"
1314
"time"
@@ -116,7 +117,12 @@ func (h *Handlers) handleCommonCompose(ctx echo.Context, composeRequest ComposeR
116117
repositories = append(repositories, snapshotRepos...)
117118

118119
// A sanity check to make sure there's a snapshot for each repo
119-
expected := len(buildRepositories(arch, composeRequest.ImageRequests[0].ImageType))
120+
expected := 0
121+
for _, r := range arch.Repositories {
122+
if len(r.ImageTypeTags) == 0 || slices.Contains(r.ImageTypeTags, string(composeRequest.ImageRequests[0].ImageType)) {
123+
expected++
124+
}
125+
}
120126
if len(repositories) != expected {
121127
return ComposeResponse{}, fmt.Errorf("no snapshots found for all repositories (found %d, expected %d)", len(repositories), expected)
122128
}
@@ -126,7 +132,10 @@ func (h *Handlers) handleCommonCompose(ctx echo.Context, composeRequest ComposeR
126132
return ComposeResponse{}, err
127133
}
128134
} else {
129-
repositories = buildRepositories(arch, composeRequest.ImageRequests[0].ImageType)
135+
repositories, err = h.buildRepositoriesWithLatestSnapshots(ctx, arch, composeRequest.ImageRequests[0].ImageType)
136+
if err != nil {
137+
return ComposeResponse{}, err
138+
}
130139
}
131140

132141
uploadOptions, imageType, err := h.buildUploadOptions(ctx, composeRequest.ImageRequests[0].UploadRequest, composeRequest.ImageRequests[0].ImageType)
@@ -218,21 +227,108 @@ func (h *Handlers) handleCommonCompose(ctx echo.Context, composeRequest ComposeR
218227
}, nil
219228
}
220229

221-
func buildRepositories(arch *distribution.Architecture, imageType ImageTypes) []composer.Repository {
230+
// buildRepositoriesWithLatestSnapshots transforms the CDN repository URLs to the latest snapshot URL
231+
// or falls back to CDN if snapshot is not available
232+
func (h *Handlers) buildRepositoriesWithLatestSnapshots(ctx echo.Context, arch *distribution.Architecture, imageType ImageTypes) ([]composer.Repository, error) {
222233
var repositories []composer.Repository
234+
var cdnRepoURLs []string
235+
var errs []error
236+
223237
for _, r := range arch.Repositories {
224-
// If no image type tags are defined for the repo, add the repo
225-
if len(r.ImageTypeTags) == 0 || slices.Contains(r.ImageTypeTags, string(imageType)) {
226-
repositories = append(repositories, composer.Repository{
227-
Baseurl: r.Baseurl,
228-
Metalink: r.Metalink,
229-
Rhsm: common.ToPtr(r.Rhsm),
230-
Gpgkey: r.GpgKey,
231-
CheckGpg: r.CheckGpg,
232-
})
238+
if (len(r.ImageTypeTags) == 0 || slices.Contains(r.ImageTypeTags, string(imageType))) && r.Rhsm {
239+
cdnRepoURLs = append(cdnRepoURLs, *r.Baseurl)
240+
}
241+
}
242+
243+
var repoMap map[string]content_sources.ApiRepositoryResponse
244+
if len(cdnRepoURLs) > 0 {
245+
var err error
246+
repoMap, err = h.server.csClient.GetRepositories(ctx.Request().Context(), cdnRepoURLs, nil, false)
247+
if err != nil {
248+
return nil, fmt.Errorf("unable to retrieve get repositories: %w", err)
233249
}
234250
}
235-
return repositories
251+
252+
for _, r := range arch.Repositories {
253+
if len(r.ImageTypeTags) > 0 && !slices.Contains(r.ImageTypeTags, string(imageType)) {
254+
continue
255+
}
256+
257+
composerRepo, err := h.buildComposerRepositoryWithLatestSnapshot(r, repoMap)
258+
if err != nil {
259+
errs = append(errs, err)
260+
continue
261+
}
262+
263+
repositories = append(repositories, composerRepo)
264+
}
265+
266+
if len(errs) > 0 {
267+
return nil, errors.Join(errs...)
268+
}
269+
return repositories, nil
270+
}
271+
272+
func (h *Handlers) buildComposerRepositoryWithLatestSnapshot(r distribution.Repository, repoMap map[string]content_sources.ApiRepositoryResponse) (composer.Repository, error) {
273+
composerRepo := composer.Repository{
274+
Baseurl: r.Baseurl,
275+
Metalink: r.Metalink,
276+
Rhsm: common.ToPtr(r.Rhsm),
277+
CheckGpg: r.CheckGpg,
278+
Gpgkey: r.GpgKey,
279+
}
280+
281+
if !r.Rhsm {
282+
return composerRepo, nil
283+
}
284+
285+
var matchedRepo *content_sources.ApiRepositoryResponse
286+
for _, apiRepo := range repoMap {
287+
if apiRepo.Url != nil && *apiRepo.Url == *r.Baseurl {
288+
matchedRepo = &apiRepo
289+
break
290+
}
291+
}
292+
293+
if matchedRepo != nil && matchedRepo.LatestSnapshotUrl != nil && *matchedRepo.LatestSnapshotUrl != "" {
294+
repoURL, err := url.Parse(*matchedRepo.LatestSnapshotUrl)
295+
if err != nil {
296+
return composer.Repository{}, fmt.Errorf("failed to parse snapshot URL for repository %s: %w", *r.Baseurl, err)
297+
}
298+
snapshotRepo, err := h.buildComposerRepositoryFromSnapshot(repoURL.Path, false, matchedRepo)
299+
if err != nil {
300+
return composer.Repository{}, err
301+
}
302+
return snapshotRepo, nil
303+
}
304+
305+
return composerRepo, nil
306+
}
307+
308+
func (h *Handlers) buildComposerRepositoryFromSnapshot(snapshotPath string, usePrefix bool, apiRepo *content_sources.ApiRepositoryResponse) (composer.Repository, error) {
309+
composerRepo := composer.Repository{}
310+
311+
var baseurl string
312+
if usePrefix {
313+
baseurl = h.server.csReposURL.JoinPath(h.server.csReposPrefix, snapshotPath).String()
314+
} else {
315+
baseurl = h.server.csReposURL.JoinPath(snapshotPath).String()
316+
}
317+
composerRepo.Baseurl = common.ToPtr(baseurl)
318+
composerRepo.Rhsm = common.ToPtr(false)
319+
320+
if apiRepo != nil {
321+
if apiRepo.GpgKey != nil && *apiRepo.GpgKey != "" {
322+
composerRepo.Gpgkey = apiRepo.GpgKey
323+
}
324+
if composerRepo.Gpgkey != nil && *composerRepo.Gpgkey != "" {
325+
composerRepo.CheckGpg = common.ToPtr(true)
326+
}
327+
composerRepo.ModuleHotfixes = apiRepo.ModuleHotfixes
328+
composerRepo.CheckRepoGpg = apiRepo.MetadataVerification
329+
}
330+
331+
return composerRepo, nil
236332
}
237333

238334
func (h *Handlers) buildRepositorySnapshots(ctx echo.Context, repoURLs []string, repoIDs []string, external bool, snapshotDate string) ([]composer.Repository, []composer.CustomRepository, error) {
@@ -287,26 +383,20 @@ func (h *Handlers) buildRepositorySnapshots(ctx echo.Context, repoURLs []string,
287383

288384
var repositories []composer.Repository
289385
var customRepositories []composer.CustomRepository
386+
var errs []error
387+
290388
for _, snap := range *csSnapshots.Data {
291389
repo, ok := repoMap[*snap.RepositoryUuid]
292390
if !ok {
293-
return repositories, customRepositories, fmt.Errorf("returned snapshot %v unexpected repository id %v", *snap.Match.Uuid, *snap.RepositoryUuid)
294-
}
295-
296-
composerRepo := composer.Repository{
297-
// unlike latest snapshot URLs, the repository path of a snapshot match doesn't contain the path prefix
298-
Baseurl: common.ToPtr(h.server.csReposURL.JoinPath(h.server.csReposPrefix, *snap.Match.RepositoryPath).String()),
299-
Rhsm: common.ToPtr(false),
391+
errs = append(errs, fmt.Errorf("returned snapshot %v unexpected repository id %v", *snap.Match.Uuid, *snap.RepositoryUuid))
392+
continue
300393
}
301394

302-
if repo.GpgKey != nil && *repo.GpgKey != "" {
303-
composerRepo.Gpgkey = repo.GpgKey
304-
}
305-
if composerRepo.Gpgkey != nil && *composerRepo.Gpgkey != "" {
306-
composerRepo.CheckGpg = common.ToPtr(true)
395+
composerRepo, err := h.buildComposerRepositoryFromSnapshot(*snap.Match.RepositoryPath, true, &repo)
396+
if err != nil {
397+
errs = append(errs, err)
398+
continue
307399
}
308-
composerRepo.ModuleHotfixes = repo.ModuleHotfixes
309-
composerRepo.CheckRepoGpg = repo.MetadataVerification
310400
repositories = append(repositories, composerRepo)
311401

312402
// Don't enable custom repositories, as they require further setup to be useable.
@@ -325,6 +415,10 @@ func (h *Handlers) buildRepositorySnapshots(ctx echo.Context, repoURLs []string,
325415
customRepositories = append(customRepositories, customRepo)
326416
}
327417

418+
if len(errs) > 0 {
419+
return repositories, customRepositories, errors.Join(errs...)
420+
}
421+
328422
ctx.Logger().Debugf("Resolved snapshots: %v", repositories)
329423
return repositories, customRepositories, nil
330424
}

0 commit comments

Comments
 (0)