From e6d25fcdbebaf5011077d9b43cad9c1c3c3c6410 Mon Sep 17 00:00:00 2001 From: Harsh4902 Date: Sat, 28 Jun 2025 18:27:43 +0530 Subject: [PATCH 1/2] fixes#163 bug: Resolved Podman socket retrival issue for windows and linux Signed-off-by: Harsh4902 --- pkg/connectors/container_client.go | 150 +++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 pkg/connectors/container_client.go diff --git a/pkg/connectors/container_client.go b/pkg/connectors/container_client.go new file mode 100644 index 00000000..84f54721 --- /dev/null +++ b/pkg/connectors/container_client.go @@ -0,0 +1,150 @@ +package connectors + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "runtime" + "strings" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/microcks/microcks-cli/pkg/errors" +) + +type ContainerClient interface { + CreateContainer(opts ContainerOpts) (string, error) + StartContainer(containerId string) error + StopContainer(continerId string) error + CloseClient() error +} + +type containerClient struct { + cli *client.Client +} + +type ContainerOpts struct { + Image string + Port string + AutoRemove bool + Name string +} + +const ( + MICROCKS_DEFAULT_PORT = "8080" + LOCALHOST_IP = "127.0.0.1" +) + +func NewContainerClient(driver string) (ContainerClient, error) { + switch driver { + case "docker": + return NewDockerClient() + + case "podman": + return NewPodmanClient() + + default: + return nil, fmt.Errorf("unsupported container driver: %s", driver) + } +} + +func NewDockerClient() (*containerClient, error) { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + + if err != nil { + return nil, err + } + + return &containerClient{cli: cli}, nil +} + +func NewPodmanClient() (*containerClient, error) { + osName := runtime.GOOS + switch osName { + case "windows": + remoteSocket, err := exec.Command("podman", "machine", "inspect", "--format", "{{.ConnectionInfo.PodmanPipe.Path}}").Output() + errors.CheckError(err) + socketPath := strings.TrimSpace(string(remoteSocket)) + err = os.Setenv("DOCKER_HOST", "npipe:////"+strings.TrimPrefix(socketPath, "\\\\")) + errors.CheckError(err) + + case "darwin": + remoteSocket, err := exec.Command("podman", "machine", "inspect", "--format", "{{.ConnectionInfo.PodmanSocket.Path}}").Output() + errors.CheckError(err) + err = os.Setenv("DOCKER_HOST", "unix://"+strings.TrimSpace(string(remoteSocket))) + errors.CheckError(err) + case "linux": + remoteSocket, err := exec.Command("podman", "info", "--format", "{{.Host.RemoteSocket.Path}}").Output() + errors.CheckError(err) + err = os.Setenv("DOCKER_HOST", "unix://"+strings.TrimSpace(string(remoteSocket))) + errors.CheckError(err) + } + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + + if err != nil { + return nil, err + } + + return &containerClient{cli: cli}, nil +} + +func (cli *containerClient) CreateContainer(opts ContainerOpts) (string, error) { + ctx := context.Background() + + // Define exposed port and bindings + exposedPort, _ := nat.NewPort("tcp", "8080") + portBindings := nat.PortMap{ + exposedPort: []nat.PortBinding{ + { + HostIP: LOCALHOST_IP, + HostPort: opts.Port, + }, + }, + } + + out, err := cli.cli.ImagePull(ctx, opts.Image, image.PullOptions{}) + if err != nil { + return "", err + } + defer out.Close() + io.Copy(os.Stdout, out) + + resp, err := cli.cli.ContainerCreate( + ctx, + &container.Config{ + Image: opts.Image, + ExposedPorts: nat.PortSet{exposedPort: struct{}{}}, + }, + &container.HostConfig{ + PortBindings: portBindings, + AutoRemove: opts.AutoRemove, + }, nil, nil, opts.Name) + + if err != nil { + return "", err + } + + return resp.ID, nil +} + +func (cli *containerClient) StartContainer(containerId string) error { + ctx := context.Background() + return cli.cli.ContainerStart(ctx, containerId, container.StartOptions{}) +} + +func (cli *containerClient) StopContainer(containerId string) error { + ctx := context.Background() + + fmt.Print("Stopping container ", containerId, "... ") + noWaitTimeout := 0 // to not wait for the container to exit gracefully + return cli.cli.ContainerStop(ctx, containerId, container.StopOptions{Timeout: &noWaitTimeout}) +} + +func (cli *containerClient) CloseClient() error { + return cli.cli.Close() +} From d9219accb8f5210f7705c1d8e42ff3b6cd19a76f Mon Sep 17 00:00:00 2001 From: Harsh4902 Date: Sat, 28 Jun 2025 18:29:06 +0530 Subject: [PATCH 2/2] feat: redesigned container-client creation and config updates after executing start and stop commands Signed-off-by: Harsh4902 --- cmd/start.go | 222 +++++++++++++++++--------------------- cmd/stop.go | 83 +++++++------- pkg/config/config.go | 75 ------------- pkg/config/config_test.go | 57 ---------- pkg/config/localconfig.go | 2 +- 5 files changed, 142 insertions(+), 297 deletions(-) delete mode 100644 pkg/config/config_test.go diff --git a/cmd/start.go b/cmd/start.go index 56a7bb33..5f8dd026 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,25 +1,17 @@ package cmd import ( - "context" "fmt" - "io" "log" - "os" - "os/exec" - "strings" - - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" + "github.com/microcks/microcks-cli/pkg/config" + "github.com/microcks/microcks-cli/pkg/connectors" + "github.com/microcks/microcks-cli/pkg/errors" "github.com/spf13/cobra" ) func NewStartCommand() *cobra.Command { var ( - hostIP string = "0.0.0.0" name string hostPort string imageName string @@ -41,62 +33,114 @@ microcks start --driver [driver you wnat either 'docker' or 'podman'] # Define name of your microcks container/instance microcks start --name [name of you container/instance]`, Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.EnsureConfig(config.ConfigPath) - - if err != nil { - log.Fatalf("Error loading config: %v", err) - } - cfg.Instance.Driver = driver + configFile, err := config.DefaultLocalConfigPath() + errors.CheckError(err) + localConfig, err := config.ReadLocalConfig(configFile) + errors.CheckError(err) - cli, err := createClient(cfg.Instance.Driver) - - if err != nil { - fmt.Println(err) - return + if localConfig == nil { + localConfig = &config.LocalConfig{} } - defer cli.Close() - - if cfg.Instance.Status == "Running" { - fmt.Println("Microcks is already running.") - return + instance, _ := localConfig.GetInstance(name) + if instance == nil { + instance = &config.Instance{} } - if cfg.Instance.Status == "Stopped" || cfg.Instance.Status == "Created" { - if err := startContainer(cfg.Instance.ContainerID, cli); err != nil { - fmt.Errorf("failed to start container: %v", err) - } - fmt.Println("Microcks started successfully...") + if instance.Status == "Running" { + fmt.Printf("Microcks instance with name %s is already running", name) return } - cfg.Instance.Name = name - cfg.Instance.Image = imageName - cfg.Instance.Port = hostPort - cfg.Instance.AutoRemove = autoRemove - - containerID, err := createContainer(cfg, hostIP, cli) - - if err != nil { - log.Fatalf("Failed to create a container: %v", err) + switch instance.Status { + case "Running": + fmt.Printf("Microcks instance with name %s is already running", name) return - } - cfg.Instance.ContainerID = containerID - cfg.Instance.Status = "Created" + case "Exited": + containerClient, err := connectors.NewContainerClient(instance.Driver) + errors.CheckError(err) + defer containerClient.CloseClient() + + if err := containerClient.StartContainer(instance.ContainerID); err != nil { + log.Fatalf("failed to start container: %v", err) + return + } + instance.Status = "Running" + default: + containerClient, err := connectors.NewContainerClient(driver) + errors.CheckError(err) + defer containerClient.CloseClient() + + containerId, err := containerClient.CreateContainer(connectors.ContainerOpts{ + Image: imageName, + Port: hostPort, + Name: name, + AutoRemove: autoRemove, + }) + if err != nil { + log.Fatalf("Failed to create a container: %v", err) + return + } - if err := startContainer(cfg.Instance.ContainerID, cli); err != nil { - fmt.Errorf("failed to start container: %v", err) - return - } - cfg.Instance.Status = "Running" - err = config.SaveConfig(config.ConfigPath, cfg) + if err := containerClient.StartContainer(containerId); err != nil { + log.Fatalf("failed to start container: %v", err) + return + } - if err != nil { - log.Fatalf("Failed to save config: %v", err) - return + instance.ContainerID = containerId + instance.AutoRemove = autoRemove + instance.Name = name + instance.Image = imageName + instance.Port = hostPort + instance.Status = "Running" + instance.Driver = driver } + //Store config and change context + localConfig.UpsertInstance(config.Instance{ + ContainerID: instance.ContainerID, + Name: instance.Name, + Image: instance.Image, + Port: instance.Port, + Status: instance.Status, + Driver: instance.Driver, + AutoRemove: instance.AutoRemove, + }) + + server := fmt.Sprintf("http://localhost:%s", instance.Port) + + localConfig.UpsertServer(config.Server{ + Name: name, + Server: server, + InsecureTLS: true, + KeycloackEnable: false, + }) + + localConfig.UpserAuth(config.Auth{ + Server: server, + ClientId: "", + ClientSecret: "", + }) + + localConfig.UpsertUser(config.User{ + Name: server, + AuthToken: "", + RefreshToken: "", + }) + + localConfig.CurrentContext = server + localConfig.UpserContext(config.ContextRef{ + Name: server, + Server: server, + User: server, + Instance: instance.Name, + }) + + // Save configs to config file + err = config.WriteLocalConfig(*localConfig, configFile) + errors.CheckError(err) + fmt.Printf("Microcks started successfully...") }, } @@ -107,75 +151,3 @@ microcks start --name [name of you container/instance]`, startCmd.Flags().StringVar(&driver, "driver", "docker", "use --driver to change driver from docker to podman") return startCmd } - -func createClient(driver string) (*client.Client, error) { - - if driver != "docker" { - out, err := exec.Command("podman", "machine", "inspect", "--format", "{{.ConnectionInfo.PodmanSocket.Path}}").Output() - if err != nil { - fmt.Println(err) - } - - err = os.Setenv("DOCKER_HOST", "unix://"+strings.TrimSpace(string(out))) - if err != nil { - fmt.Println(err) - } - } - - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - - if err != nil { - return nil, err - } - - return cli, nil -} - -func createContainer(cfg *config.Config, hostIP string, cli *client.Client) (string, error) { - ctx := context.Background() - - // Define exposed port and bindings - exposedPort, _ := nat.NewPort("tcp", "8080") - portBindings := nat.PortMap{ - exposedPort: []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: cfg.Instance.Port, - }, - }, - } - - out, err := cli.ImagePull(ctx, cfg.Instance.Image, image.PullOptions{}) - if err != nil { - return "", err - } - defer out.Close() - io.Copy(os.Stdout, out) - - resp, err := cli.ContainerCreate( - ctx, - &container.Config{ - Image: cfg.Instance.Image, - ExposedPorts: nat.PortSet{exposedPort: struct{}{}}, - }, - &container.HostConfig{ - PortBindings: portBindings, - AutoRemove: cfg.Instance.AutoRemove, - }, nil, nil, cfg.Instance.Name) - - if err != nil { - return "", err - } - - return resp.ID, nil -} - -func startContainer(cotainerID string, cli *client.Client) error { - ctx := context.Background() - - if err := cli.ContainerStart(ctx, cotainerID, container.StartOptions{}); err != nil { - panic(err) - } - - return nil -} diff --git a/cmd/stop.go b/cmd/stop.go index e943b21e..0671bf03 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -1,13 +1,12 @@ package cmd import ( - "context" "fmt" "log" - containertypes "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" "github.com/microcks/microcks-cli/pkg/config" + "github.com/microcks/microcks-cli/pkg/connectors" + "github.com/microcks/microcks-cli/pkg/errors" "github.com/spf13/cobra" ) @@ -19,54 +18,60 @@ func NewStopCommand() *cobra.Command { Long: "stop microcks instance", Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.LoadConfig(config.ConfigPath) - if err != nil { - log.Fatalf("Failed to load config: %v", err) - } + configFile, err := config.DefaultLocalConfigPath() + errors.CheckError(err) + localConfig, err := config.ReadLocalConfig(configFile) + errors.CheckError(err) - cli, err := createClient(cfg.Instance.Driver) - - if err != nil { - fmt.Println(err) + if localConfig == nil { + fmt.Println("Config not found, nothing to stop") return } - stopContainer(cfg.Instance.ContainerID, cli) + ctx, err := localConfig.ResolveContext("") + errors.CheckError(err) + instance := ctx.Instance - cfg.Instance.Status = "Stopped" - - if cfg.Instance.AutoRemove { - cfg.Instance = struct { - Name string "yaml:\"name\"" - Image string "yaml:\"image\"" - Status string "yaml:\"status\"" - Port string "yaml:\"port\"" - ContainerID string "yaml:\"containerID\"" - AutoRemove bool "yaml:\"autoRemove\"" - Driver string "yaml:\"driver\"" - }{} + if instance.Name == "" { + fmt.Println("No instance is associated with this context") + return } - err = config.SaveConfig(config.ConfigPath, cfg) + containerClient, err := connectors.NewContainerClient(instance.Driver) + errors.CheckError(err) + defer containerClient.CloseClient() + err = containerClient.StopContainer(instance.ContainerID) if err != nil { - log.Fatalf("Failed to save config: %v", err) + log.Fatalf("Failed to stop a container: %v", err) + return } - - fmt.Println("Microcks stopped successfully...") + log.Printf("Instance %s stopped successfully", instance.Name) + + // update configs + + if instance.AutoRemove { + _, ok := localConfig.RemoveContext(ctx.Name) + if !ok { + log.Fatalf("Context %s does not exist", ctx.Name) + return + } + _ = localConfig.RemoveServer(ctx.Server.Server) + _ = localConfig.RemoveUser(ctx.User.Name) + _ = localConfig.RemoveAuth(ctx.Server.Server) + _ = localConfig.RemoveInstance(instance.Name) + + localConfig.CurrentContext = "" + log.Printf("Instance %s removed successfully", instance.Name) + } else { + instance.Status = "Exited" + localConfig.UpsertInstance(instance) + log.Printf("Instance %s status update to Exited", instance.Name) + } + err = config.WriteLocalConfig(*localConfig, configFile) + errors.CheckError(err) }, } return stopCmd } - -func stopContainer(containerId string, cli *client.Client) { - ctx := context.Background() - - fmt.Print("Stopping container ", containerId, "... ") - noWaitTimeout := 0 // to not wait for the container to exit gracefully - if err := cli.ContainerStop(ctx, containerId, containertypes.StopOptions{Timeout: &noWaitTimeout}); err != nil { - panic(err) - } - fmt.Println("Success") -} diff --git a/pkg/config/config.go b/pkg/config/config.go index cfdb087f..a1bd8d5e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -25,8 +25,6 @@ import ( "os" "path/filepath" strings "strings" - - "gopkg.in/yaml.v2" ) var ( @@ -40,18 +38,6 @@ var ( ConfigPath = filepath.Join(os.Getenv("HOME"), ".microcks-cli", "config.yaml") ) -type Config struct { - Instance struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Status string `yaml:"status"` - Port string `yaml:"port"` - ContainerID string `yaml:"containerID"` - AutoRemove bool `yaml:"autoRemove"` - Driver string `yaml:"driver"` - } `yaml:"instance"` -} - // CreateTLSConfig wraps the creation of tls.Config object for use with HTTP Client for example. func CreateTLSConfig() *tls.Config { tlsConfig := &tls.Config{} @@ -109,64 +95,3 @@ func DumpResponseIfRequired(name string, resp *http.Response, body bool) { } } } - -//Functions related to configs - -func defaultConfig() *Config { - return &Config{ - Instance: struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - Status string `yaml:"status"` - Port string `yaml:"port"` - ContainerID string `yaml:"containerID"` - AutoRemove bool `yaml:"autoRemove"` - Driver string `yaml:"driver"` - }{ - Name: "microcks", - Image: "", - Status: "", - Port: "", - AutoRemove: false, - Driver: "docker", - }, - } -} - -func EnsureConfig(path string) (*Config, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - fmt.Println("Config not found. Initializing default config.") - cfg := defaultConfig() - err := SaveConfig(path, cfg) - if err != nil { - return nil, fmt.Errorf("failed to create default config: %w", err) - } - return cfg, nil - } - return LoadConfig(path) -} - -func LoadConfig(path string) (*Config, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - var cfg Config - err = yaml.Unmarshal(data, &cfg) - if err != nil { - return nil, err - } - return &cfg, nil -} - -func SaveConfig(path string, cfg *Config) error { - err := os.MkdirAll(filepath.Dir(path), 0755) - if err != nil { - return err - } - data, err := yaml.Marshal(cfg) - if err != nil { - return err - } - return os.WriteFile(path, data, 0644) -} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100644 index 1f535042..00000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package config - -import ( - "os" - "path/filepath" - "testing" -) - -func TestDefaultConfig(t *testing.T) { - cfg := defaultConfig() - if cfg.Instance.Name != "microcks" { - t.Errorf("Expected default name 'microcks', got %s", cfg.Instance.Name) - } - if cfg.Instance.Driver != "docker" { - t.Errorf("Expected default driver 'docker', got %s", cfg.Instance.Driver) - } -} - -func TestSaveAndLoadConfig(t *testing.T) { - tmpDir := t.TempDir() - cfgPath := filepath.Join(tmpDir, "test-config.yaml") - - original := defaultConfig() - original.Instance.Image = "test-image" - - err := SaveConfig(cfgPath, original) - if err != nil { - t.Fatalf("SaveConfig failed: %v", err) - } - - loaded, err := LoadConfig(cfgPath) - if err != nil { - t.Fatalf("LoadConfig failed: %v", err) - } - - if loaded.Instance.Image != "test-image" { - t.Errorf("Expected image 'test-image', got %s", loaded.Instance.Image) - } -} - -func TestEnsureConfigCreatesFile(t *testing.T) { - tmpDir := t.TempDir() - cfgPath := filepath.Join(tmpDir, "new-config.yaml") - - cfg, err := EnsureConfig(cfgPath) - if err != nil { - t.Fatalf("EnsureConfig failed: %v", err) - } - - if cfg.Instance.Name != "microcks" { - t.Errorf("Expected default name 'microcks', got %s", cfg.Instance.Name) - } - - if _, err := os.Stat(cfgPath); os.IsNotExist(err) { - t.Errorf("Config file was not created at %s", cfgPath) - } -} diff --git a/pkg/config/localconfig.go b/pkg/config/localconfig.go index 6289dae3..c188b5cc 100644 --- a/pkg/config/localconfig.go +++ b/pkg/config/localconfig.go @@ -308,7 +308,7 @@ func (l *LocalConfig) GetAuth(server string) (*Auth, error) { } } - return nil, fmt.Errorf("Auth for '%s' is undifined\n", server) + return nil, fmt.Errorf("Auth for '%s' is undifined", server) } func (l *LocalConfig) UpserAuth(auth Auth) {