From 967c2e8ef37016892052ed545704b3ee44f77dc3 Mon Sep 17 00:00:00 2001 From: nirapx <51790021+nirapx@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:39:37 +0300 Subject: [PATCH 1/4] Postgres seed --- seed/postgres/component.go | 144 +++++++++++++++++++++++++++++++++++++ seed/postgres/config.go | 25 +++++++ 2 files changed, 169 insertions(+) create mode 100644 seed/postgres/component.go create mode 100644 seed/postgres/config.go diff --git a/seed/postgres/component.go b/seed/postgres/component.go new file mode 100644 index 0000000..b0acac9 --- /dev/null +++ b/seed/postgres/component.go @@ -0,0 +1,144 @@ +package postgres + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/perimeterx/envite" +) + +// ComponentType represents the type of the Postgres seed component. +const ComponentType = "postgres seed" + +// SeedComponent is a component for seeding Postgres with data. +type SeedComponent struct { + lock sync.Mutex + config SeedConfig + status atomic.Value + writer *envite.Writer +} + +// NewSeedComponent creates a new SeedComponent instance. +func NewSeedComponent(config SeedConfig) *SeedComponent { + m := &SeedComponent{config: config} + m.status.Store(envite.ComponentStatusStopped) + return m +} + +func (m *SeedComponent) Type() string { + return ComponentType +} + +func (m *SeedComponent) AttachEnvironment(_ context.Context, _ *envite.Environment, writer *envite.Writer) error { + m.writer = writer + return nil +} + +func (m *SeedComponent) Prepare(context.Context) error { + return nil +} + +func (m *SeedComponent) Start(ctx context.Context) error { + m.lock.Lock() + defer m.lock.Unlock() + + m.status.Store(envite.ComponentStatusStarting) + + err := m.Seed() + if err != nil { + m.status.Store(envite.ComponentStatusFailed) + return err + } + + m.status.Store(envite.ComponentStatusFinished) + + return nil +} + +func (m *SeedComponent) Seed() error { + if m.writer != nil { + m.writer.WriteString("starting postgres seed") + } + + client, err := m.clientProvider() + if err != nil { + return err + } + + if _, err = client.Exec(m.config.Setup); err != nil { + return err + } + + for _, collection := range m.config.Data { + + if _, err = client.Exec(fmt.Sprintf("DELETE FROM %s", collection.Table)); err != nil { + return err + } + + for _, row := range collection.Rows { + sql, values := generateInsertSQL(collection.Table, row) + _, err := client.Exec(sql, values...) + if err != nil { + return err + } + } + + if m.writer != nil { + m.writer.WriteString(fmt.Sprintf( + "inserted %s rows to %s", + m.writer.Color.Green(strconv.Itoa(len(collection.Rows))), + m.writer.Color.Cyan(collection.Table), + )) + } + } + + return nil +} + +func (m *SeedComponent) clientProvider() (*sql.DB, error) { + return m.config.ClientProvider() +} + +func (m *SeedComponent) Stop(context.Context) error { + m.status.Store(envite.ComponentStatusStopped) + return nil +} + +func (m *SeedComponent) Cleanup(context.Context) error { + return nil +} + +func (m *SeedComponent) Status(context.Context) (envite.ComponentStatus, error) { + return m.status.Load().(envite.ComponentStatus), nil +} + +func (m *SeedComponent) Config() any { + return m.config +} + +func generateInsertSQL(table string, data any) (string, []any) { + v := reflect.ValueOf(data) + t := reflect.TypeOf(data) + var columns []string + var placeholders []string + var values []any + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + column := field.Tag.Get("column") + if column != "" { + columns = append(columns, column) + placeholders = append(placeholders, fmt.Sprintf("$%d", i+1)) + values = append(values, v.Field(i).Interface()) + } + } + columnsPart := strings.Join(columns, ", ") + placeholdersPart := strings.Join(placeholders, ", ") + sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", table, columnsPart, placeholdersPart) + return sql, values +} diff --git a/seed/postgres/config.go b/seed/postgres/config.go new file mode 100644 index 0000000..12efd46 --- /dev/null +++ b/seed/postgres/config.go @@ -0,0 +1,25 @@ +package postgres + +import "database/sql" + +// SeedConfig represents the configuration for the Postgres seed component. +type SeedConfig struct { + // ClientProvider - Provides a postgres client to use. + // available only via code, not available in config files. + ClientProvider func() (*sql.DB, error) `json:"-"` + + // Setup - a string that contains the SQL setup script to run before seeding the data. + Setup string `json:"-"` + + // Data - a list of objects, each represents a single postgres table and its data + Data []*SeedCollectionData `json:"data,omitempty"` +} + +// SeedCollectionData represents data for a Postgres table. +type SeedCollectionData struct { + // Table - the name of the target postgres table + Table string `json:"collection,omitempty"` + + // Rows - a list of rows to insert using the postgres Exec function (a `column` tag is required for each field): + Rows []any `json:"documents,omitempty"` +} From 870c3cb0a015aff10a36878ad84cb5f929fc02ac Mon Sep 17 00:00:00 2001 From: nirapx <51790021+nirapx@users.noreply.github.com> Date: Tue, 16 Jul 2024 08:20:22 +0300 Subject: [PATCH 2/4] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f200e2..4c5436d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.8](https://github.com/PerimeterX/envite/compare/v0.0.7...v0.0.8) + +### Added + +- Postgres Seed functionality. + ## [0.0.7](https://github.com/PerimeterX/envite/compare/v0.0.6...v0.0.7) ### Fixed From e91fd260b400bab2e3c8182a2e0666022c93ca0e Mon Sep 17 00:00:00 2001 From: nirapx <51790021+nirapx@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:21:44 +0300 Subject: [PATCH 3/4] rename --- seed/postgres/component.go | 12 ++++++------ seed/postgres/config.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/seed/postgres/component.go b/seed/postgres/component.go index b0acac9..a432d7b 100644 --- a/seed/postgres/component.go +++ b/seed/postgres/component.go @@ -75,14 +75,14 @@ func (m *SeedComponent) Seed() error { return err } - for _, collection := range m.config.Data { + for _, table := range m.config.Data { - if _, err = client.Exec(fmt.Sprintf("DELETE FROM %s", collection.Table)); err != nil { + if _, err = client.Exec(fmt.Sprintf("DELETE FROM %s", table.TableName)); err != nil { return err } - for _, row := range collection.Rows { - sql, values := generateInsertSQL(collection.Table, row) + for _, row := range table.Rows { + sql, values := generateInsertSQL(table.TableName, row) _, err := client.Exec(sql, values...) if err != nil { return err @@ -92,8 +92,8 @@ func (m *SeedComponent) Seed() error { if m.writer != nil { m.writer.WriteString(fmt.Sprintf( "inserted %s rows to %s", - m.writer.Color.Green(strconv.Itoa(len(collection.Rows))), - m.writer.Color.Cyan(collection.Table), + m.writer.Color.Green(strconv.Itoa(len(table.Rows))), + m.writer.Color.Cyan(table.TableName), )) } } diff --git a/seed/postgres/config.go b/seed/postgres/config.go index 12efd46..d4c8f61 100644 --- a/seed/postgres/config.go +++ b/seed/postgres/config.go @@ -12,14 +12,14 @@ type SeedConfig struct { Setup string `json:"-"` // Data - a list of objects, each represents a single postgres table and its data - Data []*SeedCollectionData `json:"data,omitempty"` + Data []*SeedTableData `json:"data,omitempty"` } -// SeedCollectionData represents data for a Postgres table. -type SeedCollectionData struct { - // Table - the name of the target postgres table - Table string `json:"collection,omitempty"` +// SeedTableData represents data for a Postgres table. +type SeedTableData struct { + // TableName - the name of the target postgres table + TableName string `json:"table,omitempty"` // Rows - a list of rows to insert using the postgres Exec function (a `column` tag is required for each field): - Rows []any `json:"documents,omitempty"` + Rows []any `json:"rows,omitempty"` } From a033759bc083144d834f37eedb4076dbf5f92a9d Mon Sep 17 00:00:00 2001 From: nirapx <51790021+nirapx@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:01:44 +0300 Subject: [PATCH 4/4] PR fixes --- seed/postgres/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seed/postgres/config.go b/seed/postgres/config.go index d4c8f61..fd9ea8f 100644 --- a/seed/postgres/config.go +++ b/seed/postgres/config.go @@ -9,7 +9,7 @@ type SeedConfig struct { ClientProvider func() (*sql.DB, error) `json:"-"` // Setup - a string that contains the SQL setup script to run before seeding the data. - Setup string `json:"-"` + Setup string `json:"setup,omitempty"` // Data - a list of objects, each represents a single postgres table and its data Data []*SeedTableData `json:"data,omitempty"` @@ -18,7 +18,7 @@ type SeedConfig struct { // SeedTableData represents data for a Postgres table. type SeedTableData struct { // TableName - the name of the target postgres table - TableName string `json:"table,omitempty"` + TableName string `json:"table_name,omitempty"` // Rows - a list of rows to insert using the postgres Exec function (a `column` tag is required for each field): Rows []any `json:"rows,omitempty"`