func RealAPIContext() *CommandContext { config, err := config.GetConfig() if err != nil { logrus.Warnf("RealAPIContext: failed to call config.GetConfig(): %v", err) return nil } apiClient, err := api.NewScalewayAPI(config.ComputeAPI, config.AccountAPI, config.Organization, config.Token) if err != nil { logrus.Warnf("RealAPIContext: failed to call api.NewScalewayAPI(): %v", err) return nil } stdout := bytes.Buffer{} stderr := bytes.Buffer{} ctx := CommandContext{ Streams: Streams{ Stdin: os.Stdin, Stdout: &stdout, Stderr: &stderr, }, Env: []string{ "HOME" + os.Getenv("HOME"), }, RawArgs: []string{}, API: apiClient, } return &ctx }
func runPs(cmd *Command, rawArgs []string) error { if psHelp { return cmd.PrintUsage() } if len(rawArgs) != 0 { return cmd.PrintShortUsage() } args := commands.PsArgs{ All: psA, Latest: psL, Quiet: psQ, NoTrunc: psNoTrunc, NLast: psN, Filters: make(map[string]string, 0), } if psFilters != "" { for _, filter := range strings.Split(psFilters, " ") { parts := strings.SplitN(filter, "=", 2) if len(parts) != 2 { logrus.Warnf("Invalid filter '%s', should be in the form 'key=value'", filter) continue } if _, ok := args.Filters[parts[0]]; ok { logrus.Warnf("Duplicated filter: %q", parts[0]) } else { args.Filters[parts[0]] = parts[1] } } } ctx := cmd.GetContext(rawArgs) return commands.RunPs(ctx, args) }
// 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 }
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 }
// RunImages is the handler for 'scw images' func RunImages(ctx CommandContext, args ImagesArgs) error { wg := sync.WaitGroup{} chEntries := make(chan api.ScalewayImageInterface) var entries = []api.ScalewayImageInterface{} filterType := args.Filters["type"] // FIXME: remove log.Fatalf in routines if filterType == "" || filterType == "image" { wg.Add(1) go func() { defer wg.Done() images, err := ctx.API.GetImages() if err != nil { logrus.Fatalf("unable to fetch images from the Scaleway API: %v", err) } for _, val := range *images { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "image", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Public: val.Public, Tag: "latest", VirtualSize: float64(val.RootVolume.Size), Organization: val.Organization, } } }() } if args.All || filterType != "" { if filterType == "" || filterType == "snapshot" { wg.Add(1) go func() { defer wg.Done() snapshots, err := ctx.API.GetSnapshots() if err != nil { logrus.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) } for _, val := range *snapshots { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "snapshot", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<snapshot>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, } } }() } if filterType == "" || filterType == "bootscript" { wg.Add(1) go func() { defer wg.Done() bootscripts, err := ctx.API.GetBootscripts() if err != nil { logrus.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) } for _, val := range *bootscripts { chEntries <- api.ScalewayImageInterface{ Type: "bootscript", Identifier: val.Identifier, Name: val.Title, Tag: "<bootscript>", Public: false, } } }() } if filterType == "" || filterType == "volume" { wg.Add(1) go func() { defer wg.Done() volumes, err := ctx.API.GetVolumes() if err != nil { logrus.Fatalf("unable to fetch volumes from the Scaleway API: %v", err) } for _, val := range *volumes { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "volume", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<volume>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, } } }() } } go func() { wg.Wait() close(chEntries) }() done := false for { select { case entry, ok := <-chEntries: if !ok { done = true break } entries = append(entries, entry) } if done { break } } for key, value := range args.Filters { switch key { case "organization", "type", "name", "public": continue default: logrus.Warnf("Unknown filter: '%s=%s'", key, value) } } w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() if !args.Quiet { fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\n") } sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { for key, value := range args.Filters { switch key { case "type": if value != image.Type { goto skipimage } case "organization": switch value { case "me": value = ctx.API.Organization case "official-distribs": value = "a283af0b-d13e-42e1-a43f-855ffbf281ab" case "official-apps": value = "c3884e19-7a3e-4b69-9db8-50e7f902aafc" } if image.Organization != value { goto skipimage } case "name": if fuzzy.RankMatch(strings.ToLower(value), strings.ToLower(image.Name)) == -1 { goto skipimage } case "public": if (value == "true" && !image.Public) || (value == "false" && image.Public) { goto skipimage } } } if args.Quiet { fmt.Fprintf(ctx.Stdout, "%s\n", image.Identifier) } else { tag := image.Tag shortID := utils.TruncIf(image.Identifier, 8, !args.NoTrunc) name := utils.Wordify(image.Name) if !image.Public && image.Type == "image" { name = "user/" + name } shortName := utils.TruncIf(name, 25, !args.NoTrunc) var creationDate, virtualSize string if image.CreationDate.IsZero() { creationDate = "n/a" } else { creationDate = units.HumanDuration(time.Now().UTC().Sub(image.CreationDate)) } if image.VirtualSize == 0 { virtualSize = "n/a" } else { virtualSize = units.HumanSize(image.VirtualSize) } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize) } skipimage: continue } return nil }
// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be // compressed or uncompressed. // Returns the size in bytes of the contents of the layer. func UnpackLayer(dest string, layer Reader) (size int64, err error) { tr := tar.NewReader(layer) trBuf := pools.BufioReader32KPool.Get(tr) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return 0, err } size += hdr.Size // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) // Windows does not support filenames with colons in them. Ignore // these files. This is not a problem though (although it might // appear that it is). Let's suppose a client is running docker pull. // The daemon it points to is Windows. Would it make sense for the // client to be doing a docker pull Ubuntu for example (which has files // with colons in the name under /usr/share/man/man3)? No, absolutely // not as it would really only make sense that they were pulling a // Windows image. However, for development, it is necessary to be able // to pull Linux images which are in the repository. // // TODO Windows. Once the registry is aware of what images are Windows- // specific or Linux-specific, this warning should be changed to an error // to cater for the situation where someone does manage to upload a Linux // image but have it tagged as Windows inadvertently. if runtime.GOOS == "windows" { if strings.Contains(hdr.Name, ":") { logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) continue } } // Note as these operations are platform specific, so must the slash be. if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { // Not the root directory, ensure that the parent directory exists. // This happened in some tests where an image had a tarfile without any // parent directories. parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = system.MkdirAll(parentPath, 0600) if err != nil { return 0, err } } } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, ".wh..wh.") { // Regular files inside /.wh..wh.plnk can be used as hardlink targets // We don't want this directory, but we need the files in them so that // such hardlinks can be resolved. if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg { basename := filepath.Base(hdr.Name) aufsHardlinks[basename] = hdr if aufsTempdir == "" { if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil { return 0, err } defer os.RemoveAll(aufsTempdir) } if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil); err != nil { return 0, err } } continue } path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return 0, err } // Note as these operations are platform specific, so must the slash be. if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } base := filepath.Base(path) if strings.HasPrefix(base, ".wh.") { originalBase := base[len(".wh."):] originalPath := filepath.Join(filepath.Dir(path), originalBase) if err := os.RemoveAll(originalPath); err != nil { return 0, err } } else { // If path exits we almost always just want to remove and replace it. // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return 0, err } } } trBuf.Reset(tr) srcData := io.Reader(trBuf) srcHdr := hdr // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // we manually retarget these into the temporary files we extracted them into if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") { linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { return 0, fmt.Errorf("Invalid aufs hardlink") } tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) if err != nil { return 0, err } defer tmpFile.Close() srcData = tmpFile } if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil { return 0, err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return 0, err } } return size, nil }
// RunPs is the handler for 'scw ps' func RunPs(ctx CommandContext, args PsArgs) error { limit := args.NLast if args.Latest { limit = 1 } filterState := args.Filters["state"] // FIXME: if filter state is defined, try to optimize the query all := args.All || args.NLast > 0 || args.Latest || filterState != "" servers, err := ctx.API.GetServers(all, limit) if err != nil { return fmt.Errorf("Unable to fetch servers from the Scaleway API: %v", err) } for key, value := range args.Filters { switch key { case "state", "name", "tags", "image", "ip": continue default: logrus.Warnf("Unknown filter: '%s=%s'", key, value) } } w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() if !args.Quiet { fmt.Fprintf(w, "SERVER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAME\n") } for _, server := range *servers { // filtering for key, value := range args.Filters { switch key { case "state": if value != server.State { goto skipServer } case "name": if fuzzy.RankMatch(strings.ToLower(value), strings.ToLower(server.Name)) == -1 { goto skipServer } case "tags": found := false for _, tag := range server.Tags { if tag == value { found = true continue } } if !found { goto skipServer } case "image": imageID := ctx.API.GetImageID(value, true) if imageID != server.Image.Identifier { goto skipServer } case "ip": if value != server.PublicAddress.IP { goto skipServer } } } if args.Quiet { fmt.Fprintf(w, "%s\n", server.Identifier) } else { shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) shortImage := utils.TruncIf(utils.Wordify(server.Image.Name), 25, !args.NoTrunc) shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) creationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.CreationDate) shortCreationDate := units.HumanDuration(time.Now().UTC().Sub(creationTime)) port := server.PublicAddress.IP fmt.Fprintf(w, "%s\t%s\t\t%s\t%s\t%s\t%s\n", shortID, shortImage, shortCreationDate, server.State, port, shortName) } skipServer: continue } return nil }