From 7147da7f9d88d611d42604362423ed6b5a6a5626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Tue, 7 May 2024 14:43:56 +0200 Subject: [PATCH] fix(configmap): command fixed to grant permissions to the parent folders (#313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(configmap): command fixed to grant permissions to the parent folders Signed-off-by: Jose Ramon Mañes * fix(command): init command fixed Signed-off-by: Jose Ramon Mañes * fix(command): utils added Signed-off-by: Jose Ramon Mañes * feat(example): example tests added Signed-off-by: Jose Ramon Mañes * fix(comments): update comments + use assert Signed-off-by: Jose Ramon Mañes * fix(command): crash in case of multiple volumes Signed-off-by: Jose Ramon Mañes * fix(command): add eof Signed-off-by: Jose Ramon Mañes * fix(command): tmp feat Signed-off-by: Jose Ramon Mañes * fix(command): fix Signed-off-by: Jose Ramon Mañes * fix(command): undo fix Signed-off-by: Jose Ramon Mañes * fix(comments): fix assert comments Signed-off-by: Jose Ramon Mañes * fix(comments): fix assert comments Signed-off-by: Jose Ramon Mañes * fix(comments): fix assert comments Signed-off-by: Jose Ramon Mañes --------- Signed-off-by: Jose Ramon Mañes --- e2e/basic/assert_cleanups.go | 52 +++ e2e/basic/file_test_image_cached_test.go | 17 +- e2e/basic/files_to_volumes_cm_test.go | 434 +++++++++++++++++++ e2e/basic/folder_test_image_cached_test.go | 20 +- e2e/basic/resources/file_cm_to_folder/test_1 | 1 + e2e/basic/resources/file_cm_to_folder/test_2 | 1 + e2e/basic/resources/file_cm_to_folder/test_3 | 1 + go.mod | 2 +- go.sum | 2 + pkg/k8s/k8s_pod.go | 50 ++- pkg/knuu/errors.go | 1 + pkg/knuu/instance.go | 10 + 12 files changed, 549 insertions(+), 42 deletions(-) create mode 100644 e2e/basic/assert_cleanups.go create mode 100644 e2e/basic/files_to_volumes_cm_test.go create mode 100644 e2e/basic/resources/file_cm_to_folder/test_1 create mode 100644 e2e/basic/resources/file_cm_to_folder/test_2 create mode 100644 e2e/basic/resources/file_cm_to_folder/test_3 diff --git a/e2e/basic/assert_cleanups.go b/e2e/basic/assert_cleanups.go new file mode 100644 index 0000000..6fa3294 --- /dev/null +++ b/e2e/basic/assert_cleanups.go @@ -0,0 +1,52 @@ +package basic + +import ( + "os" + "testing" + + "github.com/celestiaorg/knuu/pkg/knuu" +) + +// assertCleanupInstance is a helper function that cleans up a single instance. +func assertCleanupInstance(t *testing.T, instance *knuu.Instance) error { + if instance != nil { + err := instance.Destroy() + if err != nil { + t.Fatalf("Error destroying instance: %v", err) + } + } + return nil +} + +// assertCleanupInstances is a helper function that cleans up a list of instances. +func assertCleanupInstances(t *testing.T, executor *knuu.Executor, instances []*knuu.Instance) error { + if os.Getenv("KNUU_SKIP_CLEANUP") != "true" { + err := executor.Destroy() + if err != nil { + t.Fatalf("Error destroying executor: %v", err) + } + + for _, instance := range instances { + if instance != nil { + err := instance.Destroy() + if err != nil { + t.Fatalf("Error destroying instance: %v", err) + } + } + } + } + return nil +} + +// BatchDestroy destroys a list of instances. +func BatchDestroy(instances ...*knuu.Instance) error { + for _, instance := range instances { + if instance != nil { + err := instance.Destroy() + if err != nil { + return err + } + } + } + return nil +} diff --git a/e2e/basic/file_test_image_cached_test.go b/e2e/basic/file_test_image_cached_test.go index 4016171..0f6b4e2 100644 --- a/e2e/basic/file_test_image_cached_test.go +++ b/e2e/basic/file_test_image_cached_test.go @@ -63,20 +63,9 @@ func TestFileCached(t *testing.T) { t.Cleanup(func() { // Cleanup - if os.Getenv("KNUU_SKIP_CLEANUP") != "true" { - err := executor.Destroy() - if err != nil { - t.Fatalf("Error destroying executor: %v", err) - } - - for _, instance := range instances { - if instance != nil { - err := instance.Destroy() - if err != nil { - t.Fatalf("Error destroying instance: %v", err) - } - } - } + err := assertCleanupInstances(t, executor, instances) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) } }) diff --git a/e2e/basic/files_to_volumes_cm_test.go b/e2e/basic/files_to_volumes_cm_test.go new file mode 100644 index 0000000..af6ee4d --- /dev/null +++ b/e2e/basic/files_to_volumes_cm_test.go @@ -0,0 +1,434 @@ +package basic + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/knuu/pkg/knuu" +) + +// TestOneVolumeNoFiles tests the scenario where we have one volume and no files. +// the initContainer command that it generates looks like: +// no initContainer command, as there is no volumes, nor files. +func TestNoVolumesNoFiles(t *testing.T) { + t.Parallel() + // Setup + + executor, err := knuu.NewExecutor() + if err != nil { + t.Fatalf("Error creating executor: %v", err) + } + + instanceName := fmt.Sprintf("web-1") + instance, err := knuu.NewInstance(instanceName) + if err != nil { + t.Fatalf("Error creating instance '%v': %v", instanceName, err) + } + err = instance.SetImage("docker.io/nginx:latest") + if err != nil { + t.Fatalf("Error setting image for '%v': %v", instanceName, err) + } + err = instance.AddPortTCP(80) + if err != nil { + t.Fatalf("Error adding port for '%v': %v", instanceName, err) + } + err = instance.Commit() + if err != nil { + t.Fatalf("Error committing instance '%v': %v", instanceName, err) + } + + // Cleanup + t.Cleanup(func() { + err := assertCleanupInstance(t, instance) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) + } + }) + + // Test logic + err = instance.StartAsync() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + webIP, err := instance.GetIP() + if err != nil { + t.Fatalf("Error getting IP: %v", err) + } + + err = instance.WaitInstanceIsRunning() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + wget, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + assert.Equal(t, "\n\n\nWelcome to nginx!\n\n\n\n

Welcome to nginx!

\n

If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.

\n\n

For online documentation and support please refer to\nnginx.org.
\nCommercial support is available at\nnginx.com.

\n\n

Thank you for using nginx.

\n\n\n", wget) +} + +// TestOneVolumeNoFiles tests the scenario where we have one volume and no files. +// the initContainer command that it generates looks like: +// mkdir -p /knuu && if [ -d /opt/vol1 ] && [ \"$(ls -A /opt/vol1)\" ]; then cp -r /opt/vol1/* /knuu//opt/vol1 && chown -R 0:0 /knuu/* ;fi +func TestOneVolumeNoFiles(t *testing.T) { + t.Parallel() + // Setup + + executor, err := knuu.NewExecutor() + if err != nil { + t.Fatalf("Error creating executor: %v", err) + } + + instanceName := fmt.Sprintf("web-1") + instance, err := knuu.NewInstance(instanceName) + if err != nil { + t.Fatalf("Error creating instance '%v': %v", instanceName, err) + } + err = instance.SetImage("docker.io/nginx:latest") + if err != nil { + t.Fatalf("Error setting image for '%v': %v", instanceName, err) + } + err = instance.AddPortTCP(80) + if err != nil { + t.Fatalf("Error adding port for '%v': %v", instanceName, err) + } + err = instance.AddVolumeWithOwner("/opt/vol1", "1Gi", 0) + if err != nil { + t.Fatalf("Error adding volume: %v", err) + } + err = instance.Commit() + if err != nil { + t.Fatalf("Error committing instance '%v': %v", instanceName, err) + } + + // Cleanup + t.Cleanup(func() { + err := assertCleanupInstance(t, instance) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) + } + }) + + // Test logic + err = instance.StartAsync() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + webIP, err := instance.GetIP() + if err != nil { + t.Fatalf("Error getting IP: %v", err) + } + + err = instance.WaitInstanceIsRunning() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + wget, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + assert.Equal(t, "\n\n\nWelcome to nginx!\n\n\n\n

Welcome to nginx!

\n

If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.

\n\n

For online documentation and support please refer to\nnginx.org.
\nCommercial support is available at\nnginx.com.

\n\n

Thank you for using nginx.

\n\n\n", wget) +} + +// TestNoVolumesOneFile tests the scenario where we have no volumes and one file. +// the initContainer command that it generates looks like: +// no initContainer command, as we do not have volumes. +func TestNoVolumesOneFile(t *testing.T) { + t.Parallel() + // Setup + + executor, err := knuu.NewExecutor() + if err != nil { + t.Fatalf("Error creating executor: %v", err) + } + + const numberOfInstances = 2 + instances := make([]*knuu.Instance, numberOfInstances) + + for i := 0; i < numberOfInstances; i++ { + instanceName := fmt.Sprintf("web%d", i+1) + instance, err := knuu.NewInstance(instanceName) + if err != nil { + t.Fatalf("Error creating instance '%v': %v", instanceName, err) + } + err = instance.SetImage("docker.io/nginx:latest") + if err != nil { + t.Fatalf("Error setting image for '%v': %v", instanceName, err) + } + err = instance.AddPortTCP(80) + if err != nil { + t.Fatalf("Error adding port for '%v': %v", instanceName, err) + } + _, err = instance.ExecuteCommand("mkdir", "-p", "/usr/share/nginx/html") + if err != nil { + t.Fatalf("Error executing command for '%v': %v", instanceName, err) + } + err = instance.Commit() + if err != nil { + t.Fatalf("Error committing instance '%v': %v", instanceName, err) + } + + instances[i] = instance + } + + var wgFolders sync.WaitGroup + for i, instance := range instances { + wgFolders.Add(1) + go func(i int, instance *knuu.Instance) { + defer wgFolders.Done() + instanceName := fmt.Sprintf("web%d", i+1) + // adding the folder after the Commit, it will help us to use a cached image. + err = instance.AddFile("resources/file_cm_to_folder/test_1", "/usr/share/nginx/html/index.html", "0:0") + if err != nil { + t.Fatalf("Error adding file to '%v': %v", instanceName, err) + } + }(i, instance) + } + wgFolders.Wait() + + // Cleanup + t.Cleanup(func() { + err := assertCleanupInstances(t, executor, instances) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) + } + }) + + // Test logic + for _, instance := range instances { + err = instance.StartAsync() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + } + + for _, instance := range instances { + webIP, err := instance.GetIP() + if err != nil { + t.Fatalf("Error getting IP: %v", err) + } + + err = instance.WaitInstanceIsRunning() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + wget, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + assert.Equal(t, "hello from 1", wget) + } +} + +// TestOneVolumeOneFile tests the scenario where we have one volume and one file. +// the initContainer command that it generates looks like: +// mkdir -p /knuu && mkdir -p /knuu/usr/share/nginx/html && chmod -R 777 /knuu//usr/share/nginx/html && if [ -d /usr/share/nginx/html ] && [ \"$(ls -A /usr/share/nginx/html)\" ]; then cp -r /usr/share/nginx/html/* /knuu//usr/share/nginx/html && chown -R 0:0 /knuu/* ;fi +func TestOneVolumeOneFile(t *testing.T) { + t.Parallel() + // Setup + + executor, err := knuu.NewExecutor() + if err != nil { + t.Fatalf("Error creating executor: %v", err) + } + + const numberOfInstances = 2 + instances := make([]*knuu.Instance, numberOfInstances) + + for i := 0; i < numberOfInstances; i++ { + instanceName := fmt.Sprintf("web%d", i+1) + instance, err := knuu.NewInstance(instanceName) + if err != nil { + t.Fatalf("Error creating instance '%v': %v", instanceName, err) + } + err = instance.SetImage("docker.io/nginx:latest") + if err != nil { + t.Fatalf("Error setting image for '%v': %v", instanceName, err) + } + err = instance.AddPortTCP(80) + if err != nil { + t.Fatalf("Error adding port for '%v': %v", instanceName, err) + } + _, err = instance.ExecuteCommand("mkdir", "-p", "/usr/share/nginx/html") + if err != nil { + t.Fatalf("Error executing command for '%v': %v", instanceName, err) + } + err = instance.AddVolumeWithOwner("/usr/share/nginx/html", "1Gi", 0) + if err != nil { + t.Fatalf("Error adding volume: %v", err) + } + err = instance.Commit() + if err != nil { + t.Fatalf("Error committing instance '%v': %v", instanceName, err) + } + + instances[i] = instance + } + + var wgFolders sync.WaitGroup + for i, instance := range instances { + wgFolders.Add(1) + go func(i int, instance *knuu.Instance) { + defer wgFolders.Done() + instanceName := fmt.Sprintf("web%d", i+1) + // adding the folder after the Commit, it will help us to use a cached image. + err = instance.AddFile("resources/file_cm_to_folder/test_1", "/usr/share/nginx/html/index.html", "0:0") + if err != nil { + t.Fatalf("Error adding file to '%v': %v", instanceName, err) + } + }(i, instance) + } + wgFolders.Wait() + + t.Cleanup(func() { + // Cleanup + err := assertCleanupInstances(t, executor, instances) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) + } + }) + + // Test logic + for _, instance := range instances { + err = instance.StartAsync() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + } + + for _, instance := range instances { + webIP, err := instance.GetIP() + if err != nil { + t.Fatalf("Error getting IP: %v", err) + } + + err = instance.WaitInstanceIsRunning() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + wget, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + assert.Equal(t, "hello from 1", wget) + } +} + +// TestOneVolumeOneFile tests the scenario where we have one volume and one file. +// the initContainer command that it generates looks like: +// mkdir -p /knuu && mkdir -p /knuu/usr/share/nginx/html && chmod -R 777 /knuu//usr/share/nginx/html && if [ -d /usr/share/nginx/html ] && [ \"$(ls -A /usr/share/nginx/html)\" ]; then cp -r /usr/share/nginx/html/* /knuu//usr/share/nginx/html && chown -R 0:0 /knuu/* ;fi +func TestOneVolumeTwoFiles(t *testing.T) { + t.Parallel() + // Setup + + executor, err := knuu.NewExecutor() + if err != nil { + t.Fatalf("Error creating executor: %v", err) + } + + const numberOfInstances = 2 + instances := make([]*knuu.Instance, numberOfInstances) + + for i := 0; i < numberOfInstances; i++ { + instanceName := fmt.Sprintf("web%d", i+1) + instance, err := knuu.NewInstance(instanceName) + if err != nil { + t.Fatalf("Error creating instance '%v': %v", instanceName, err) + } + err = instance.SetImage("docker.io/nginx:latest") + if err != nil { + t.Fatalf("Error setting image for '%v': %v", instanceName, err) + } + err = instance.AddPortTCP(80) + if err != nil { + t.Fatalf("Error adding port for '%v': %v", instanceName, err) + } + _, err = instance.ExecuteCommand("mkdir", "-p", "/usr/share/nginx/html") + if err != nil { + t.Fatalf("Error executing command for '%v': %v", instanceName, err) + } + err = instance.AddVolumeWithOwner("/usr/share/nginx/html", "1Gi", 0) + if err != nil { + t.Fatalf("Error adding volume: %v", err) + } + err = instance.Commit() + if err != nil { + t.Fatalf("Error committing instance '%v': %v", instanceName, err) + } + + instances[i] = instance + } + + var wgFolders sync.WaitGroup + for i, instance := range instances { + wgFolders.Add(1) + go func(i int, instance *knuu.Instance) { + defer wgFolders.Done() + instanceName := fmt.Sprintf("web%d", i+1) + // adding the folder after the Commit, it will help us to use a cached image. + err = instance.AddFile("resources/file_cm_to_folder/test_1", "/usr/share/nginx/html/index.html", "0:0") + if err != nil { + t.Fatalf("Error adding file to '%v': %v", instanceName, err) + } + err = instance.AddFile("resources/file_cm_to_folder/test_2", "/usr/share/nginx/html/index-2.html", "0:0") + if err != nil { + t.Fatalf("Error adding file to '%v': %v", instanceName, err) + } + }(i, instance) + } + wgFolders.Wait() + + t.Cleanup(func() { + // Cleanup + err := assertCleanupInstances(t, executor, instances) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) + } + }) + + // Test logic + for _, instance := range instances { + err = instance.StartAsync() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + } + + for _, instance := range instances { + webIP, err := instance.GetIP() + if err != nil { + t.Fatalf("Error getting IP: %v", err) + } + + err = instance.WaitInstanceIsRunning() + if err != nil { + t.Fatalf("Error waiting for instance to be running: %v", err) + } + + wgetIndex, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + webIP2 := webIP + "/index-2.html" + wgetIndex2, err := executor.ExecuteCommand("wget", "-q", "-O", "-", webIP2) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + assert.Equal(t, "hello from 1", wgetIndex) + assert.Equal(t, "hello from 2", wgetIndex2) + } +} diff --git a/e2e/basic/folder_test_image_cached_test.go b/e2e/basic/folder_test_image_cached_test.go index 2313f7d..ac2b67a 100644 --- a/e2e/basic/folder_test_image_cached_test.go +++ b/e2e/basic/folder_test_image_cached_test.go @@ -2,7 +2,6 @@ package basic import ( "fmt" - "os" "sync" "testing" @@ -62,22 +61,11 @@ func TestFolderCached(t *testing.T) { } wgFolders.Wait() + // Cleanup t.Cleanup(func() { - // Cleanup - if os.Getenv("KNUU_SKIP_CLEANUP") != "true" { - err := executor.Destroy() - if err != nil { - t.Fatalf("Error destroying executor: %v", err) - } - - for _, instance := range instances { - if instance != nil { - err := instance.Destroy() - if err != nil { - t.Fatalf("Error destroying instance: %v", err) - } - } - } + err := assertCleanupInstances(t, executor, instances) + if err != nil { + t.Fatalf("Error cleaning up: %v", err) } }) diff --git a/e2e/basic/resources/file_cm_to_folder/test_1 b/e2e/basic/resources/file_cm_to_folder/test_1 new file mode 100644 index 0000000..2fb44ba --- /dev/null +++ b/e2e/basic/resources/file_cm_to_folder/test_1 @@ -0,0 +1 @@ +hello from 1 diff --git a/e2e/basic/resources/file_cm_to_folder/test_2 b/e2e/basic/resources/file_cm_to_folder/test_2 new file mode 100644 index 0000000..e3082f7 --- /dev/null +++ b/e2e/basic/resources/file_cm_to_folder/test_2 @@ -0,0 +1 @@ +hello from 2 diff --git a/e2e/basic/resources/file_cm_to_folder/test_3 b/e2e/basic/resources/file_cm_to_folder/test_3 new file mode 100644 index 0000000..2c2823d --- /dev/null +++ b/e2e/basic/resources/file_cm_to_folder/test_3 @@ -0,0 +1 @@ +hello from 3 diff --git a/go.mod b/go.mod index b9d388e..1ac73e2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.2 require ( github.com/celestiaorg/bittwister v0.0.0-20231213180407-65cdbaf5b8c7 - github.com/celestiaorg/celestia-app v1.8.0 + github.com/celestiaorg/celestia-app v1.9.0 github.com/celestiaorg/celestia-node v0.13.4 github.com/celestiaorg/knuu-example v0.0.0-20240422111045-61cd0cbaa37d github.com/cosmos/cosmos-sdk v0.46.16 diff --git a/go.sum b/go.sum index 1e05fe1..fbd0811 100644 --- a/go.sum +++ b/go.sum @@ -353,6 +353,8 @@ github.com/celestiaorg/bittwister v0.0.0-20231213180407-65cdbaf5b8c7 h1:nxplQi8w github.com/celestiaorg/bittwister v0.0.0-20231213180407-65cdbaf5b8c7/go.mod h1:1EF5MfOxVf0WC51Gb7pJ6bcZxnXKNAf9pqWtjgPBAYc= github.com/celestiaorg/celestia-app v1.8.0 h1:kvIxuUoEkVjo1ax+xUn0SUHLB6Qc+K9uV5ZK83x+gpU= github.com/celestiaorg/celestia-app v1.8.0/go.mod h1:a4yD4A691nNcjuwy3KJt3fBf+rD1/KE6BGOtZ574gGw= +github.com/celestiaorg/celestia-app v1.9.0 h1:B0Sou7uGsAwRXMzVwMpb2wVXtMFC4FR9ODfXrWTIDaw= +github.com/celestiaorg/celestia-app v1.9.0/go.mod h1:Z50B4+LvY0JIusd0qlQvA4/bNM2GzkFyDloYGU6A3fw= github.com/celestiaorg/celestia-core v1.35.0-tm-v0.34.29 h1:sXERzNXgyHyqTKNQx4S29C/NMDzgav62DaQDNF49HUQ= github.com/celestiaorg/celestia-core v1.35.0-tm-v0.34.29/go.mod h1:weZR4wYx1Vcw3g1Jc5G8VipG4M+KUDSqeIzyyWszmsQ= github.com/celestiaorg/celestia-node v0.13.4 h1:aawI44sVUx/LTCsF4WqyyOtKjZugn7aXM8VHBKtMLoU= diff --git a/pkg/k8s/k8s_pod.go b/pkg/k8s/k8s_pod.go index 076d5fe..bd83d39 100644 --- a/pkg/k8s/k8s_pod.go +++ b/pkg/k8s/k8s_pod.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "net/http" + "path/filepath" "strings" "time" @@ -20,7 +21,13 @@ import ( ) // the loops that keep checking something and wait for it to be done -const retryInterval = 100 * time.Millisecond +const ( + // retryInterval is the interval to wait between retries + retryInterval = 100 * time.Millisecond + + // knuuPath is the path where the knuu volume is mounted + knuuPath = "/knuu" +) type ContainerConfig struct { Name string // Name to assign to the Container @@ -409,7 +416,7 @@ func buildInitContainerVolumes(name string, volumes []*Volume) ([]v1.VolumeMount containerVolumes := []v1.VolumeMount{ { Name: name, - MountPath: "/knuu", // set the path to "/knuu" as per the requirements + MountPath: knuuPath, // set the path to "/knuu" as per the requirements }, } @@ -417,18 +424,39 @@ func buildInitContainerVolumes(name string, volumes []*Volume) ([]v1.VolumeMount } // buildInitContainerCommand generates a command for an init container based on the given name and volumes. -func buildInitContainerCommand(volumes []*Volume) ([]string, error) { - if len(volumes) == 0 { - return []string{}, nil // return empty slice if no volumes are specified +func buildInitContainerCommand(volumes []*Volume, files []*File) ([]string, error) { + var commands = []string{"sh", "-c"} + dirsProcessed := make(map[string]bool) + baseCmd := fmt.Sprintf("mkdir -p %s && ", knuuPath) + cmds := []string{baseCmd} + + for _, file := range files { + // get the directory of the file + folder := filepath.Dir(file.Dest) + if _, processed := dirsProcessed[folder]; !processed { + knuuFolder := fmt.Sprintf("%s%s", knuuPath, folder) + parentDirCmd := fmt.Sprintf("mkdir -p %s && chmod -R 777 %s && ", knuuFolder, knuuFolder) + cmds = append(cmds, parentDirCmd) + dirsProcessed[folder] = true + } } - var command = []string{"sh", "-c"} // initialize the command slice with the required shell interpreter - for _, volume := range volumes { - cmd := fmt.Sprintf("mkdir -p /knuu/%s && cp -r %s/* /knuu/%s && chown -R %d:%d /knuu/*", volume.Path, volume.Path, volume.Path, volume.Owner, volume.Owner) - command = append(command, cmd) // add each command to the command slice + for i, volume := range volumes { + knuuVolumePath := fmt.Sprintf("%s%s", knuuPath, volume.Path) + cmd := fmt.Sprintf("if [ -d %s ] && [ \"$(ls -A %s)\" ]; then cp -r %s/* %s && chown -R %d:%d %s", volume.Path, volume.Path, volume.Path, knuuVolumePath, volume.Owner, volume.Owner, knuuVolumePath) + if i < len(volumes)-1 { + cmd += " ;fi && " + } else { + cmd += " ;fi" + } + cmds = append(cmds, cmd) } - return command, nil + fullCommand := strings.Join(cmds, "") + commands = append(commands, fullCommand) + + logrus.Debugf("Init container command: %s", fullCommand) + return commands, nil } // buildResources generates a resource configuration for a container based on the given CPU and memory requests and limits. @@ -509,7 +537,7 @@ func prepareInitContainers(config ContainerConfig, init bool) ([]v1.Container, e if err != nil { return nil, ErrBuildingInitContainerVolumes.Wrap(err) } - initContainerCommand, err := buildInitContainerCommand(config.Volumes) + initContainerCommand, err := buildInitContainerCommand(config.Volumes, config.Files) if err != nil { return nil, ErrBuildingInitContainerCommand.Wrap(err) } diff --git a/pkg/knuu/errors.go b/pkg/knuu/errors.go index 12fbc45..c5a9893 100644 --- a/pkg/knuu/errors.go +++ b/pkg/knuu/errors.go @@ -212,4 +212,5 @@ var ( ErrMinioNotInitialized = &Error{Code: "MinioNotInitialized", Message: "minio not initialized"} ErrGeneratingK8sNameForPreloader = &Error{Code: "GeneratingK8sNameForPreloader", Message: "error generating k8s name for preloader"} ErrCannotLoadEnv = &Error{Code: "Cannot Load Env", Message: "cannot load env"} + ErrMaximumVolumesExceeded = &Error{Code: "MaximumVolumesExceeded", Message: "maximum volumes exceeded for instance '%s'"} ) diff --git a/pkg/knuu/instance.go b/pkg/knuu/instance.go index 7a345c8..33c47bf 100644 --- a/pkg/knuu/instance.go +++ b/pkg/knuu/instance.go @@ -652,6 +652,11 @@ func (i *Instance) Commit() error { // The owner of the volume is set to 0, if you want to set a custom owner use AddVolumeWithOwner // This function can only be called in the states 'Preparing' and 'Committed' func (i *Instance) AddVolume(path, size string) error { + // temporary feat, we will remove it once we can add multiple volumes + if len(i.volumes) > 0 { + logrus.Debugf("Maximum volumes exceeded for instance '%s', volumes: %d", i.name, len(i.volumes)) + return ErrMaximumVolumesExceeded.WithParams(i.name) + } i.AddVolumeWithOwner(path, size, 0) return nil } @@ -662,6 +667,11 @@ func (i *Instance) AddVolumeWithOwner(path, size string, owner int64) error { if !i.IsInState(Preparing, Committed) { return ErrAddingVolumeNotAllowed.WithParams(i.state.String()) } + // temporary feat, we will remove it once we can add multiple volumes + if len(i.volumes) > 0 { + logrus.Debugf("Maximum volumes exceeded for instance '%s', volumes: %d", i.name, len(i.volumes)) + return ErrMaximumVolumesExceeded.WithParams(i.name) + } volume := k8sClient.NewVolume(path, size, owner) i.volumes = append(i.volumes, volume) logrus.Debugf("Added volume '%s' with size '%s' and owner '%d' to instance '%s'", path, size, owner, i.name)