From 94e6ecaa14a2ec3445c0fcf3fb5bcbf85dda4188 Mon Sep 17 00:00:00 2001 From: Djebran Lezzoum Date: Wed, 4 Oct 2023 18:34:22 +0200 Subject: [PATCH] groups-migration: edge-parity groups migration. In the context of edge-parity implement the groups migration script. FIXES: https://issues.redhat.com/browse/THEEDGE-3537 --- Dockerfile | 2 + cmd/migrategroups/main.go | 71 +++++ .../migrategroups/migrategroups.go | 245 ++++++++++++++++++ .../migrategroups/migrategroups_suite_test.go | 40 +++ .../migrategroups/migrategroups_test.go | 215 +++++++++++++++ deploy/clowdapp.yaml | 6 + pkg/clients/inventorygroups/client.go | 6 +- .../mock_inventorygroups/client.go | 126 +++++++++ unleash/features/feature.go | 3 + 9 files changed, 711 insertions(+), 3 deletions(-) create mode 100644 cmd/migrategroups/main.go create mode 100644 cmd/migrategroups/migrategroups/migrategroups.go create mode 100644 cmd/migrategroups/migrategroups/migrategroups_suite_test.go create mode 100644 cmd/migrategroups/migrategroups/migrategroups_test.go create mode 100644 pkg/clients/inventorygroups/mock_inventorygroups/client.go diff --git a/Dockerfile b/Dockerfile index b6875d3b7..b5344a34e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN go build -o /go/bin/edge-api-migrate cmd/migrate/main.go RUN go build -o /go/bin/edge-api-wipe cmd/db/wipe.go RUN go build -o /go/bin/edge-api-migrate-device cmd/db/updDb/set_account_on_device.go RUN go build -o /go/bin/edge-api-migrate-repositories cmd/migraterepos/main.go +RUN go build -o /go/bin/edge-api-migrate-groups cmd/migrategroups/main.go # Run the doc binary RUN go install github.com/swaggo/swag/cmd/swag@latest @@ -82,6 +83,7 @@ COPY --from=edge-builder /go/bin/edge-api-migrate /usr/bin COPY --from=edge-builder /go/bin/edge-api-wipe /usr/bin COPY --from=edge-builder /go/bin/edge-api-migrate-device /usr/bin COPY --from=edge-builder /go/bin/edge-api-migrate-repositories /usr/bin +COPY --from=edge-builder /go/bin/edge-api-migrate-groups /usr/bin COPY --from=edge-builder /go/bin/edge-api-ibvents /usr/bin COPY --from=edge-builder /go/bin/edge-api-images-build /usr/bin COPY --from=edge-builder /go/bin/edge-api-isos-build /usr/bin diff --git a/cmd/migrategroups/main.go b/cmd/migrategroups/main.go new file mode 100644 index 000000000..2c78bd0a6 --- /dev/null +++ b/cmd/migrategroups/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "time" + + "github.com/redhatinsights/edge-api/cmd/migrategroups/migrategroups" + "github.com/redhatinsights/edge-api/config" + "github.com/redhatinsights/edge-api/logger" + "github.com/redhatinsights/edge-api/pkg/db" + edgeunleash "github.com/redhatinsights/edge-api/unleash" + + "github.com/Unleash/unleash-client-go/v3" + log "github.com/sirupsen/logrus" +) + +func initializeUnleash() { + cfg := config.Get() + if cfg.FeatureFlagsURL != "" { + err := unleash.Initialize( + unleash.WithListener(&edgeunleash.EdgeListener{}), + unleash.WithAppName("edge-api"), + unleash.WithUrl(cfg.UnleashURL), + unleash.WithRefreshInterval(5*time.Second), + unleash.WithMetricsInterval(5*time.Second), + unleash.WithCustomHeaders(http.Header{"Authorization": {fmt.Sprintf("Bearer %s", cfg.UnleashSecretName)}}), + ) + if err != nil { + log.WithField("Error", err).Error("Unleash client failed to initialize") + } else { + log.WithField("FeatureFlagURL", cfg.UnleashURL).Info("Unleash client initialized successfully") + } + } else { + log.WithField("FeatureFlagURL", cfg.UnleashURL).Warning("FeatureFlag service initialization was skipped.") + } +} + +func initConfiguration() { + config.Init() + logger.InitLogger(os.Stdout) + cfg := config.Get() + config.LogConfigAtStartup(cfg) + db.InitDB() + initializeUnleash() +} + +func flushLogAndExit(err error) { + // flush logger before app exit + logger.FlushLogger() + if err != nil { + os.Exit(2) + } + os.Exit(0) +} + +func main() { + initConfiguration() + // wait for 5 seconds, for the unleash client to refresh + time.Sleep(5 * time.Second) + + var mainErr error + + if err := migrategroups.MigrateAllGroups(db.DB); err != nil && err != migrategroups.ErrMigrationFeatureNotAvailable { + // consider error only if feature flag is enabled + mainErr = err + } + + flushLogAndExit(mainErr) +} diff --git a/cmd/migrategroups/migrategroups/migrategroups.go b/cmd/migrategroups/migrategroups/migrategroups.go new file mode 100644 index 000000000..c368278c8 --- /dev/null +++ b/cmd/migrategroups/migrategroups/migrategroups.go @@ -0,0 +1,245 @@ +package migrategroups + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" + "github.com/redhatinsights/edge-api/pkg/db" + "github.com/redhatinsights/edge-api/pkg/models" + "github.com/redhatinsights/edge-api/pkg/routes/common" + feature "github.com/redhatinsights/edge-api/unleash/features" + + "github.com/redhatinsights/platform-go-middlewares/identity" + "github.com/redhatinsights/platform-go-middlewares/request_id" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +// ErrMigrationFeatureNotAvailable error returned when the migration feature flag is disabled +var ErrMigrationFeatureNotAvailable = errors.New("groups migrations is not available") + +// ErrOrgIDIsMandatory error returned when the org_id with empty value is passed +var ErrOrgIDIsMandatory = errors.New("org_id is mandatory") + +// ErrInventoryGroupAlreadyExist error returned when trying to migrate an already existing group +var ErrInventoryGroupAlreadyExist = errors.New("inventory group already exist") + +// DefaultDataLimit the default data limit to use when collecting data +var DefaultDataLimit = 100 + +// DefaultMaxDataPageNumber the default data pages to handle as preventive way to enter an indefinite loop +var DefaultMaxDataPageNumber = 100 + +// DefaultIdentityType the default identity type used in header when requesting inventory groups end-point +var DefaultIdentityType = "System" + +// OrgsGroupsFilter the filter added to filter an organization groups (if the org_id is defined in the map as a key) +var OrgsGroupsFilter = map[string][]interface{}{ + "11789772": {"device_groups.name LIKE ?", "%-Store-%"}, +} + +// DefaultOrgsIDS if the slice is not empty, only the organizations with this ids will be taken into account when migrating +var DefaultOrgsIDS = []string{ + "11789772", +} + +// OrgCandidate the org candidate queried from the database +type OrgCandidate struct { + OrgID string `json:"org_id"` + GroupsCount int `json:"groups_count"` + DevicesCount int `json:"devices_count"` +} + +// GetNewInventoryGroupClient the function to get the client to inventory groups end-point, for testing convenience +var GetNewInventoryGroupClient = inventorygroups.InitClient + +func newInventoryGroupsOrgClient(orgID string) (inventorygroups.ClientInterface, error) { + // create a new inventory-groups client and set organization identity in the initialization context + ident := identity.XRHID{Identity: identity.Identity{ + OrgID: orgID, + Type: DefaultIdentityType, + Internal: identity.Internal{OrgID: orgID}, + }} + jsonIdent, err := json.Marshal(&ident) + if err != nil { + return nil, err + } + base64Identity := base64.StdEncoding.EncodeToString(jsonIdent) + + ctx := context.Background() + ctx = context.WithValue(ctx, request_id.RequestIDKey, uuid.NewString()) + ctx = common.SetOriginalIdentity(ctx, base64Identity) + client := GetNewInventoryGroupClient(ctx, log.NewEntry(log.StandardLogger())) + + return client, nil +} + +func createInventoryGroup(client inventorygroups.ClientInterface, edgeGroup models.DeviceGroup) error { + groupsHosts := make([]string, 0, len(edgeGroup.Devices)) + for _, device := range edgeGroup.Devices { + if device.UUID == "" { + continue + } + groupsHosts = append(groupsHosts, device.UUID) + } + logger := log.WithFields(log.Fields{ + "context": "org-group-migration", + "org_id": edgeGroup.OrgID, + "group_name": edgeGroup.Name, + "hosts": groupsHosts, + }) + + logger.Info("inventory group create started") + inventoryGroup, err := client.CreateGroup(edgeGroup.Name, groupsHosts) + if err != nil { + logger.WithField("error", err.Error()).Info("error occurred while creating inventory group") + return err + } + + // update edge group with inventory group id + edgeGroup.UUID = inventoryGroup.ID + if err := db.DB.Omit("Devices").Save(&edgeGroup).Error; err != nil { + logger.WithField("error", err.Error()).Info("error occurred saving local edge group") + return err + } + + logger.Info("inventory group finished successfully") + return nil +} + +func migrateGroup(client inventorygroups.ClientInterface, edgeGroup models.DeviceGroup) error { + logger := log.WithFields(log.Fields{ + "context": "org-group-migration", + "org_id": edgeGroup.OrgID, + "group_name": edgeGroup.Name, + }) + logger.Info("group migration started") + + // check if group exist in inventory group + if _, err := client.GetGroupByName(edgeGroup.Name); err != nil && err != inventorygroups.ErrGroupNotFound { + logger.WithField("error", err.Error()).Error("unknown error occurred while getting inventory group") + return err + } else if err == nil { + // inventory group should not exist to continue + logger.Error("group already exists") + return ErrInventoryGroupAlreadyExist + } + + if err := createInventoryGroup(client, edgeGroup); err != nil { + logger.WithField("error", err.Error()).Error("error occurred while creating inventory group") + return err + } + logger.Info("group migration finished") + return nil +} + +func migrateOrgGroups(orgID string, gormDB *gorm.DB) error { + if orgID == "" { + return ErrOrgIDIsMandatory + } + + logger := log.WithFields(log.Fields{"context": "org-groups-migration", "org_id": orgID}) + logger.Info("organization groups migration started") + + client, err := newInventoryGroupsOrgClient(orgID) + if err != nil { + logger.WithField("error", err.Error()).Error("error occurred while creating organization inventory-groups client") + return err + } + + // get all org groups + var orgGroupsToMigrate []models.DeviceGroup + baseQuery := db.OrgDB(orgID, gormDB, "device_groups").Debug().Where("device_groups.uuid IS NULL OR device_groups.uuid = ''") + if orgGroupsFilter, ok := OrgsGroupsFilter[orgID]; ok && len(orgGroupsFilter) > 0 { + query, args := orgGroupsFilter[0], orgGroupsFilter[1:] + baseQuery = baseQuery.Where(query, args...) + } + + if err := baseQuery.Preload("Devices").Order("created_at").Find(&orgGroupsToMigrate).Error; err != nil { + return err + } + + logger = log.WithField("groups_count", len(orgGroupsToMigrate)) + + for _, group := range orgGroupsToMigrate { + if err := migrateGroup(client, group); err != nil { + return err + } + } + + logger.Info("organization groups migration finished") + return nil +} + +func getAllOrgs(gormDB *gorm.DB) ([]OrgCandidate, error) { + var orgsData []OrgCandidate + baseQuery := gormDB.Debug().Table("device_groups"). + Select("device_groups.org_id as org_id, count(distinct(device_groups.name)) as groups_count, count(distinct(devices.id)) as devices_count"). + Joins("LEFT JOIN device_groups_devices ON device_groups_devices.device_group_id = device_groups.id"). + Joins("LEFT JOIN devices ON devices.id = device_groups_devices.device_id"). + Where("device_groups.uuid IS NULL OR device_groups.uuid = ''"). // consider only orgs with empty inventory group id + Where("device_groups.deleted_at IS NULL"). // with non deleted groups + Where("devices.deleted_at IS NULL"). // with non deleted devices + Where("devices.id IS NOT NULL") // we take only groups with hosts + + if len(DefaultOrgsIDS) > 0 { + baseQuery = baseQuery.Where("device_groups.org_id IN (?)", DefaultOrgsIDS) + } + if err := baseQuery.Group("device_groups.org_id"). + Order("device_groups.org_id"). + Limit(DefaultDataLimit). + Scan(&orgsData).Error; err != nil { + return nil, err + } + + return orgsData, nil +} + +func MigrateAllGroups(gormDB *gorm.DB) error { + logger := log.WithField("context", "orgs-groups-migration") + if !feature.EdgeParityGroupsMigration.IsEnabled() { + logger.Info("group migration feature is disabled, migration is not available") + return ErrMigrationFeatureNotAvailable + + } + if gormDB == nil { + gormDB = db.DB + } + + page := 0 + orgsCount := 0 + for page < DefaultMaxDataPageNumber { + orgsToMigrate, err := getAllOrgs(gormDB) + if err != nil { + logger.WithField("error", err.Error()).Error("error occurred while getting orgs to migrate") + return err + } + if len(orgsToMigrate) == 0 { + break + } + for _, orgToMigrate := range orgsToMigrate { + orgLogger := logger.WithFields(log.Fields{ + "org_id": orgToMigrate.OrgID, + "groups_count": orgToMigrate.GroupsCount, + "devices_count": orgToMigrate.DevicesCount, + }) + orgLogger.Info("starting migration of organization groups") + err := migrateOrgGroups(orgToMigrate.OrgID, gormDB) + if err != nil { + orgLogger.WithField("error", err.Error()).Error("error occurred while migrating organization groups") + return err + } + + } + orgsCount += len(orgsToMigrate) + page++ + } + + logger.WithFields(log.Fields{"orgs_count": orgsCount}).Info("migration of organizations groups finished") + return nil +} diff --git a/cmd/migrategroups/migrategroups/migrategroups_suite_test.go b/cmd/migrategroups/migrategroups/migrategroups_suite_test.go new file mode 100644 index 000000000..f4e5b62ff --- /dev/null +++ b/cmd/migrategroups/migrategroups/migrategroups_suite_test.go @@ -0,0 +1,40 @@ +package migrategroups_test + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/redhatinsights/edge-api/config" + "github.com/redhatinsights/edge-api/pkg/db" + "github.com/redhatinsights/edge-api/pkg/models" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestMigrate(t *testing.T) { + RegisterFailHandler(Fail) + dbName := setupTestDB() + defer tearDownTestDB(dbName) + RunSpecs(t, "Migrate device groups Suite") +} + +func setupTestDB() string { + config.Init() + dbName := fmt.Sprintf("%d-migrategroups.db", time.Now().UnixNano()) + config.Get().Database.Name = dbName + db.InitDB() + if err := db.DB.AutoMigrate( + &models.DeviceGroup{}, + &models.Device{}, + ); err != nil { + panic(err) + } + return dbName +} + +func tearDownTestDB(dbName string) { + _ = os.Remove(dbName) +} diff --git a/cmd/migrategroups/migrategroups/migrategroups_test.go b/cmd/migrategroups/migrategroups/migrategroups_test.go new file mode 100644 index 000000000..b886359bb --- /dev/null +++ b/cmd/migrategroups/migrategroups/migrategroups_test.go @@ -0,0 +1,215 @@ +package migrategroups_test + +import ( + "context" + "errors" + "os" + + "github.com/redhatinsights/edge-api/cmd/migrategroups/migrategroups" + + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" + "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups/mock_inventorygroups" + "github.com/redhatinsights/edge-api/pkg/db" + "github.com/redhatinsights/edge-api/pkg/models" + "github.com/redhatinsights/edge-api/pkg/routes/common" + + feature "github.com/redhatinsights/edge-api/unleash/features" + + "github.com/bxcodec/faker/v3" + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" +) + +var _ = Describe("Migrate device groups", func() { + + Context("feature flag disabled", func() { + BeforeEach(func() { + // ensure migration feature is disabled, feature should be disabled by default + err := os.Unsetenv(feature.EdgeParityGroupsMigration.EnvVar) + Expect(err).ToNot(HaveOccurred()) + }) + + It("migrate groups should not be available", func() { + err := migrategroups.MigrateAllGroups(nil) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(migrategroups.ErrMigrationFeatureNotAvailable)) + }) + }) + + Context("feature flag enabled", func() { + var ctrl *gomock.Controller + var mockInventoryGroupClient *mock_inventorygroups.MockClientInterface + + BeforeEach(func() { + err := os.Setenv(feature.EdgeParityGroupsMigration.EnvVar, "true") + Expect(err).ToNot(HaveOccurred()) + + ctrl = gomock.NewController(GinkgoT()) + mockInventoryGroupClient = mock_inventorygroups.NewMockClientInterface(ctrl) + }) + + AfterEach(func() { + ctrl.Finish() + err := os.Unsetenv(feature.EdgeParityGroupsMigration.EnvVar) + Expect(err).ToNot(HaveOccurred()) + + }) + + Context("MigrateAllGroups", func() { + var orgID string + var group models.DeviceGroup + var otherGroup models.DeviceGroup + var initialDefaultOrgsIDS []string + var initialOrgsGroupsFilter map[string][]any + + BeforeEach(func() { + initialDefaultOrgsIDS = migrategroups.DefaultOrgsIDS + initialOrgsGroupsFilter = migrategroups.OrgsGroupsFilter + + if orgID == "" { + orgID = faker.UUIDHyphenated() + + group = models.DeviceGroup{ + OrgID: orgID, + Name: faker.Name(), + Devices: []models.Device{ + { + OrgID: orgID, + Name: faker.Name(), + UUID: faker.UUIDHyphenated(), + }, + { + OrgID: orgID, + Name: faker.Name(), + UUID: "", + }, + }, + } + err := db.DB.Create(&group).Error + Expect(err).ToNot(HaveOccurred()) + + // create another group + otherGroup = models.DeviceGroup{ + OrgID: orgID, + Name: faker.Name(), + Devices: []models.Device{{OrgID: orgID, Name: faker.Name(), UUID: faker.UUIDHyphenated()}}, + } + err = db.DB.Create(&otherGroup).Error + Expect(err).ToNot(HaveOccurred()) + } + + migrategroups.DefaultOrgsIDS = []string{orgID} + migrategroups.OrgsGroupsFilter = map[string][]any{ + orgID: {"device_groups.name = ?", group.Name}, + } + + }) + + AfterEach(func() { + migrategroups.DefaultOrgsIDS = initialDefaultOrgsIDS + migrategroups.OrgsGroupsFilter = initialOrgsGroupsFilter + migrategroups.GetNewInventoryGroupClient = inventorygroups.InitClient + }) + + It("should migrate group successfully", func() { + migrategroups.GetNewInventoryGroupClient = func(ctx context.Context, log *log.Entry) inventorygroups.ClientInterface { + rhIndent, err := common.GetIdentityInstanceFromContext(ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(rhIndent.Identity.OrgID).To(Equal(orgID)) + Expect(rhIndent.Identity.Type).To(Equal(migrategroups.DefaultIdentityType)) + return mockInventoryGroupClient + } + + expectedInventoryGroup := inventorygroups.Group{ + ID: faker.UUIDHyphenated(), + Name: group.Name, + OrgID: orgID, + } + + mockInventoryGroupClient.EXPECT().GetGroupByName(group.Name).Return(nil, inventorygroups.ErrGroupNotFound).Times(1) + mockInventoryGroupClient.EXPECT().CreateGroup(group.Name, []string{group.Devices[0].UUID}).Return(&expectedInventoryGroup, nil).Times(1) + + err := migrategroups.MigrateAllGroups(db.DB) + Expect(err).ToNot(HaveOccurred()) + + // reload group from db + err = db.DB.First(&group).Error + Expect(err).ToNot(HaveOccurred()) + Expect(group.UUID).To(Equal(expectedInventoryGroup.ID)) + }) + + When("on failure", func() { + BeforeEach(func() { + migrategroups.OrgsGroupsFilter = map[string][]any{ + orgID: {"device_groups.name = ?", otherGroup.Name}, + } + migrategroups.GetNewInventoryGroupClient = func(ctx context.Context, log *log.Entry) inventorygroups.ClientInterface { + return mockInventoryGroupClient + } + }) + + It("should return error when inventory group exist with same name", func() { + expectedInventoryGroup := inventorygroups.Group{ + ID: faker.UUIDHyphenated(), + Name: otherGroup.Name, + OrgID: orgID, + } + mockInventoryGroupClient.EXPECT().GetGroupByName(otherGroup.Name).Return(&expectedInventoryGroup, nil).Times(1) + err := migrategroups.MigrateAllGroups(db.DB) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(migrategroups.ErrInventoryGroupAlreadyExist)) + }) + + It("should return error when GetGroupByName fails", func() { + expectedError := errors.New("expected error when getting inventory group by name") + mockInventoryGroupClient.EXPECT().GetGroupByName(otherGroup.Name).Return(nil, expectedError).Times(1) + err := migrategroups.MigrateAllGroups(db.DB) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(expectedError)) + }) + + It("should return error when CreateGroup fails", func() { + expectedError := errors.New("expected error when creating inventory group") + mockInventoryGroupClient.EXPECT().GetGroupByName(otherGroup.Name).Return(nil, inventorygroups.ErrGroupNotFound).Times(1) + mockInventoryGroupClient.EXPECT().CreateGroup(otherGroup.Name, []string{otherGroup.Devices[0].UUID}).Return(nil, expectedError).Times(1) + + err := migrategroups.MigrateAllGroups(db.DB) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(expectedError)) + }) + }) + + When("orgID not in groups filter", func() { + BeforeEach(func() { + migrategroups.OrgsGroupsFilter = map[string][]any{ + faker.UUIDHyphenated(): {"device_groups.name = ?", group.Name}, + } + migrategroups.GetNewInventoryGroupClient = func(ctx context.Context, log *log.Entry) inventorygroups.ClientInterface { + return mockInventoryGroupClient + } + }) + + It("group should be migrated successfully", func() { + expectedInventoryGroup := inventorygroups.Group{ + ID: faker.UUIDHyphenated(), + Name: otherGroup.Name, + OrgID: orgID, + } + + mockInventoryGroupClient.EXPECT().GetGroupByName(otherGroup.Name).Return(nil, inventorygroups.ErrGroupNotFound).Times(1) + mockInventoryGroupClient.EXPECT().CreateGroup(otherGroup.Name, []string{otherGroup.Devices[0].UUID}).Return(&expectedInventoryGroup, nil).Times(1) + + err := migrategroups.MigrateAllGroups(db.DB) + Expect(err).ToNot(HaveOccurred()) + + // reload group from db + err = db.DB.First(&otherGroup).Error + Expect(err).ToNot(HaveOccurred()) + Expect(otherGroup.UUID).To(Equal(expectedInventoryGroup.ID)) + }) + }) + }) + }) +}) diff --git a/deploy/clowdapp.yaml b/deploy/clowdapp.yaml index 056231615..46e16efe0 100644 --- a/deploy/clowdapp.yaml +++ b/deploy/clowdapp.yaml @@ -52,6 +52,12 @@ objects: - -c - edge-api-migrate-repositories inheritEnv: true + - name: run-groups-migration + command: + - bash + - -c + - edge-api-migrate-groups + inheritEnv: true livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/clients/inventorygroups/client.go b/pkg/clients/inventorygroups/client.go index 286a60ef9..35966ed65 100644 --- a/pkg/clients/inventorygroups/client.go +++ b/pkg/clients/inventorygroups/client.go @@ -77,9 +77,9 @@ type ClientInterface interface { GetBaseURL() (*url2.URL, error) GetGroupByName(name string) (*Group, error) GetGroupByUUID(groupUUID string) (*Group, error) - CreateGroup(groupName string) (*Group, error) + CreateGroup(groupName string, hostIDS []string) (*Group, error) AddHostsToGroup(groupUUID string, hosts []string) (*Group, error) - ListGroups(requestParams ListGroupsParams) ([]Group, error) + ListGroups(requestParams ListGroupsParams) (*Response, error) } // Client is the implementation of an ClientInterface @@ -89,7 +89,7 @@ type Client struct { } // InitClient initializes the client for Image Builder -func InitClient(ctx context.Context, log *log.Entry) *Client { +func InitClient(ctx context.Context, log *log.Entry) ClientInterface { return &Client{ctx: ctx, log: log} } diff --git a/pkg/clients/inventorygroups/mock_inventorygroups/client.go b/pkg/clients/inventorygroups/mock_inventorygroups/client.go new file mode 100644 index 000000000..d2873027a --- /dev/null +++ b/pkg/clients/inventorygroups/mock_inventorygroups/client.go @@ -0,0 +1,126 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/clients/inventorygroups/client.go + +// Package mock_inventorygroups is a generated GoMock package. +package mock_inventorygroups + +import ( + url "net/url" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + inventorygroups "github.com/redhatinsights/edge-api/pkg/clients/inventorygroups" +) + +// MockClientInterface is a mock of ClientInterface interface. +type MockClientInterface struct { + ctrl *gomock.Controller + recorder *MockClientInterfaceMockRecorder +} + +// MockClientInterfaceMockRecorder is the mock recorder for MockClientInterface. +type MockClientInterfaceMockRecorder struct { + mock *MockClientInterface +} + +// NewMockClientInterface creates a new mock instance. +func NewMockClientInterface(ctrl *gomock.Controller) *MockClientInterface { + mock := &MockClientInterface{ctrl: ctrl} + mock.recorder = &MockClientInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClientInterface) EXPECT() *MockClientInterfaceMockRecorder { + return m.recorder +} + +// AddHostsToGroup mocks base method. +func (m *MockClientInterface) AddHostsToGroup(groupUUID string, hosts []string) (*inventorygroups.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddHostsToGroup", groupUUID, hosts) + ret0, _ := ret[0].(*inventorygroups.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddHostsToGroup indicates an expected call of AddHostsToGroup. +func (mr *MockClientInterfaceMockRecorder) AddHostsToGroup(groupUUID, hosts interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHostsToGroup", reflect.TypeOf((*MockClientInterface)(nil).AddHostsToGroup), groupUUID, hosts) +} + +// CreateGroup mocks base method. +func (m *MockClientInterface) CreateGroup(groupName string, hostIDS []string) (*inventorygroups.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGroup", groupName, hostIDS) + ret0, _ := ret[0].(*inventorygroups.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateGroup indicates an expected call of CreateGroup. +func (mr *MockClientInterfaceMockRecorder) CreateGroup(groupName, hostIDS interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroup", reflect.TypeOf((*MockClientInterface)(nil).CreateGroup), groupName, hostIDS) +} + +// GetBaseURL mocks base method. +func (m *MockClientInterface) GetBaseURL() (*url.URL, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBaseURL") + ret0, _ := ret[0].(*url.URL) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBaseURL indicates an expected call of GetBaseURL. +func (mr *MockClientInterfaceMockRecorder) GetBaseURL() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseURL", reflect.TypeOf((*MockClientInterface)(nil).GetBaseURL)) +} + +// GetGroupByName mocks base method. +func (m *MockClientInterface) GetGroupByName(name string) (*inventorygroups.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroupByName", name) + ret0, _ := ret[0].(*inventorygroups.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGroupByName indicates an expected call of GetGroupByName. +func (mr *MockClientInterfaceMockRecorder) GetGroupByName(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupByName", reflect.TypeOf((*MockClientInterface)(nil).GetGroupByName), name) +} + +// GetGroupByUUID mocks base method. +func (m *MockClientInterface) GetGroupByUUID(groupUUID string) (*inventorygroups.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroupByUUID", groupUUID) + ret0, _ := ret[0].(*inventorygroups.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGroupByUUID indicates an expected call of GetGroupByUUID. +func (mr *MockClientInterfaceMockRecorder) GetGroupByUUID(groupUUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupByUUID", reflect.TypeOf((*MockClientInterface)(nil).GetGroupByUUID), groupUUID) +} + +// ListGroups mocks base method. +func (m *MockClientInterface) ListGroups(requestParams inventorygroups.ListGroupsParams) (*inventorygroups.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListGroups", requestParams) + ret0, _ := ret[0].(*inventorygroups.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListGroups indicates an expected call of ListGroups. +func (mr *MockClientInterfaceMockRecorder) ListGroups(requestParams interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListGroups", reflect.TypeOf((*MockClientInterface)(nil).ListGroups), requestParams) +} diff --git a/unleash/features/feature.go b/unleash/features/feature.go index 1da5d9b80..0a6e90c07 100644 --- a/unleash/features/feature.go +++ b/unleash/features/feature.go @@ -119,6 +119,9 @@ var StaticDeltaGenerate = &Flag{Name: "edge-management.static_delta_generate", E // CreateGroup toggles creation of static deltas var HideCreateGroup = &Flag{Name: "edge-management.hide-create-group", EnvVar: "FEATURE_HIDE_CREATE_GROUP"} +// EdgeParityGroupsMigration toggles edge parity groups migration +var EdgeParityGroupsMigration = &Flag{Name: "edgeParity.groups-migration", EnvVar: "FEATURE_EDGE-PARITY-GROUPS-MIGRATION"} + // DB LOGGING FLAGS // SilentGormLogging toggles noisy logging from Gorm (using for tests during development on slow machines/connections