From 903a6f1f77420ca00a0b77b1d052b46257fb5773 Mon Sep 17 00:00:00 2001 From: Cosmin Tupangiu Date: Tue, 25 Feb 2025 09:50:06 +0100 Subject: [PATCH] add proxy-url to agent's image This commit add a new model to hold all the agent's image parameters: proxy paramters, certificate_chain. For now, only proxy parameters are used in the ignition files. Signed-off-by: Cosmin Tupangiu --- api/v1alpha1/openapi.yaml | 14 +++ api/v1alpha1/spec.gen.go | 73 ++++++++-------- api/v1alpha1/types.gen.go | 13 ++- data/ignition.template | 9 ++ internal/cli/generate.go | 15 +++- internal/image/builder.go | 21 +++++ internal/image/ova.go | 3 + internal/service/mappers/inbound.go | 24 ++++++ internal/service/source.go | 19 +++++ internal/service/source_test.go | 66 ++++++++++++++ internal/store/image.go | 41 +++++++++ internal/store/image_test.go | 128 ++++++++++++++++++++++++++++ internal/store/model/image.go | 15 ++++ internal/store/model/source.go | 3 +- internal/store/source.go | 4 +- internal/store/store.go | 24 ++++-- 16 files changed, 424 insertions(+), 48 deletions(-) create mode 100644 internal/store/image.go create mode 100644 internal/store/image_test.go create mode 100644 internal/store/model/image.go diff --git a/api/v1alpha1/openapi.yaml b/api/v1alpha1/openapi.yaml index 24e09a12..283f3361 100644 --- a/api/v1alpha1/openapi.yaml +++ b/api/v1alpha1/openapi.yaml @@ -457,6 +457,16 @@ components: - updatedAt - onPremises + AgentProxy: + type: object + properties: + httpUrl: + type: string + httpsUrl: + type: string + noProxy: + type: string + SourceCreate: type: object properties: @@ -464,6 +474,10 @@ components: type: string sshPublicKey: type: string + proxy: + $ref: '#/components/schemas/AgentProxy' + certificateChain: + type: string required: - name diff --git a/api/v1alpha1/spec.gen.go b/api/v1alpha1/spec.gen.go index b3b3a025..1d46ee95 100644 --- a/api/v1alpha1/spec.gen.go +++ b/api/v1alpha1/spec.gen.go @@ -18,42 +18,43 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v1alpha1/types.gen.go b/api/v1alpha1/types.gen.go index bc12015c..ea17928e 100644 --- a/api/v1alpha1/types.gen.go +++ b/api/v1alpha1/types.gen.go @@ -41,6 +41,13 @@ type Agent struct { // AgentStatus defines model for Agent.Status. type AgentStatus string +// AgentProxy defines model for AgentProxy. +type AgentProxy struct { + HttpUrl *string `json:"httpUrl,omitempty"` + HttpsUrl *string `json:"httpsUrl,omitempty"` + NoProxy *string `json:"noProxy,omitempty"` +} + // Error defines model for Error. type Error struct { // Message Error message @@ -108,8 +115,10 @@ type Source struct { // SourceCreate defines model for SourceCreate. type SourceCreate struct { - Name string `json:"name"` - SshPublicKey *string `json:"sshPublicKey,omitempty"` + CertificateChain *string `json:"certificateChain,omitempty"` + Name string `json:"name"` + Proxy *AgentProxy `json:"proxy,omitempty"` + SshPublicKey *string `json:"sshPublicKey,omitempty"` } // SourceList defines model for SourceList. diff --git a/data/ignition.template b/data/ignition.template index 07ca5d24..cf062d79 100644 --- a/data/ignition.template +++ b/data/ignition.template @@ -137,6 +137,15 @@ storage: Volume=/home/core/.migration-planner:/agent:Z Volume=/var/lib/data:/agent/persistent-data:Z Environment=OPA_SERVER=127.0.0.1:8181 +{{ if .HttpProxyUrl }} + Environment=HTTP_PROXY={{ .HttpProxyUrl }} +{{ end }} +{{ if .HttpsProxyUrl }} + Environment=HTTPS_PROXY={{ .HttpsProxyUrl }} +{{ end }} +{{ if .NoProxyDomain }} + Environment=NO_PROXY={{ .NoProxyDomain }} +{{ end }} Network=host UserNS=keep-id:uid=1001 diff --git a/internal/cli/generate.go b/internal/cli/generate.go index 64fd4ad9..bf0ab4ae 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -19,6 +19,9 @@ type GenerateOptions struct { AgentImageURL string ServiceIP string OutputImageFilePath string + HttpProxyUrl string + HttpsProxyUrl string + NoProxyDomain string } func DefaultGenerateOptions() *GenerateOptions { @@ -90,6 +93,9 @@ func (o *GenerateOptions) Bind(fs *pflag.FlagSet) { fs.StringVarP(&o.ImageType, "image-type", "t", "ova", "Type of the image. Only accepts ova and iso") fs.StringVarP(&o.AgentImageURL, "agent-image-url", "u", "quay.io/kubev2v/migration-planner-agent:latest", "Quay url of the agent's image. Defaults to quay.io/kubev2v/migration-planner-agent:latest") fs.StringVarP(&o.OutputImageFilePath, "output-file", "o", "", "Output image file path") + fs.StringVarP(&o.HttpProxyUrl, "http-proxy", "", "", "Url of HTTP_PROXY") + fs.StringVarP(&o.HttpsProxyUrl, "https-proxy", "", "", "Url of HTTPS_PROXY") + fs.StringVarP(&o.NoProxyDomain, "no-proxy", "", "", "list of domains without proxy") } func (o *GenerateOptions) Run(ctx context.Context, args []string) error { @@ -109,7 +115,14 @@ func (o *GenerateOptions) Run(ctx context.Context, args []string) error { source := *resp.JSON200 - imageBuilder := image.NewImageBuilder(source.Id).WithPlannerAgentImage(o.AgentImageURL).WithPlannerService(o.ServiceIP) + imageBuilder := image.NewImageBuilder(source.Id). + WithPlannerAgentImage(o.AgentImageURL). + WithPlannerService(o.ServiceIP). + WithProxy(image.Proxy{ + HttpUrl: o.HttpProxyUrl, + HttpsUrl: o.HttpsProxyUrl, + NoProxyDomain: o.NoProxyDomain, + }) switch o.ImageType { case "iso": diff --git a/internal/image/builder.go b/internal/image/builder.go index fdb3aa5a..9db13097 100644 --- a/internal/image/builder.go +++ b/internal/image/builder.go @@ -38,9 +38,17 @@ const ( defaultRHCOSImage = "rhcos-live.x86_64.iso" ) +type Proxy struct { + HttpUrl string + HttpsUrl string + NoProxyDomain string +} + type ImageBuilder struct { SourceID string SshKey string + Proxy Proxy + CertificateChain string PlannerServiceUI string PlannerService string MigrationPlannerAgentImage string @@ -149,6 +157,9 @@ func (b *ImageBuilder) generateIgnition() (string, error) { InsecureRegistry: b.InsecureRegistry, Token: b.Token, PersistentDiskDevice: b.PersistentDiskDevice, + HttpProxyUrl: b.Proxy.HttpUrl, + HttpsProxyUrl: b.Proxy.HttpsUrl, + NoProxyDomain: b.Proxy.NoProxyDomain, } var buf bytes.Buffer @@ -347,3 +358,13 @@ func (b *ImageBuilder) WithImageType(imageType ImageType) *ImageBuilder { b.imageType = imageType return b } + +func (b *ImageBuilder) WithProxy(proxy Proxy) *ImageBuilder { + b.Proxy = proxy + return b +} + +func (b *ImageBuilder) WithCertificateChain(certs string) *ImageBuilder { + b.CertificateChain = certs + return b +} diff --git a/internal/image/ova.go b/internal/image/ova.go index a3da38bc..c07beb24 100644 --- a/internal/image/ova.go +++ b/internal/image/ova.go @@ -40,6 +40,9 @@ type IgnitionData struct { Token string PersistentDiskDevice string SourceID string + HttpProxyUrl string + HttpsProxyUrl string + NoProxyDomain string } type Image interface { diff --git a/internal/service/mappers/inbound.go b/internal/service/mappers/inbound.go index fb09e301..7a20a3ec 100644 --- a/internal/service/mappers/inbound.go +++ b/internal/service/mappers/inbound.go @@ -50,6 +50,30 @@ func SourceFromApi(id uuid.UUID, user auth.User, imageTokenKey string, resource return source } +func ImageInfraFromApi(sourceID uuid.UUID, resource *v1alpha1.CreateSourceJSONRequestBody) model.ImageInfra { + imageInfra := model.ImageInfra{ + SourceID: sourceID, + } + + if resource.Proxy != nil { + if resource.Proxy.HttpUrl != nil { + imageInfra.HttpProxyUrl = *resource.Proxy.HttpUrl + } + if resource.Proxy.HttpsUrl != nil { + imageInfra.HttpsProxyUrl = *resource.Proxy.HttpsUrl + } + if resource.Proxy.NoProxy != nil { + imageInfra.NoProxyDomains = *resource.Proxy.NoProxy + } + } + + if resource.CertificateChain != nil { + imageInfra.CertificateChain = *resource.CertificateChain + } + + return imageInfra +} + func UpdateSourceFromApi(m *model.Source, inventory api.Inventory) *model.Source { m.Inventory = model.MakeJSONField(inventory) m.VCenterID = inventory.Vcenter.Id diff --git a/internal/service/source.go b/internal/service/source.go index 5d42513e..7812357f 100644 --- a/internal/service/source.go +++ b/internal/service/source.go @@ -57,7 +57,13 @@ func (h *ServiceHandler) ListSources(ctx context.Context, request server.ListSou func (h *ServiceHandler) CreateSource(ctx context.Context, request server.CreateSourceRequestObject) (server.CreateSourceResponseObject, error) { user := auth.MustHaveUser(ctx) + ctx, err := h.store.NewTransactionContext(ctx) + if err != nil { + return server.CreateSource500JSONResponse{}, nil + } + // Generate a signing key for tokens for the source + // TODO: merge imageTokenKey and sshPublickKey with the rest of image infra imageTokenKey, err := image.HMACKey(32) if err != nil { return server.CreateSource400JSONResponse{Message: err.Error()}, nil @@ -69,6 +75,19 @@ func (h *ServiceHandler) CreateSource(ctx context.Context, request server.Create return server.CreateSource400JSONResponse{Message: err.Error()}, nil } + if request.Body.Proxy != nil || request.Body.CertificateChain != nil { + imageInfra := mappers.ImageInfraFromApi(source.ID, request.Body) + _, err := h.store.ImageInfra().Create(ctx, imageInfra) + if err != nil { + _, _ = store.Rollback(ctx) + return server.CreateSource500JSONResponse{}, nil + } + } + + if _, err := store.Commit(ctx); err != nil { + return server.CreateSource500JSONResponse{}, nil + } + return server.CreateSource201JSONResponse(mappers.SourceToApi(*result)), nil } diff --git a/internal/service/source_test.go b/internal/service/source_test.go index 2cf3bee2..7b100fb8 100644 --- a/internal/service/source_test.go +++ b/internal/service/source_test.go @@ -136,6 +136,72 @@ var _ = Describe("source handler", Ordered, func() { Expect(source.Name).To(Equal("test")) }) + It("successfully creates a source -- with proxy paramters defined", func() { + eventWriter := newTestWriter() + + user := auth.User{ + Username: "admin", + Organization: "admin", + } + ctx := auth.NewUserContext(context.TODO(), user) + + toStrPtr := func(s string) *string { + return &s + } + + srv := service.NewServiceHandler(s, events.NewEventProducer(eventWriter)) + resp, err := srv.CreateSource(ctx, server.CreateSourceRequestObject{ + Body: &v1alpha1.CreateSourceJSONRequestBody{ + Name: "test", + Proxy: &v1alpha1.AgentProxy{ + HttpUrl: toStrPtr("http"), + HttpsUrl: toStrPtr("https"), + NoProxy: toStrPtr("noproxy"), + }, + }, + }) + Expect(err).To(BeNil()) + source, ok := resp.(server.CreateSource201JSONResponse) + Expect(ok).To(BeTrue()) + Expect(source.Name).To(Equal("test")) + + count := 0 + tx := gormdb.Raw("SELECT COUNT(*) FROM image_infras;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(1)) + }) + + It("successfully creates a source -- with certificate chain defined", func() { + eventWriter := newTestWriter() + + user := auth.User{ + Username: "admin", + Organization: "admin", + } + ctx := auth.NewUserContext(context.TODO(), user) + + toStrPtr := func(s string) *string { + return &s + } + + srv := service.NewServiceHandler(s, events.NewEventProducer(eventWriter)) + resp, err := srv.CreateSource(ctx, server.CreateSourceRequestObject{ + Body: &v1alpha1.CreateSourceJSONRequestBody{ + Name: "test", + CertificateChain: toStrPtr("chain"), + }, + }) + Expect(err).To(BeNil()) + source, ok := resp.(server.CreateSource201JSONResponse) + Expect(ok).To(BeTrue()) + Expect(source.Name).To(Equal("test")) + + count := 0 + tx := gormdb.Raw("SELECT COUNT(*) FROM image_infras;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(1)) + }) + AfterEach(func() { gormdb.Exec("DELETE FROM agents;") gormdb.Exec("DELETE FROM sources;") diff --git a/internal/store/image.go b/internal/store/image.go new file mode 100644 index 00000000..7edf64bd --- /dev/null +++ b/internal/store/image.go @@ -0,0 +1,41 @@ +package store + +import ( + "context" + + "github.com/kubev2v/migration-planner/internal/store/model" + "gorm.io/gorm" +) + +type ImageInfra interface { + Create(ctx context.Context, imageInfra model.ImageInfra) (*model.ImageInfra, error) + InitialMigration(context.Context) error +} + +type ImageInfraStore struct { + db *gorm.DB +} + +func NewImageInfraStore(db *gorm.DB) ImageInfra { + return &ImageInfraStore{db: db} +} + +func (i *ImageInfraStore) InitialMigration(ctx context.Context) error { + return i.getDB(ctx).AutoMigrate(&model.ImageInfra{}) +} + +func (i *ImageInfraStore) Create(ctx context.Context, image model.ImageInfra) (*model.ImageInfra, error) { + if err := i.getDB(ctx).WithContext(ctx).Create(&image).Error; err != nil { + return nil, err + } + + return &image, nil +} + +func (i *ImageInfraStore) getDB(ctx context.Context) *gorm.DB { + tx := FromContext(ctx) + if tx != nil { + return tx + } + return i.db +} diff --git a/internal/store/image_test.go b/internal/store/image_test.go new file mode 100644 index 00000000..301f7f10 --- /dev/null +++ b/internal/store/image_test.go @@ -0,0 +1,128 @@ +package store_test + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/kubev2v/migration-planner/internal/config" + "github.com/kubev2v/migration-planner/internal/store" + "github.com/kubev2v/migration-planner/internal/store/model" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gorm.io/gorm" +) + +const ( + insertImageStm = "INSERT INTO image_infras (source_id, http_proxy_url, https_proxy_url, no_proxy_domains, certificate_chain) VALUES ('%s', '%s', '%s', '%s', '%s');" +) + +var _ = Describe("image infra store", Ordered, func() { + var ( + s store.Store + gormdb *gorm.DB + ) + + BeforeAll(func() { + cfg, err := config.NewDefault() + Expect(err).To(BeNil()) + db, err := store.InitDB(cfg) + Expect(err).To(BeNil()) + + s = store.NewStore(db) + gormdb = db + }) + + AfterAll(func() { + s.Close() + }) + + Context("create", func() { + It("successfully create an image", func() { + sourceID := uuid.New() + m := model.Source{ + ID: sourceID, + Username: "admin", + OrgID: "org", + } + source, err := s.Source().Create(context.TODO(), m) + Expect(err).To(BeNil()) + Expect(source).NotTo(BeNil()) + + var count int + tx := gormdb.Raw("SELECT COUNT(*) FROM sources;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(1)) + + // create the image + image := model.ImageInfra{ + SourceID: m.ID, + HttpProxyUrl: "http", + HttpsProxyUrl: "https", + NoProxyDomains: "noproxy", + CertificateChain: "certs", + } + + img, err := s.ImageInfra().Create(context.TODO(), image) + Expect(err).To(BeNil()) + Expect(img).ToNot(BeNil()) + + count = -1 + tx = gormdb.Raw("SELECT COUNT(*) FROM image_infras;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(1)) + }) + + AfterEach(func() { + gormdb.Exec("DELETE from sources;") + }) + }) + + Context("get", func() { + It("successfully get a source with image infra", func() { + sourceID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, sourceID, "source1", "admin", "admin")) + Expect(tx.Error).To(BeNil()) + + tx = gormdb.Exec(fmt.Sprintf(insertImageStm, sourceID, "http", "https", "noproxy", "certs")) + Expect(tx.Error).To(BeNil()) + + source, err := s.Source().Get(context.TODO(), sourceID) + Expect(err).To(BeNil()) + Expect(source).ToNot(BeNil()) + + Expect(source.ImageInfra.HttpProxyUrl).To(Equal("http")) + Expect(source.ImageInfra.HttpsProxyUrl).To(Equal("https")) + Expect(source.ImageInfra.NoProxyDomains).To(Equal("noproxy")) + Expect(source.ImageInfra.CertificateChain).To(Equal("certs")) + }) + + AfterEach(func() { + gormdb.Exec("DELETE from sources;") + }) + }) + + Context("list", func() { + It("successfully list sources with image infra", func() { + sourceID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, sourceID, "source1", "admin", "admin")) + Expect(tx.Error).To(BeNil()) + + tx = gormdb.Exec(fmt.Sprintf(insertImageStm, sourceID, "http", "https", "noproxy", "certs")) + Expect(tx.Error).To(BeNil()) + + sources, err := s.Source().List(context.TODO(), store.NewSourceQueryFilter()) + Expect(err).To(BeNil()) + Expect(sources).To(HaveLen(1)) + + Expect(sources[0].ImageInfra.HttpProxyUrl).To(Equal("http")) + Expect(sources[0].ImageInfra.HttpsProxyUrl).To(Equal("https")) + Expect(sources[0].ImageInfra.NoProxyDomains).To(Equal("noproxy")) + Expect(sources[0].ImageInfra.CertificateChain).To(Equal("certs")) + }) + + AfterEach(func() { + gormdb.Exec("DELETE from sources;") + }) + }) +}) diff --git a/internal/store/model/image.go b/internal/store/model/image.go new file mode 100644 index 00000000..0e5f6f7a --- /dev/null +++ b/internal/store/model/image.go @@ -0,0 +1,15 @@ +package model + +import ( + "github.com/google/uuid" + "gorm.io/gorm" +) + +type ImageInfra struct { + gorm.Model + SourceID uuid.UUID `gorm:"primaryKey"` + HttpProxyUrl string + HttpsProxyUrl string + NoProxyDomains string + CertificateChain string +} diff --git a/internal/store/model/source.go b/internal/store/model/source.go index ebea3f25..e1052769 100644 --- a/internal/store/model/source.go +++ b/internal/store/model/source.go @@ -19,7 +19,8 @@ type Source struct { OnPremises bool SshPublicKey *string ImageTokenKey string - Agents []Agent `gorm:"constraint:OnDelete:CASCADE;"` + Agents []Agent `gorm:"constraint:OnDelete:CASCADE;"` + ImageInfra ImageInfra `gorm:"constraint:OnDelete:CASCADE;"` } type SourceList []Source diff --git a/internal/store/source.go b/internal/store/source.go index bc506fa0..7162a35f 100644 --- a/internal/store/source.go +++ b/internal/store/source.go @@ -42,7 +42,7 @@ func (s *SourceStore) InitialMigration(ctx context.Context) error { func (s *SourceStore) List(ctx context.Context, filter *SourceQueryFilter) (model.SourceList, error) { var sources model.SourceList - tx := s.getDB(ctx).Model(&sources).Order("id").Preload("Agents") + tx := s.getDB(ctx).Model(&sources).Order("id").Preload("Agents").Preload("ImageInfra") if filter != nil { for _, fn := range filter.QueryFn { @@ -72,7 +72,7 @@ func (s *SourceStore) DeleteAll(ctx context.Context) error { func (s *SourceStore) Get(ctx context.Context, id uuid.UUID) (*model.Source, error) { source := model.Source{ID: id} - result := s.getDB(ctx).Preload("Agents").First(&source) + result := s.getDB(ctx).Preload("Agents").Preload("ImageInfra").First(&source) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, ErrRecordNotFound diff --git a/internal/store/store.go b/internal/store/store.go index a96b9c14..3297891d 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -13,22 +13,25 @@ type Store interface { NewTransactionContext(ctx context.Context) (context.Context, error) Agent() Agent Source() Source + ImageInfra() ImageInfra Seed() error InitialMigration() error Close() error } type DataStore struct { - agent Agent - db *gorm.DB - source Source + agent Agent + db *gorm.DB + source Source + imageInfra ImageInfra } func NewStore(db *gorm.DB) Store { return &DataStore{ - agent: NewAgentSource(db), - source: NewSource(db), - db: db, + agent: NewAgentSource(db), + source: NewSource(db), + imageInfra: NewImageInfraStore(db), + db: db, } } @@ -44,6 +47,10 @@ func (s *DataStore) Agent() Agent { return s.agent } +func (s *DataStore) ImageInfra() ImageInfra { + return s.imageInfra +} + func (s *DataStore) InitialMigration() error { ctx, err := s.NewTransactionContext(context.Background()) if err != nil { @@ -58,6 +65,11 @@ func (s *DataStore) InitialMigration() error { if err := s.Agent().InitialMigration(ctx); err != nil { return err } + + if err := s.ImageInfra().InitialMigration(ctx); err != nil { + return err + } + _, err = Commit(ctx) return err }