diff --git a/commands/serve.go b/commands/serve.go new file mode 100644 index 0000000000000000000000000000000000000000..86703757da8abd8365cdd2db290b472aad31204e --- /dev/null +++ b/commands/serve.go @@ -0,0 +1,211 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "datasmith/config" + + "github.com/fsnotify/fsnotify" + "gopkg.in/yaml.v2" +) + + + +// Load the datasmith.yaml file +func loadConfig(projectDir string) (config.DatasmithConfig, error) { + configFilePath := filepath.Join(projectDir, "datasmith.yaml") + file, err := os.Open(configFilePath) + if err != nil { + return config.DatasmithConfig{}, fmt.Errorf("error opening config file: %v", err) + } + defer file.Close() + + var cfg config.DatasmithConfig + decoder := yaml.NewDecoder(file) + if err := decoder.Decode(&cfg); err != nil { + return config.DatasmithConfig{}, fmt.Errorf("error decoding config file: %v", err) + } + + return cfg, nil +} + +func Serve() { + projectDir := "." // Assuming current directory is the project directory + config, err := loadConfig(projectDir) + if err != nil { + fmt.Printf("Error loading config: %v\n", err) + return + } + fmt.Println("Config loaded successfully.") + + // Determine which container tool to use (Docker or Podman) + containerTool := "docker" + if _, err := exec.LookPath("podman"); err == nil { + containerTool = "podman" + } else if _, err := exec.LookPath("docker"); err != nil { + fmt.Println("Error: neither Docker nor Podman is installed.") + return + } + + // Build the Docker image + fmt.Println("Building the Docker image...") + if err := buildDockerImage(containerTool, config); err != nil { + fmt.Printf("Error building Docker image: %v\n", err) + return + } + fmt.Println("Docker image built successfully.") + + // Start the database container + fmt.Println("Starting the database container...") + if err := startDatabaseContainer(containerTool, config); err != nil { + fmt.Printf("Error starting database container: %v\n", err) + return + } + fmt.Println("Database container started successfully.") + + // Print connection information + printConnectionInfo(config) + + fmt.Println("Watching for changes in the SQL files...") + + // Watch for changes in the SQL files + watcher, err := fsnotify.NewWatcher() + if err != nil { + fmt.Printf("Error creating file watcher: %v\n", err) + return + } + defer watcher.Close() + + done := make(chan bool) + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + fmt.Println("Event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + fmt.Println("Modified file:", event.Name) + if err := rebuildAndRestartContainer(containerTool, config); err != nil { + fmt.Printf("Error rebuilding and restarting container: %v\n", err) + } else { + fmt.Println("Container rebuilt and restarted successfully.") + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + fmt.Println("Error:", err) + } + } + }() + + err = watcher.Add("sql") + if err != nil { + fmt.Printf("Error adding watcher: %v\n", err) + return + } + + <-done +} + +func buildDockerImage(containerTool string, config config.DatasmithConfig) error { + imageName := fmt.Sprintf("ds_%s:%s", config.Name, config.Version) + fmt.Printf("Building Docker image: %s\n", imageName) + + var buildCmd *exec.Cmd + switch config.DbType { + case "mysql": + buildCmd = exec.Command(containerTool, "build", + "-t", imageName, + "--build-arg", "DB_USER=user", + "--build-arg", "DB_PASSWORD=password", + "--build-arg", fmt.Sprintf("DB_DATABASE=%s", config.Name), + "--build-arg", "MARIADB_ROOT_PASSWORD=example", + ".") + case "postgres": + buildCmd = exec.Command(containerTool, "build", + "-t", imageName, + "--build-arg", "DB_USER=user", + "--build-arg", "DB_PASSWORD=password", + "--build-arg", fmt.Sprintf("DB_DATABASE=%s", config.Name), + "--build-arg", "POSTGRES_PASSWORD=example", + ".") + default: + return fmt.Errorf("unsupported database type: %s", config.DbType) + } + + buildCmd.Stdout = os.Stdout + buildCmd.Stderr = os.Stderr + return buildCmd.Run() +} + +func startDatabaseContainer(containerTool string, config config.DatasmithConfig) error { + var runCmd *exec.Cmd + switch config.DbType { + case "mysql": + runCmd = exec.Command(containerTool, "run", "--rm", "-d", + "-e", "MYSQL_ROOT_PASSWORD=example", + "-e", fmt.Sprintf("DB_DATABASE=%s", config.Name), + "-e", "DB_USER=user", + "-e", "DB_PASSWORD=password", + "-p", "3306:3306", + "--name", "datasmith_db_container", + "datasmith_db_image") + case "postgres": + runCmd = exec.Command(containerTool, "run", "--rm", "-d", + "-e", "POSTGRES_PASSWORD=example", + "-e", fmt.Sprintf("POSTGRES_DB=%s", config.Name), + "-e", "POSTGRES_USER=user", + "-p", "5432:5432", + "--name", "datasmith_db_container", + "datasmith_db_image") + default: + return fmt.Errorf("unsupported database type: %s", config.DbType) + } + + runCmd.Stdout = os.Stdout + runCmd.Stderr = os.Stderr + return runCmd.Run() +} + +func rebuildAndRestartContainer(containerTool string, config config.DatasmithConfig) error { + fmt.Println("Stopping the current database container...") + stopCmd := exec.Command(containerTool, "stop", "datasmith_db_container") + stopCmd.Stdout = os.Stdout + stopCmd.Stderr = os.Stderr + if err := stopCmd.Run(); err != nil { + return fmt.Errorf("error stopping database container: %v", err) + } + + fmt.Println("Rebuilding the Docker image...") + if err := buildDockerImage(containerTool, config); err != nil { + return fmt.Errorf("error rebuilding Docker image: %v", err) + } + + fmt.Println("Restarting the database container...") + if err := startDatabaseContainer(containerTool, config); err != nil { + return fmt.Errorf("error restarting database container: %v", err) + } + + return nil +} + +func printConnectionInfo(config config.DatasmithConfig) { + fmt.Println("Connection Information:") + switch config.DbType { + case "mysql": + fmt.Println("MySQL connection string:") + fmt.Printf("Host: localhost\nPort: 3306\nDatabase: %s\nUser: user\nPassword: password\n", config.Name) + case "postgres": + fmt.Println("PostgreSQL connection string:") + fmt.Printf("Host: localhost\nPort: 5432\nDatabase: %s\nUser: user\nPassword: password\n", config.Name) + default: + fmt.Printf("Unsupported database type: %s\n", config.DbType) + } +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index bb2e6a4c5ba1b37371fc782598cf7f5ac70a9750..0e618f7b5412d00f99b5c609c9f9b3ac7b28814b 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,29 @@ import ( "os/user" "path/filepath" ) +type Field struct { + Name string `json:"name"` + Type string `json:"type"` + PrimaryKey bool `json:"primary_key,omitempty"` + ForeignKey string `json:"foreign_key,omitempty"` + Unique bool `json:"unique,omitempty"` + NotNull bool `json:"not_null,omitempty"` + AutoIncrement bool `json:"auto_increment,omitempty"` + DefaultValue interface{} `json:"default_value,omitempty"` +} + +type TableModel struct { + Fields []Field `json:"fields"` +} + +type DatasmithConfig struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + CreatedAt string `yaml:"created_at"` + DbType string `yaml:"database_type"` + Tables map[string]TableModel `yaml:"tables"` +} + type Configuration struct { User string `json:"author"` diff --git a/go.mod b/go.mod index cbc7f691ec8f7e4668ecad76f3086ad15387f110..6aef5d38927b6fc0d41ab1a93c145dc78f6df527 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,13 @@ module datasmith go 1.22.4 +require ( + github.com/fsnotify/fsnotify v1.7.0 + gopkg.in/yaml.v2 v2.4.0 +) + require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/manifoldco/promptui v0.9.0 // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect - gopkg.in/yaml.v2 v2.4.0 + golang.org/x/sys v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index 3dd0071b7c7823495bfec4c68528f8444265a69d..32d3e0028b91c7ccc53bb8d39996be4776e6eee7 100644 --- a/go.sum +++ b/go.sum @@ -2,11 +2,15 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go index 8ed57fb705e335623cba96f4ec50579b3533caa9..876e7dde3935cc6a55a91f64c4a219157a7b1926 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "datasmith/config" ) -var version = "0.0.1" +var version = "1.0.0" func main() { config.LoadConfig() @@ -56,11 +56,13 @@ func main() { return } commands.Add(tableName, model) + case "serve": + commands.Serve() case "version": commands.Version(version) case "help": commands.Help() - // TODO: list tables, show table, remove table, generate test data + // TODO: generate test data default: fmt.Printf("Unknown command: %s\n", command) commands.Help()