Skip to content

Commit fecd3e5

Browse files
author
Guruprasad Kulkarni
committed
Initial Program
Functional but can be improved. No Tests Yet
1 parent d49d381 commit fecd3e5

File tree

3 files changed

+384
-0
lines changed

3 files changed

+384
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13+
github-release
14+
bin/dlv

github-release.go

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"log"
10+
"net/http"
11+
"os"
12+
"strings"
13+
"time"
14+
)
15+
16+
type release struct {
17+
TagName string `json:"tag_name"`
18+
TargetBranch string `json:"target_commitish"`
19+
ReleaseName string `json:"name"`
20+
Body string `json:"body"`
21+
PreRelease bool `json:"prerelease"`
22+
}
23+
24+
type userInputs struct {
25+
tag string
26+
releaseName string
27+
previousTag string
28+
preRelease bool
29+
projects []string
30+
user string
31+
source string
32+
fallbackBranch string
33+
timeout int
34+
supportBranchName string
35+
}
36+
37+
/* {
38+
"ref": "refs/tags/4.29.1",
39+
"node_id": "MDM6UmVmMTIwMzAxOTM2OjQuMjkuMQ==",
40+
"url": "https://api.github.com/repos/idnowgmbh/de.idnow.ai/git/refs/tags/4.29.1",
41+
"object": {
42+
"sha": "1e6757d5d09730b84d1a29eff891c206457d1049",
43+
"type": "commit",
44+
"url": "https://api.github.com/repos/idnowgmbh/de.idnow.ai/git/commits/1e6757d5d09730b84d1a29eff891c206457d1049"
45+
}
46+
} */
47+
type referenceResponse struct {
48+
Ref string `json:"ref"`
49+
NodeID string `json:"node_id"`
50+
URL string `json:"url"`
51+
Object objectInReference `json:"object"`
52+
}
53+
54+
type objectInReference struct {
55+
Sha string `json:"sha"`
56+
TypeInfo string `json:"type"`
57+
URL string `json:"url"`
58+
}
59+
60+
/*
61+
https://api.github.com/repos/<AUTHOR>/<REPO>/git/refs
62+
{
63+
"ref": "refs/heads/<NEW-BRANCH-NAME>",
64+
"sha": "<HASH-TO-BRANCH-FROM>"
65+
}
66+
*/
67+
type createBranchBody struct {
68+
Ref string `json:"ref"`
69+
Sha string `json:"sha"`
70+
}
71+
72+
const environmentTokenKey = "OAUTH_TOKEN"
73+
const dockerNamesURL = "https://frightanic.com/goodies_content/docker-names.php"
74+
const apiBaseURL = "https://api.github.com/repos"
75+
const githubURL = "https://github.com/repos/%s/%s/compare/%s...%s"
76+
77+
func main() {
78+
if token, present := os.LookupEnv(environmentTokenKey); !present || token == "" {
79+
log.Fatalf("Please set a environment variable named %s created on github.", environmentTokenKey)
80+
}
81+
82+
var userInput userInputs
83+
flag.StringVar(&userInput.user, "user", "idnowgmbh", "The User / Owner of the repository")
84+
flag.StringVar(&userInput.source, "source", "master", "The source branch/tag to create the new tag")
85+
flag.StringVar(&userInput.fallbackBranch, "fallback-branch", "master", "The fallback branch to create the TAG on if the source branch does not exist in the repository.")
86+
flag.IntVar(&userInput.timeout, "timeout", 5, "The Timeout for Github API Calls")
87+
flag.StringVar(&userInput.supportBranchName, "support-branch-name", "", "The name of the support branch to create if source branch is a tag")
88+
89+
flag.StringVar(&userInput.tag, "tag", "", "The tag to create.")
90+
flag.StringVar(&userInput.releaseName, "release-name", "", "The name of the Release")
91+
flag.StringVar(&userInput.previousTag, "previous-tag", "", "The previous tag to use in the message")
92+
flag.BoolVar(&userInput.preRelease, "pre-release", true, "If this is a pre-release, use -pre-release=false to change")
93+
94+
flag.Usage = usage
95+
flag.Parse()
96+
97+
userInput.projects = flag.Args()
98+
99+
inputValidaton(userInput)
100+
userInput.releaseName = getReleaseName(userInput)
101+
102+
client := &http.Client{
103+
Timeout: time.Duration(time.Second * time.Duration(userInput.timeout)),
104+
}
105+
106+
for index, project := range userInput.projects {
107+
log.Printf("%2d : Starting Release %s for %s with Tag Version %s on branch %s with fallback branch %s and possible support branch %s", index+1, userInput.releaseName, project, userInput.tag, userInput.source, userInput.fallbackBranch, userInput.supportBranchName)
108+
109+
projectAPIBaseURL := fmt.Sprintf("%s/%s/%s", apiBaseURL, userInput.user, project)
110+
targetBranch, err := checkBranch(client, userInput, project, projectAPIBaseURL)
111+
if err != nil {
112+
log.Fatalf("Could not get the Target Branch %v", err)
113+
}
114+
log.Printf("Selected Branch %s to create tag %s", targetBranch, userInput.tag)
115+
createRelease(client, userInput, targetBranch, project, projectAPIBaseURL)
116+
}
117+
}
118+
119+
func usage() {
120+
executableName := os.Args[0]
121+
fmt.Fprintf(flag.CommandLine.Output(), "\n%s is an opinionated implementation of some Github APIs that can be used to create release tags for multiple projects\n", executableName)
122+
fmt.Fprintf(flag.CommandLine.Output(), "\nUsage: %s -user comdotlinux -source master -tag v0.0.2 -previous-tag v0.0.1 java-design-patterns TasteOfJavaEE7", executableName)
123+
fmt.Fprintf(flag.CommandLine.Output(), "\nUsage: %s -user comdotlinux -source support/v0.0.x -tag v0.0.3 -fallback-branch master -previous-tag v0.0.1 -release-name Duke -pre-release=false java-design-patterns TasteOfJavaEE7", executableName)
124+
fmt.Fprintf(flag.CommandLine.Output(), "\nUsage: %s -user comdotlinux -source v.0.0.1 -tag v0.0.2-RC.1 -support-branch-name support/v0.0.x -previous-tag v0.0.1 java-design-patterns TasteOfJavaEE7", executableName)
125+
fmt.Fprintf(flag.CommandLine.Output(), "\nWhen -source is a TAG -support-branch-name is mandatory. \n")
126+
fmt.Fprintf(flag.CommandLine.Output(), "An environment variable with the name %s is mandatory for all actions!\nSee https://developer.github.com/v3/#oauth2-token-sent-in-a-header to get one.\n\n", environmentTokenKey)
127+
fmt.Fprintf(flag.CommandLine.Output(), "Below are the possible parameters:\n")
128+
flag.PrintDefaults()
129+
os.Exit(3)
130+
}
131+
132+
func checkBranch(client *http.Client, userInput userInputs, project string, projectAPIBaseURL string) (string, error) {
133+
url := fmt.Sprintf("%s/branches/%s", projectAPIBaseURL, userInput.source)
134+
res, err := doGet(client, url)
135+
if err != nil {
136+
log.Fatalf("Received error %v", err)
137+
}
138+
139+
if statusSuccess(res.StatusCode) {
140+
log.Printf("Branch %s looks good, will be selected. Response : %v", userInput.source, http.StatusText(res.StatusCode))
141+
return userInput.source, nil
142+
}
143+
144+
if res.StatusCode == http.StatusNotFound {
145+
log.Printf("Checking if source %s is a TAG", userInput.source)
146+
url = fmt.Sprintf("%s/releases/tags/%s", projectAPIBaseURL, userInput.source)
147+
res, err := doGet(client, url)
148+
if err != nil {
149+
log.Fatalf("Could not Check release tags : %v", err)
150+
}
151+
152+
if statusSuccess(res.StatusCode) {
153+
log.Printf("%s is a Tag", userInput.source)
154+
if userInput.supportBranchName == "" {
155+
log.Fatalf("If Source is a tag, name of support branch to create from this is necessary!")
156+
}
157+
log.Printf("Since %s is a Tag, Creating support branch %s", userInput.source, userInput.supportBranchName)
158+
159+
url = fmt.Sprintf("%s/git/refs/tags/%s", projectAPIBaseURL, userInput.source)
160+
res, err := doGet(client, url)
161+
if err != nil {
162+
log.Fatalf("Could not get Tag %s commit userInput. : %v", userInput.source, err)
163+
}
164+
165+
if statusSuccess(res.StatusCode) {
166+
log.Println("Reading response body to get commit hash")
167+
defer res.Body.Close()
168+
tagInfoResponseBody, errorReadingBody := ioutil.ReadAll(res.Body)
169+
if errorReadingBody != nil {
170+
log.Fatalf("Could not read Response body, cannot proceed : %v", errorReadingBody)
171+
}
172+
173+
log.Println("Read Response body, trying to unmarshal the Json")
174+
var tagReference referenceResponse
175+
if err := json.Unmarshal(tagInfoResponseBody, &tagReference); err != nil {
176+
log.Fatalf("Could not read response for tag info to get sha commit %v", err)
177+
}
178+
179+
log.Printf("Response body read into referenceResponse, sha hash : %s", tagReference.Object.Sha)
180+
181+
createBranch := createBranchBody{
182+
Ref: fmt.Sprintf("refs/heads/%s", userInput.supportBranchName),
183+
Sha: tagReference.Object.Sha,
184+
}
185+
186+
log.Printf("Created Request Object to create branch : %v", createBranch)
187+
bodyBytes, err := json.Marshal(createBranch)
188+
if err != nil {
189+
log.Fatalf("Could not create body for creating branch. %v", err)
190+
}
191+
192+
log.Println("structconverted to Json Bytes")
193+
url = fmt.Sprintf("%s/git/refs", projectAPIBaseURL)
194+
log.Printf("Calling URL %s to create Branch %s", url, createBranch.Ref)
195+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyBytes))
196+
if err != nil {
197+
log.Fatalf("Could not create Request for creating branch. %v", err)
198+
}
199+
200+
addAuthAndAcceptHeader(req)
201+
req.Header.Add(http.CanonicalHeaderKey("Content-Type"), "application/json")
202+
res, err := client.Do(req)
203+
if err != nil {
204+
log.Fatalf("Error in creating the branch : %v", err)
205+
}
206+
log.Printf("POST call to create branch completed with status %s", http.StatusText(res.StatusCode))
207+
if res.StatusCode == http.StatusCreated {
208+
defer res.Body.Close()
209+
createBranchResponseBody, errorReadingBody := ioutil.ReadAll(res.Body)
210+
if errorReadingBody != nil {
211+
log.Fatalf("Could not read Response body, cannot proceed : %v", errorReadingBody)
212+
}
213+
214+
var branchReference referenceResponse
215+
if err := json.Unmarshal(createBranchResponseBody, &branchReference); err != nil {
216+
log.Fatalf("Could not read response for tag info to get sha commit %v", err)
217+
}
218+
219+
log.Printf("Created Branch with Details : %v", branchReference)
220+
221+
branchNameArray := strings.SplitN(branchReference.Ref, "/", 3)
222+
if len(branchNameArray) == 3 {
223+
return branchNameArray[2], nil
224+
}
225+
return branchReference.Ref, nil
226+
}
227+
log.Printf("POST call to create branch completed with response %v", res)
228+
}
229+
} else {
230+
log.Printf("Use fallback branch since %s is neither a branch nor a Tag", userInput.source)
231+
url := fmt.Sprintf("%s/branches/%s", projectAPIBaseURL, userInput.fallbackBranch)
232+
res, err := doGet(client, url)
233+
if err != nil {
234+
log.Fatalf("Received error %v", err)
235+
}
236+
237+
if statusSuccess(res.StatusCode) {
238+
log.Printf("Branch %s looks good, will be selected. Response : %v", userInput.fallbackBranch, http.StatusText(res.StatusCode))
239+
return userInput.fallbackBranch, nil
240+
}
241+
}
242+
}
243+
244+
log.Println("The source branch parameter and the fallback cannot be used to create release, so using master")
245+
return "master", nil
246+
}
247+
248+
func statusSuccess(statusCode int) bool {
249+
log.Printf("checking status code %d", statusCode)
250+
return statusCode >= http.StatusOK && statusCode <= 299
251+
}
252+
253+
func doGet(client *http.Client, url string) (*http.Response, error) {
254+
log.Printf("Calling URL %s", url)
255+
req, err := http.NewRequest(http.MethodGet, url, nil)
256+
if err != nil {
257+
return nil, err
258+
}
259+
260+
addAuthAndAcceptHeader(req)
261+
262+
res, err := client.Do(req)
263+
if err != nil {
264+
return nil, err
265+
}
266+
267+
log.Printf("Response %v", res)
268+
return res, nil
269+
}
270+
271+
func addAuthAndAcceptHeader(request *http.Request) {
272+
request.Header.Add(http.CanonicalHeaderKey("Authorization"), fmt.Sprintf("token %s", os.Getenv(environmentTokenKey)))
273+
request.Header.Add(http.CanonicalHeaderKey("Accept"), "application/vnd.github.v3+json")
274+
}
275+
276+
func createRelease(client *http.Client, userInput userInputs, targetBranch string, project string, projectAPIBaseURL string) {
277+
previousComparePoint := targetBranch
278+
if !isEmpty(userInput.previousTag) {
279+
previousComparePoint = userInput.previousTag
280+
}
281+
releaseCompareBody := fmt.Sprintf(githubURL, userInput.user, project, previousComparePoint, userInput.tag)
282+
releaseRequest := release{
283+
TagName: userInput.tag,
284+
ReleaseName: userInput.releaseName,
285+
PreRelease: userInput.preRelease,
286+
TargetBranch: targetBranch,
287+
Body: releaseCompareBody,
288+
}
289+
290+
b, _ := json.MarshalIndent(releaseRequest, "", " ")
291+
os.Stdout.Write(b)
292+
293+
url := fmt.Sprintf("%s/releases", projectAPIBaseURL)
294+
log.Printf("Calling URL %s to create Release %v", url, releaseRequest)
295+
bodyBytes, err := json.Marshal(releaseRequest)
296+
if err != nil {
297+
log.Fatalf("Could not create body for creating release. %v", err)
298+
}
299+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyBytes))
300+
if err != nil {
301+
log.Fatalf("Could not create Body Json Bytes, from release object. %v", err)
302+
}
303+
304+
addAuthAndAcceptHeader(req)
305+
req.Header.Add(http.CanonicalHeaderKey("Content-Type"), "application/json")
306+
res, err := client.Do(req)
307+
if err != nil {
308+
log.Fatalf("Create release Failed, %v", err)
309+
}
310+
311+
if !statusSuccess(res.StatusCode) {
312+
log.Fatalf("Create release Failed with status, %d : %s", res.StatusCode, http.StatusText(res.StatusCode))
313+
}
314+
315+
log.Printf("Release Tag Created : %v", res)
316+
}
317+
318+
func getReleaseName(userInput userInputs) string {
319+
if isEmpty(userInput.releaseName) {
320+
log.Printf("Since the release name is unavailable, getting a random release name using %s", dockerNamesURL)
321+
return getRandomReleaseName()
322+
if isEmpty(userInput.releaseName) {
323+
releaseName := "Release of " + userInput.tag
324+
log.Printf("Since getting release name was not possible, using %s as release name", releaseName)
325+
return releaseName
326+
}
327+
}
328+
return userInput.releaseName
329+
}
330+
331+
func getRandomReleaseName() string {
332+
client := http.Client{}
333+
resp, err := client.Get(dockerNamesURL)
334+
if err != nil {
335+
log.Printf("Unable to get Docker Container Names for random release name. %v", err)
336+
return ""
337+
}
338+
defer resp.Body.Close()
339+
body, err := ioutil.ReadAll(resp.Body)
340+
if err != nil {
341+
log.Printf("Error reading body for the release name : %v", err)
342+
return ""
343+
}
344+
345+
return strings.TrimRight(fmt.Sprintf("%s", body), "\n")
346+
}
347+
348+
func isEmpty(input string) bool {
349+
return len(input) == 0 || input == ""
350+
}
351+
352+
func inputValidaton(userInput userInputs) {
353+
354+
var errors []string
355+
356+
if isEmpty(userInput.user) {
357+
errors = append(errors, "User / Organization parameter is mandatory")
358+
}
359+
360+
if isEmpty(userInput.source) {
361+
errors = append(errors, "source parameter is mandatory and must either be a branch OR a existing TAG on Github")
362+
}
363+
364+
if isEmpty(userInput.tag) {
365+
errors = append(errors, "tag parameter is mandatory, otherwise what are we releasing?")
366+
}
367+
368+
if len(userInput.projects) == 0 {
369+
errors = append(errors, "Atleast provide one project, otherwise where do we create the tag?")
370+
}
371+
372+
if len(errors) != 0 {
373+
fmt.Fprintln(flag.CommandLine.Output(), "")
374+
for index, err := range errors {
375+
fmt.Fprintf(flag.CommandLine.Output(), "%2d : %s\n", index+1, err)
376+
}
377+
fmt.Fprintln(flag.CommandLine.Output(), "")
378+
flag.Usage()
379+
}
380+
381+
}

src/github.com/go-delve/delve

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 04834a781abd1388c21670d2c1eb49045d5f1b04

0 commit comments

Comments
 (0)