Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type EdgeConfig struct {
SubscriptionServerURL string `json:"subscription_server_url"`
SubscriptionBaseUrl string `json:"subscription_base_url"`
PulpURL string `json:"pulp_url,omitempty"`
PulpContentURL string `json:"pulp_content_url,omitempty"`
PulpUsername string `json:"pulp_username,omitempty"`
PulpPassword string `json:"pulp_password,omitempty"`
PulpContentUsername string `json:"pulp_content_username,omitempty"`
Expand Down Expand Up @@ -130,6 +131,7 @@ type Pulp struct {
URL string
Username string
Password string
ContentURL string `mapstructure:"content_url,omitempty"`
ContentUsername string `mapstructure:"content_username,omitempty"`
ContentPassword string `mapstructure:"content_password,omitempty"`
IdentityName string `mapstructure:"identity_name,omitempty"`
Expand Down Expand Up @@ -175,7 +177,8 @@ func readPulpConfig() (Pulp, error) {
options.SetDefault("url", "http://pulp-service:8080")
options.SetDefault("username", "edge-api-dev")
options.SetDefault("password", "")
options.SetDefault("content_username", "edge-content-dev")
options.SetDefault("content_url", "http://pulp-service:8080")
options.SetDefault("content_username", "")
options.SetDefault("content_password", "")
options.SetDefault("guard_subject_dn", "")
options.SetDefault("identity_name", "edge-api-dev")
Expand Down Expand Up @@ -331,6 +334,7 @@ func CreateEdgeAPIConfig() (*EdgeConfig, error) {
SubscriptionServerURL: options.GetString("SUBSCRIPTION_SERVER_URL"),
SubscriptionBaseUrl: options.GetString("SUBSCRIPTION_BASE_URL"),
PulpURL: pulpConfig.URL, // these pulp entries are temporary for backward compatibility
PulpContentURL: pulpConfig.ContentURL,
PulpUsername: pulpConfig.Username,
PulpPassword: pulpConfig.Password,
PulpContentUsername: pulpConfig.ContentUsername,
Expand Down Expand Up @@ -500,7 +504,7 @@ func Get() *EdgeConfig {
return config
}

func cleanup() {
func Cleanup() {
configMu.Lock()
defer configMu.Unlock()

Expand Down Expand Up @@ -562,6 +566,7 @@ func LogConfigAtStartup(cfg *EdgeConfig) {
"RepoFileUploadDelay": cfg.RepoFileUploadDelay,
"UploadWorkers": cfg.UploadWorkers,
"PulpURL": cfg.PulpURL,
"PulpContentURL": cfg.PulpContentURL,
"PulpGuardSubjectDN": cfg.PulpGuardSubjectDN,
}

Expand Down
6 changes: 3 additions & 3 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestKafkaBroker(t *testing.T) {

}(originalConfig, originalClowderEnvConfig, originalClowderLoadedConfig, originalClowderObjectBuckets, originalEDGETarBallsBucket)

defer cleanup()
defer Cleanup()

err := os.Setenv("ACG_CONFIG", "need some value only, as the config path is not needed here")
assert.NoError(t, err)
Expand Down Expand Up @@ -170,9 +170,9 @@ func TestKafkaBroker(t *testing.T) {
clowder.LoadedConfig = testCase.clowderConfig
clowder.KafkaServers = testCase.KafkaServers
// init the configuration
cleanup()
Cleanup()
Init()
defer cleanup()
defer Cleanup()
assert.Equal(t, config.KafkaBroker, testCase.ExpectedKafkaBroker)
assert.Equal(t, config.KafkaServers, testCase.ExpectedKafkaServers)
if testCase.ExpectedKafkaBroker != nil {
Expand Down
5 changes: 5 additions & 0 deletions deploy/clowdapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ objects:
secretKeyRef:
name: edge-pulp-password
key: key
- name : PULP_CONTENT_URL
value: ${PULP_CONTENT_URL}
- name : PULP_CONTENT_USERNAME
value: ${PULP_CONTENT_USERNAME}
- name : PULP_CONTENT_PASSWORD
Expand Down Expand Up @@ -555,6 +557,9 @@ parameters:
value: "pulp-user"
- name: PULP_PASSWORD
description: Password for Pulp API authentication
- name: PULP_CONTENT_URL
description: Pulp service content URL
value: "http://pulp-service:8080"
- name: PULP_CONTENT_USERNAME
description: Username for Pulp Content API authentication
value: "pulp-content-user"
Expand Down
22 changes: 19 additions & 3 deletions pkg/clients/imagebuilder/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type ImageRequest struct {

// ComposeRequest is the request to Compose one or more Images
type ComposeRequest struct {
Name string `json:"image_name"`
Customizations *Customizations `json:"customizations"`
Distribution string `json:"distribution"`
ImageRequests []ImageRequest `json:"image_requests"`
Expand Down Expand Up @@ -224,12 +225,15 @@ func (c *Client) compose(composeReq *ComposeRequest) (*ComposeResult, error) {
return nil, err
}
cfg := config.Get()
url := fmt.Sprintf("%s/api/image-builder/v1/compose", cfg.ImageBuilderConfig.URL)
reqURL := fmt.Sprintf("%s/api/image-builder/v1/compose", cfg.ImageBuilderConfig.URL)

parsedURL, _ := url.Parse(reqURL)
c.log.WithFields(log.Fields{
"url": url,
"url": parsedURL.Redacted(),
"payload": payloadBuf.String(),
}).Debug("Image Builder Compose Request Started")
req, _ := http.NewRequest("POST", url, payloadBuf)

req, _ := http.NewRequest("POST", reqURL, payloadBuf)
for key, value := range clients.GetOutgoingHeaders(c.ctx) {
req.Header.Add(key, value)
}
Expand Down Expand Up @@ -267,11 +271,14 @@ func (c *Client) compose(composeReq *ComposeRequest) (*ComposeResult, error) {

// ComposeCommit composes a Commit on ImageBuilder
func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {
c.log.Debug("COMPOSING COMMIT")

payloadRepos, err := c.GetImageThirdPartyRepos(image)
if err != nil {
return nil, errors.New("error getting information on third Party repository")
}
req := &ComposeRequest{
Name: image.Name,
Customizations: &Customizations{
Packages: image.GetALLPackagesList(),
PayloadRepositories: &payloadRepos,
Expand Down Expand Up @@ -303,6 +310,7 @@ func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {
}

}

if image.Commit.OSTreeRef != "" {
if req.ImageRequests[0].Ostree == nil {
req.ImageRequests[0].Ostree = &OSTree{}
Expand Down Expand Up @@ -338,6 +346,8 @@ func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {

// ComposeInstaller composes an Installer on ImageBuilder
func (c *Client) ComposeInstaller(image *models.Image) (*models.Image, error) {
c.log.Debug("COMPOSING INSTALLER")

pkgs := make([]string, 0)
var repoURL string
var rhsm bool
Expand All @@ -349,6 +359,12 @@ func (c *Client) ComposeInstaller(image *models.Image) (*models.Image, error) {
rhsm = false
}

if feature.PulpIntegration.IsEnabled() && image.Commit.Repo.PulpURL != "" {
repoURL = image.Commit.Repo.PulpURL
parsedURL, _ := url.Parse(repoURL)
c.log.WithField("redacted_url", parsedURL.Redacted()).Debug("Using Pulp repo URL for ISO installer request")
}

users := make([]User, 0)
if image.Installer != nil && image.Installer.Username != "" && image.Installer.SSHKey != "" {
users = append(users, User{Name: image.Installer.Username,
Expand Down
65 changes: 59 additions & 6 deletions pkg/clients/pulp/guards_composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"errors"
"fmt"
"slices"

"github.com/google/uuid"
"github.com/redhatinsights/edge-api/config"
"github.com/redhatinsights/edge-api/pkg/ptr"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -65,12 +67,44 @@ func (ps *PulpService) CompositeGuardReadByOrgID(ctx context.Context, orgID stri
return ps.CompositeGuardRead(ctx, id)
}

func contentGuardHrefsAreEqual(cghrefa, cghrefb []string) bool {
// compare how many hrefs are in the content guard versus expected
logrus.Debug("Comparing Content Guard hrefs")
if len(cghrefa) != len(cghrefb) {
logrus.WithFields(logrus.Fields{
"href_count_1": len(cghrefa),
"href_count_2": len(cghrefb),
}).Warning("Content Guards do not have the same number of hrefs")

return false
}

logrus.WithFields(logrus.Fields{
"href_count_1": len(cghrefa),
"href_count_2": len(cghrefb),
}).Debug("Content Guards have the same number of hrefs")

// compare content guard hrefs (actual vs. expected)
for i := range cghrefa {
if !slices.Contains(cghrefb, cghrefa[i]) {
logrus.WithField("href_mismatch", cghrefa[i]).Warning("Content Guard href mismatch")

return false
}
}

logrus.Debug("Content Guard has the expected hrefs")

return true
}

// CompositeGuardEnsure ensures that the composite guard is created and returns it. The method is idempotent.
func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHref, rbacHref string) (*CompositeContentGuardResponse, error) {
func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID string, pulpGuardHrefs []string) (*CompositeContentGuardResponse, error) {
cg, err := ps.CompositeGuardReadByOrgID(ctx, compositeGuardName(orgID))
// nolint: gocritic
if errors.Is(err, ErrRecordNotFound) {
cg, err = ps.CompositeGuardCreate(ctx, orgID, headerHref, rbacHref)
logrus.Warning("No composite guard found. Creating one.")
cg, err = ps.CompositeGuardCreate(ctx, orgID, pulpGuardHrefs...)
if err != nil {
return nil, err
}
Expand All @@ -83,14 +117,15 @@ func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHr
}

gs := ptr.FromOrEmpty(cg.Guards)
if !(len(gs) == 2 && (gs[0] != headerHref && gs[1] != rbacHref) || (gs[1] != headerHref && gs[0] != rbacHref)) {
if !contentGuardHrefsAreEqual(gs, pulpGuardHrefs) {
logrus.WithContext(ctx).Warnf("unexpected Composite Content Guard: %v, deleting it", gs)
err = ps.CompositeGuardDelete(ctx, ScanUUID(cg.PulpHref))
if err != nil {
return nil, err
}

cg, err = ps.CompositeGuardCreate(ctx, orgID, headerHref, rbacHref)
logrus.Warning("Matching composite guard not found. Creating one.")
cg, err = ps.CompositeGuardCreate(ctx, orgID, pulpGuardHrefs...)
if err != nil {
return nil, err
}
Expand All @@ -102,17 +137,35 @@ func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHr
// that the composite guard is not created or the guards are not the same as the ones provided, it will delete it
// and recreate it. This method is idempotent and will not create the guards if they already exist.
func (ps *PulpService) ContentGuardEnsure(ctx context.Context, orgID string) (*CompositeContentGuardResponse, error) {
var contentGuardHrefs []string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, two appends will create a slice of capacity two, one extra copy operation is not big deal: https://go.dev/play/p/SAxjQTDwNCT

Interesting fact: Go increases capacity in power of twos up until 256 and then it is just 1.25x: https://github.com/golang/go/blob/master/src/runtime/slice.go#L289

cfg := config.Get()

logrus.WithContext(ctx).Debug("Creating header content guard")
hcg, err := ps.HeaderGuardEnsure(ctx, orgID)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *hcg.PulpHref)

rcg, err := ps.RbacGuardEnsure(ctx)
// Turnpike Content Guard is primarily for Image Builder to Pulp calls using the URL configured in PULP_CONTENT_URL
logrus.WithContext(ctx).Debug("Creating turnpike content guard")
tpcg, err := ps.TurnpikeGuardEnsure(ctx)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *tpcg.PulpHref)

// to create an RBAC Content Guard, add PULP_CONTENT_USERNAME and PULP_CONTENT_PASSWORD to environment
if cfg.Pulp.ContentUsername != "" {
logrus.WithContext(ctx).Debug("Creating RBAC content guard")
rcg, err := ps.RbacGuardEnsure(ctx)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *rcg.PulpHref)
}

ccg, err := ps.CompositeGuardEnsure(ctx, orgID, *hcg.PulpHref, *rcg.PulpHref)
ccg, err := ps.CompositeGuardEnsure(ctx, orgID, contentGuardHrefs)
if err != nil {
return nil, err
}
Expand Down
91 changes: 91 additions & 0 deletions pkg/clients/pulp/guards_composite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package pulp

import (
"fmt"
"testing"

"github.com/bxcodec/faker/v3"
"github.com/magiconair/properties/assert"
)

func TestContentGuardHrefsAreEqual(t *testing.T) {
var hrefTemplate = "/api/pulp/em%sd/api/v3/contentguards/core/%s/%s/"
var orgID = faker.UUIDDigit()

var href1 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // id
var href2 = fmt.Sprintf(hrefTemplate, orgID, "rbac", faker.UUIDHyphenated()) // rbac
var href3 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // turnpike
var href4 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // different

var baseSlice = []string{href1, href2, href3}
var sameSizeDiffOrder = []string{href3, href2, href1}
var sameSizeSameOrder = []string{href1, href2, href3}
var sameSizeDiffContent = []string{href1, href2, href4}
var diffSmaller = []string{href1, href2}
var diffLarger = []string{href1, href2, href3, href4}
var sameSizeEmptyString = []string{href1, href2, ""}
var sameSizeEmptyStringDiffOrder = []string{"", href2, href1}
var sameSizeAllEmptyStrings = []string{"", "", ""}
var diffSizeSmallerAllEmptyStrings = []string{"", ""}
var diffSizeLargerAllEmptyStrings = []string{"", "", "", ""}

t.Run("sameslice", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, baseSlice), true)
})

t.Run("samesize_differentorder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeDiffOrder), true)
})

t.Run("samesize_sameorder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeSameOrder), true)
})

t.Run("negative_samesize_differentcontent", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeDiffContent), false)
})

t.Run("negative_differentsizesmaller", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, diffSmaller), false)
})

t.Run("negative_differentsizelarger", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, diffLarger), false)
})

t.Run("empty_both", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual([]string{}, []string{}), true)
})

t.Run("negative_empty_a", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual([]string{}, baseSlice), false)
})

t.Run("negative_empty_b", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, []string{}), false)
})

t.Run("negative_samesize_emptystring", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeEmptyString), false)
})

t.Run("samesize_oneemptystring_difforder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeEmptyString, sameSizeEmptyStringDiffOrder), true)
})

t.Run("negative_samesize_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeAllEmptyStrings), false)
})

t.Run("negative_differentsizesmaller_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, diffSizeSmallerAllEmptyStrings), false)
})

t.Run("negative_differentsizelarger_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, diffSizeLargerAllEmptyStrings), false)
})

t.Run("sameslice_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, sameSizeAllEmptyStrings), true)
})
}
Loading
Loading