forked from gengo/goship
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.go
152 lines (142 loc) · 5.16 KB
/
github.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
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"sync"
goship "github.com/gengo/goship/lib"
"github.com/gengo/goship/lib/acl"
"github.com/gengo/goship/lib/auth"
githublib "github.com/gengo/goship/lib/github"
"github.com/google/go-github/github"
"golang.org/x/crypto/ssh"
)
// remoteCmdOutput runs the given command on a remote server at the given hostname as the given user.
func remoteCmdOutput(username, hostname, cmd string, privateKey []byte) (b []byte, err error) {
p, err := ssh.ParseRawPrivateKey(privateKey)
if err != nil {
return b, err
}
s, err := ssh.NewSignerFromKey(p)
if err != nil {
return b, err
}
pub := ssh.PublicKeys(s)
clientConfig := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{pub},
}
client, err := ssh.Dial("tcp", hostname, clientConfig)
if err != nil {
return b, errors.New("ERROR: Failed to dial: " + err.Error())
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return b, errors.New("ERROR: Failed to create session: " + err.Error())
}
defer session.Close()
b, err = session.Output(cmd)
if err != nil {
return b, fmt.Errorf("ERROR: Failed to run cmd on host %s: %s", hostname, err.Error())
}
return b, nil
}
// latestDeployedCommit gets the latest commit hash on the host.
func latestDeployedCommit(username, hostname string, e goship.Environment) (b []byte, err error) {
p, err := ioutil.ReadFile(*keyPath)
if err != nil {
return b, errors.New("Failed to open private key file: " + err.Error())
}
o, err := remoteCmdOutput(username, hostname, fmt.Sprintf("git --git-dir=%s rev-parse HEAD", e.RepoPath), p)
if err != nil {
return b, err
}
return o, nil
}
// getCommit is called in a goroutine and gets the latest deployed commit on a host.
// It updates the Environment in-place.
func getCommit(wg *sync.WaitGroup, project goship.Project, env goship.Environment, host goship.Host, deployUser string, i, j int) {
defer wg.Done()
lc, err := latestDeployedCommit(deployUser, host.URI+":"+sshPort, env)
if err != nil {
log.Printf("ERROR: failed to get latest deployed commit: %s, %s. Error: %v", host.URI, deployUser, err)
host.LatestCommit = string(lc)
project.Environments[i].Hosts[j] = host
}
host.LatestCommit = strings.TrimSpace(string(lc))
project.Environments[i].Hosts[j] = host
}
// getLatestGitHubCommit is called in a goroutine and retrieves the latest commit
// from GitHub for a given branch of a project. It updates the Environment in-place.
func getLatestGitHubCommit(wg *sync.WaitGroup, project goship.Project, environment goship.Environment, gcl githublib.Client, repoOwner, repoName string, i int) {
defer wg.Done()
opts := &github.CommitsListOptions{SHA: environment.Branch}
commits, _, err := gcl.ListCommits(repoOwner, repoName, opts)
if err != nil {
log.Println("ERROR: Failed to get commits from GitHub: ", err)
environment.LatestGitHubCommit = ""
} else {
environment.LatestGitHubCommit = *commits[0].SHA
}
project.Environments[i] = environment
}
// retrieveCommits fetches the latest deployed commits as well
// as the latest GitHub commits for a given Project.
// it will also check if the user has permission to pull.
func retrieveCommits(gcl githublib.Client, ac acl.AccessControl, r *http.Request, project goship.Project, deployUser string) (goship.Project, error) {
// define a wait group to wait for all goroutines to finish
var wg sync.WaitGroup
for i, environment := range project.Environments {
for j, host := range environment.Hosts {
// start a goroutine for SSHing on to the machine
wg.Add(1)
go getCommit(&wg, project, environment, host, deployUser, i, j)
}
wg.Add(1)
go getLatestGitHubCommit(&wg, project, environment, gcl, project.RepoOwner, project.RepoName, i)
}
// wait for goroutines to finish
wg.Wait()
for i, e := range project.Environments {
project.Environments[i] = e
for j, host := range e.Hosts {
host.GitHubCommitURL = host.LatestGitHubCommitURL(project)
host.GitHubDiffURL = host.LatestGitHubDiffURL(project, e)
host.ShortCommitHash = host.LatestShortCommitHash()
project.Environments[i].Hosts[j] = host
}
}
u, err := auth.CurrentUser(r)
if err != nil {
log.Printf("Failed to get user %s", err)
}
return filterProject(ac, project, r, u), err
}
// set projects to lock where user is only in a pull only repo and append a comment
func filterProject(ac acl.AccessControl, p goship.Project, r *http.Request, u auth.User) goship.Project {
for i, e := range p.Environments {
// If the repo isn't already locked.. lock it if the user doesnt have permission
// and add to the comments
if e.IsLocked {
if p.Environments[i].Comment != "" {
p.Environments[i].Comment = p.Environments[i].Comment + " | "
}
p.Environments[i].Comment = p.Environments[i].Comment + "repo is locked."
continue
}
locked := !ac.Deployable(p.RepoOwner, p.RepoName, u.Name)
p.Environments[i].IsLocked = locked
// Add a line break if there is already a comment
if p.Environments[i].Comment != "" {
p.Environments[i].Comment = p.Environments[i].Comment + " | "
}
if locked {
p.Environments[i].Comment = p.Environments[i].Comment + "you do not have permission to deploy "
}
}
return p
}