CodeCommitsIssuesPull requestsActionsInsightsSecurity
662e15d56f44b94cef4fd6ff7b6245de5ba181fe

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/admin.go

316lines · modecode

1package main
2
3import (
4 "fmt"
5 "os"
6 "os/exec"
7 "strings"
8
9 "code.google.com/p/go.net/publicsuffix"
10 "github.com/zackbloom/goamz/cloudfront"
11 "github.com/zackbloom/goamz/iam"
12 "github.com/zackbloom/goamz/route53"
13 "github.com/zackbloom/goamz/s3"
14 "golang.org/x/crypto/ssh/terminal"
15)
16
17func CreateBucket(options Options) error {
18 bucket := s3Session.Bucket(options.Bucket)
19
20 err := bucket.PutBucket("public-read")
21 // TODO Ignore found error
22 if err != nil {
23 return err
24 }
25
26 err = bucket.PutBucketWebsite(s3.WebsiteConfiguration{
27 IndexDocument: &s3.IndexDocument{"index.html"},
28 ErrorDocument: &s3.ErrorDocument{"error.html"},
29 })
30 if err != nil {
31 return err
32 }
33
34 err = bucket.PutPolicy([]byte(`{
35 "Version": "2008-10-17",
36 "Statement": [
37 {
38 "Sid": "PublicReadForGetBucketObjects",
39 "Effect": "Allow",
40 "Principal": {
41 "AWS": "*"
42 },
43 "Action": "s3:GetObject",
44 "Resource": "arn:aws:s3:::` + options.Bucket + `/*"
45 }
46 ]
47 }`,
48 ))
49 if err != nil {
50 return err
51 }
52
53 return nil
54}
55
56func GetDistribution(options Options) (dist cloudfront.DistributionSummary, err error) {
57 distP, err := cfSession.FindDistributionByAlias(options.Bucket)
58 if err != nil {
59 return
60 }
61
62 if distP != nil {
63 fmt.Println("CloudFront distribution found with the provided bucket name, assuming config matches.")
64 fmt.Println("If you run into issues, delete the distribution and rerun this command.")
65
66 dist = *distP
67 return
68 }
69
70 conf := cloudfront.DistributionConfig{
71 Origins: cloudfront.Origins{
72 cloudfront.Origin{
73 Id: "S3-" + options.Bucket,
74 DomainName: options.Bucket + ".s3-website-" + options.AWSRegion + ".amazonaws.com",
75 CustomOriginConfig: &cloudfront.CustomOriginConfig{
76 HTTPPort: 80,
77 HTTPSPort: 443,
78 OriginProtocolPolicy: "http-only",
79 },
80 },
81 },
82 DefaultRootObject: "index.html",
83 PriceClass: "PriceClass_All",
84 Enabled: true,
85 DefaultCacheBehavior: cloudfront.CacheBehavior{
86 TargetOriginId: "S3-" + options.Bucket,
87 ViewerProtocolPolicy: "allow-all",
88 AllowedMethods: cloudfront.AllowedMethods{
89 Allowed: []string{"GET", "HEAD"},
90 Cached: []string{"GET", "HEAD"},
91 },
92 },
93 ViewerCertificate: &cloudfront.ViewerCertificate{
94 CloudFrontDefaultCertificate: true,
95 MinimumProtocolVersion: "TLSv1",
96 SSLSupportMethod: "sni-only",
97 },
98 Aliases: cloudfront.Aliases{
99 options.Bucket,
100 },
101 }
102
103 return cfSession.Create(conf)
104}
105
106func CreateUser(options Options) (key iam.AccessKey, err error) {
107 name := options.Bucket + "_deploy"
108
109 _, err = iamSession.CreateUser(name, "/")
110 if err != nil {
111 iamErr, ok := err.(*iam.Error)
112 if ok && iamErr.Code == "EntityAlreadyExists" {
113 err = nil
114 } else {
115 return
116 }
117 }
118
119 _, err = iamSession.PutUserPolicy(name, name, `{
120 "Version": "2012-10-17",
121 "Statement": [
122 {
123 "Effect": "Allow",
124 "Action": [
125 "s3:DeleteObject",
126 "s3:ListBucket",
127 "s3:PutObject",
128 "s3:PutObjectAcl",
129 "s3:GetObject"
130 ],
131 "Resource": [
132 "arn:aws:s3:::`+options.Bucket+`", "arn:aws:s3:::`+options.Bucket+`/*"
133 ]
134 }
135 ]
136 }`,
137 )
138 if err != nil {
139 return
140 }
141
142 keyResp, err := iamSession.CreateAccessKey(name)
143 if err != nil {
144 return
145 }
146
147 return keyResp.AccessKey, nil
148}
149
150func UpdateRoute(options Options, dist cloudfront.DistributionSummary) error {
151 zoneName, err := publicsuffix.EffectiveTLDPlusOne(options.Bucket)
152 if err != nil {
153 return err
154 }
155
156 resp, err := r53Session.ListHostedZonesByName(zoneName, "", 100)
157
158 if resp.IsTruncated {
159 panic("More than 100 zones in the account")
160 }
161
162 // TODO: Figure out what happens when the zone isnt found
163 noZone := false
164 // END
165
166 if noZone {
167 fmt.Printf("A Route 53 hosted zone was not found for %s", zoneName)
168 if zoneName != options.Bucket {
169 fmt.Println("If you would like to use Route 53 to manage your DNS, create a zone for this domain, and update your registrar's configuration to point to the DNS servers Amazon provides and rerun this command. Note that you must copy any existing DNS configuration you have to Route 53 if you do not wish existing services hosted on this domain to stop working.")
170 fmt.Printf("If you would like to continue to use your existing DNS, create a CNAME record pointing %s to %s and the site setup will be finished.", options.Bucket, dist.DomainName)
171 } else {
172 fmt.Println("Since you are hosting the root of your domain, using an alternative DNS host is unfortunately not possible.")
173 fmt.Println("If you wish to host your site at the root of your domain, you must switch your sites DNS to Amazon's Route 53 and retry this command.")
174 }
175 }
176
177 if err != nil {
178 return err
179 }
180
181 if len(resp.HostedZones) > 1 {
182 panic("Multiple matching hosted zones found")
183 }
184 if len(resp.HostedZones) == 0 {
185 panic("Hosted zone not listed")
186 }
187
188 zone := resp.HostedZones[0]
189
190 fmt.Printf("Adding %s to %s Route 53 zone\n", options.Bucket, zone.Name)
191 parts := strings.Split(zone.Id, "/")
192 idValue := parts[2]
193
194 _, err = r53Session.ChangeResourceRecordSet(&route53.ChangeResourceRecordSetsRequest{
195 Changes: []route53.Change{
196 route53.Change{
197 Action: "CREATE",
198 Name: options.Bucket,
199 Type: "A",
200 AliasTarget: route53.AliasTarget{
201 HostedZoneId: "Z2FDTNDATAQYW2",
202 DNSName: dist.DomainName,
203 EvaluateTargetHealth: false,
204 },
205 },
206 },
207 }, idValue)
208
209 if err != nil {
210 if strings.Contains(err.Error(), "it already exists") {
211 fmt.Println("Existing route found, assuming it is correct")
212 fmt.Printf("If you run into trouble, you may need to delete the %s route in Route53 and try again\n", options.Bucket)
213 return nil
214 }
215 return err
216 }
217
218 return nil
219}
220
221func Create(options Options) {
222 if s3Session == nil {
223 s3Session = openS3(options.AWSKey, options.AWSSecret, options.AWSRegion)
224 }
225 if iamSession == nil {
226 iamSession = openIAM(options.AWSKey, options.AWSSecret, options.AWSRegion)
227 }
228 if r53Session == nil {
229 r53Session = openRoute53(options.AWSKey, options.AWSSecret)
230 }
231 if cfSession == nil {
232 cfSession = openCloudFront(options.AWSKey, options.AWSSecret)
233 }
234
235 _, err := exec.LookPath("aws")
236 if err != nil {
237 fmt.Println("The aws CLI executable was not found in the PATH")
238 fmt.Println("Install it from http://aws.amazon.com/cli/ and try again")
239 }
240
241 fmt.Println("Creating Bucket")
242 err = CreateBucket(options)
243
244 if err != nil {
245 fmt.Println("Error creating S3 bucket")
246 fmt.Println(err)
247 return
248 }
249
250 fmt.Println("Loading/Creating CloudFront Distribution")
251 dist, err := GetDistribution(options)
252
253 if err != nil {
254 fmt.Println("Error loading/creating CloudFront distribution")
255 fmt.Println(err)
256 return
257 }
258
259 fmt.Println("Adding Route")
260 err = UpdateRoute(options, dist)
261
262 if err != nil {
263 fmt.Println("Error adding route to Route53 DNS config")
264 fmt.Println(err)
265 return
266 }
267
268 key, err := CreateUser(options)
269
270 if err != nil {
271 fmt.Println("Error creating user")
272 fmt.Println(err)
273 return
274 }
275
276 fmt.Println("An access key has been created with just the permissions required to deploy / rollback this site")
277 fmt.Println("It is strongly recommended you use this limited account to deploy this project in the future\n")
278 fmt.Printf("ACCESS_KEY_ID=%s\n", key.Id)
279 fmt.Printf("ACCESS_KEY_SECRET=%s\n\n", key.Secret)
280
281 if terminal.IsTerminal(int(os.Stdin.Fd())) {
282 fmt.Println(`You can either add these credentials to the deploy.yaml file,
283or specify them as arguments to the stout deploy / stout rollback commands.
284You MUST NOT add them to the deploy.yaml file if this project is public
285(i.e. a public GitHub repo).
286
287If you can't add them to the deploy.yaml file, you can specify them as
288arguments on the command line. If you use a build system like CircleCI, you
289can add them as environment variables and pass those variables to the deploy
290commands (see the README).
291
292Your first deploy command might be:
293
294 stout deploy --bucket ` + options.Bucket + ` --key ` + key.Id + ` --secret '` + key.Secret + `'
295`)
296 }
297
298 fmt.Println("You can begin deploying now, but it can take up to ten minutes for your site to begin to work")
299 fmt.Println("Depending on the configuration of your site, you might need to set the 'root', 'dest' or 'files' options to get your deploys working as you wish. See the README for details.")
300 fmt.Println("It's also a good idea to look into the 'env' option, as in real-world situations it usually makes sense to have a development and/or staging site for each of your production sites.")
301}
302
303func createCmd() {
304 options, _ := parseOptions()
305 loadConfigFile(&options)
306
307 if options.Bucket == "" {
308 panic("You must specify a bucket")
309 }
310
311 if options.AWSKey == "" || options.AWSSecret == "" {
312 panic("You must specify your AWS credentials")
313 }
314
315 Create(options)
316}