This repository was archived by the owner on Jun 26, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathcli.go
187 lines (156 loc) · 5.46 KB
/
cli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/hashicorp/atlas-go/archive"
"github.com/hashicorp/logutils"
"github.com/mitchellh/ioprogress"
)
// Exit codes are int values that represent an exit code for a particular error.
// Sub-systems may check this unique error to determine the cause of an error
// without parsing the output or help text.
const (
ExitCodeOK int = 0
// Errors start at 10
ExitCodeError = 10 + iota
ExitCodeParseFlagsError
ExitCodeBadArgs
ExitCodeArchiveError
ExitCodeUploadError
)
// levelFilter is the log filter with pre-defined levels
var levelFilter = &logutils.LevelFilter{
Levels: []logutils.LogLevel{"DEBUG", "INFO", "WARN", "ERR"},
}
// CLI is the command line object
type CLI struct {
// outStream and errStream are the standard out and standard error streams to
// write messages from the CLI.
outStream, errStream io.Writer
}
// Run invokes the CLI with the given arguments. The first argument is always
// the name of the application. This method slices accordingly.
func (cli *CLI) Run(args []string) int {
// Initialize the logger to start (overridden later if debug is given)
cli.initLogger(os.Getenv("ATLAS_LOG"))
var debug, version bool
var archiveOpts archive.ArchiveOpts
var uploadOpts UploadOpts
flags := flag.NewFlagSet(Name, flag.ContinueOnError)
flags.SetOutput(cli.errStream)
flags.Usage = func() {
fmt.Fprintf(cli.errStream, usage, Name)
}
flags.BoolVar(&archiveOpts.VCS, "vcs", false,
"Uses VCS to determine files to exclude and include")
flags.StringVar(&uploadOpts.URL, "address", "",
"Atlas server address")
flags.StringVar(&uploadOpts.Token, "token", "",
"Atlas API token")
flags.Var((*FlagSliceVar)(&archiveOpts.Exclude), "exclude",
"files/folders to exclude")
flags.Var((*FlagSliceVar)(&archiveOpts.Include), "include",
"files/folders to include")
flags.Var((*FlagMetadataVar)(&uploadOpts.Metadata), "metadata",
"arbitrary metadata to pass along with the request")
flags.BoolVar(&debug, "debug", false,
"turn on debug output")
flags.BoolVar(&version, "version", false,
"display the version")
// Parse all the flags
if err := flags.Parse(args[1:]); err != nil {
return ExitCodeParseFlagsError
}
// Turn on debug mode if requested
if debug {
levelFilter.SetMinLevel(logutils.LogLevel("DEBUG"))
}
// Version
if version {
fmt.Fprintf(cli.errStream, "%s v%s\n", Name, Version)
return ExitCodeOK
}
// Get the parsed arguments (the ones left over after all the flags have been
// parsed)
parsedArgs := flags.Args()
if len(parsedArgs) != 2 {
fmt.Fprintf(cli.errStream, "cli: must specify two arguments - slug, path\n")
flags.Usage()
return ExitCodeBadArgs
}
// Get the name of the app and the path to archive
slug, path := parsedArgs[0], parsedArgs[1]
uploadOpts.Slug = slug
// Get the archive reader
r, err := archive.CreateArchive(path, &archiveOpts)
if err != nil {
fmt.Fprintf(cli.errStream, "error archiving: %s\n", err)
return ExitCodeArchiveError
}
defer r.Close()
// Put a progress bar around the reader
pr := &ioprogress.Reader{
Reader: r,
Size: r.Size,
DrawFunc: ioprogress.DrawTerminalf(os.Stdout, func(p, t int64) string {
return fmt.Sprintf(
"Uploading %s: %s",
slug,
ioprogress.DrawTextFormatBytes(p, t))
}),
}
// Start the upload
doneCh, uploadErrCh, err := Upload(pr, r.Size, &uploadOpts)
if err != nil {
fmt.Fprintf(cli.errStream, "error starting upload: %s\n", err)
return ExitCodeUploadError
}
select {
case err := <-uploadErrCh:
fmt.Fprintf(cli.errStream, "error uploading: %s\n", err)
return ExitCodeUploadError
case version := <-doneCh:
fmt.Printf("Uploaded %s v%d\n", slug, version)
}
return ExitCodeOK
}
// initLogger gets the log level from the environment, falling back to DEBUG if
// nothing was given.
func (cli *CLI) initLogger(level string) {
minLevel := strings.ToUpper(strings.TrimSpace(level))
if minLevel == "" {
minLevel = "WARN"
}
levelFilter.Writer = cli.errStream
levelFilter.SetMinLevel(logutils.LogLevel(level))
log.SetOutput(levelFilter)
}
const usage = `
Usage: %s [options] slug path
Upload application code or artifacts to Atlas for initiating deployments.
"slug" is the name of the <username>/<application_name> to upload to within Atlas.
If path is a directory, it will be compressed (gzip tar) and uploaded
in its entirety. The root of the archive will be the path. For clarity:
if you upload the "foo/" directory, then the file "foo/version" will be
"version" in the archive since "foo/" is the root.
A path must be specified. Due to the nature of this application, it does
not default to using the current working directory automatically.
Options:
-exclude=<path> Glob pattern of files or directories to exclude (this may
be specified multiple times)
-include=<path> Glob pattern of files/directories to include (this may be
specified multiple times, any excludes will override
conflicting includes)
-address=<url> The address of the Atlas server
-token=<token> The Atlas API token
-vcs Get lists of files to exclude and include from a version
control system (Git, Mercurial or Subversion)
-metadata<k=v> Arbitrary key-value (string) metadata to be sent with the
upload; may be specified multiple times
-debug Turn on debug output
-version Print the version of this application
`