func runBilling(cmd *Command, rawArgs []string) error { if billingHelp { return cmd.PrintUsage() } if len(rawArgs) > 0 { return cmd.PrintShortUsage() } // cli parsing args := commands.PsArgs{ NoTrunc: billingNoTrunc, } ctx := cmd.GetContext(rawArgs) logrus.Warn("") logrus.Warn("Warning: 'scw _billing' is a work-in-progress price estimation tool") logrus.Warn("For real usage, visit https://cloud.scaleway.com/#/billing") logrus.Warn("") // table w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() fmt.Fprintf(w, "ID\tNAME\tSTARTED\tMONTH PRICE\n") // servers servers, err := cmd.API.GetServers(true, 0) if err != nil { return err } totalMonthPrice := new(big.Rat) for _, server := range *servers { if server.State != "running" { continue } shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) modificationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.ModificationDate) modificationAgo := time.Now().UTC().Sub(modificationTime) shortModificationDate := units.HumanDuration(modificationAgo) usage := pricing.NewUsageByPath("/compute/c1/run") usage.SetStartEnd(modificationTime, time.Now().UTC()) totalMonthPrice = totalMonthPrice.Add(totalMonthPrice, usage.Total()) fmt.Fprintf(w, "server/%s\t%s\t%s\t%s\n", shortID, shortName, shortModificationDate, usage.TotalString()) } fmt.Fprintf(w, "TOTAL\t\t\t%s\n", pricing.PriceString(totalMonthPrice, "EUR")) return 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 }