Skip to content

Commit 144a096

Browse files
author
Cedric BAIL
committedJun 7, 2022
Add support for uploading to a S3 AWS bucket.
1 parent 6c71e18 commit 144a096

File tree

5 files changed

+335
-0
lines changed

5 files changed

+335
-0
lines changed
 
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package cloud
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"sync/atomic"
10+
11+
"github.com/aws/aws-sdk-go/aws"
12+
"github.com/aws/aws-sdk-go/aws/credentials"
13+
"github.com/aws/aws-sdk-go/aws/session"
14+
"github.com/aws/aws-sdk-go/service/s3/s3manager"
15+
)
16+
17+
type AWSSession struct {
18+
s *session.Session
19+
bucket string
20+
}
21+
22+
func Exists(path string) bool {
23+
_, err := os.Stat(path)
24+
if err != nil {
25+
return !errors.Is(err, os.ErrNotExist)
26+
}
27+
return true
28+
}
29+
30+
func NewAWSSessionFromEnvironment() (*AWSSession, error) {
31+
return NewAWSSession("", "", os.Getenv("AWS_S3_ENDPOINT"), os.Getenv("AWS_S3_REGION"), os.Getenv("AWS_S3_BUCKET"))
32+
}
33+
34+
func NewAWSSession(akid string, secret string, endpoint string, region string, bucket string) (*AWSSession, error) {
35+
var cred *credentials.Credentials
36+
37+
if akid != "" && secret != "" {
38+
cred = credentials.NewStaticCredentials(akid, secret, "")
39+
}
40+
41+
s, err := session.NewSession(
42+
&aws.Config{
43+
Endpoint: aws.String(endpoint),
44+
Region: aws.String(region),
45+
Credentials: cred,
46+
},
47+
)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
return &AWSSession{s: s, bucket: bucket}, nil
53+
}
54+
55+
func (a *AWSSession) GetCredentials() (credentials.Value, error) {
56+
return a.s.Config.Credentials.Get()
57+
}
58+
59+
func (a *AWSSession) UploadFile(localFile string, s3FilePath string) error {
60+
file, err := os.Open(localFile)
61+
if err != nil {
62+
return err
63+
}
64+
defer file.Close()
65+
66+
st, err := file.Stat()
67+
if err != nil {
68+
return err
69+
}
70+
71+
pa := &progressAWS{File: file, file: s3FilePath, contentLength: st.Size()}
72+
73+
uploader := s3manager.NewUploader(a.s)
74+
75+
_, err = uploader.UploadWithContext(context.Background(), &s3manager.UploadInput{
76+
Bucket: aws.String(a.bucket),
77+
Key: aws.String(s3FilePath),
78+
79+
Body: pa,
80+
})
81+
82+
return err
83+
}
84+
85+
func (a *AWSSession) GetBucket() string {
86+
return a.bucket
87+
}
88+
89+
type progressAWS struct {
90+
*os.File
91+
file string
92+
contentLength int64
93+
downloaded int64
94+
ticker int
95+
}
96+
97+
var _ io.Reader = (*progressAWS)(nil)
98+
var _ io.ReaderAt = (*progressAWS)(nil)
99+
var _ io.Seeker = (*progressAWS)(nil)
100+
101+
func (pa *progressAWS) Read(p []byte) (int, error) {
102+
return pa.File.Read(p)
103+
}
104+
105+
func (pa *progressAWS) ReadAt(p []byte, off int64) (int, error) {
106+
n, err := pa.File.ReadAt(p, off)
107+
if err != nil {
108+
return n, err
109+
}
110+
111+
atomic.AddInt64(&pa.downloaded, int64(n))
112+
113+
fmt.Printf("\r%v: %v%% %c", pa.file, 100*pa.downloaded/(pa.contentLength*2), pa.tick())
114+
115+
return n, err
116+
}
117+
118+
func (pa *progressAWS) Seek(offset int64, whence int) (int64, error) {
119+
return pa.File.Seek(offset, whence)
120+
}
121+
122+
func (pa *progressAWS) Size() int64 {
123+
return pa.contentLength
124+
}
125+
126+
var ticker = `|\-/`
127+
128+
func (pa *progressAWS) tick() rune {
129+
pa.ticker = (pa.ticker + 1) % len(ticker)
130+
return rune(ticker[pa.ticker])
131+
}

‎cmd/selfupdatectl/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func main() {
1717
sign(),
1818
check(),
1919
keyPrint(),
20+
upload(),
2021
},
2122
}
2223

‎cmd/selfupdatectl/upload.go

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"html/template"
7+
"log"
8+
"runtime"
9+
10+
"github.com/fynelabs/selfupdate/cmd/selfupdatectl/internal/cloud"
11+
"github.com/urfave/cli/v2"
12+
)
13+
14+
func upload() *cli.Command {
15+
var endpoint string
16+
var region string
17+
var bucket string
18+
var akid string
19+
var secret string
20+
var baseS3Path string
21+
var binaryPattern string
22+
23+
a := &application{}
24+
25+
return &cli.Command{
26+
Name: "upload",
27+
Usage: "Upload an executable which has been properly signed to S3",
28+
Description: "The executable specified will get its signature generated and checked before being uploaded to a S3 bucket than can be optionally specified.",
29+
Flags: []cli.Flag{
30+
&cli.StringFlag{
31+
Name: "private-key",
32+
Aliases: []string{"priv"},
33+
Usage: "The private key file to store the new key in.",
34+
Destination: &a.privateKey,
35+
Value: "ed25519.key",
36+
},
37+
&cli.StringFlag{
38+
Name: "public-key",
39+
Aliases: []string{"pub"},
40+
Usage: "The public key file to store the new key in.",
41+
Destination: &a.publicKey,
42+
Value: "ed25519.pem",
43+
},
44+
&cli.StringFlag{
45+
Name: "aws-endpoint",
46+
Aliases: []string{"e"},
47+
Usage: "AWS endpoint to connect to (can be used to connect to non AWS S3 services)",
48+
EnvVars: []string{"AWS_S3_ENDPOINT"},
49+
Destination: &endpoint,
50+
},
51+
&cli.StringFlag{
52+
Name: "aws-region",
53+
Aliases: []string{"r"},
54+
Usage: "AWS region to connect to",
55+
EnvVars: []string{"AWS_S3_REGION"},
56+
Destination: &region,
57+
},
58+
&cli.StringFlag{
59+
Name: "aws-bucket",
60+
Aliases: []string{"b"},
61+
Usage: "AWS bucket to store data into",
62+
EnvVars: []string{"AWS_S3_BUCKET"},
63+
Destination: &bucket,
64+
},
65+
&cli.StringFlag{
66+
Name: "aws-secret",
67+
Aliases: []string{"s"},
68+
Usage: "AWS secret to use to establish S3 connection",
69+
Destination: &secret,
70+
},
71+
&cli.StringFlag{
72+
Name: "aws-AKID",
73+
Aliases: []string{"a"},
74+
Usage: "AWS Access Key ID to use to establish S3 connection",
75+
Destination: &akid,
76+
},
77+
&cli.StringFlag{
78+
Name: "aws-base-s3-path",
79+
Aliases: []string{"path", "p"},
80+
Usage: "Specify the sub path in which the binary will be uploaded",
81+
Destination: &baseS3Path,
82+
},
83+
&cli.StringFlag{
84+
Name: "binary-template",
85+
Aliases: []string{"template", "t"},
86+
Usage: "Specify the pattern to use for the binary once uploaded",
87+
Destination: &binaryPattern,
88+
Value: "{{.Executable}}-{{.OS}}-{{.Arch}}{{.Ext}}",
89+
},
90+
},
91+
Action: func(ctx *cli.Context) error {
92+
if ctx.Args().Len() == 0 {
93+
return fmt.Errorf("at least one executable to upload")
94+
}
95+
96+
log.Println("Connecting to AWS")
97+
aws, err := cloud.NewAWSSession(akid, secret, endpoint, region, bucket)
98+
if err != nil {
99+
return err
100+
}
101+
102+
ext := ""
103+
if runtime.GOOS == "windows" {
104+
ext = ".exe"
105+
}
106+
107+
p := platform{
108+
OS: runtime.GOOS,
109+
Arch: runtime.GOARCH,
110+
Ext: ext,
111+
}
112+
113+
t, err := template.New("platform").Parse(binaryPattern)
114+
if err != nil {
115+
return err
116+
}
117+
118+
for _, exe := range ctx.Args().Slice() {
119+
if runtime.GOOS == "windows" {
120+
p.Executable = exe[:len(exe)-len(".exe")]
121+
} else {
122+
p.Executable = exe
123+
}
124+
125+
buf := &bytes.Buffer{}
126+
err = t.Execute(buf, p)
127+
if err != nil {
128+
return err
129+
}
130+
131+
err = a.upload(aws, exe, baseS3Path+"/"+buf.String())
132+
if err != nil {
133+
return err
134+
}
135+
}
136+
return nil
137+
},
138+
}
139+
}
140+
141+
type platform struct {
142+
OS string
143+
Arch string
144+
Ext string
145+
Executable string
146+
}
147+
148+
func (a *application) upload(aws *cloud.AWSSession, executable string, destination string) error {
149+
if a.check(executable) != nil {
150+
if err := a.sign(executable); err != nil {
151+
return err
152+
}
153+
if err := a.check(executable); err != nil {
154+
return err
155+
}
156+
}
157+
158+
err := aws.UploadFile(executable, destination)
159+
if err != nil {
160+
return err
161+
}
162+
fmt.Println()
163+
164+
defer fmt.Println()
165+
return aws.UploadFile(executable+".ed25519", destination+".ed25519")
166+
}

‎go.mod

+7
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@ module github.com/fynelabs/selfupdate
33
go 1.18
44

55
require (
6+
github.com/aws/aws-sdk-go v1.44.28
7+
github.com/icza/session v1.2.0
68
github.com/stretchr/testify v1.7.1
79
github.com/urfave/cli/v2 v2.8.1
810
)
911

1012
require (
1113
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
1214
github.com/davecgh/go-spew v1.1.0 // indirect
15+
github.com/golang/protobuf v1.3.1 // indirect
16+
github.com/icza/mighty v0.0.0-20210726202234-1719e2dcca1b // indirect
17+
github.com/jmespath/go-jmespath v0.4.0 // indirect
1318
github.com/pmezard/go-difflib v1.0.0 // indirect
1419
github.com/russross/blackfriday/v2 v2.1.0 // indirect
1520
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
21+
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
22+
google.golang.org/appengine v1.6.7 // indirect
1623
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
1724
)

‎go.sum

+30
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1+
github.com/aws/aws-sdk-go v1.44.28 h1:h/OAqEqY18wq//v6h4GNPMmCkxuzSDrWuGyrvSiRqf4=
2+
github.com/aws/aws-sdk-go v1.44.28/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
13
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
24
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
35
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
46
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
8+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
9+
github.com/icza/mighty v0.0.0-20210726202234-1719e2dcca1b h1:QlxXKayqve7+bZqMOOCe6Klqi838wGZtW6lZQvKl6vc=
10+
github.com/icza/mighty v0.0.0-20210726202234-1719e2dcca1b/go.mod h1:klfNufgs1IcVNz2fWjXufNHkhl2cqIUbFoia2580Iv4=
11+
github.com/icza/session v1.2.0 h1:4ncbGF7UN3Cq/GrZZq2tbnEJZAElIS6vy10Hkt21Lvw=
12+
github.com/icza/session v1.2.0/go.mod h1:YR0WpaAv86zKUYA/9ftt0jgzHB/faiGRPx7Dk9omoew=
13+
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
14+
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
15+
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
16+
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
17+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
518
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
619
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
720
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -13,7 +26,24 @@ github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
1326
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
1427
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
1528
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
29+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
30+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
31+
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
32+
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
33+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
34+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
37+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
38+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
39+
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
40+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
41+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
42+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
43+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1644
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1745
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
46+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
47+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
1848
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
1949
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)