github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/sync/sync.go (about) 1 package sync 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 "time" 11 12 "github.com/olli-ai/jx/v2/pkg/ksync" 13 "github.com/pkg/errors" 14 15 "github.com/olli-ai/jx/v2/pkg/cmd/helper" 16 17 "github.com/spf13/cobra" 18 "k8s.io/client-go/kubernetes" 19 20 "github.com/jenkins-x/jx-logging/pkg/log" 21 "github.com/olli-ai/jx/v2/pkg/cmd/opts" 22 "github.com/olli-ai/jx/v2/pkg/cmd/templates" 23 "github.com/olli-ai/jx/v2/pkg/kube" 24 "github.com/olli-ai/jx/v2/pkg/util" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 ) 27 28 type SyncOptions struct { 29 *opts.CommonOptions 30 31 Daemon bool 32 NoKsyncInit bool 33 SingleMode bool 34 35 Container string 36 Namespace string 37 Pod string 38 Dir string 39 RemoteDir string 40 Reload bool 41 WatchOnly bool 42 } 43 44 var ( 45 sync_long = templates.LongDesc(` 46 Synchronises your local files to a DevPod so you an build and test your code easily on the cloud 47 48 For more documentation see: [https://jenkins-x.io/developing/devpods/](https://jenkins-x.io/developing/devpods/) 49 50 `) 51 52 sync_example = templates.Examples(` 53 # Starts synchronizing the current directory files to the users DevPod 54 jx sync 55 `) 56 57 defaultStignoreFile = `.git 58 .idea 59 .settings 60 .vscode 61 bin 62 build 63 target 64 node_modules 65 ` 66 ) 67 68 func NewCmdSync(commonOpts *opts.CommonOptions) *cobra.Command { 69 options := &SyncOptions{ 70 CommonOptions: commonOpts, 71 } 72 cmd := &cobra.Command{ 73 Use: "sync", 74 Short: "Synchronises your local files to a DevPod", 75 Long: sync_long, 76 Example: sync_example, 77 Aliases: []string{"log"}, 78 Run: func(cmd *cobra.Command, args []string) { 79 options.Cmd = cmd 80 options.Args = args 81 err := options.Run() 82 helper.CheckErr(err) 83 }, 84 } 85 /* cmd.Flags().StringVarP(&options.Container, "container", "c", "", "The name of the container to log") 86 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", "", "the namespace to look for the Deployment. Defaults to the current namespace") 87 cmd.Flags().StringVarP(&options.Pod, "pod", "p", "", "the pod name to use") 88 cmd.Flags().StringVarP(&options.Dir, "dir", "d", "", "The directory to watch. Defaults to the current directory") 89 cmd.Flags().StringVarP(&options.RemoteDir, "remote-dir", "r", "", "The remote directory in the DevPod to sync") 90 cmd.Flags().BoolVarP(&options.Reload, "reload", "", false, "Should we reload the remote container on file changes?") 91 */ 92 cmd.Flags().BoolVarP(&options.Daemon, "daemon", "", false, "Runs ksync in a background daemon") 93 cmd.Flags().BoolVarP(&options.NoKsyncInit, "no-init", "", false, "Disables the use of 'ksync init' to ensure we have initialised ksync") 94 cmd.Flags().BoolVarP(&options.SingleMode, "single-mode", "", false, "Terminates eagerly if `ksync watch` fails") 95 96 // deprecated 97 cmd.Flags().BoolVarP(&options.WatchOnly, "watch-only", "", false, "Deprecated this flag is now ignored!") 98 return cmd 99 } 100 101 func (o *SyncOptions) Run() error { 102 103 // ksync is installed to the jx/bin dir, so we can add it for the user 104 err := os.Setenv("PATH", util.PathWithBinary()) 105 if err != nil { 106 return errors.Wrap(err, "failed to set PATH env variable") 107 } 108 109 client, err := o.KubeClient() 110 if err != nil { 111 return err 112 } 113 sha, err := ksync.InstallKSync() 114 if err != nil { 115 return err 116 } 117 118 if !o.NoKsyncInit { 119 flag, err := kube.IsDaemonSetExists(client, "ksync", "kube-system") 120 if !flag || err != nil { 121 log.Logger().Infof("Initialising ksync") 122 kcli, err := ksync.NewCLI() 123 if err != nil { 124 return err 125 } 126 // Deal with https://github.com/ksync/ksync/issues/218 127 _, err = kcli.Init("--upgrade", "--image", fmt.Sprintf("ksync/ksync:git-%s", sha)) 128 if err != nil { 129 return err 130 } 131 } 132 } 133 134 if o.SingleMode { 135 return o.KsyncWatch() 136 } 137 for { 138 err = o.KsyncWatch() 139 if err != nil { 140 log.Logger().Warnf("Failed on ksync watch: %s", err) 141 } 142 } 143 } 144 145 func (o *SyncOptions) waitForKsyncWatchToFail() { 146 logged := false 147 for { 148 _, err := o.GetCommandOutput("", "ksync", "get") 149 if err != nil { 150 // lets assume watch is no longer running 151 log.Logger().Infof("Looks like 'ksync watch' is not running: %s", err) 152 return 153 } 154 if !logged { 155 logged = true 156 log.Logger().Infof("It looks like 'ksync watch' is already running so we don't need to run it yet...") 157 } 158 time.Sleep(time.Second * 5) 159 } 160 } 161 162 func (o *SyncOptions) KsyncWatch() error { 163 o.waitForKsyncWatchToFail() 164 165 args := []string{"watch"} 166 if o.Daemon { 167 args = append(args, "--daemon") 168 } 169 cmd := exec.Command("ksync", args...) 170 cmd.Stdout = o.Out 171 cmd.Stderr = o.Out 172 err := cmd.Start() 173 if err != nil { 174 return err 175 } 176 177 log.Logger().Infof("Started the ksync watch") 178 time.Sleep(1 * time.Second) 179 180 state := cmd.ProcessState 181 if state != nil && state.Exited() { 182 return fmt.Errorf("ksync watch terminated") 183 } 184 return cmd.Wait() 185 } 186 187 // CreateKsync removes the exiting ksync if it already exists then create a new ksync of the given name 188 func (o *SyncOptions) CreateKsync(client kubernetes.Interface, ns string, name string, dir string, remoteDir string, username string) error { 189 190 // ksync is installed to the jx/bin dir, so we can add it for the user 191 err := os.Setenv("PATH", util.PathWithBinary()) 192 if err != nil { 193 return errors.Wrap(err, "failed to set PATH env variable") 194 } 195 196 info := util.ColorInfo 197 log.Logger().Infof("synchronizing directory %s to DevPod %s path %s", info(dir), info(name), info(remoteDir)) 198 199 ignoreFile := filepath.Join(dir, ".stignore") 200 exists, err := util.FileExists(ignoreFile) 201 if err != nil { 202 return err 203 } 204 if !exists { 205 err = ioutil.WriteFile(ignoreFile, []byte(defaultStignoreFile), util.DefaultWritePermissions) 206 if err != nil { 207 return err 208 } 209 } 210 matchLabels := map[string]string{ 211 kube.LabelDevPodUsername: username, 212 } 213 selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: matchLabels}) 214 if err != nil { 215 return err 216 } 217 218 _, pods, err := kube.GetPodsWithLabels(client, ns, selector.String()) 219 if err != nil { 220 return err 221 } 222 223 // ignore the bad lines that come 224 ignoreNames := []string{"starting", "watching"} 225 226 deleteNames := []string{} 227 err = o.Retry(5, time.Second, func() error { 228 text, err := o.GetCommandOutput(dir, "ksync", "get") 229 if err == nil { 230 for i, line := range strings.Split(text, "\n") { 231 if i > 1 { 232 cols := strings.Split(strings.TrimSpace(line), " ") 233 if len(cols) > 2 { 234 n := strings.TrimSpace(cols[0]) 235 if n == name || pods[n] == nil { 236 if util.StringArrayIndex(deleteNames, n) < 0 && util.StringArrayIndex(ignoreNames, n) < 0 { 237 deleteNames = append(deleteNames, n) 238 } 239 } 240 } 241 } 242 } 243 } 244 return err 245 }) 246 if err != nil { 247 log.Logger().Warnf("Failed to get from ksync daemon: %s", err) 248 } 249 250 reload := "--reload=false" 251 if o.Reload { 252 reload = "--reload=true" 253 } 254 255 for _, n := range deleteNames { 256 // ignore results as we may not have a spec yet for this name 257 log.Logger().Infof("Removing old ksync %s", n) 258 259 err = o.RunCommand("ksync", "delete", n) 260 if err != nil { 261 return err 262 } 263 } 264 265 time.Sleep(1 * time.Second) 266 267 return o.RunCommand("ksync", "create", "--name", name, "-l", "jenkins.io/devpod="+name, reload, "-n", ns, dir, remoteDir) 268 }