diff --git a/src/helm/chart.go b/src/helm/chart.go index 93760f6..1c5545a 100644 --- a/src/helm/chart.go +++ b/src/helm/chart.go @@ -1,159 +1,162 @@ -package helm - -import ( - "fmt" - "strconv" - "os" - "os/exec" - "strings" - "errors" - "path/filepath" - "encoding/json" - log "oc-deploy/log_wrapper" -) - -type HelmChart struct { - Bin string - Name string - Chart string - Version string - Url string - - Workspace string - Opts string - Values string - FileValues string -} - -type installInfoOutput struct { - Description string `json:"description"` - Notes string `json:"notes"` - Status string `json:"status"` -} - -type installOutput struct { - Info installInfoOutput `json:"info"` -} - -func (this HelmChart) Install() (string, error) { - bin := this.Bin - - existe, err := this.exists() - if err != nil { - return "", err - } - - if existe { - return "Existe déjà", nil - } - - ficChart := this.Chart - // Recherche locale - if _, err := os.Stat(ficChart); err != nil { - } else { - // Recherche voa le Workspace - ficChart := filepath.Join(this.Workspace, this.Chart) - if _, err := os.Stat(ficChart); err == nil { - } else { - if this.Url != "" { - fmt.Println("============ 52 Télechargement", this.Url) - } - } - } - - msg := fmt.Sprintf("%s install %s %s %s --output json", bin, this.Name, ficChart, this.Opts) - - if this.Version != "" { - msg = fmt.Sprintf("%s --version %s", msg, this.Version) - } - - if this.FileValues != "" { - fic := filepath.Join(this.Workspace, this.FileValues) - if _, err := os.Stat(fic); err != nil { - log.Log().Warn().Msg(fic) - } else { - msg = fmt.Sprintf("%s --values %s", msg, fic) - } - } - - msg = strings.Replace(msg, " ", " ", -1) - - log.Log().Debug().Msg(msg) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - - if err != nil { - res := string(stdout) - res = strings.TrimSuffix(res, "\n") - return "", errors.New(res) - } - - var objmap installOutput - - err = json.Unmarshal(stdout, &objmap) - if err != nil { - return "", err - } - - res := objmap.Info.Status - - return res, nil -} - -func (this HelmChart) Uninstall() (string, error) { - bin := this.Bin - - log.Log().Info().Msg(" >> Chart : " + this.Name) - - existe, err := this.exists() - if err != nil { - return "", err - } - if ! existe { - return "Non présent", nil - } - - msg := fmt.Sprintf("%s uninstall %s", bin, this.Name) - log.Log().Debug().Msg(msg) - - cmd := exec.Command(bin, "uninstall", this.Name) - stdout, err := cmd.CombinedOutput() - - return string(stdout), err -} - -// ../bin/helm list --filter phpmyadminm --short -func (this HelmChart) exists() (bool, error) { - bin := this.Bin - - msg := fmt.Sprintf("%s list --filter %s --no-headers", bin, this.Name) - log.Log().Debug().Msg(msg) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - if err != nil { - log.Log().Debug().Msg(string(stdout)) - return false, errors.New(string(stdout)) - } - - res := string(stdout) - res = strings.TrimSuffix(res, "\n") - - log.Log().Debug().Msg(string(stdout)) - log.Log().Debug().Msg(strconv.FormatBool(res != "")) - - return res != "", nil -} - -func (this HelmChart) GetRessources() (map[string]string, error) { - hs := HelmStatus{Name: this.Name} - hs.New(this.Bin) - data, _ := hs.getRessources() - - return data, nil -} \ No newline at end of file +package helm + +import ( + "fmt" + "strconv" + "os" + "strings" + "errors" + "path/filepath" + "encoding/json" + log "oc-deploy/log_wrapper" +) + +type HelmChart struct { + Bin string + Name string + Chart string + Version string + Url string + + Workspace string + Opts string + Values string + FileValues string +} + +type installInfoOutput struct { + Description string `json:"description"` + Notes string `json:"notes"` + Status string `json:"status"` +} + +type installOutput struct { + Info installInfoOutput `json:"info"` +} + +func (this HelmCommand) ChartInstall(data HelmChart) (string, error) { + bin := this.Bin + + existe, err := this.chartExists(data) + if err != nil { + return "", err + } + + if existe { + return "Existe déjà", nil + } + + ficChart := data.Chart + // Recherche locale + if _, err := os.Stat(ficChart); err != nil { + } else { + // Recherche voa le Workspace + ficChart := filepath.Join(data.Workspace, data.Chart) + if _, err := os.Stat(ficChart); err == nil { + } else { + if data.Url != "" { + fmt.Println("============ 52 Télechargement", data.Url) + } + } + } + + msg := fmt.Sprintf("%s install %s %s %s --output json", bin, data.Name, ficChart, data.Opts) + + if data.Version != "" { + msg = fmt.Sprintf("%s --version %s", msg, data.Version) + } + + if data.FileValues != "" { + fic := filepath.Join(data.Workspace, data.FileValues) + if _, err := os.Stat(fic); err != nil { + log.Log().Warn().Msg(fic) + } else { + msg = fmt.Sprintf("%s --values %s", msg, fic) + } + } + + msg = strings.Replace(msg, " ", " ", -1) + + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + + if err != nil { + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + return "", errors.New(res) + } + + var objmap installOutput + + err = json.Unmarshal(stdout, &objmap) + if err != nil { + return "", err + } + + res := objmap.Info.Status + + return res, nil +} + +func (this HelmCommand) ChartUninstall(data HelmChart) (string, error) { + bin := this.Bin + + log.Log().Info().Msg(" >> Chart : " + data.Name) + + existe, err := this.chartExists(data) + if err != nil { + return "", err + } + if ! existe { + return "Non présent", nil + } + + msg := fmt.Sprintf("%s uninstall %s", bin, data.Name) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + + log.Log().Debug().Msg(res) + + return res, err +} + +// ../bin/helm list --filter phpmyadminm --short +func (this HelmCommand) chartExists(data HelmChart) (bool, error) { + bin := this.Bin + + msg := fmt.Sprintf("%s list --filter %s --no-headers", bin, data.Name) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + if err != nil { + log.Log().Debug().Msg(string(stdout)) + return false, errors.New(string(stdout)) + } + + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + + log.Log().Debug().Msg(string(stdout)) + log.Log().Debug().Msg(strconv.FormatBool(res != "")) + + return res != "", nil +} + +// func (this HelmChart) GetRessources() (map[string]string, error) { +// hs := HelmStatus{Name: this.Name} +// hs.New(this.Bin) +// data, _ := hs.getRessources() + +// return data, nil +// } \ No newline at end of file diff --git a/src/helm/chart_test.go b/src/helm/chart_test.go new file mode 100644 index 0000000..bfc2bb3 --- /dev/null +++ b/src/helm/chart_test.go @@ -0,0 +1,30 @@ +package helm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHelmChartExists(t *testing.T){ + + cmd := getCmdHelm(true, `oc-catalog default 1 2024-09-06 16:01:49.17368605 +0200 CEST deployed oc-catalog-0.1.0 1.0`) + + data := HelmChart{Name: "oc-catalog"} + res, err := cmd.chartExists(data) + + assert.Nilf(t, err, "error message %s", err) + assert.Truef(t, res, "TestHelmVersion error") +} + + +func TestHelmChartNotExists(t *testing.T){ + + cmd := getCmdHelm(true, "\n") + + data := HelmChart{Name: "phpmyadmin"} + res, err := cmd.chartExists(data) + + assert.Nilf(t, err, "error message %s", err) + assert.Falsef(t, res, "TestHelmVersion error") +} \ No newline at end of file diff --git a/src/helm/command.go b/src/helm/command.go deleted file mode 100644 index 5fad825..0000000 --- a/src/helm/command.go +++ /dev/null @@ -1,16 +0,0 @@ -package helm - -import ( -) - -type HelmCommandInterface interface { - Status(string) (string, error) - AddRepository(HelmRepo) (string, error) -} - -type HelmCommandData struct { - Bin string -} - -type RealHelmCommand HelmCommandData - diff --git a/src/helm/helm.go b/src/helm/helm.go new file mode 100644 index 0000000..5c73d7e --- /dev/null +++ b/src/helm/helm.go @@ -0,0 +1,22 @@ +package helm + +import ( + "os/exec" +) + +type HelmCommand struct { + Bin string + Exec func(string,...string) commandExecutor +} + +//// +type commandExecutor interface { + Output() ([]byte, error) + CombinedOutput() ([]byte, error) +} + +func (this *HelmCommand) New() { + this.Exec = func(name string, arg ...string) commandExecutor { + return exec.Command(name, arg...) + } +} diff --git a/src/helm/main_test.go b/src/helm/main_test.go index 7c5042e..776311a 100644 --- a/src/helm/main_test.go +++ b/src/helm/main_test.go @@ -1,8 +1,9 @@ package helm import ( - "os" - "testing" + "os" + "strings" + "testing" "path/filepath" ) @@ -11,14 +12,73 @@ var TEST_SRC_DIR = filepath.Join("../../test", "helm") var TEST_BIN_DIR = filepath.Join("../../test", "bin") func TestMain(m *testing.M) { - folderPath := TEST_DEST_DIR + folderPath := TEST_DEST_DIR - os.RemoveAll(folderPath) - os.MkdirAll(folderPath, os.ModePerm) + os.RemoveAll(folderPath) + os.MkdirAll(folderPath, os.ModePerm) - // call flag.Parse() here if TestMain uses flags - exitCode := m.Run() + // call flag.Parse() here if TestMain uses flags + exitCode := m.Run() - os.RemoveAll(folderPath) - os.Exit(exitCode) -} \ No newline at end of file + os.RemoveAll(folderPath) + os.Exit(exitCode) +} + +// Mock + +type MockCommandExecutor struct { + // Used to stub the return of the Output method + // Could add other properties depending on testing needs + output string +} + +// Implements the commandExecutor interface +func (m *MockCommandExecutor) Output() ([]byte, error) { + return []byte(m.output), nil +} + +func (m *MockCommandExecutor) CombinedOutput() ([]byte, error) { + return []byte(m.output), nil +} + +// + +func getCmdHelm(mock bool, output string) (HelmCommand) { + if mock == true { + + mock := func(name string, args ...string) commandExecutor { + return &MockCommandExecutor{output: output} + } + + cmd := HelmCommand{Bin: "mock", Exec: mock} + return cmd + } else { + bin := filepath.Join(TEST_BIN_DIR, "helm") + os.Chmod(bin, 0700) + + cmd := HelmCommand{Bin: bin} + cmd.New() + return cmd + } +} + +func getCmdsHelm(mock bool, outputs map[string]string) (HelmCommand) { + if mock == true { + + mock := func(name string, args ...string) commandExecutor { + cmd := strings.TrimSuffix(strings.Join(args," "), " ") + output := outputs[cmd] + return &MockCommandExecutor{output: output} + } + + cmd := HelmCommand{Bin: "mock", Exec: mock} + return cmd + } else { + bin := filepath.Join(TEST_BIN_DIR, "helm") + os.Chmod(bin, 0700) + + cmd := HelmCommand{Bin: bin} + cmd.New() + return cmd + } +} diff --git a/src/helm/repo.go b/src/helm/repo.go index 42b7443..41144e9 100644 --- a/src/helm/repo.go +++ b/src/helm/repo.go @@ -1,96 +1,98 @@ -package helm - -import ( - "fmt" - "strings" - "os/exec" - "encoding/json" - - log "oc-deploy/log_wrapper" - "oc-deploy/utils" -) - -type HelmRepo struct { - Name string - Repository string // Url du dépôt - ForceUpdate bool - Opts string -} - -func (this RealHelmCommand) AddRepository(repo HelmRepo) (string, error) { - - helm_bin := this.Bin - - force_update := "--force-update=false" - if repo.ForceUpdate { - force_update = "--force-update=true" - } else { - list, _ := this.ListRepository() - if utils.StringInSlice(repo.Name, list) { - return "Existe déjà", nil - } - } - - msg := fmt.Sprintf("%s repo add %s %s %s %s", helm_bin, repo.Name, repo.Repository, force_update, repo.Opts) - log.Log().Debug().Msg(msg) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - - res := string(stdout) - res = strings.TrimSuffix(res, "\n") - - return res, err -} - -type parseList struct { - Name string `json:"name"` -} - -func (this RealHelmCommand) ListRepository() ([]string, error) { - - helm_bin := this.Bin - res := make([]string, 0, 0) - - msg := fmt.Sprintf("%s repo list -o json", helm_bin) - log.Log().Debug().Msg(msg) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - if err != nil { - return res, err - } - - var objmap []parseList - - err = json.Unmarshal([]byte(stdout), &objmap) - if err != nil { - return res, err - } - - for _, ele := range objmap { - res = append(res, ele.Name) - } - - return res, err -} - -// helm repo remove [NAME] -func (this RealHelmCommand) RemoveRepository(repo HelmRepo) (string, error) { - helm_bin := this.Bin - - msg := fmt.Sprintf("%s repo remove %s", helm_bin, repo.Name) - log.Log().Debug().Msg(msg) - - cmd := exec.Command(helm_bin, "repo", "remove", repo.Name) - stdout, err := cmd.CombinedOutput() - - res := string(stdout) - res = strings.TrimSuffix(res, "\n") - - return res, err -} +package helm + +import ( + "fmt" + "strings" + "encoding/json" + + log "oc-deploy/log_wrapper" + "oc-deploy/utils" +) + +type HelmRepo struct { + Name string + Repository string // Url du dépôt + ForceUpdate bool + Opts string +} + +func (this HelmCommand) AddRepository(repo HelmRepo) (string, error) { + + helm_bin := this.Bin + + force_update := "--force-update=false" + if repo.ForceUpdate { + force_update = "--force-update=true" + } else { + list, _ := this.ListRepository() + if utils.StringInSlice(repo.Name, list) { + return "Existe déjà", nil + } + } + + msg := fmt.Sprintf("%s repo add %s %s %s %s", helm_bin, repo.Name, repo.Repository, force_update, repo.Opts) + log.Log().Debug().Msg(msg) + + msg = strings.TrimSuffix(msg, " ") + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + + return res, err +} + +type parseList struct { + Name string `json:"name"` +} + +func (this HelmCommand) ListRepository() ([]string, error) { + + helm_bin := this.Bin + res := make([]string, 0, 0) + + msg := fmt.Sprintf("%s repo list -o json", helm_bin) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return res, err + } + + var objmap []parseList + + err = json.Unmarshal(stdout, &objmap) + if err != nil { + return res, err + } + + for _, ele := range objmap { + res = append(res, ele.Name) + } + + return res, err +} + +// helm repo remove [NAME] +func (this HelmCommand) RemoveRepository(repo HelmRepo) (string, error) { + helm_bin := this.Bin + + msg := fmt.Sprintf("%s repo remove %s", helm_bin, repo.Name) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + + return res, err +} diff --git a/src/helm/repo_test.go b/src/helm/repo_test.go index c9fd806..6cfd99b 100644 --- a/src/helm/repo_test.go +++ b/src/helm/repo_test.go @@ -1,39 +1,72 @@ -package helm - -import ( - "fmt" - // "os" - // "path/filepath" - - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHelmRepoAdd(t *testing.T) { - - repo := HelmRepo{ - Bin: "./helm", - Name: "repooc", - Repository: "https://url", - ForceUpdate: true, - Opts: ""} - fmt.Println(" TestHelmRepoAdd ", repo) - - res, err := repo.AddRepository() - fmt.Println("TestHelmRepoAdd.24", res, err) -} - -// helm : not found -func TestHelmRepoAddErr(t *testing.T) { - - repo := HelmRepo{ - Bin: "./helm", - Name: "repooc", - Repository: "https://url", - ForceUpdate: true, - Opts: ""} - - _, err := repo.AddRepository() - assert.NotNilf(t, err, "error message %s", "./helm") -} \ No newline at end of file +package helm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHelmListRepository(t *testing.T){ + + cmd := getCmdHelm(true, `[{"name":"bitnami","url":"https://charts.bitnami.com/bitnami"}]`) + + res, err := cmd.ListRepository() + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "bitnami", res[0], "TestHelmVersion error") +} + +func TestHelmRemoveRepository(t *testing.T){ + + cmd := getCmdHelm(true, `"bitnami" has been removed from your repositories`) + + repo := HelmRepo{Name: "bitnami"} + res, err := cmd.RemoveRepository(repo) + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, `"bitnami" has been removed from your repositories`, res, "TestHelmRemoveRepository error") +} + +func TestHelmRemoveRepository2(t *testing.T){ + + cmd := getCmdHelm(true, `Error: no repositories configured`) + + repo := HelmRepo{Name: "bitnami"} + res, err := cmd.RemoveRepository(repo) + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, `Error: no repositories configured`, res, "TestHelmRemoveRepository error") +} + +func TestHelmAddRepositoryNew(t *testing.T){ + + cmd_output := map[string]string{ + "repo list -o json": `[{"name":"repo1","url":"https://repo.com"}]`, + "repo add repo2 https://repo2.com --force-update=false": `"repo2" has been added to your repositories"`, + } + + cmd := getCmdsHelm(true, cmd_output) + + repo := HelmRepo{Name: "repo2", Repository: "https://repo2.com", ForceUpdate: false} + res, err := cmd.AddRepository(repo) + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, `"repo2" has been added to your repositories"`, res, "TestHelmAddRepositoryNew error") +} + + +func TestHelmAddRepositoryExists(t *testing.T){ + + cmd_output := map[string]string{ + "repo list -o json": `[{"name":"repo1","url":"https://repo.com"}]`, + "version --short": "v3.15.4+gfa9efb0", + } + + cmd := getCmdsHelm(true, cmd_output) + + repo := HelmRepo{Name: "repo1", Repository: "https://repo.com", ForceUpdate: false} + res, err := cmd.AddRepository(repo) + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, `Existe déjà`, res, "TestHelmRemoveRepository error") +} diff --git a/src/helm/ressources.go b/src/helm/ressources.go index dde5b33..9910237 100644 --- a/src/helm/ressources.go +++ b/src/helm/ressources.go @@ -4,20 +4,6 @@ import ( // "fmt" "encoding/json" ) - -type HelmStatus struct { - Name string // Nom - command HelmCommandInterface -} - -func (this *HelmStatus) New(bin string) { - this.command = RealHelmCommand{Bin: bin} -} - -func (this *HelmStatus) NewMock(mock HelmCommandInterface) { - this.command = mock -} - //// type parseStatusInfoResourcesMetadata struct { @@ -57,11 +43,11 @@ type parseStatus struct { Info parseStatusInfo `json:"info"` } -func (this HelmStatus) getRessources() (map[string]string, error) { +func (this HelmCommand) GetRessources(data HelmChart) (map[string]string, error) { res := make(map[string]string) - status, err := this.command.Status(this.Name) + status, err := this.Status(data) if err != nil { return res, err } diff --git a/src/helm/ressources_test.go b/src/helm/ressources_test.go index 488f504..f818581 100644 --- a/src/helm/ressources_test.go +++ b/src/helm/ressources_test.go @@ -9,20 +9,16 @@ import ( "github.com/stretchr/testify/assert" ) -type MockCommandStatus HelmCommandData +func TestHelmRessources(t *testing.T){ -func TestHelmStatus(t *testing.T){ - - hs := HelmStatus{Name: "oc-catalog"} - hs.NewMock(MockCommandStatus{}) - - data, _ := hs.getRessources() - assert.Equal(t, "StatefulSet", data["oc-catalog-oc-catalog"], "TestHelmStatus error") -} - -// Mock -func (this MockCommandStatus) Status(name string) (string, error) { fileName := filepath.Join(TEST_SRC_DIR, "helm_status.json") - data, _ := os.ReadFile(fileName) - return string(data), nil -} \ No newline at end of file + res_json, _ := os.ReadFile(fileName) + + cmd := getCmdHelm(true, string(res_json)) + + data := HelmChart{Name: "test1"} + res, err := cmd.GetRessources(data) + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "StatefulSet", res["oc-catalog-oc-catalog"], "TestHelmStatus error") +} diff --git a/src/helm/status.go b/src/helm/status.go index 5a7a991..5b35cb0 100644 --- a/src/helm/status.go +++ b/src/helm/status.go @@ -3,22 +3,25 @@ package helm import ( "fmt" "strings" - "errors" - "os/exec" + "errors" log "oc-deploy/log_wrapper" ) -func (this RealHelmCommand) Status(name string) (string, error) { +// type HelmData struct { +// Name string +// } + +func (this HelmCommand) Status(data HelmChart) (string, error) { helm_bin := this.Bin - msg := fmt.Sprintf("%s status %s --show-resources -o json", helm_bin, name) + msg := fmt.Sprintf("%s status %s --show-resources -o json", helm_bin, data.Name) log.Log().Debug().Msg(msg) cmd_args := strings.Split(msg, " ") - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) stdout, err := cmd.CombinedOutput() if err != nil { log.Log().Debug().Msg(string(stdout)) @@ -27,4 +30,3 @@ func (this RealHelmCommand) Status(name string) (string, error) { return string(stdout), nil } - diff --git a/src/helm/version.go b/src/helm/version.go index 94ac244..5e22584 100644 --- a/src/helm/version.go +++ b/src/helm/version.go @@ -1,20 +1,20 @@ -package helm - -import ( - "strings" - "os/exec" -) - -func Version(path string) (string, error) { - - cmd := exec.Command(path, "version", "--short") - stdout, err := cmd.CombinedOutput() - - if err != nil { - return "", err - } - - res := string(stdout) - res = strings.TrimSuffix(res, "\n") - return res, nil -} +package helm + +import ( + "strings" +) + + +func (this HelmCommand) GetVersion() (string, error) { + + cmd := this.Exec(this.Bin, "version", "--short") + stdout, err := cmd.CombinedOutput() + + if err != nil { + return "", err + } + + res := string(stdout) + res = strings.TrimSuffix(res, "\n") + return res, nil +} \ No newline at end of file diff --git a/src/helm/version_test.go b/src/helm/version_test.go index 7a97070..680d1a2 100644 --- a/src/helm/version_test.go +++ b/src/helm/version_test.go @@ -1,22 +1,18 @@ -package helm - -import ( - "os" - "path/filepath" - - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHelmVersion(t *testing.T){ - - bin := filepath.Join(TEST_BIN_DIR, "helm") - os.Chmod(bin, 0700) - assert.FileExists(t, bin, "TestHelmVersion error") - - version, err := Version(bin) - - assert.Nilf(t, err, "error message %s", bin) - assert.Equal(t, "v3.15.4+gfa9efb0", version, "TestHelmVersion error") -} \ No newline at end of file +package helm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHelmVersion(t *testing.T){ + + cmd := getCmdHelm(true, "v3.15.4+gfa9efb0\n") + + version, err := cmd.GetVersion() + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "v3.15.4+gfa9efb0", version, "TestHelmVersion error") +} + diff --git a/src/install/common.go b/src/install/common.go index 67ba0f5..3d12684 100644 --- a/src/install/common.go +++ b/src/install/common.go @@ -51,15 +51,17 @@ func (this *InstallClass) Tools() (error) { func (this *InstallClass) SetCommands() { helm_bin, _ := this.getToolBin("helm") - this.commandHelm = helm.RealHelmCommand{Bin: helm_bin} + this.commandHelm = helm.HelmCommand{Bin: helm_bin} + this.commandHelm.New() - // kubectl_bin, _ := this.getToolBin("kubectl") - // this.commandKubectl = helm.RealHelmCommand{Bin: kubectl_bin} + kubectl_bin, _ := this.getToolBin("kubectl") + this.commandKubectl = kubectl.KubectlCommand{Bin: kubectl_bin} + this.commandKubectl.New() } -func (this *InstallClass) SetCommandsMock(mock helm.HelmCommandInterface) { - this.commandHelm = mock -} +// func (this *InstallClass) SetCommandsMock(mock helm.HelmCommandInterface) { +// this.commandHelm = mock +// } func (this *InstallClass) getToolBin(name string) (string, error) { for key, value := range this.toolsBin { @@ -72,9 +74,8 @@ func (this *InstallClass) getToolBin(name string) (string, error) { } func (this *InstallClass) K8s(context string) (error) { - bin_path, _ := this.getToolBin("kubectl") - kube := kubectl.KubeContext{Bin: bin_path} + kube := this.commandKubectl err := kube.UseContext(context) if err != nil { diff --git a/src/install/install.go b/src/install/install.go index 83d29a1..ce957ad 100644 --- a/src/install/install.go +++ b/src/install/install.go @@ -22,7 +22,8 @@ type InstallClass struct { toolsBin map[string]string charts []chart.ChartRepoData - commandHelm helm.HelmCommandInterface + commandHelm helm.HelmCommand + commandKubectl kubectl.KubectlCommand } func (this *InstallClass) NewInstall() (string, error) { @@ -91,8 +92,8 @@ func (this *InstallClass) ChartRepo() (error) { func (this *InstallClass) InstallCharts(modules []string) (error) { - helm_bin, _ := this.getToolBin("helm") - kubectl_bin, _ := this.getToolBin("kubectl") + // helm_bin, _ := this.getToolBin("helm") + // kubectl_bin, _ := this.getToolBin("kubectl") var wg sync.WaitGroup @@ -103,7 +104,7 @@ func (this *InstallClass) InstallCharts(modules []string) (error) { go func() { defer wg.Done() - this.installChart(helm_bin, kubectl_bin, v1) + this.installChart(v1) } () } } @@ -112,35 +113,32 @@ func (this *InstallClass) InstallCharts(modules []string) (error) { return nil } -func (this *InstallClass) installChart(helm_bin string, kubectl_bin string, chart chart.ChartData) { +func (this *InstallClass) installChart(chart chart.ChartData) { log.Log().Info().Msg(fmt.Sprintf(" << Chart : %s ", chart.Name)) - helmchart := helm.HelmChart{Bin: helm_bin, - Name: chart.Name, - Chart: chart.Chart, - Url: chart.Url, - Version: chart.Version, - Workspace: this.Workspace, - Opts: chart.Opts, - Values: chart.Values, - FileValues: chart.FileValues} + data := helm.HelmChart{Name: chart.Name, + Chart: chart.Chart, + Url: chart.Url, + Version: chart.Version, + Workspace: this.Workspace, + Opts: chart.Opts, + Values: chart.Values, + FileValues: chart.FileValues} - res, err := helmchart.Install() + res, err := this.commandHelm.ChartInstall(data) if err != nil { - log.Log().Error().Msg(fmt.Sprintf(" >> %s %s (%s)", helmchart.Name, "KO", err)) + log.Log().Error().Msg(fmt.Sprintf(" >> %s %s (%s)", data.Name, "KO", err)) return } - log.Log().Info().Msg(fmt.Sprintf(" >> %s (%s)", helmchart.Name, res)) + log.Log().Info().Msg(fmt.Sprintf(" >> %s (%s)", data.Name, res)) - ressources, _ := helmchart.GetRessources() + ressources, _ := this.commandHelm.GetRessources(data) for key, value := range ressources { - obj := kubectl.KubeObject{Bin: kubectl_bin, - Kind: value, - Name: key} - err := obj.Wait() + obj := kubectl.KubectlObject{Name: key, Kind: value} + err := this.commandKubectl.Wait(obj) if err != nil { log.Log().Error().Msg(fmt.Sprintf(" >> %s/%s KO (%s)", chart.Name, key, err)) } else { diff --git a/src/install/uninstall.go b/src/install/uninstall.go index b432ad5..0872933 100644 --- a/src/install/uninstall.go +++ b/src/install/uninstall.go @@ -63,13 +63,19 @@ func (this *InstallClass) uninstallChart(helm_bin string, kubectl_bin string, ch log.Log().Info().Msg(fmt.Sprintf(" << Chart : %s ", chart.Name)) - helmchart := helm.HelmChart{Bin: helm_bin, - Name: chart.Name} - res, err := helmchart.Uninstall() + helm_cmd := helm.HelmCommand{Bin: helm_bin} + helm_cmd.New() + + data := helm.HelmChart{Name: chart.Name} + + // helmchart := helm.HelmChart{Bin: helm_bin, + // Name: chart.Name} + + res, err := helm_cmd.ChartUninstall(data) if err != nil { - log.Log().Error().Msg(fmt.Sprintf(" >> %s %s (%s)", helmchart.Name, "KO", err)) + log.Log().Error().Msg(fmt.Sprintf(" >> %s %s (%s)", data.Name, "KO", err)) return } - log.Log().Info().Msg(fmt.Sprintf(" >> %s (%s)", helmchart.Name, res)) + log.Log().Info().Msg(fmt.Sprintf(" >> %s (%s)", data.Name, res)) } diff --git a/src/kubectl/command.go b/src/kubectl/command.go deleted file mode 100644 index 56ab060..0000000 --- a/src/kubectl/command.go +++ /dev/null @@ -1,29 +0,0 @@ -package kubectl - -// import ( -// "fmt" -// ) - -// type KubectlCommandInterface interface { -// // GetDeployment() (string, error) -// GetDeployment() (string, error) -// } - -// type KubectlCommandData struct { -// bin string -// command string -// } - -// type Real KubectlCommandStatus KubectlCommandData - -// // func (this KubectlCommandStatus) Status() (string, error) { -// // fmt.Println("BIN ", this.bin) -// // fmt.Println("COMMAND status") -// // return "Res de KubectlCommandStatus", nil -// // } - -// func (this KubectlCommandStatus) GetDeployment() (string, error) { -// fmt.Println("BIN ", this.bin) -// fmt.Println("COMMAND status") -// return "Res de GetDeployment", nil -// } diff --git a/src/kubectl/context.go b/src/kubectl/context.go index d0a783d..579ef72 100644 --- a/src/kubectl/context.go +++ b/src/kubectl/context.go @@ -1,7 +1,7 @@ package kubectl import ( - // "fmt" + "fmt" "strings" "errors" "os/exec" @@ -9,9 +9,9 @@ import ( log "oc-deploy/log_wrapper" ) -type KubeContext struct { - Bin string // Chemin vers le binaire -} +// type KubeContext struct { +// Bin string // Chemin vers le binaire +// } type kubeConfig struct { @@ -40,9 +40,14 @@ type kubeConfigClusters struct { Cluster kubeConfigCluster `json:"cluster"` } -func (this KubeContext) GetCurrentContext() (string, error) { +func (this KubectlCommand) GetCurrentContext() (string, error) { + bin := this.Bin - cmd := exec.Command(this.Bin, "config", "current-context") + msg := fmt.Sprintf("%s get config current-context", bin) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) stdout, err := cmd.CombinedOutput() res := string(stdout) @@ -53,14 +58,23 @@ func (this KubeContext) GetCurrentContext() (string, error) { // Current Context // namespace, server -func (this KubeContext) GetContext() (string, string, string, error) { +func (this KubectlCommand) GetContext() (string, string, string, error) { - cmd := exec.Command(this.Bin, "config", "view", "-o", "json") - stdout, _ := cmd.CombinedOutput() + bin := this.Bin + + msg := fmt.Sprintf("%s config view -o json", bin) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return "", "", "", errors.New(string(stdout)) + } var objmap kubeConfig - err := json.Unmarshal(stdout, &objmap) + err = json.Unmarshal(stdout, &objmap) if err != nil { return "", "", "", err } @@ -85,7 +99,7 @@ func (this KubeContext) GetContext() (string, string, string, error) { return currentContext, currentNamespace, currentServer, nil } -func (this KubeContext) UseContext(newContext string) (error) { +func (this KubectlCommand) UseContext(newContext string) (error) { cmd := exec.Command(this.Bin, "config", "use-context", newContext) stdout, err := cmd.CombinedOutput() @@ -98,9 +112,14 @@ func (this KubeContext) UseContext(newContext string) (error) { return nil } -func (this KubeContext) Check() (error) { +func (this KubectlCommand) Check() (error) { + bin := this.Bin - cmd := exec.Command(this.Bin, "cluster-info") + msg := fmt.Sprintf("%s cluster-info", bin) + log.Log().Debug().Msg(msg) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) stdout, err := cmd.CombinedOutput() if err != nil { log.Log().Debug().Msg(string(stdout)) diff --git a/src/kubectl/deployment.go b/src/kubectl/deployment.go new file mode 100644 index 0000000..4198471 --- /dev/null +++ b/src/kubectl/deployment.go @@ -0,0 +1,40 @@ +package kubectl + +import ( + "fmt" + "strings" + "errors" + "encoding/json" + + log "oc-deploy/log_wrapper" +) + +func (this KubectlCommand) getDeployment(data KubectlObject) (map[string]any, error) { + bin := this.Bin + + msg := fmt.Sprintf("%s get deployment %s -o json", bin, data.Name) + log.Log().Debug().Msg(msg) + + m := make(map[string]any) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return m, errors.New(string(stdout)) + } + + var objmap getOutput + + json.Unmarshal(stdout, &objmap) + + kind := objmap.Kind + status := objmap.Status + + m["name"] = data.Name + m["kind"] = kind + m["replicas"] = status.Replicas + m["UnavailableReplicas"] = status.UnavailableReplicas + + return m, nil +} diff --git a/src/kubectl/deployment_test.go b/src/kubectl/deployment_test.go new file mode 100644 index 0000000..58587f1 --- /dev/null +++ b/src/kubectl/deployment_test.go @@ -0,0 +1,29 @@ +package kubectl + +import ( + "os" + "path/filepath" + + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKubectDeployment(t *testing.T) { + + fileName := filepath.Join(TEST_SRC_DIR, "deployment.json") + cmd_json, _ := os.ReadFile(fileName) + + cmd := getCmdKubectl(true, string(cmd_json)) + + + data := KubectlObject{Name: "dep1", Kind: "Deployment"} + + res, err := cmd.getDeployment(data) + + // map[string]interface {}(map[string]interface {}{"UnavailableReplicas":0, "kind":"Deployment", "name":"dep1", "replicas":1}) + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "Deployment", res["kind"], "TestKubectDeployment error") + assert.Equal(t, 1, res["replicas"], "TestKubectDeployment error") +} + diff --git a/src/kubectl/kubectl.go b/src/kubectl/kubectl.go new file mode 100644 index 0000000..6984b31 --- /dev/null +++ b/src/kubectl/kubectl.go @@ -0,0 +1,22 @@ +package kubectl + +import ( + "os/exec" +) + +type KubectlCommand struct { + Bin string + Exec func(string,...string) commandExecutor +} + +//// +type commandExecutor interface { + Output() ([]byte, error) + CombinedOutput() ([]byte, error) +} + +func (this *KubectlCommand) New() { + this.Exec = func(name string, arg ...string) commandExecutor { + return exec.Command(name, arg...) + } +} diff --git a/src/kubectl/main_test.go b/src/kubectl/main_test.go index 4613268..c93bd3d 100644 --- a/src/kubectl/main_test.go +++ b/src/kubectl/main_test.go @@ -1,8 +1,9 @@ package kubectl import ( - "os" - "testing" + "os" + "strings" + "testing" "path/filepath" ) @@ -11,14 +12,73 @@ var TEST_SRC_DIR = filepath.Join("../../test", "kubectl") var TEST_BIN_DIR = filepath.Join("../../test", "bin") func TestMain(m *testing.M) { - folderPath := TEST_DEST_DIR + folderPath := TEST_DEST_DIR - os.RemoveAll(folderPath) - os.MkdirAll(folderPath, os.ModePerm) + os.RemoveAll(folderPath) + os.MkdirAll(folderPath, os.ModePerm) - // call flag.Parse() here if TestMain uses flags - exitCode := m.Run() + // call flag.Parse() here if TestMain uses flags + exitCode := m.Run() - os.RemoveAll(folderPath) - os.Exit(exitCode) -} \ No newline at end of file + os.RemoveAll(folderPath) + os.Exit(exitCode) +} + +// Mock + +type MockCommandExecutor struct { + // Used to stub the return of the Output method + // Could add other properties depending on testing needs + output string +} + +// Implements the commandExecutor interface +func (m *MockCommandExecutor) Output() ([]byte, error) { + return []byte(m.output), nil +} + +func (m *MockCommandExecutor) CombinedOutput() ([]byte, error) { + return []byte(m.output), nil +} + +// + +func getCmdKubectl(mock bool, output string) (KubectlCommand) { + if mock == true { + + mock := func(name string, args ...string) commandExecutor { + return &MockCommandExecutor{output: output} + } + + cmd := KubectlCommand{Bin: "mock", Exec: mock} + return cmd + } else { + bin := filepath.Join(TEST_BIN_DIR, "kubectl") + os.Chmod(bin, 0700) + + cmd := KubectlCommand{Bin: bin} + cmd.New() + return cmd + } +} + +func getCmdsKubectl(mock bool, outputs map[string]string) (KubectlCommand) { + if mock == true { + + mock := func(name string, args ...string) commandExecutor { + cmd := strings.TrimSuffix(strings.Join(args," "), " ") + output := outputs[cmd] + return &MockCommandExecutor{output: output} + } + + cmd := KubectlCommand{Bin: "mock", Exec: mock} + return cmd + } else { + bin := filepath.Join(TEST_BIN_DIR, "Kubectl") + os.Chmod(bin, 0700) + + cmd := KubectlCommand{Bin: bin} + cmd.New() + return cmd + } +} diff --git a/src/kubectl/object.go b/src/kubectl/object.go index e2c3791..d8994dd 100644 --- a/src/kubectl/object.go +++ b/src/kubectl/object.go @@ -1,122 +1,56 @@ -package kubectl - -import ( - "fmt" - "strings" - "errors" - "time" - "os/exec" - "encoding/json" - log "oc-deploy/log_wrapper" -) - -type KubeObject struct { - Bin string // Chemin vers le binaire - Name string - Kind string -} - -type getOutput struct { - Kind string `json:"kind"` - Status getStatusOutput `json:"status"` -} - -type getStatusOutput struct { - Replicas int `json:"replicas"` - UnavailableReplicas int `json:"unavailableReplicas"` -} - -func (this KubeObject) Get() (map[string]any, error) { - if this.Kind == "Deployment" {return this.getDeployment()} - if this.Kind == "StatefulSet" {return this.getStatefulSet()} - return make(map[string]any), fmt.Errorf("Kind %s inconnu", this.Kind) -} - -func (this KubeObject) getDeployment() (map[string]any, error) { - bin := this.Bin - name := this.Name - - msg := fmt.Sprintf("%s get deployment %s -o json", bin, name) - log.Log().Debug().Msg(msg) - - m := make(map[string]any) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - if err != nil { - return m, errors.New(string(stdout)) - } - - var objmap getOutput - - json.Unmarshal(stdout, &objmap) - - kind := objmap.Kind - status := objmap.Status - - m["name"] = name - m["kind"] = kind - m["replicas"] = status.Replicas - m["UnavailableReplicas"] = status.UnavailableReplicas - - return m, nil -} - -func (this KubeObject) getStatefulSet() (map[string]any, error) { - bin := this.Bin - name := this.Name - - msg := fmt.Sprintf("%s get statefulset %s -o json", bin, name) - log.Log().Debug().Msg(msg) - - m := make(map[string]any) - - cmd_args := strings.Split(msg, " ") - - cmd := exec.Command(cmd_args[0], cmd_args[1:]...) - stdout, err := cmd.CombinedOutput() - if err != nil { - return m, errors.New(string(stdout)) - } - - var objmap getOutput - - json.Unmarshal(stdout, &objmap) - - kind := objmap.Kind - status := objmap.Status - - m["name"] = name - m["kind"] = kind - m["replicas"] = status.Replicas - m["UnavailableReplicas"] = status.UnavailableReplicas - - return m, nil -} - -func (this KubeObject) Wait() (error) { - - boucle := 10 - sleep := 10000 * time.Millisecond - - for _ = range boucle { - - log.Log().Debug().Msg(fmt.Sprintf("Check Deployement %s", this.Name)) - - m, err := this.Get() - if err != nil { - return err - } - ko := m["UnavailableReplicas"].(int) - if ko == 0 { - return nil - } - - log.Log().Info().Msg(fmt.Sprintf(" >> %s (Unavailable : %d)...", this.Name, ko)) - time.Sleep(sleep) - - } - return errors.New("Temps d'attente dépassé") +package kubectl + +import ( + "fmt" + "time" + "errors" + + log "oc-deploy/log_wrapper" +) + + +type KubectlObject struct { + Name string + Kind string +} + +type getOutput struct { + Kind string `json:"kind"` + Status getStatusOutput `json:"status"` +} + +type getStatusOutput struct { + Replicas int `json:"replicas"` + UnavailableReplicas int `json:"unavailableReplicas"` +} + +func (this KubectlCommand) Get(data KubectlObject) (map[string]any, error) { + if data.Kind == "Deployment" {return this.getDeployment(data)} + if data.Kind == "StatefulSet" {return this.getStatefulSet(data)} + return make(map[string]any), fmt.Errorf("Kind %s inconnu", data.Kind) +} + +func (this KubectlCommand) Wait(data KubectlObject) (error) { + + boucle := 10 + sleep := 10000 * time.Millisecond + + for _ = range boucle { + + log.Log().Debug().Msg(fmt.Sprintf("Check Deployement %s", data.Name)) + + m, err := this.Get(data) + if err != nil { + return err + } + ko := m["UnavailableReplicas"].(int) + if ko == 0 { + return nil + } + + log.Log().Info().Msg(fmt.Sprintf(" >> %s (Unavailable : %d)...", data.Name, ko)) + time.Sleep(sleep) + + } + return errors.New("Temps d'attente dépassé") } \ No newline at end of file diff --git a/src/kubectl/stateful_test.go b/src/kubectl/stateful_test.go new file mode 100644 index 0000000..bd16b0d --- /dev/null +++ b/src/kubectl/stateful_test.go @@ -0,0 +1,28 @@ +package kubectl + +import ( + "os" + "path/filepath" + + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKubectStatefulset(t *testing.T) { + + fileName := filepath.Join(TEST_SRC_DIR, "statefulset.json") + cmd_json, _ := os.ReadFile(fileName) + + cmd := getCmdKubectl(true, string(cmd_json)) + + + data := KubectlObject{Name: "dep1", Kind: "Statefulset"} + + res, err := cmd.getDeployment(data) + + // map[string]interface {}(map[string]interface {}{"UnavailableReplicas":0, "kind":"StatefulSet", "name":"dep1", "replicas":1}) + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "StatefulSet", res["kind"], "TestKubectDeployment error") + assert.Equal(t, 1, res["replicas"], "TestKubectDeployment error") +} diff --git a/src/kubectl/statefulset.go b/src/kubectl/statefulset.go new file mode 100644 index 0000000..c6399a6 --- /dev/null +++ b/src/kubectl/statefulset.go @@ -0,0 +1,43 @@ +package kubectl + +import ( + "fmt" + "strings" + "errors" + "encoding/json" + + log "oc-deploy/log_wrapper" +) + +func (this KubectlCommand) getStatefulSet(data KubectlObject) (map[string]any, error) { + + bin := this.Bin + name := data.Name + + msg := fmt.Sprintf("%s get statefulset %s -o json", bin, name) + log.Log().Debug().Msg(msg) + + m := make(map[string]any) + + cmd_args := strings.Split(msg, " ") + cmd := this.Exec(cmd_args[0], cmd_args[1:]...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return m, errors.New(string(stdout)) + } + + var objmap getOutput + + json.Unmarshal(stdout, &objmap) + + kind := objmap.Kind + status := objmap.Status + + m["name"] = name + m["kind"] = kind + m["replicas"] = status.Replicas + m["UnavailableReplicas"] = status.UnavailableReplicas + + return m, nil +} + diff --git a/src/kubectl/version.go b/src/kubectl/version.go index f0cb174..0916bd6 100644 --- a/src/kubectl/version.go +++ b/src/kubectl/version.go @@ -1,31 +1,31 @@ -package kubectl - -import ( - "os/exec" - "encoding/json" -) - -type toolClientVersion struct { - GitVersion string `json:"gitVersion"` -} - -type toolVersion struct { - ClientVersion toolClientVersion `json:"clientVersion"` -} - -func Version(path string) (string, error) { - - cmd := exec.Command(path, "version", "-o", "json", "--client=true") - stdout, err := cmd.CombinedOutput() - - if err != nil { - return "", err - } - - var objmap toolVersion - - json.Unmarshal(stdout, &objmap) - res := objmap.ClientVersion.GitVersion - - return res, nil -} +package kubectl + +import ( + "encoding/json" +) + +type toolClientVersion struct { + GitVersion string `json:"gitVersion"` +} + +type toolVersion struct { + ClientVersion toolClientVersion `json:"clientVersion"` +} + + +func (this KubectlCommand) GetVersion() (string, error) { + + cmd := this.Exec(this.Bin, "version", "-o", "json", "--client=true") + stdout, err := cmd.CombinedOutput() + + if err != nil { + return "", err + } + + var objmap toolVersion + + json.Unmarshal(stdout, &objmap) + res := objmap.ClientVersion.GitVersion + + return res, nil +} \ No newline at end of file diff --git a/src/kubectl/version_test.go b/src/kubectl/version_test.go index 5f9e03d..30ec8dd 100644 --- a/src/kubectl/version_test.go +++ b/src/kubectl/version_test.go @@ -1,22 +1,33 @@ -package kubectl - -import ( - "os" - "path/filepath" - - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestKubectlVersion(t *testing.T){ - - bin := filepath.Join(TEST_BIN_DIR, "kubectl") - os.Chmod(bin, 0700) - assert.FileExists(t, bin, "TestKubectlVersion error") - - version, err := Version(bin) - - assert.Nilf(t, err, "error message %s", bin) - assert.Equal(t, "v1.30.3", version, "TestKubectlVersion error") -} \ No newline at end of file +package kubectl + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKubectlVersion(t *testing.T) { + + cmd_json := ` + { + "clientVersion": { + "major": "1", + "minor": "30", + "gitVersion": "v1.30.3", + "gitCommit": "6fc0a69044f1ac4c13841ec4391224a2df241460", + "gitTreeState": "clean", + "buildDate": "2024-07-16T23:54:40Z", + "goVersion": "go1.22.5", + "compiler": "gc", + "platform": "linux/amd64" + }, + "kustomizeVersion": "v5.0.4-0.20230601165947-6ce0bf390ce3" + }` + cmd := getCmdKubectl(true, cmd_json) + + version, err := cmd.GetVersion() + + assert.Nilf(t, err, "error message %s", err) + assert.Equal(t, "v1.30.3", version, "TestkubectlVersion error") +} + diff --git a/src/occonst/main_test.go b/src/occonst/main_test.go new file mode 100644 index 0000000..5f95c5a --- /dev/null +++ b/src/occonst/main_test.go @@ -0,0 +1,9 @@ +package occonst + +import ( + "testing" +) + + +func TestMain(m *testing.M) { +} \ No newline at end of file diff --git a/src/occonst/variables.go b/src/occonst/variables.go index 6fa10e6..b9a5b5c 100644 --- a/src/occonst/variables.go +++ b/src/occonst/variables.go @@ -1,6 +1,6 @@ package occonst -const ONLINE_URL = "https://cloud.o-forge.io" -const ONLINE_VERSION = "core/oc-deploy" +var ONLINE_URL = "https://cloud.o-forge.io" +var ONLINE_VERSION = "core/oc-deploy" var OFFLINE_DIR = "../../offline" diff --git a/src/tool/helm.go b/src/tool/helm.go index 878d0d4..c7d3b52 100644 --- a/src/tool/helm.go +++ b/src/tool/helm.go @@ -49,5 +49,7 @@ func (this HelmInstallData) Download() (error) { /////////////// func (this HelmInstallData) Version(path string) (string, error) { - return helm.Version(path) + cmd := helm.HelmCommand{Bin: path} + cmd.New() + return cmd.GetVersion() } \ No newline at end of file diff --git a/src/tool/kubectl.go b/src/tool/kubectl.go index 69142f5..5bb45ce 100644 --- a/src/tool/kubectl.go +++ b/src/tool/kubectl.go @@ -37,5 +37,7 @@ func (this KubecltInstallData) Download() (error) { /////////////// func (this KubecltInstallData) Version(path string) (string, error) { - return kubectl.Version(path) + cmd := kubectl.KubectlCommand{Bin: path} + cmd.New() + return cmd.GetVersion() } diff --git a/test/kubectl/deployment.json b/test/kubectl/deployment.json new file mode 100644 index 0000000..5df5f8b --- /dev/null +++ b/test/kubectl/deployment.json @@ -0,0 +1,312 @@ +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "1", + "meta.helm.sh/release-name": "phpmyadmin", + "meta.helm.sh/release-namespace": "default" + }, + "creationTimestamp": "2024-09-06T12:29:16Z", + "generation": 1, + "labels": { + "app.kubernetes.io/instance": "phpmyadmin", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "phpmyadmin", + "app.kubernetes.io/version": "5.2.1", + "helm.sh/chart": "phpmyadmin-17.0.4" + }, + "name": "phpmyadmin", + "namespace": "default", + "resourceVersion": "87308", + "uid": "b8518be1-1dd0-45a1-80f3-ddb835a56a5d" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/instance": "phpmyadmin", + "app.kubernetes.io/name": "phpmyadmin" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": "25%" + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/instance": "phpmyadmin", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "phpmyadmin", + "app.kubernetes.io/version": "5.2.1", + "helm.sh/chart": "phpmyadmin-17.0.4" + } + }, + "spec": { + "affinity": { + "podAntiAffinity": { + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "app.kubernetes.io/instance": "phpmyadmin", + "app.kubernetes.io/name": "phpmyadmin" + } + }, + "topologyKey": "kubernetes.io/hostname" + }, + "weight": 1 + } + ] + } + }, + "automountServiceAccountToken": false, + "containers": [ + { + "env": [ + { + "name": "BITNAMI_DEBUG", + "value": "false" + }, + { + "name": "DATABASE_PORT_NUMBER", + "value": "3306" + }, + { + "name": "DATABASE_HOST" + }, + { + "name": "PHPMYADMIN_ALLOW_NO_PASSWORD", + "value": "true" + }, + { + "name": "PHPMYADMIN_ALLOW_ARBITRARY_SERVER", + "value": "true" + }, + { + "name": "DATABASE_ENABLE_SSL", + "value": "no" + } + ], + "image": "docker.io/bitnami/phpmyadmin:5.2.1-debian-12-r36", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 6, + "initialDelaySeconds": 30, + "periodSeconds": 10, + "successThreshold": 1, + "tcpSocket": { + "port": "http" + }, + "timeoutSeconds": 30 + }, + "name": "phpmyadmin", + "ports": [ + { + "containerPort": 8080, + "name": "http", + "protocol": "TCP" + }, + { + "containerPort": 8443, + "name": "https", + "protocol": "TCP" + } + ], + "readinessProbe": { + "failureThreshold": 6, + "httpGet": { + "path": "/", + "port": "http", + "scheme": "HTTP" + }, + "initialDelaySeconds": 30, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 30 + }, + "resources": { + "limits": { + "cpu": "375m", + "ephemeral-storage": "2Gi", + "memory": "384Mi" + }, + "requests": { + "cpu": "250m", + "ephemeral-storage": "50Mi", + "memory": "256Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "privileged": false, + "readOnlyRootFilesystem": true, + "runAsGroup": 1001, + "runAsNonRoot": true, + "runAsUser": 1001, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/opt/bitnami/apache/conf", + "name": "empty-dir", + "subPath": "apache-conf-dir" + }, + { + "mountPath": "/opt/bitnami/apache/logs", + "name": "empty-dir", + "subPath": "apache-logs-dir" + }, + { + "mountPath": "/opt/bitnami/apache/var/run", + "name": "empty-dir", + "subPath": "apache-tmp-dir" + }, + { + "mountPath": "/opt/bitnami/php/etc", + "name": "empty-dir", + "subPath": "php-conf-dir" + }, + { + "mountPath": "/opt/bitnami/php/tmp", + "name": "empty-dir", + "subPath": "php-tmp-dir" + }, + { + "mountPath": "/opt/bitnami/php/var", + "name": "empty-dir", + "subPath": "php-var-dir" + }, + { + "mountPath": "/tmp", + "name": "empty-dir", + "subPath": "tmp-dir" + }, + { + "mountPath": "/opt/bitnami/phpmyadmin", + "name": "empty-dir", + "subPath": "app-base-dir" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "hostAliases": [ + { + "hostnames": [ + "status.localhost" + ], + "ip": "127.0.0.1" + } + ], + "initContainers": [ + { + "args": [ + "-ec", + "#!/bin/bash\n\n. /opt/bitnami/scripts/liblog.sh\n. /opt/bitnami/scripts/libfs.sh\n\ninfo \"Copying base dir to empty dir\"\n# In order to not break the application functionality (such as upgrades or plugins) we need\n# to make the base directory writable, so we need to copy it to an empty dir volume\ncp -r --preserve=mode /opt/bitnami/phpmyadmin /emptydir/app-base-dir\n\ninfo \"Copying symlinks to stdout/stderr\"\n# We copy the logs folder because it has symlinks to stdout and stderr\nif ! is_dir_empty /opt/bitnami/apache/logs; then\n cp -r /opt/bitnami/apache/logs /emptydir/apache-logs-dir\nfi\ninfo \"Copying php var directory\"\n# PhpMyAdmin will fail to start if the php var folder is not populated\nif ! is_dir_empty /opt/bitnami/php/var; then\n cp -r /opt/bitnami/php/var /emptydir/php-var-dir\nfi\ninfo \"Copy operation completed\"\n" + ], + "command": [ + "/bin/bash" + ], + "image": "docker.io/bitnami/phpmyadmin:5.2.1-debian-12-r36", + "imagePullPolicy": "IfNotPresent", + "name": "prepare-base-dir", + "resources": { + "limits": { + "cpu": "375m", + "ephemeral-storage": "2Gi", + "memory": "384Mi" + }, + "requests": { + "cpu": "250m", + "ephemeral-storage": "50Mi", + "memory": "256Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "privileged": false, + "readOnlyRootFilesystem": true, + "runAsGroup": 1001, + "runAsNonRoot": true, + "runAsUser": 1001, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/emptydir", + "name": "empty-dir" + } + ] + } + ], + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1001, + "fsGroupChangePolicy": "Always" + }, + "serviceAccount": "phpmyadmin", + "serviceAccountName": "phpmyadmin", + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "emptyDir": {}, + "name": "empty-dir" + } + ] + } + } + }, + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-09-06T12:29:17Z", + "lastUpdateTime": "2024-09-06T12:30:08Z", + "message": "ReplicaSet \"phpmyadmin-7f7bbf7bd7\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + }, + { + "lastTransitionTime": "2024-09-09T11:54:00Z", + "lastUpdateTime": "2024-09-09T11:54:00Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + } + ], + "observedGeneration": 1, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } +} diff --git a/test/kubectl/statefulset.json b/test/kubectl/statefulset.json new file mode 100644 index 0000000..a849416 --- /dev/null +++ b/test/kubectl/statefulset.json @@ -0,0 +1,332 @@ +{ + "apiVersion": "apps/v1", + "kind": "StatefulSet", + "metadata": { + "annotations": { + "meta.helm.sh/release-name": "wordpress", + "meta.helm.sh/release-namespace": "default" + }, + "creationTimestamp": "2024-09-09T12:29:35Z", + "generation": 1, + "labels": { + "app.kubernetes.io/component": "primary", + "app.kubernetes.io/instance": "wordpress", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "mariadb", + "app.kubernetes.io/version": "11.4.2", + "helm.sh/chart": "mariadb-19.0.3" + }, + "name": "wordpress-mariadb", + "namespace": "default", + "resourceVersion": "92784", + "uid": "82197de7-3b4f-4225-b1a2-58e8ac0fad44" + }, + "spec": { + "persistentVolumeClaimRetentionPolicy": { + "whenDeleted": "Retain", + "whenScaled": "Retain" + }, + "podManagementPolicy": "OrderedReady", + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/component": "primary", + "app.kubernetes.io/instance": "wordpress", + "app.kubernetes.io/name": "mariadb" + } + }, + "serviceName": "wordpress-mariadb", + "template": { + "metadata": { + "annotations": { + "checksum/configuration": "0fdca6295cb246435ed7edb5801c3445ca97c878dcce94b652f2fec128efd78a" + }, + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "primary", + "app.kubernetes.io/instance": "wordpress", + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "mariadb", + "app.kubernetes.io/version": "11.4.2", + "helm.sh/chart": "mariadb-19.0.3" + } + }, + "spec": { + "affinity": { + "podAntiAffinity": { + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "podAffinityTerm": { + "labelSelector": { + "matchLabels": { + "app.kubernetes.io/component": "primary", + "app.kubernetes.io/instance": "wordpress", + "app.kubernetes.io/name": "mariadb" + } + }, + "topologyKey": "kubernetes.io/hostname" + }, + "weight": 1 + } + ] + } + }, + "automountServiceAccountToken": false, + "containers": [ + { + "env": [ + { + "name": "BITNAMI_DEBUG", + "value": "false" + }, + { + "name": "MARIADB_ROOT_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "key": "mariadb-root-password", + "name": "wordpress-mariadb" + } + } + }, + { + "name": "MARIADB_USER", + "value": "bn_wordpress" + }, + { + "name": "MARIADB_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "key": "mariadb-password", + "name": "wordpress-mariadb" + } + } + }, + { + "name": "MARIADB_DATABASE", + "value": "bitnami_wordpress" + } + ], + "image": "docker.io/bitnami/mariadb:11.4.2-debian-12-r2", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "exec": { + "command": [ + "/bin/bash", + "-ec", + "password_aux=\"${MARIADB_ROOT_PASSWORD:-}\"\nif [[ -f \"${MARIADB_ROOT_PASSWORD_FILE:-}\" ]]; then\n password_aux=$(cat \"$MARIADB_ROOT_PASSWORD_FILE\")\nfi\nmysqladmin status -uroot -p\"${password_aux}\"\n" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 120, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "mariadb", + "ports": [ + { + "containerPort": 3306, + "name": "mysql", + "protocol": "TCP" + } + ], + "readinessProbe": { + "exec": { + "command": [ + "/bin/bash", + "-ec", + "password_aux=\"${MARIADB_ROOT_PASSWORD:-}\"\nif [[ -f \"${MARIADB_ROOT_PASSWORD_FILE:-}\" ]]; then\n password_aux=$(cat \"$MARIADB_ROOT_PASSWORD_FILE\")\nfi\nmysqladmin ping -uroot -p\"${password_aux}\"\n" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 30, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "resources": { + "limits": { + "cpu": "375m", + "ephemeral-storage": "2Gi", + "memory": "384Mi" + }, + "requests": { + "cpu": "250m", + "ephemeral-storage": "50Mi", + "memory": "256Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "privileged": false, + "readOnlyRootFilesystem": true, + "runAsGroup": 1001, + "runAsNonRoot": true, + "runAsUser": 1001, + "seLinuxOptions": {}, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/bitnami/mariadb", + "name": "data" + }, + { + "mountPath": "/opt/bitnami/mariadb/conf/my.cnf", + "name": "config", + "subPath": "my.cnf" + }, + { + "mountPath": "/tmp", + "name": "empty-dir", + "subPath": "tmp-dir" + }, + { + "mountPath": "/opt/bitnami/mariadb/conf", + "name": "empty-dir", + "subPath": "app-conf-dir" + }, + { + "mountPath": "/opt/bitnami/mariadb/tmp", + "name": "empty-dir", + "subPath": "app-tmp-dir" + }, + { + "mountPath": "/opt/bitnami/mariadb/logs", + "name": "empty-dir", + "subPath": "app-logs-dir" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "initContainers": [ + { + "args": [ + "-ec", + "#!/bin/bash\n\n. /opt/bitnami/scripts/libfs.sh\n# We copy the logs folder because it has symlinks to stdout and stderr\nif ! is_dir_empty /opt/bitnami/mariadb/logs; then\n cp -r /opt/bitnami/mariadb/logs /emptydir/app-logs-dir\nfi\n" + ], + "command": [ + "/bin/bash" + ], + "image": "docker.io/bitnami/mariadb:11.4.2-debian-12-r2", + "imagePullPolicy": "IfNotPresent", + "name": "preserve-logs-symlinks", + "resources": { + "limits": { + "cpu": "375m", + "ephemeral-storage": "2Gi", + "memory": "384Mi" + }, + "requests": { + "cpu": "250m", + "ephemeral-storage": "50Mi", + "memory": "256Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "privileged": false, + "readOnlyRootFilesystem": true, + "runAsGroup": 1001, + "runAsNonRoot": true, + "runAsUser": 1001, + "seLinuxOptions": {}, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/emptydir", + "name": "empty-dir" + } + ] + } + ], + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "fsGroup": 1001, + "fsGroupChangePolicy": "Always" + }, + "serviceAccount": "wordpress-mariadb", + "serviceAccountName": "wordpress-mariadb", + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "emptyDir": {}, + "name": "empty-dir" + }, + { + "configMap": { + "defaultMode": 420, + "name": "wordpress-mariadb" + }, + "name": "config" + } + ] + } + }, + "updateStrategy": { + "type": "RollingUpdate" + }, + "volumeClaimTemplates": [ + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "primary", + "app.kubernetes.io/instance": "wordpress", + "app.kubernetes.io/name": "mariadb" + }, + "name": "data" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "8Gi" + } + }, + "volumeMode": "Filesystem" + }, + "status": { + "phase": "Pending" + } + } + ] + }, + "status": { + "availableReplicas": 1, + "collisionCount": 0, + "currentReplicas": 1, + "currentRevision": "wordpress-mariadb-599b74c5bc", + "observedGeneration": 1, + "readyReplicas": 1, + "replicas": 1, + "updateRevision": "wordpress-mariadb-599b74c5bc", + "updatedReplicas": 1 + } +}