-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
221 lines (187 loc) · 5.56 KB
/
main.go
File metadata and controls
221 lines (187 loc) · 5.56 KB
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
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/aws/aws-lambda-go/events"
runtime "github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/google/go-github/v28/github"
"github.com/mholt/archiver"
)
const (
// RepoOwner is the owner of the repository.
RepoOwner = "umwics"
// RepoName is the name of the repository.
RepoName = "wics-site"
// RemotePath is the path to the site on the remote server.
RemotePath = "wics@aviary.cs.umanitoba.ca:~/public_html"
// SSHZip is the path to our .zip file with SSH files.
SSHZip = "ssh.zip"
)
var (
// DeployFunction is the name of the deploy Lambda function.
DeployFunction = os.Getenv("DEPLOY_FUNCTION")
// Lambda is an AWS Lambda client.
Lambda = lambda.New(session.Must(session.NewSession()))
// RepoURL is the download URL of the repo.
RepoURL = fmt.Sprintf("https://github.com/%s/%s/archive/master.zip", RepoOwner, RepoName)
// SSHConfig is the path to our SSH config file.
SSHConfig = filepath.Join(os.TempDir(), "ssh", "config")
// WebhookSecret is the secret for GitHub.
WebhookSecret = []byte(os.Getenv("WEBHOOK_SECRET"))
)
type (
// Request is the type we get from Lambda.
Request events.APIGatewayProxyRequest
// Response is the type we give back to Lambda.
Response events.APIGatewayProxyResponse
)
// AsHTTP returns a http.Request with its Body set to that of req.
func (req Request) AsHTTP() *http.Request {
return &http.Request{Body: ioutil.NopCloser(strings.NewReader(req.Body))}
}
// Validate validates the request.
func (req Request) Validate() error {
r := req.AsHTTP()
payload, err := github.ValidatePayload(r, WebhookSecret)
if err != nil {
return err
}
event, err := github.ParseWebHook(github.WebHookType(r), payload)
if err != nil {
return err
}
switch event.(type) {
case github.PushEvent:
pe := event.(github.PushEvent)
// The branch should be the default branch.
expected := fmt.Sprintf("refs/heads/%s", pe.GetRepo().GetDefaultBranch())
if pe.GetRef() != expected {
return fmt.Errorf("Ref %s is not the default branch", pe.GetRef())
}
default:
// The event should be a push event.
return fmt.Errorf("Unknown event type %T", event)
}
return nil
}
// downloadRepo downloads and unzip the repository, and returns its directory.
func downloadRepo() (string, error) {
// Request the zip file.
resp, err := http.Get(RepoURL)
if err != nil {
return "", err
} else if resp.StatusCode != 200 {
return "", fmt.Errorf("Bad status code: %d", resp.StatusCode)
}
// Read it into a buffer.
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
// Create a file to write the zip contents to.
f, err := ioutil.TempFile("", "wics.*.zip")
if err != nil {
return "", err
}
defer os.Remove(f.Name())
// Write the file.
if _, err = f.Write(b); err != nil {
return "", err
}
dest := filepath.Join(os.TempDir(), fmt.Sprintf("%s-master", RepoName))
// Delete any old copies of the repo.
os.RemoveAll(dest)
// Unzip the file.
if err = archiver.Unarchive(f.Name(), filepath.Dir(dest)); err != nil {
return "", err
}
return dest, nil
}
// buildSite builds the site with Jekyll.
func buildSite(dir string) (string, error) {
if err := doCmd(dir, "jekyll", "build"); err != nil {
return "", err
}
return filepath.Join(dir, "_site"), nil
}
// syncSite pushes the site to the remote server with rsync.
func syncSite(dir string) error {
defer os.RemoveAll(dir)
// To recursively sync the directory, the path must end with a slash.
if !strings.HasSuffix(dir, "/") {
dir += "/"
}
// Unzip our SSH resources.
if err := archiver.Unarchive(SSHZip, os.TempDir()); err != nil {
return err
}
defer os.RemoveAll(filepath.Join(os.TempDir(), "ssh"))
// Set a custom SSH command that uses our config file.
ssh := fmt.Sprintf("ssh -F %s", SSHConfig)
return doCmd("", "rsync", "-e", ssh, "-a", "--delete", dir, RemotePath)
}
// doCmd runs some shell command and prints its output.
func doCmd(dir, program string, args ...string) error {
cmd := exec.Command(program, args...)
if dir != "" {
cmd.Dir = dir
}
b, err := cmd.CombinedOutput()
if len(b) > 0 {
fmt.Println(string(b))
}
return err
}
func main() {
runtime.Start(func(req Request) (resp Response, err error) {
if strings.HasSuffix(os.Args[0], "webhook") {
resp.StatusCode = 200
// Make sure that the event is a push to the master branch.
if err = req.Validate(); err != nil {
fmt.Println("validate request:", err)
resp.StatusCode = 400
}
// Invoke the deploy function so that it can have more time to run and the chance to retry.
input := lambda.InvokeInput{
FunctionName: aws.String(DeployFunction),
InvocationType: aws.String("Event"),
}
if _, err = Lambda.Invoke(&input); err != nil {
fmt.Println("invoke function:", err)
resp.StatusCode = 500
}
return resp, nil
} else if strings.HasSuffix(os.Args[0], "deploy") {
var dir string
// Download the site repository.
if dir, err = downloadRepo(); err != nil {
fmt.Println("download repo:", err)
return
}
// Build the site.
if dir, err = buildSite(dir); err != nil {
fmt.Println("build site:", err)
return
}
// Sync the site with the remote server.
if err = syncSite(dir); err != nil {
fmt.Println("sync site:", err)
return
}
fmt.Println("Successfully synchronized site")
return resp, nil
} else {
fmt.Println("unknown command:", os.Args[0])
resp.StatusCode = 400
return resp, nil
}
})
}