forked from buildkite/github-release
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
285 lines (229 loc) · 8.2 KB
/
main.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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/google/go-github/github"
"github.com/oleiade/reflections"
"golang.org/x/oauth2"
)
var commandLineName = "github-release"
var commandLineVersion = "1.0"
var commandLineUsage = `github-release is a utility to create GitHub releases and upload packages.
Usage:
$ github-release "v1.0" pkg/*.tar.gz --commit "branch-or-sha" \ # defaults to master
--tag "1-0-0-stable" \ # defaults to the name of the release
--prerelease \ # defaults to false
--github-repository "your/repo" \
--github-access-token [..]
Environment variables can also be used:
$ export GITHUB_RELEASE_ACCESS_TOKEN="..."
$ export GITHUB_RELEASE_REPOSITORY="..."
$ export GITHUB_RELEASE_TAG="..."
$ export GITHUB_RELEASE_COMMIT="..."
$ export GITHUB_RELEASE_PRERELEASE="..."
$ github-release "v1.0" pkg/*.tar.gz
Version:
$ github-release --version
Help:
$ github-release --help
See https://github.com/buildkite/github-release and the GitHub
create release documentation https://developer.github.com/v3/repos/releases/#create-a-release
for more information.`
type commandLineOptions struct {
GithubAccessToken string `flag:"github-access-token" env:"GITHUB_RELEASE_ACCESS_TOKEN" required:"true"`
GithubRepository string `flag:"github-repository" env:"GITHUB_RELEASE_REPOSITORY" required:"true"`
Tag string `flag:"tag" env:"GITHUB_RELEASE_TAG"`
Commit string `flag:"commit" env:"GITHUB_RELEASE_COMMIT"`
Prerelease bool `flag:"prerelease" env:"GITHUB_RELEASE_PRERELEASE"`
}
// tokenSource is an oauth2.TokenSource which returns a static access token
type tokenSource struct {
token *oauth2.Token
}
// Token implements the oauth2.TokenSource interface
func (t *tokenSource) Token() (*oauth2.Token, error) {
return t.token, nil
}
func main() {
if len(os.Args) == 1 {
exitAndError("invalid usage")
}
// Are they checking version?
if os.Args[1] == "--version" {
fmt.Printf("%s version %s\n", commandLineName, commandLineVersion)
os.Exit(0)
}
// Collect the release assets from the command line
releaseAssets := collectReleaseAssets(os.Args[1:])
// Parse our command line options
options := commandLineOptions{}
parseArgs(&options, os.Args)
// Grab our release name. If it starts with a '--', then they haven't
// entered one.
releaseName := os.Args[1]
if strings.HasPrefix(releaseName, "--") {
exitAndError("invalid usage")
}
// fmt.Printf("name: %s\n", releaseName)
// fmt.Printf("assets: %s\n", releaseAssets)
// fmt.Printf("options: %s\n", options.GithubAccessToken)
// Finally do the release
release(releaseName, releaseAssets, &options)
}
// Finds the assets from the argument list by looping over every argument,
// and checking if it's a file.
func collectReleaseAssets(args []string) (files []string) {
for i := 0; i < len(args); i++ {
arg := args[i]
// Check if the file exists
if _, err := os.Stat(arg); err == nil {
files = append(files, arg)
}
// If the arg is an option, we've gone to far
if strings.HasPrefix(arg, "--") {
break
}
}
return
}
// Reflects on the values in the commandLineOptions structure, and fills it
// with values either from the command line, or from the ENV.
func parseArgs(opts *commandLineOptions, args []string) {
// Create a flag set for args
flags := flag.NewFlagSet(commandLineName, flag.ExitOnError)
// Get the fields for the strucutre
fields, _ := reflections.Fields(opts)
// Loop through each field of the structure, and define a flag for it
for i := 0; i < len(fields); i++ {
fieldName := fields[i]
flagName, _ := reflections.GetFieldTag(opts, fieldName, "flag")
fieldKind, _ := reflections.GetFieldKind(opts, fieldName)
if fieldKind == reflect.String {
flags.String(flagName, "", "")
} else if fieldKind == reflect.Bool {
flags.Bool(flagName, false, "")
} else {
exitAndError(fmt.Sprintf("Could not create flag for %s", fieldName))
}
}
// Define our custom usage text
flags.Usage = func() {
fmt.Printf("%s\n", commandLineUsage)
}
// Search the args until we find a "--", which signifies the user has started
// defining options.
var argumentFlags []string
started := false
for i := 0; i < len(args); i++ {
if strings.HasPrefix(args[i], "--") {
started = true
}
if started {
argumentFlags = append(argumentFlags, args[i])
}
}
// Now parse the flags
flags.Parse(argumentFlags)
// Now the flag set has been populated with values, retrieve them and
// set them (or use the ENV variable if empty)
for i := 0; i < len(fields); i++ {
fieldName := fields[i]
fieldKind, _ := reflections.GetFieldKind(opts, fieldName)
// Grab the flags we need
flagName, _ := reflections.GetFieldTag(opts, fieldName, "flag")
envName, _ := reflections.GetFieldTag(opts, fieldName, "env")
required, _ := reflections.GetFieldTag(opts, fieldName, "required")
// Grab the value from the flag
value := flags.Lookup(flagName).Value.String()
// If the value of the flag is empty, try the ENV
if value == "" {
value = os.Getenv(envName)
}
// Do validation
if required == "true" && value == "" {
exitAndError(fmt.Sprintf("missing %s", flagName))
}
// Set the value in the right way
if fieldKind == reflect.String {
reflections.SetField(opts, fieldName, value)
} else if fieldKind == reflect.Bool {
// The bool is converted to a string above
if value == "true" {
reflections.SetField(opts, fieldName, true)
} else {
reflections.SetField(opts, fieldName, false)
}
} else {
exitAndError(fmt.Sprintf("Could not set value of %s", fieldName))
}
}
}
func exitAndError(message interface{}) {
fmt.Printf("%s: %s\nSee '%s --help'\n", commandLineName, message, commandLineName)
os.Exit(1)
}
func release(releaseName string, releaseAssets []string, options *commandLineOptions) {
if options.Prerelease {
log.Printf("Creating prerelease %s for repository: %s", releaseName, options.GithubRepository)
} else {
log.Printf("Creating release %s for repository: %s", releaseName, options.GithubRepository)
}
// Split the repository into two parts (owner and repository)
repositoryParts := strings.Split(options.GithubRepository, "/")
if len(repositoryParts) != 2 {
exitAndError("github-repository is in the wrong format")
}
// If no tag was provided, use the name of the release
tagName := options.Tag
if tagName == "" {
tagName = releaseName
}
// log.Printf("%s", repos)
// log.Printf("name: %s", releaseName)
// log.Printf("assets: %s", releaseAssets)
// log.Printf("commit: %s", options.Commit)
// log.Printf("tag: %s", tag)
// Create an oAuth Token Source
ts := &tokenSource{
&oauth2.Token{AccessToken: options.GithubAccessToken},
}
// New oAuth client
tc := oauth2.NewClient(oauth2.NoContext, ts)
// Github Client
client := github.NewClient(tc)
ctx := context.TODO()
// Create an object that represents the release
release := &github.RepositoryRelease{
Name: &releaseName,
TagName: &tagName,
TargetCommitish: &options.Commit,
Prerelease: &options.Prerelease,
}
// Create the GitHub release
createdRelease, _, err := client.Repositories.CreateRelease(ctx, repositoryParts[0], repositoryParts[1], release)
if err != nil {
log.Fatalf("Failed to create release (%T %v)", err, err)
}
// log.Printf("DEBUG: %s", github.Stringify(createdRelease))
// Start uploading the assets
for i := 0; i < len(releaseAssets); i++ {
fileName := releaseAssets[i]
file, err := os.Open(fileName)
if err != nil {
log.Fatalf("Could not open file \"%s\" (%T %v)", fileName, err, err)
}
releaseAssetOptions := &github.UploadOptions{Name: filepath.Base(fileName)}
createdReleaseAsset, _, err := client.Repositories.UploadReleaseAsset(ctx,repositoryParts[0], repositoryParts[1], *createdRelease.ID, releaseAssetOptions, file)
if err != nil {
log.Fatalf("Failed to upload asset \"%s\" (%T %v)", fileName, err, err)
}
log.Printf("Successfully uploaded asset: %s", github.Stringify(createdReleaseAsset.URL))
}
log.Printf("Successfully created release: %s", github.Stringify(createdRelease.HTMLURL))
}