diff --git a/config/config.go b/config/config.go index 7b922c3b6..5beb3195f 100644 --- a/config/config.go +++ b/config/config.go @@ -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"` @@ -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"` @@ -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") @@ -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, @@ -500,7 +504,7 @@ func Get() *EdgeConfig { return config } -func cleanup() { +func Cleanup() { configMu.Lock() defer configMu.Unlock() @@ -562,6 +566,7 @@ func LogConfigAtStartup(cfg *EdgeConfig) { "RepoFileUploadDelay": cfg.RepoFileUploadDelay, "UploadWorkers": cfg.UploadWorkers, "PulpURL": cfg.PulpURL, + "PulpContentURL": cfg.PulpContentURL, "PulpGuardSubjectDN": cfg.PulpGuardSubjectDN, } diff --git a/config/config_test.go b/config/config_test.go index 906b3ef53..c87867126 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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) @@ -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 { diff --git a/deploy/clowdapp.yaml b/deploy/clowdapp.yaml index 66ca7fc1a..9bbf5b9d6 100644 --- a/deploy/clowdapp.yaml +++ b/deploy/clowdapp.yaml @@ -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 @@ -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" diff --git a/pkg/clients/imagebuilder/client.go b/pkg/clients/imagebuilder/client.go index a835e5351..d531d68ee 100644 --- a/pkg/clients/imagebuilder/client.go +++ b/pkg/clients/imagebuilder/client.go @@ -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"` @@ -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) } @@ -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, @@ -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{} @@ -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 @@ -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, diff --git a/pkg/clients/pulp/guards_composite.go b/pkg/clients/pulp/guards_composite.go index 9d1ee800b..a12eb3cf8 100644 --- a/pkg/clients/pulp/guards_composite.go +++ b/pkg/clients/pulp/guards_composite.go @@ -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" ) @@ -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 } @@ -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 } @@ -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 + 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 } diff --git a/pkg/clients/pulp/guards_composite_test.go b/pkg/clients/pulp/guards_composite_test.go new file mode 100644 index 000000000..c24341924 --- /dev/null +++ b/pkg/clients/pulp/guards_composite_test.go @@ -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) + }) +} diff --git a/pkg/clients/pulp/guards_turnpike.go b/pkg/clients/pulp/guards_turnpike.go new file mode 100644 index 000000000..f5589ea08 --- /dev/null +++ b/pkg/clients/pulp/guards_turnpike.go @@ -0,0 +1,179 @@ +// Package pulp implements routines for Edge API to interact with the Pulp OSTree service. +// +// The Turnpike Content Guard enables cert-based communication between services and the Pulp service. +package pulp + +import ( + "context" + "errors" + "fmt" + + "github.com/google/uuid" + "github.com/redhatinsights/edge-api/config" + "github.com/redhatinsights/edge-api/pkg/ptr" + "github.com/sirupsen/logrus" +) + +const TurnpikeGuardName = "ostree_turnpike_guard" +const TurnpikeJQFilter = ".identity.x509.subject_dn" + +// TurnpikeGuardList returns a list of header guards. The nameFilter can be used to filter the results. +func (ps *PulpService) TurnpikeGuardList(ctx context.Context, nameFilter string) ([]HeaderContentGuardResponse, error) { + req := ContentguardsCoreHeaderListParams{ + Limit: &DefaultPageSize, + } + if nameFilter != "" { + req.Name = &nameFilter + } + + resp, err := ps.cwr.ContentguardsCoreHeaderListWithResponse(ctx, ps.dom, &req, addAuthenticationHeader) + if err != nil { + return nil, err + } + + if resp.JSON200 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + if resp.JSON200.Count > DefaultPageSize { + return nil, fmt.Errorf("default page size too small: %d", resp.JSON200.Count) + } + + if resp.JSON200.Count == 0 || resp.JSON200.Results[0].PulpHref == nil { + return nil, ErrRecordNotFound + } + + return resp.JSON200.Results, nil +} + +// TurnpikeGuardRead returns the Turnpike guard with the given ID. +func (ps *PulpService) TurnpikeGuardRead(ctx context.Context, id uuid.UUID) (*HeaderContentGuardResponse, error) { + req := ContentguardsCoreHeaderReadParams{} + resp, err := ps.cwr.ContentguardsCoreHeaderReadWithResponse(ctx, ps.dom, id, &req, addAuthenticationHeader) + + if err != nil { + return nil, err + } + + if resp.JSON200 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + return resp.JSON200, nil +} + +// TurnpikeGuardFind returns the Turnpike guard. +func (ps *PulpService) TurnpikeGuardFind(ctx context.Context) (*HeaderContentGuardResponse, error) { + hgl, err := ps.TurnpikeGuardList(ctx, TurnpikeGuardName) + if err != nil { + logrus.WithFields(logrus.Fields{ + "name": TurnpikeGuardName, + "error": err.Error(), + }).Error("Turnpike content guard not found") + + if errors.Is(err, ErrRecordNotFound) { + return nil, ErrRecordNotFound + } + + return nil, err + } + + id := ScanUUID(hgl[0].PulpHref) + return ps.TurnpikeGuardRead(ctx, id) +} + +// TurnpikeGuardCreate creates a new Turnpike guard and returns it. +func (ps *PulpService) TurnpikeGuardCreate(ctx context.Context) (*HeaderContentGuardResponse, error) { + cfg := config.Get() + + if cfg.Pulp.GuardSubjectDN == "" { + return nil, fmt.Errorf("guard subject dn is not set") + } + + req := HeaderContentGuard{ + Name: TurnpikeGuardName, + Description: ptr.To("EDGE"), + HeaderName: "x-rh-identity", + HeaderValue: cfg.Pulp.GuardSubjectDN, + JqFilter: ptr.To(TurnpikeJQFilter), + } + + resp, err := ps.cwr.ContentguardsCoreHeaderCreateWithResponse(ctx, ps.dom, req, addAuthenticationHeader) + + if err != nil { + return nil, err + } + + if resp.JSON201 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + return resp.JSON201, nil +} + +// TurnpikeGuardEnsure ensures that the Turnpike guard is created and returns it. The method is idempotent. +func (ps *PulpService) TurnpikeGuardEnsure(ctx context.Context) (*HeaderContentGuardResponse, error) { + cg, err := ps.TurnpikeGuardFind(ctx) + if errors.Is(err, ErrRecordNotFound) { + // turnpike guard is not found, so create one + cg, err = ps.TurnpikeGuardCreate(ctx) + if err != nil { + return nil, err + } + + return cg, nil + } + if err != nil { + return nil, err + } + if cg == nil { + return nil, fmt.Errorf("unexpected nil guard") + } + + return cg, nil +} + +// TurnpikeGuardDelete deletes the Turnpike guard with the given ID. +func (ps *PulpService) TurnpikeGuardDelete(ctx context.Context, id uuid.UUID) error { + listParams := ContentguardsCoreHeaderListRolesParams{} + roles, err := ps.cwr.ContentguardsCoreHeaderListRolesWithResponse(ctx, ps.dom, id, &listParams, addAuthenticationHeader) + + if err != nil { + return err + } + + if roles.JSON200 == nil { + return fmt.Errorf("unexpected response: %d, body: %s", roles.StatusCode(), string(roles.Body)) + } + + for _, role := range roles.JSON200.Roles { + nr := NestedRole{ + Role: role.Role, + Users: &[]string{}, + } + + logrus.WithContext(ctx).Warnf("removing Turnpike guardrole: %s", role.Role) + removed, err := ps.cwr.ContentguardsCoreHeaderRemoveRoleWithResponse(ctx, ps.dom, id, nr, addAuthenticationHeader) + + if err != nil { + return err + } + + if removed.JSON201 == nil { + return fmt.Errorf("unexpected response: %d, body: %s", removed.StatusCode(), string(removed.Body)) + } + } + + resp, err := ps.cwr.ContentguardsCoreHeaderDelete(ctx, ps.dom, id, addAuthenticationHeader) + + if err != nil { + return err + } + resp.Body.Close() + + if resp.StatusCode != 204 { + return fmt.Errorf("unexpected response: %d", resp.StatusCode) + } + + return nil +} diff --git a/pkg/services/images.go b/pkg/services/images.go index 8040c5b31..9b6d3e141 100644 --- a/pkg/services/images.go +++ b/pkg/services/images.go @@ -958,7 +958,7 @@ func (s *ImageService) CreateRepoForImage(ctx context.Context, img *models.Image "aws_status": repo.Status, "pulp_url": parsedPulpURL.Redacted(), "pulp_status": repo.PulpStatus, - }).Info("OSTree repo is ready") + }).Info("OSTree repo process complete") if repo.Status != models.RepoStatusSuccess && repo.PulpStatus != models.RepoStatusSuccess { return nil, goErrors.New("No repo has been created") diff --git a/pkg/services/repostore/pulpstore.go b/pkg/services/repostore/pulpstore.go index 9cbcbdc14..ec3ba31f0 100644 --- a/pkg/services/repostore/pulpstore.go +++ b/pkg/services/repostore/pulpstore.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/url" - "strings" "github.com/google/uuid" "github.com/redhatinsights/edge-api/config" @@ -49,8 +48,6 @@ func PulpRepoStore(ctx context.Context, orgID string, sourceRepoID uint, sourceU "pulp_href": pulpHref, }).Info("Pulp OSTree Repo created with Content Guard and Distribution") - // TODO: check for an existing OSTree repo for image commit versions > 1 and add the commit for updates - repoURL, err := ostreeRepoImport(ctx, pserv, pulpHref, repoName, distBaseURL, fileRepoArtifact) if err != nil { log.WithContext(ctx).Error("Error importing tarfile into pulp ostree repository") @@ -142,8 +139,7 @@ func createOSTreeRepository(ctx context.Context, pulpService *pulp.PulpService, cgPulpHref := *cg.PulpHref log.WithContext(ctx).WithFields(log.Fields{ "contentguard_href": cgPulpHref, - "contentguard_0": (*cg.Guards)[0], - "contentguard_1": (*cg.Guards)[1], + "content_guards": *cg.Guards, }).Info("Pulp Content Guard found or created") distribution, err := pulpService.DistributionsCreate(ctx, name, name, *pulpRepo.PulpHref, cgPulpHref) @@ -153,7 +149,7 @@ func createOSTreeRepository(ctx context.Context, pulpService *pulp.PulpService, log.WithContext(ctx).WithFields(log.Fields{ "name": distribution.Name, "base_path": distribution.BasePath, - "base_url": distribution.BaseUrl, + "base_url": *distribution.BaseUrl, "pulp_href": distribution.PulpHref, }).Info("Pulp Distribution created") @@ -161,32 +157,24 @@ func createOSTreeRepository(ctx context.Context, pulpService *pulp.PulpService, } // returns the complete distribution URL -func distributionURL(ctx context.Context, distBaseURL string, domain string, repoName string) (string, error) { +func distributionURL(ctx context.Context, domain string, repoName string) (string, error) { cfg := config.Get() + var distURL string + distURL = fmt.Sprintf("%s/api/pulp-content/%s/%s", cfg.PulpContentURL, domain, repoName) - prodDistURL, err := url.Parse(distBaseURL) - if err != nil { - return "", errors.New("Unable to set user:password for Pulp distribution URL") - } - prodDistURL.User = url.UserPassword(cfg.PulpContentUsername, cfg.PulpContentPassword) - distURL := prodDistURL.String() - - // temporarily handle stage URLs so Image Builder worker can get to stage Pulp - if strings.Contains(distBaseURL, "stage") { - stagePulpURL := fmt.Sprintf("%s/api/pulp-content/%s/%s", cfg.PulpURL, domain, repoName) - stageDistURL, err := url.Parse(stagePulpURL) + if cfg.Pulp.ContentUsername != "" { + rbacDistURL, err := url.Parse(distURL) if err != nil { return "", errors.New("Unable to set user:password for Pulp distribution URL") } - stageDistURL.User = url.UserPassword(cfg.PulpContentUsername, cfg.PulpContentPassword) - - distURL = stageDistURL.String() + rbacDistURL.User = url.UserPassword(cfg.PulpContentUsername, cfg.PulpContentPassword) + distURL = rbacDistURL.String() } parsedURL, _ := url.Parse(distURL) log.WithContext(ctx).WithField("distribution_url", parsedURL.Redacted()).Debug("Distribution URL retrieved") - return distURL, err + return distURL, nil } // imports an image builder tarfile into a Pulp file repo @@ -204,6 +192,8 @@ func fileRepoImport(ctx context.Context, pulpService *pulp.PulpService, sourceUR return "", "", err } + log.WithContext(ctx).WithFields(log.Fields{"artifact_href": artifact, "version_href": version}).Debug("Artifact and version href") + return artifact, version, nil } @@ -211,14 +201,19 @@ func fileRepoImport(ctx context.Context, pulpService *pulp.PulpService, sourceUR func ostreeRepoImport(ctx context.Context, pulpService *pulp.PulpService, pulpHref string, pulpRepoName string, distBaseURL string, fileRepoArtifact string) (string, error) { - log.WithContext(ctx).Debug("Starting tarfile import into Pulp OSTree repository") + log.WithContext(ctx).WithFields(log.Fields{ + "pulp_href": pulp.ScanUUID(&pulpHref), + "pulp_reponame": pulpRepoName, + "distribution_baseurl": distBaseURL, + "artifact_href": fileRepoArtifact, + }).Debug("Starting tarfile import into Pulp OSTree repository") repoImported, err := pulpService.RepositoriesImport(ctx, pulp.ScanUUID(&pulpHref), "repo", fileRepoArtifact) if err != nil { return "", err } - log.WithContext(ctx).Info("Repository imported", *repoImported.PulpHref) + log.WithContext(ctx).WithField("repo_href", *repoImported.PulpHref).Info("Repository imported") - repoURL, err := distributionURL(ctx, distBaseURL, pulpService.Domain(), pulpRepoName) + repoURL, err := distributionURL(ctx, pulpService.Domain(), pulpRepoName) if err != nil { log.WithContext(ctx).WithField("error", err.Error()).Error("Error getting distibution URL for Pulp repo") return "", err diff --git a/pkg/services/repostore/pulpstore_test.go b/pkg/services/repostore/pulpstore_test.go new file mode 100644 index 000000000..ba331a5b7 --- /dev/null +++ b/pkg/services/repostore/pulpstore_test.go @@ -0,0 +1,43 @@ +package repostore + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/bxcodec/faker/v3" + "github.com/magiconair/properties/assert" + "github.com/redhatinsights/edge-api/config" +) + +func TestDistributionURL(t *testing.T) { + var testURL = "https://example.com:8080" + var testURLWithPassword = "https://william:tell@example.com:8080" + var domain = faker.UUIDDigit() + var repoName = faker.UUIDDigit() + + t.Run("without username", func(t *testing.T) { + defer config.Cleanup() + os.Setenv("PULP_CONTENT_URL", testURL) + + var urlTemplate = "%s/api/pulp-content/%s/%s" + var expectedDistURL = fmt.Sprintf(urlTemplate, testURL, domain, repoName) + + distURL, _ := distributionURL(context.Background(), domain, repoName) + assert.Equal(t, distURL, expectedDistURL) + }) + + t.Run("with username", func(t *testing.T) { + defer config.Cleanup() + os.Setenv("PULP_CONTENT_URL", testURL) + os.Setenv("PULP_CONTENT_USERNAME", "william") + os.Setenv("PULP_CONTENT_PASSWORD", "tell") + + var urlTemplate = "%s/api/pulp-content/%s/%s" + var expectedDistURL = fmt.Sprintf(urlTemplate, testURLWithPassword, domain, repoName) + + distURL, _ := distributionURL(context.Background(), domain, repoName) + assert.Equal(t, distURL, expectedDistURL) + }) +}