github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/cluster/data.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package cluster contains Jackal-specific cluster management functions. 5 package cluster 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "sync" 14 15 "github.com/Racer159/jackal/src/config" 16 "github.com/Racer159/jackal/src/pkg/k8s" 17 "github.com/Racer159/jackal/src/pkg/layout" 18 "github.com/Racer159/jackal/src/pkg/message" 19 "github.com/Racer159/jackal/src/pkg/utils" 20 "github.com/Racer159/jackal/src/pkg/utils/exec" 21 "github.com/Racer159/jackal/src/types" 22 "github.com/defenseunicorns/pkg/helpers" 23 corev1 "k8s.io/api/core/v1" 24 ) 25 26 // HandleDataInjection waits for the target pod(s) to come up and inject the data into them 27 // todo: this currently requires kubectl but we should have enough k8s work to make this native now. 28 func (c *Cluster) HandleDataInjection(wg *sync.WaitGroup, data types.JackalDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { 29 defer wg.Done() 30 31 injectionCompletionMarker := filepath.Join(componentPath.DataInjections, config.GetDataInjectionMarker()) 32 if err := os.WriteFile(injectionCompletionMarker, []byte("🦄"), helpers.ReadWriteUser); err != nil { 33 message.WarnErrf(err, "Unable to create the data injection completion marker") 34 return 35 } 36 37 tarCompressFlag := "" 38 if data.Compress { 39 tarCompressFlag = "-z" 40 } 41 42 // Pod filter to ensure we only use the current deployment's pods 43 podFilterByInitContainer := func(pod corev1.Pod) bool { 44 // Look everywhere in the pod for a matching data injection marker 45 return strings.Contains(message.JSONValue(pod), config.GetDataInjectionMarker()) 46 } 47 48 // Get the OS shell to execute commands in 49 shell, shellArgs := exec.GetOSShell(exec.Shell{Windows: "cmd"}) 50 51 if _, _, err := exec.Cmd(shell, append(shellArgs, "tar --version")...); err != nil { 52 message.WarnErr(err, "Unable to execute tar on this system. Please ensure it is installed and on your $PATH.") 53 return 54 } 55 56 iterator: 57 // The eternal loop because some data injections can take a very long time 58 for { 59 message.Debugf("Attempting to inject data into %s", data.Target) 60 source := filepath.Join(componentPath.DataInjections, filepath.Base(data.Target.Path)) 61 if helpers.InvalidPath(source) { 62 // The path is likely invalid because of how we compose OCI components, add an index suffix to the filename 63 source = filepath.Join(componentPath.DataInjections, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path)) 64 if helpers.InvalidPath(source) { 65 message.Warnf("Unable to find the data injection source path %s", source) 66 return 67 } 68 } 69 70 target := k8s.PodLookup{ 71 Namespace: data.Target.Namespace, 72 Selector: data.Target.Selector, 73 Container: data.Target.Container, 74 } 75 76 // Wait until the pod we are injecting data into becomes available 77 pods := c.WaitForPodsAndContainers(target, podFilterByInitContainer) 78 if len(pods) < 1 { 79 continue 80 } 81 82 // Inject into all the pods 83 for _, pod := range pods { 84 // Try to use the embedded kubectl if we can 85 jackalCommand, err := utils.GetFinalExecutableCommand() 86 kubectlBinPath := "kubectl" 87 if err != nil { 88 message.Warnf("Unable to get the jackal executable path, falling back to host kubectl: %s", err) 89 } else { 90 kubectlBinPath = fmt.Sprintf("%s tools kubectl", jackalCommand) 91 } 92 kubectlCmd := fmt.Sprintf("%s exec -i -n %s %s -c %s ", kubectlBinPath, data.Target.Namespace, pod.Name, data.Target.Container) 93 94 // Note that each command flag is separated to provide the widest cross-platform tar support 95 tarCmd := fmt.Sprintf("tar -c %s -f -", tarCompressFlag) 96 untarCmd := fmt.Sprintf("tar -x %s -v -f - -C %s", tarCompressFlag, data.Target.Path) 97 98 // Must create the target directory before trying to change to it for untar 99 mkdirCmd := fmt.Sprintf("%s -- mkdir -p %s", kubectlCmd, data.Target.Path) 100 if err := exec.CmdWithPrint(shell, append(shellArgs, mkdirCmd)...); err != nil { 101 message.Warnf("Unable to create the data injection target directory %s in pod %s", data.Target.Path, pod.Name) 102 continue iterator 103 } 104 105 cpPodCmd := fmt.Sprintf("%s -C %s . | %s -- %s", 106 tarCmd, 107 source, 108 kubectlCmd, 109 untarCmd, 110 ) 111 112 // Do the actual data injection 113 if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { 114 message.Warnf("Error copying data into the pod %#v: %#v\n", pod.Name, err) 115 continue iterator 116 } 117 118 // Leave a marker in the target container for pods to track the sync action 119 cpPodCmd = fmt.Sprintf("%s -C %s %s | %s -- %s", 120 tarCmd, 121 componentPath.DataInjections, 122 config.GetDataInjectionMarker(), 123 kubectlCmd, 124 untarCmd, 125 ) 126 127 if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { 128 message.Warnf("Error saving the jackal sync completion file after injection into pod %#v\n", pod.Name) 129 continue iterator 130 } 131 } 132 133 // Do not look for a specific container after injection in case they are running an init container 134 podOnlyTarget := k8s.PodLookup{ 135 Namespace: data.Target.Namespace, 136 Selector: data.Target.Selector, 137 } 138 139 // Block one final time to make sure at least one pod has come up and injected the data 140 // Using only the pod as the final selector because we don't know what the container name will be 141 // Still using the init container filter to make sure we have the right running pod 142 _ = c.WaitForPodsAndContainers(podOnlyTarget, podFilterByInitContainer) 143 144 // Cleanup now to reduce disk pressure 145 _ = os.RemoveAll(source) 146 147 // Return to stop the loop 148 return 149 } 150 }