// WaitForServerReady wait for a server state to be running, then wait for the SSH port to be available func WaitForServerReady(api *ScalewayAPI, serverID string, gateway string) (*ScalewayServer, error) { promise := make(chan bool) var server *ScalewayServer var err error go func() { defer close(promise) server, err = WaitForServerState(api, serverID, "running") if err != nil { promise <- false return } if gateway == "" { log.Debugf("Waiting for server SSH port") dest := fmt.Sprintf("%s:22", server.PublicAddress.IP) err = utils.WaitForTCPPortOpen(dest) if err != nil { promise <- false return } } else { log.Debugf("Waiting for gateway SSH port") dest := fmt.Sprintf("%s:22", gateway) err = utils.WaitForTCPPortOpen(dest) if err != nil { promise <- false return } log.Debugf("Waiting 30 more seconds, for SSH to be ready") time.Sleep(30 * time.Second) // FIXME: check for SSH port through the gateway } promise <- true }() loop := 0 for { select { case done := <-promise: utils.LogQuiet("\r \r") if done == false { return nil, err } return server, nil case <-time.After(time.Millisecond * 100): utils.LogQuiet(fmt.Sprintf("\r%c\r", "-\\|/"[loop%4])) loop = loop + 1 if loop == 5 { loop = 0 } } } }
// Save atomically overwrites the current cache database func (c *ScalewayCache) Save() error { c.Lock.Lock() defer c.Lock.Unlock() logrus.Debugf("Writing cache file to disk") if c.Modified { file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path)) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) err = encoder.Encode(*c) if err != nil { os.Remove(file.Name()) return err } if err := os.Rename(file.Name(), c.Path); err != nil { os.Remove(file.Name()) return err } } return nil }
// RunKill is the handler for 'scw kill' func RunKill(ctx CommandContext, args KillArgs) error { serverID := ctx.API.GetServerID(args.Server) command := "halt" server, err := ctx.API.GetServer(serverID) if err != nil { return fmt.Errorf("failed to get server information for %s: %v", serverID, err) } // Resolve gateway if args.Gateway == "" { args.Gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string if args.Gateway == serverID || args.Gateway == args.Server { gateway = "" } else { gateway, err = api.ResolveGateway(ctx.API, args.Gateway) if err != nil { return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) } } sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, []string{command}, gateway) logrus.Debugf("Executing: %s", sshCommand) spawn := exec.Command("ssh", sshCommand.Slice()[1:]...) spawn.Stdout = ctx.Stdout spawn.Stdin = ctx.Stdin spawn.Stderr = ctx.Stderr return spawn.Run() }
// CopyWithTar creates a tar archive of filesystem path `src`, and // unpacks it at filesystem path `dst`. // The archive is streamed directly with fixed buffering and no // intermediary disk IO. func (archiver *Archiver) CopyWithTar(src, dst string) error { srcSt, err := os.Stat(src) if err != nil { return err } if !srcSt.IsDir() { return archiver.CopyFileWithTar(src, dst) } // Create dst, copy src's content into it logrus.Debugf("Creating dest directory: %s", dst) if err := system.MkdirAll(dst, 0755); err != nil { return err } logrus.Debugf("Calling TarUntar(%s, %s)", src, dst) return archiver.TarUntar(src, dst) }
// RunTop is the handler for 'scw top' func RunTop(ctx CommandContext, args TopArgs) error { serverID := ctx.API.GetServerID(args.Server) command := "ps" server, err := ctx.API.GetServer(serverID) if err != nil { return fmt.Errorf("failed to get server information for %s: %v", serverID, err) } // Resolve gateway if args.Gateway == "" { args.Gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string if args.Gateway == serverID || args.Gateway == args.Server { gateway = "" } else { gateway, err = api.ResolveGateway(ctx.API, args.Gateway) if err != nil { return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) } } sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, []string{command}, gateway) logrus.Debugf("Executing: %s", sshCommand) out, err := exec.Command("ssh", sshCommand.Slice()[1:]...).CombinedOutput() fmt.Printf("%s", out) return err }
// AttachToSerial tries to connect to server serial using 'term.js-cli' and fallback with a help message func AttachToSerial(serverID string, apiToken string, attachStdin bool) error { termjsURL := fmt.Sprintf("https://tty.cloud.online.net?server_id=%s&type=serial&auth_token=%s", serverID, apiToken) args := []string{} if !attachStdin { args = append(args, "--no-stdin") } args = append(args, termjsURL) log.Debugf("Executing: %s %v", termjsBin, args) // FIXME: check if termjs-cli is installed spawn := exec.Command(termjsBin, args...) spawn.Stdout = os.Stdout spawn.Stdin = os.Stdin spawn.Stderr = os.Stderr err := spawn.Run() if err != nil { log.Warnf(` You need to install '%s' from https://github.com/moul/term.js-cli npm install -g term.js-cli However, you can access your serial using a web browser: %s `, termjsBin, termjsURL) return err } return nil }
// TarResourceRebase is like TarResource but renames the first path element of // items in the resulting tar archive to match the given rebaseName if not "". func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) { sourcePath = normalizePath(sourcePath) if _, err = os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an // error. return } // Separate the source path between it's directory and // the entry in that directory which we are archiving. sourceDir, sourceBase := SplitPathDirEntry(sourcePath) filter := []string{sourceBase} logrus.Debugf("copying %q from %q", sourceBase, sourceDir) return TarWithOptions(sourceDir, &TarOptions{ Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, RebaseNames: map[string]string{ sourceBase: rebaseName, }, }) }
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. // If either Tar or Untar fails, TarUntar aborts and returns the error. func (archiver *Archiver) TarUntar(src, dst string) error { logrus.Debugf("TarUntar(%s %s)", src, dst) archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) if err != nil { return err } defer archive.Close() return archiver.Untar(archive, dst, nil) }
// CopyFileWithTar emulates the behavior of the 'cp' command-line // for a single file. It copies a regular file from path `src` to // path `dst`, and preserves all its metadata. func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing slash. This must be done in an operating // system specific manner. if dst[len(dst)-1] == os.PathSeparator { dst = filepath.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil { return err } r, w := io.Pipe() errC := promise.Go(func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() hdr, err := tar.FileInfoHeader(srcSt, "") if err != nil { return err } hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) tw := tar.NewWriter(w) defer tw.Close() if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(tw, srcF); err != nil { return err } return nil }) defer func() { if er := <-errC; err != nil { err = er } }() return archiver.Untar(r, filepath.Dir(dst), nil) }
// Debug create a debug log entry with HTTP error informations func (e ScalewayAPIError) Debug() { log.WithFields(log.Fields{ "StatusCode": e.StatusCode, "Type": e.Type, "Message": e.Message, }).Debug(e.APIMessage) // error.Fields handling for k, v := range e.Fields { log.Debugf(" %-30s %s", fmt.Sprintf("%s: ", k), v) } }
// DetectCompression detects the compression algorithm of the source. func DetectCompression(source []byte) Compression { for compression, m := range map[Compression][]byte{ Bzip2: {0x42, 0x5A, 0x68}, Gzip: {0x1F, 0x8B, 0x08}, Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, } { if len(source) < len(m) { logrus.Debugf("Len too short") continue } if bytes.Compare(m, source[:len(m)]) == 0 { return compression } } return Uncompressed }
// Save atomically overwrites the current cache database func (c *ScalewayCache) Save() error { c.Lock.Lock() defer c.Lock.Unlock() logrus.Debugf("Writing cache file to disk") if c.Modified { file, err := ioutil.TempFile("", "") if err != nil { return err } encoder := json.NewEncoder(file) err = encoder.Encode(*c) if err != nil { return err } return os.Rename(file.Name(), c.Path) } return nil }
// SSHExec executes a command over SSH and redirects file-descriptors func SSHExec(publicIPAddress string, privateIPAddress string, command []string, checkConnection bool, gateway string) error { gatewayUser := "******" gatewayIPAddress := gateway if strings.Contains(gateway, "@") { parts := strings.Split(gatewayIPAddress, "@") gatewayUser = parts[0] gatewayIPAddress = parts[1] gateway = gatewayUser + "@" + gatewayIPAddress } if publicIPAddress == "" && gatewayIPAddress == "" { return errors.New("server does not have public IP") } if privateIPAddress == "" && gatewayIPAddress != "" { return errors.New("server does not have private IP") } if checkConnection { useGateway := gatewayIPAddress != "" if useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", gatewayIPAddress)) { return errors.New("gateway is not available, try again later") } if !useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", publicIPAddress)) { return errors.New("server is not ready, try again later") } } sshCommand := NewSSHExecCmd(publicIPAddress, privateIPAddress, true, command, gateway) log.Debugf("Executing: %s", sshCommand) spawn := exec.Command("ssh", sshCommand.Slice()[1:]...) spawn.Stdout = os.Stdout spawn.Stdin = os.Stdin spawn.Stderr = os.Stderr return spawn.Run() }
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. // It will assume that the inputs have been preprocessed and therefore the function // doen't need to do as much error checking and clean-up. This was done to avoid // repeating these steps on each file being checked during the archive process. // The more generic fileutils.Matches() can't make these assumptions. func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { matched := false parentPath := filepath.Dir(file) parentPathDirs := strings.Split(parentPath, "/") for i, pattern := range patterns { negative := false if exclusion(pattern) { negative = true pattern = pattern[1:] } match, err := filepath.Match(pattern, file) if err != nil { return false, err } if !match && parentPath != "." { // Check to see if the pattern matches one of our parent dirs. if len(patDirs[i]) <= len(parentPathDirs) { match, _ = filepath.Match(strings.Join(patDirs[i], "/"), strings.Join(parentPathDirs[:len(patDirs[i])], "/")) } } if match { matched = !negative } } if matched { logrus.Debugf("Skipping excluded path: %s", file) } return matched, nil }
// fillIdentifierCache fills the cache by fetching fro the API func fillIdentifierCache(api *ScalewayAPI, identifierType int) { log.Debugf("Filling the cache") var wg sync.WaitGroup wg.Add(5) go func() { if identifierType&(IdentifierUnknown|IdentifierServer) > 0 { api.GetServers(true, 0) } wg.Done() }() go func() { if identifierType&(IdentifierUnknown|IdentifierImage) > 0 { api.GetImages() } wg.Done() }() go func() { if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 { api.GetSnapshots() } wg.Done() }() go func() { if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 { api.GetVolumes() } wg.Done() }() go func() { if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 { api.GetBootscripts() } wg.Done() }() wg.Wait() }
// Run is the handler for 'scw run' func Run(ctx CommandContext, args RunArgs) error { if args.Gateway == "" { args.Gateway = ctx.Getenv("SCW_GATEWAY") } if args.TmpSSHKey { err := AddSSHKeyToTags(ctx, &args.Tags, args.Image) if err != nil { return err } } env := strings.Join(args.Tags, " ") volume := strings.Join(args.Volumes, " ") // create IMAGE logrus.Info("Server creation ...") dynamicIPRequired := args.Gateway == "" serverID, err := api.CreateServer(ctx.API, args.Image, args.Name, args.Bootscript, env, volume, dynamicIPRequired) if err != nil { return fmt.Errorf("failed to create server: %v", err) } logrus.Infof("Server created: %s", serverID) if args.AutoRemove { defer ctx.API.DeleteServerSafe(serverID) } // start SERVER logrus.Info("Server start requested ...") err = api.StartServer(ctx.API, serverID, false) if err != nil { return fmt.Errorf("failed to start server %s: %v", serverID, err) } logrus.Info("Server is starting, this may take up to a minute ...") if args.Detach { fmt.Fprintln(ctx.Stdout, serverID) return nil } else { // Sync cache on disk ctx.API.Sync() } if args.Attach { // Attach to server serial logrus.Info("Attaching to server console ...") err = utils.AttachToSerial(serverID, ctx.API.Token, true) if err != nil { return fmt.Errorf("cannot attach to server serial: %v", err) } } else { // Resolve gateway gateway, err := api.ResolveGateway(ctx.API, args.Gateway) if err != nil { return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) } // waiting for server to be ready logrus.Debug("Waiting for server to be ready") // We wait for 30 seconds, which is the minimal amount of time needed by a server to boot server, err := api.WaitForServerReady(ctx.API, serverID, gateway) if err != nil { return fmt.Errorf("cannot get access to server %s: %v", serverID, err) } logrus.Debugf("SSH server is available: %s:22", server.PublicAddress.IP) logrus.Info("Server is ready !") // exec -w SERVER COMMAND ARGS... if len(args.Command) < 1 { logrus.Info("Connecting to server ...") err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, []string{}, false, gateway) } else { logrus.Infof("Executing command: %s ...", args.Command) err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.Command, false, gateway) } if err != nil { return fmt.Errorf("command execution failed: %v", err) } logrus.Info("Command successfuly executed") } return nil }
// TarFromSource creates a stream buffer with the tarballed content of the user source func TarFromSource(ctx CommandContext, source string, gateway string) (*io.ReadCloser, error) { var tarOutputStream io.ReadCloser // source is a server address + path (scp-like uri) if strings.Index(source, ":") > -1 { logrus.Debugf("Creating a tarball remotely and streaming it using SSH") serverParts := strings.Split(source, ":") if len(serverParts) != 2 { return nil, fmt.Errorf("invalid source uri, see 'scw cp -h' for usage") } serverID := ctx.API.GetServerID(serverParts[0]) server, err := ctx.API.GetServer(serverID) if err != nil { return nil, err } dir, base := utils.PathToTARPathparts(serverParts[1]) logrus.Debugf("Equivalent to 'scp root@%s:%s/%s ...'", server.PublicAddress.IP, dir, base) // remoteCommand is executed on the remote server // it streams a tarball raw content remoteCommand := []string{"tar"} remoteCommand = append(remoteCommand, "-C", dir) if ctx.Getenv("DEBUG") == "1" { remoteCommand = append(remoteCommand, "-v") } remoteCommand = append(remoteCommand, "-cf", "-") remoteCommand = append(remoteCommand, base) // Resolve gateway if gateway == "" { gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string if gateway == serverID || gateway == serverParts[0] { gateway = "" } else { gateway, err = api.ResolveGateway(ctx.API, gateway) if err != nil { return nil, fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err) } } // execCmd contains the ssh connection + the remoteCommand sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, false, remoteCommand, gateway) logrus.Debugf("Executing: %s", sshCommand) spawnSrc := exec.Command("ssh", sshCommand.Slice()[1:]...) tarOutputStream, err = spawnSrc.StdoutPipe() if err != nil { return nil, err } tarErrorStream, err := spawnSrc.StderrPipe() if err != nil { return nil, err } defer tarErrorStream.Close() io.Copy(ctx.Stderr, tarErrorStream) err = spawnSrc.Start() if err != nil { return nil, err } defer spawnSrc.Wait() return &tarOutputStream, nil } // source is stdin if source == "-" { logrus.Debugf("Streaming tarball from stdin") // FIXME: should be ctx.Stdin tarOutputStream = os.Stdin return &tarOutputStream, nil } // source is a path on localhost logrus.Debugf("Taring local path %s", source) path, err := filepath.Abs(source) if err != nil { return nil, err } path, err = filepath.EvalSymlinks(path) if err != nil { return nil, err } logrus.Debugf("Real local path is %s", path) dir, base := utils.PathToTARPathparts(path) tarOutputStream, err = archive.TarWithOptions(dir, &archive.TarOptions{ Compression: archive.Uncompressed, IncludeFiles: []string{base}, }) if err != nil { return nil, err } return &tarOutputStream, nil }
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { // Fix the source path to work with long path names. This is a no-op // on platforms other than Windows. srcPath = fixVolumePathPrefix(srcPath) patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns) if err != nil { return nil, err } pipeReader, pipeWriter := io.Pipe() compressWriter, err := CompressStream(pipeWriter, options.Compression) if err != nil { return nil, err } go func() { ta := &tarAppender{ TarWriter: tar.NewWriter(compressWriter), Buffer: pools.BufioWriter32KPool.Get(nil), SeenFiles: make(map[uint64]string), } defer func() { // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { logrus.Debugf("Can't close tar writer: %s", err) } if err := compressWriter.Close(); err != nil { logrus.Debugf("Can't close compress writer: %s", err) } if err := pipeWriter.Close(); err != nil { logrus.Debugf("Can't close pipe writer: %s", err) } }() // this buffer is needed for the duration of this piped stream defer pools.BufioWriter32KPool.Put(ta.Buffer) // In general we log errors here but ignore them because // during e.g. a diff operation the container can continue // mutating the filesystem and we can see transient errors // from this stat, err := os.Lstat(srcPath) if err != nil { return } if !stat.IsDir() { // We can't later join a non-dir with any includes because the // 'walk' will error if "file/." is stat-ed and "file" is not a // directory. So, we must split the source path and use the // basename as the include. if len(options.IncludeFiles) > 0 { logrus.Warn("Tar: Can't archive a file with includes") } dir, base := SplitPathDirEntry(srcPath) srcPath = dir options.IncludeFiles = []string{base} } if len(options.IncludeFiles) == 0 { options.IncludeFiles = []string{"."} } seen := make(map[string]bool) for _, include := range options.IncludeFiles { rebaseName := options.RebaseNames[include] walkRoot := getWalkRoot(srcPath, include) filepath.Walk(walkRoot, func(filePath string, f os.FileInfo, err error) error { if err != nil { logrus.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err) return nil } relFilePath, err := filepath.Rel(srcPath, filePath) if err != nil || (!options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { // Error getting relative path OR we are looking // at the source directory path. Skip in both situations. return nil } if options.IncludeSourceDir && include == "." && relFilePath != "." { relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) } skip := false // If "include" is an exact match for the current file // then even if there's an "excludePatterns" pattern that // matches it, don't skip it. IOW, assume an explicit 'include' // is asking for that file no matter what - which is true // for some files, like .dockerignore and Dockerfile (sometimes) if include != relFilePath { skip, err = fileutils.OptimizedMatches(relFilePath, patterns, patDirs) if err != nil { logrus.Debugf("Error matching %s: %v", relFilePath, err) return err } } if skip { if !exceptions && f.IsDir() { return filepath.SkipDir } return nil } if seen[relFilePath] { return nil } seen[relFilePath] = true // Rename the base resource. if rebaseName != "" { var replacement string if rebaseName != string(filepath.Separator) { // Special case the root directory to replace with an // empty string instead so that we don't end up with // double slashes in the paths. replacement = rebaseName } relFilePath = strings.Replace(relFilePath, include, replacement, 1) } if err := ta.addTarFile(filePath, relFilePath); err != nil { logrus.Debugf("Can't add file %s to tar: %s", filePath, err) } return nil }) } }() return pipeReader, nil }
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *TarChownOptions) error { // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) hdrInfo := hdr.FileInfo() switch hdr.Typeflag { case tar.TypeDir: // Create directory unless it exists as a directory already. // In that case we just want to merge the two if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { if err := os.Mkdir(path, hdrInfo.Mode()); err != nil { return err } } case tar.TypeReg, tar.TypeRegA: // Source is regular file file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, hdrInfo.Mode()) if err != nil { return err } if _, err := io.Copy(file, reader); err != nil { file.Close() return err } file.Close() case tar.TypeBlock, tar.TypeChar, tar.TypeFifo: // Handle this is an OS-specific way if err := handleTarTypeBlockCharFifo(hdr, path); err != nil { return err } case tar.TypeLink: targetPath := filepath.Join(extractDir, hdr.Linkname) // check for hardlink breakout if !strings.HasPrefix(targetPath, extractDir) { return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname)) } if err := os.Link(targetPath, path); err != nil { return err } case tar.TypeSymlink: // path -> hdr.Linkname = targetPath // e.g. /extractDir/path/to/symlink -> ../2/file = /extractDir/path/2/file targetPath := filepath.Join(filepath.Dir(path), hdr.Linkname) // the reason we don't need to check symlinks in the path (with FollowSymlinkInScope) is because // that symlink would first have to be created, which would be caught earlier, at this very check: if !strings.HasPrefix(targetPath, extractDir) { return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)) } if err := os.Symlink(hdr.Linkname, path); err != nil { return err } case tar.TypeXGlobalHeader: logrus.Debugf("PAX Global Extended Headers found and ignored") return nil default: return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } // Lchown is not supported on Windows. if Lchown && runtime.GOOS != "windows" { if chownOpts == nil { chownOpts = &TarChownOptions{UID: hdr.Uid, GID: hdr.Gid} } if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { return err } } for key, value := range hdr.Xattrs { if err := system.Lsetxattr(path, key, []byte(value), 0); err != nil { return err } } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if err := handleLChmod(hdr, path, hdrInfo); err != nil { return err } ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} // syscall.UtimesNano doesn't support a NOFOLLOW flag atm if hdr.Typeflag == tar.TypeLink { if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { return err } } } else if hdr.Typeflag != tar.TypeSymlink { if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { return err } } else { if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { return err } } return nil }
func Start(rawArgs []string, streams *commands.Streams) (int, error) { if streams == nil { streams = &commands.Streams{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } } flag.CommandLine.Parse(rawArgs) config, cfgErr := config.GetConfig() if cfgErr != nil && !os.IsNotExist(cfgErr) { return 1, fmt.Errorf("unable to open .scwrc config file: %v", cfgErr) } if config != nil { defaultComputeAPI := os.Getenv("scaleway_api_endpoint") if defaultComputeAPI == "" { defaultComputeAPI = config.ComputeAPI } if flAPIEndPoint == nil { flAPIEndPoint = flag.String([]string{"-api-endpoint"}, defaultComputeAPI, "Set the API endpoint") } } if *flVersion { fmt.Fprintf(streams.Stderr, "scw version %s, build %s\n", scwversion.VERSION, scwversion.GITCOMMIT) return 0, nil } if flAPIEndPoint != nil { os.Setenv("scaleway_api_endpoint", *flAPIEndPoint) } if *flSensitive { os.Setenv("SCW_SENSITIVE", "1") } if *flDebug { os.Setenv("DEBUG", "1") } utils.Quiet(*flQuiet) initLogging(os.Getenv("DEBUG") != "", *flVerbose, streams) args := flag.Args() if len(args) < 1 { CmdHelp.Exec(CmdHelp, []string{}) return 1, nil } name := args[0] args = args[1:] // Apply default values for _, cmd := range Commands { cmd.streams = streams } for _, cmd := range Commands { if cmd.Name() == name { cmd.Flag.SetOutput(ioutil.Discard) err := cmd.Flag.Parse(args) if err != nil { return 1, fmt.Errorf("usage: scw %s", cmd.UsageLine) } if cmd.Name() != "login" && cmd.Name() != "help" && cmd.Name() != "version" { if cfgErr != nil { if name != "login" && config == nil { logrus.Debugf("cfgErr: %v", cfgErr) fmt.Fprintf(streams.Stderr, "You need to login first: 'scw login'\n") return 1, nil } } api, err := getScalewayAPI() if err != nil { return 1, fmt.Errorf("unable to initialize scw api: %s", err) } cmd.API = api } err = cmd.Exec(cmd, cmd.Flag.Args()) switch err { case nil: case ErrExitFailure: return 1, nil case ErrExitSuccess: return 0, nil default: return 1, fmt.Errorf("cannot execute '%s': %v", cmd.Name(), err) } if cmd.API != nil { cmd.API.Sync() } return 0, nil } } return 1, fmt.Errorf("scw: unknown subcommand %s\nRun 'scw help' for usage.", name) }
func runPatch(cmd *Command, args []string) error { if patchHelp { return cmd.PrintUsage() } if len(args) != 2 { return cmd.PrintShortUsage() } // Parsing FIELD=VALUE updateParts := strings.SplitN(args[1], "=", 2) if len(updateParts) != 2 { return cmd.PrintShortUsage() } fieldName := updateParts[0] newValue := updateParts[1] changes := 0 ident := api.GetIdentifier(cmd.API, args[0]) switch ident.Type { case api.IdentifierServer: currentServer, err := cmd.API.GetServer(ident.Identifier) if err != nil { log.Fatalf("Cannot get server %s: %v", ident.Identifier, err) } var payload api.ScalewayServerPatchDefinition switch fieldName { case "state_detail": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.StateDetail, fieldName, newValue) if currentServer.StateDetail != newValue { changes++ payload.StateDetail = &newValue } case "name": log.Warnf("To rename a server, Use 'scw rename'") log.Debugf("%s=%s => %s=%s", fieldName, currentServer.StateDetail, fieldName, newValue) if currentServer.Name != newValue { changes++ payload.Name = &newValue } case "bootscript": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.Bootscript.Identifier, fieldName, newValue) if currentServer.Bootscript.Identifier != newValue { changes++ payload.Bootscript.Identifier = newValue } case "security_group": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.SecurityGroup.Identifier, fieldName, newValue) if currentServer.SecurityGroup.Identifier != newValue { changes++ payload.SecurityGroup.Identifier = newValue } case "tags": newTags := strings.Split(newValue, " ") log.Debugf("%s=%s => %s=%s", fieldName, currentServer.Tags, fieldName, newTags) // fixme test equality with reflect.DeepEqual ? changes++ payload.Tags = &newTags default: log.Fatalf("'_patch server %s=' not implemented", fieldName) } // FIXME: volumes, tags, dynamic_ip_required if changes > 0 { log.Debugf("updating server: %d change(s)", changes) err = cmd.API.PatchServer(ident.Identifier, payload) } else { log.Debugf("no changes, not updating server") } if err != nil { log.Fatalf("Cannot update server: %v", err) } default: log.Fatalf("_patch not implemented for this kind of object") } fmt.Println(ident.Identifier) return nil }
// UntarToDest writes to user destination the streamed tarball in input func UntarToDest(ctx CommandContext, sourceStream *io.ReadCloser, destination string, gateway string) error { // destination is a server address + path (scp-like uri) if strings.Index(destination, ":") > -1 { logrus.Debugf("Streaming using ssh and untaring remotely") serverParts := strings.Split(destination, ":") if len(serverParts) != 2 { return fmt.Errorf("invalid destination uri, see 'scw cp -h' for usage") } serverID := ctx.API.GetServerID(serverParts[0]) server, err := ctx.API.GetServer(serverID) if err != nil { return err } // remoteCommand is executed on the remote server // it streams a tarball raw content remoteCommand := []string{"tar"} remoteCommand = append(remoteCommand, "-C", serverParts[1]) if ctx.Getenv("DEBUG") == "1" { remoteCommand = append(remoteCommand, "-v") } remoteCommand = append(remoteCommand, "-xf", "-") // Resolve gateway if gateway == "" { gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string if gateway == serverID || gateway == serverParts[0] { gateway = "" } else { gateway, err = api.ResolveGateway(ctx.API, gateway) if err != nil { return fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err) } } // execCmd contains the ssh connection + the remoteCommand sshCommand := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, false, remoteCommand, gateway) logrus.Debugf("Executing: %s", sshCommand) spawnDst := exec.Command("ssh", sshCommand.Slice()[1:]...) untarInputStream, err := spawnDst.StdinPipe() if err != nil { return err } defer untarInputStream.Close() // spawnDst.Stderr = ctx.Stderr // spawnDst.Stdout = ctx.Stdout err = spawnDst.Start() if err != nil { return err } _, err = io.Copy(untarInputStream, *sourceStream) return err } // destination is stdout if destination == "-" { // stdout logrus.Debugf("Writing sourceStream(%v) to ctx.Stdout(%v)", sourceStream, ctx.Stdout) _, err := io.Copy(ctx.Stdout, *sourceStream) return err } // destination is a path on localhost logrus.Debugf("Untaring to local path: %s", destination) err := archive.Untar(*sourceStream, destination, &archive.TarOptions{NoLchown: true}) return err }