From 10cccee51494d5a2499cc3057359835d4068af71 Mon Sep 17 00:00:00 2001 From: aantoni Date: Tue, 10 Feb 2026 14:50:06 +0100 Subject: [PATCH 1/2] centralize all helper functions into internal/utils package --- internal/k8s/actions/apply.go | 3 +- internal/k8s/actions/health.go | 3 +- internal/k8s/actions/helpers.go | 27 ---- internal/k8s/actions/helpers_test.go | 125 ------------------- internal/k8s/actions/scale.go | 3 +- internal/k8s/actions/start.go | 7 +- internal/k8s/actions/stop.go | 3 +- internal/k8s/actions/update_backendmanage.go | 3 +- internal/k8s/actions/update_instance.go | 3 +- internal/utils/utils.go | 20 ++- internal/utils/utils_test.go | 122 ++++++++++++++++++ 11 files changed, 158 insertions(+), 161 deletions(-) delete mode 100644 internal/k8s/actions/helpers.go delete mode 100644 internal/k8s/actions/helpers_test.go diff --git a/internal/k8s/actions/apply.go b/internal/k8s/actions/apply.go index 95969d0..f152b2b 100644 --- a/internal/k8s/actions/apply.go +++ b/internal/k8s/actions/apply.go @@ -8,6 +8,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -107,7 +108,7 @@ func applyDirectory(ctx context.Context, k8sClient *client.Client, dirPath strin continue } - if !isYAMLFile(file.Name()) { + if !utils.IsYAMLFile(file.Name()) { logger.Debug("Skipping non-YAML file: %s", file.Name()) continue } diff --git a/internal/k8s/actions/health.go b/internal/k8s/actions/health.go index 0d5303d..06b2ce9 100644 --- a/internal/k8s/actions/health.go +++ b/internal/k8s/actions/health.go @@ -7,6 +7,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" ) @@ -34,7 +35,7 @@ func HealthCmd() *cobra.Command { cmd.RunE = func(cmd *cobra.Command, args []string) error { logger.Info("=== K8S HEALTH CHECK ===") instanceDir := args[0] - namespace := extractNamespace(instanceDir) + namespace := utils.ExtractNamespace(instanceDir) logger.Debug("Namespace: %s", namespace) k8sClient, err := client.New(*kubeconfig) diff --git a/internal/k8s/actions/helpers.go b/internal/k8s/actions/helpers.go deleted file mode 100644 index 882441c..0000000 --- a/internal/k8s/actions/helpers.go +++ /dev/null @@ -1,27 +0,0 @@ -package actions - -import ( - "os" - "path/filepath" - "strings" -) - -// extractNamespace gets the namespace from instance directory path -// Example: "/real/path/to/my.instance.dir.url" -> "myinstancedirurl" -func extractNamespace(instanceDir string) string { - dirName := filepath.Base(instanceDir) - namespace := strings.ReplaceAll(dirName, ".", "") - return namespace -} - -// fileExists checks if a file exists -func fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -// isYAMLFile checks if filename has YAML extension -func isYAMLFile(filename string) bool { - ext := filepath.Ext(filename) - return ext == ".yaml" || ext == ".yml" -} diff --git a/internal/k8s/actions/helpers_test.go b/internal/k8s/actions/helpers_test.go deleted file mode 100644 index 195b4da..0000000 --- a/internal/k8s/actions/helpers_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package actions - -import ( - "os" - "testing" -) - -func TestExtractNamespace(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - { - name: "simple directory", - input: "my-instance", - expected: "my-instance", - }, - { - name: "directory with dots", - input: "my.instance.org", - expected: "myinstanceorg", - }, - { - name: "full path with dots", - input: "/home/user/projects/my.instance.org", - expected: "myinstanceorg", - }, - { - name: "nested path without dots", - input: "/var/lib/test/prod-instance", - expected: "prod-instance", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := extractNamespace(tt.input) - if result != tt.expected { - t.Errorf("extractNamespace(%q) = %q, want %q", tt.input, result, tt.expected) - } - }) - } -} - -func TestFileExists(t *testing.T) { - // Create a temporary file - tmpFile, err := os.CreateTemp("", "test-file-*") - if err != nil { - t.Fatalf("Failed to create temp file: %v", err) - } - defer func() { - _ = tmpFile.Close() - _ = os.Remove(tmpFile.Name()) - }() - - tests := []struct { - name string - path string - expected bool - }{ - { - name: "existing file", - path: tmpFile.Name(), - expected: true, - }, - { - name: "non-existing file", - path: "/tmp/definitely-does-not-exist-12345", - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := fileExists(tt.path) - if result != tt.expected { - t.Errorf("fileExists(%q) = %v, want %v", tt.path, result, tt.expected) - } - }) - } -} - -func TestIsYAMLFile(t *testing.T) { - tests := []struct { - name string - filename string - expected bool - }{ - { - name: "yaml extension", - filename: "deployment.yaml", - expected: true, - }, - { - name: "yml extension", - filename: "service.yml", - expected: true, - }, - { - name: "json file", - filename: "config.json", - expected: false, - }, - { - name: "no extension", - filename: "Makefile", - expected: false, - }, - { - name: "yaml in path but not extension", - filename: "/path/yaml/file.txt", - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := isYAMLFile(tt.filename) - if result != tt.expected { - t.Errorf("isYAMLFile(%q) = %v, want %v", tt.filename, result, tt.expected) - } - }) - } -} diff --git a/internal/k8s/actions/scale.go b/internal/k8s/actions/scale.go index f2c94c4..788221d 100644 --- a/internal/k8s/actions/scale.go +++ b/internal/k8s/actions/scale.go @@ -9,6 +9,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" ) @@ -49,7 +50,7 @@ func ScaleCmd() *cobra.Command { logger.Debug("Instance directory: %s", instanceDir) logger.Info("Service: %s", *service) - namespace := extractNamespace(instanceDir) + namespace := utils.ExtractNamespace(instanceDir) logger.Info("Namespace: %s", namespace) k8sClient, err := client.New(*kubeconfig) diff --git a/internal/k8s/actions/start.go b/internal/k8s/actions/start.go index c530547..b18a749 100644 --- a/internal/k8s/actions/start.go +++ b/internal/k8s/actions/start.go @@ -8,6 +8,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" ) @@ -53,7 +54,11 @@ func StartCmd() *cobra.Command { logger.Info("Applied namespace: %s", namespace) tlsSecretPath := filepath.Join(instanceDir, constants.SecretsDirName, constants.TlsCertSecretYAML) - if fileExists(tlsSecretPath) { + tlsExists, err := utils.FileExists(tlsSecretPath) + if err != nil { + return fmt.Errorf("checking tls secret path %s: %w", tlsSecretPath, err) + } + if tlsExists { logger.Info("Found and applying %s", tlsSecretPath) if _, err := applyManifest(ctx, k8sClient, tlsSecretPath); err != nil { return fmt.Errorf("applying TLS secret: %w", err) diff --git a/internal/k8s/actions/stop.go b/internal/k8s/actions/stop.go index cdafa27..b46a7ce 100644 --- a/internal/k8s/actions/stop.go +++ b/internal/k8s/actions/stop.go @@ -10,6 +10,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,7 +50,7 @@ func StopCmd() *cobra.Command { ctx := context.Background() - namespace := extractNamespace(instanceDir) + namespace := utils.ExtractNamespace(instanceDir) if err := saveTLSSecret(ctx, k8sClient, namespace, instanceDir); err != nil { logger.Warn("Failed to save TLS secret: %v", err) } diff --git a/internal/k8s/actions/update_backendmanage.go b/internal/k8s/actions/update_backendmanage.go index f715983..f5e143c 100644 --- a/internal/k8s/actions/update_backendmanage.go +++ b/internal/k8s/actions/update_backendmanage.go @@ -9,6 +9,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -51,7 +52,7 @@ func UpdateBackendmanageCmd() *cobra.Command { logger.Info("=== K8S UPDATE/REVERT BACKENDMANAGE ===") instanceDir := args[0] - namespace := extractNamespace(instanceDir) + namespace := utils.ExtractNamespace(instanceDir) logger.Info("Namespace: %s", namespace) diff --git a/internal/k8s/actions/update_instance.go b/internal/k8s/actions/update_instance.go index 6ab1bff..3d6b66b 100644 --- a/internal/k8s/actions/update_instance.go +++ b/internal/k8s/actions/update_instance.go @@ -8,6 +8,7 @@ import ( "github.com/OpenSlides/openslides-cli/internal/constants" "github.com/OpenSlides/openslides-cli/internal/k8s/client" "github.com/OpenSlides/openslides-cli/internal/logger" + "github.com/OpenSlides/openslides-cli/internal/utils" "github.com/spf13/cobra" ) @@ -39,7 +40,7 @@ func UpdateInstanceCmd() *cobra.Command { logger.Debug("Instance directory: %s", instanceDir) - namespace := extractNamespace(instanceDir) + namespace := utils.ExtractNamespace(instanceDir) logger.Info("Namespace: %s", namespace) k8sClient, err := client.New(*kubeconfig) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 6987575..78bd432 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -7,6 +7,8 @@ import ( "io/fs" "os" "path" + "path/filepath" + "strings" "github.com/OpenSlides/openslides-cli/internal/logger" ) @@ -16,7 +18,7 @@ import ( func CreateFile(dir string, force bool, name string, content []byte, perm fs.FileMode) error { p := path.Join(dir, name) - pExists, err := fileExists(p) + pExists, err := FileExists(p) if err != nil { return fmt.Errorf("checking file existance: %w", err) } @@ -33,7 +35,7 @@ func CreateFile(dir string, force bool, name string, content []byte, perm fs.Fil // fileExists is a small helper function to check if a file already exists. It is not // save in concurrent usage. -func fileExists(p string) (bool, error) { +func FileExists(p string) (bool, error) { _, err := os.Stat(p) if err == nil { return true, nil @@ -104,3 +106,17 @@ func ReadPassword(passwordFile string) (string, error) { logger.Debug("Password read successfully (%d bytes)", len(password)) return password, nil } + +// extractNamespace gets the namespace from instance directory path +// Example: "/real/path/to/my.instance.dir.url" -> "myinstancedirurl" +func ExtractNamespace(instanceDir string) string { + dirName := filepath.Base(instanceDir) + namespace := strings.ReplaceAll(dirName, ".", "") + return namespace +} + +// isYAMLFile checks if filename has YAML extension +func IsYAMLFile(filename string) bool { + ext := filepath.Ext(filename) + return ext == ".yaml" || ext == ".yml" +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index 00769c9..7e88095 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -235,3 +235,125 @@ func TestCreateFile(t *testing.T) { } }) } + +func TestExtractNamespace(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "simple directory", + input: "my-instance", + expected: "my-instance", + }, + { + name: "directory with dots", + input: "my.instance.org", + expected: "myinstanceorg", + }, + { + name: "full path with dots", + input: "/home/user/projects/my.instance.org", + expected: "myinstanceorg", + }, + { + name: "nested path without dots", + input: "/var/lib/test/prod-instance", + expected: "prod-instance", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ExtractNamespace(tt.input) + if result != tt.expected { + t.Errorf("extractNamespace(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + +func TestFileExists(t *testing.T) { + // Create a temporary file + tmpFile, err := os.CreateTemp("", "test-file-*") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + defer func() { + _ = tmpFile.Close() + _ = os.Remove(tmpFile.Name()) + }() + + tests := []struct { + name string + path string + expected bool + }{ + { + name: "existing file", + path: tmpFile.Name(), + expected: true, + }, + { + name: "non-existing file", + path: "/tmp/definitely-does-not-exist-12345", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := FileExists(tt.path) + if err != nil { + t.Fatalf("Failed to check if file %s exists: %v", tt.path, err) + } + if result != tt.expected { + t.Errorf("fileExists(%q) = %v, want %v", tt.path, result, tt.expected) + } + }) + } +} + +func TestIsYAMLFile(t *testing.T) { + tests := []struct { + name string + filename string + expected bool + }{ + { + name: "yaml extension", + filename: "deployment.yaml", + expected: true, + }, + { + name: "yml extension", + filename: "service.yml", + expected: true, + }, + { + name: "json file", + filename: "config.json", + expected: false, + }, + { + name: "no extension", + filename: "Makefile", + expected: false, + }, + { + name: "yaml in path but not extension", + filename: "/path/yaml/file.txt", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsYAMLFile(tt.filename) + if result != tt.expected { + t.Errorf("isYAMLFile(%q) = %v, want %v", tt.filename, result, tt.expected) + } + }) + } +} From e794928bec392e8632fa6a2795510c8ab8050697 Mon Sep 17 00:00:00 2001 From: aantoni Date: Tue, 10 Feb 2026 15:01:49 +0100 Subject: [PATCH 2/2] adjust README for the changes --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1eed834..ffbe71e 100644 --- a/README.md +++ b/README.md @@ -1098,8 +1098,6 @@ openslides-cli/ │ │ │ ├── health.go │ │ │ ├── health_check.go │ │ │ ├── health_check_test.go -│ │ │ ├── helpers.go -│ │ │ ├── helpers_test.go │ │ │ ├── scale.go │ │ │ ├── start.go │ │ │ ├── stop.go