github.com/masahide/goansible@v0.0.0-20160116054156-01eac649e9f2/net/s3/s3.go (about) 1 package s3 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "crypto/md5" 7 "encoding/base64" 8 "encoding/hex" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 14 "github.com/aws/aws-sdk-go/aws" 15 "github.com/aws/aws-sdk-go/aws/session" 16 "github.com/aws/aws-sdk-go/service/s3" 17 18 "github.com/masahide/goansible" 19 ) 20 21 type S3 struct { 22 Bucket string `goansible:"bucket,required"` 23 Region string `goansible:"region"` 24 PutFile string `goansible:"put_file"` 25 GetFile string `goansible:"get_file"` 26 Mkdir bool `goansible:"mkdir"` 27 At string `goansible:"at"` 28 Public bool `goansible:"public"` 29 ContentType string `goansible:"content_type"` 30 Writable bool `goansible:"writable"` 31 GZip bool `goansible:"gzip"` 32 } 33 34 type ioReaderCloser struct { 35 io.Reader 36 } 37 38 func (i ioReaderCloser) Close() error { 39 return nil //TODO: とりあえずダミークローザー 多分問題ないはず 40 } 41 42 func (s *S3) Run(env *goansible.CommandEnv) (*goansible.Result, error) { 43 return s.s3cp(env, "", "") 44 } 45 46 func (s *S3) s3cp(env *goansible.CommandEnv, a, b string) (*goansible.Result, error) { 47 if s.PutFile != "" { 48 s.PutFile = env.Paths.File(s.PutFile) 49 } 50 return s.S3CpFile(a, b) 51 } 52 func (s *S3) S3CpFile(a, b string) (*goansible.Result, error) { 53 if s.Region == "" { 54 s.Region = "ap-northeast-1" 55 } 56 S3 := s3.New(session.New(), &aws.Config{Region: aws.String(s.Region)}) 57 58 res := goansible.NewResult(true) 59 60 res.Add("bucket", s.Bucket) 61 res.Add("remote", s.At) 62 63 if s.PutFile != "" { 64 65 f, err := os.Open(s.PutFile) 66 if err != nil { 67 return nil, err 68 } 69 70 if f == nil { 71 return nil, fmt.Errorf("Unknown local file %s", s.PutFile) 72 } 73 74 defer f.Close() 75 76 var perm string 77 78 if s.Public { 79 if s.Writable { 80 perm = "public-read-write" 81 } else { 82 perm = "public-read" 83 } 84 } else { 85 perm = "private" 86 } 87 88 ct := s.ContentType 89 if ct == "" { 90 ct = "application/octet-stream" 91 } 92 93 var po *s3.PutObjectOutput 94 // var pr *s3.PutObjectInput 95 if s.GZip { 96 _, po, err = s.ZipUploadReaderD(S3, s.At, f, ct, perm) 97 } else { 98 _, po, err = s.UploadFileD(S3, s.At, f, ct, perm) 99 } 100 if err != nil { 101 return nil, err 102 } 103 104 //s.At: path ct:contentType, perm ,opts 105 106 md5 := (*po.ETag)[1 : len(*po.ETag)-1] 107 108 //pp.Print(pr) 109 //res.Add("wrote", *pr.ContentLength) 110 res.Add("local", s.PutFile) 111 res.Add("md5sum", md5) 112 113 } else if s.GetFile != "" { 114 _, err := os.Stat(s.GetFile) 115 if !os.IsNotExist(err) { 116 f, err := os.Open(s.GetFile) 117 if err != nil { 118 return nil, err 119 } 120 md5hex, _, _, err := Md5Sum(f) 121 f.Close() 122 if err != nil { 123 return nil, err 124 } 125 req := &s3.HeadObjectInput{ 126 Bucket: &s.Bucket, 127 Key: &s.At, 128 } 129 r, err := S3.HeadObject(req) 130 if err != nil { 131 return nil, err 132 } 133 if *r.ETag == `"`+md5hex+`"` { 134 res.Changed = false 135 res.Add("size", *r.ContentLength) 136 res.Add("md5sum", md5hex) 137 res.Add("local", s.GetFile) 138 return res, nil 139 } 140 141 } 142 if s.Mkdir { 143 if err := os.MkdirAll(filepath.Dir(s.GetFile), 0755); err != nil { 144 return nil, err 145 } 146 } 147 148 req := &s3.GetObjectInput{ 149 Bucket: &s.Bucket, 150 Key: &s.At, 151 } 152 resGetObj, err := S3.GetObject(req) 153 if err != nil { 154 return nil, err 155 } 156 defer resGetObj.Body.Close() 157 158 dist, err := os.OpenFile(s.GetFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 159 if err != nil { 160 return nil, err 161 } 162 defer dist.Close() 163 n, err := io.Copy(dist, resGetObj.Body) 164 if err != nil { 165 return nil, err 166 } 167 168 res.Add("read", n) 169 res.Add("md5sum", (*resGetObj.ETag)[1:len(*resGetObj.ETag)-1]) 170 res.Add("local", s.GetFile) 171 } else { 172 return nil, fmt.Errorf("Specify put_file or get_file") 173 } 174 175 return res, nil 176 } 177 178 func init() { 179 goansible.RegisterCommand("s3", &S3{}) 180 } 181 182 func (s *S3) UploadFileD(S3 *s3.S3, key string, file *os.File, contentType, perm string) (*s3.PutObjectInput, *s3.PutObjectOutput, error) { 183 184 md5hex, _, _, err := Md5Sum(file) 185 if err != nil { 186 return nil, nil, err 187 } 188 //s.Log.Debugf("key:%s md5=%s", key, md5hex) 189 req := &s3.PutObjectInput{ 190 ACL: &perm, 191 Body: file, 192 Bucket: &s.Bucket, 193 //ContentLength: &size, 194 ContentType: &contentType, 195 Key: &key, 196 } 197 res, err := S3.PutObject(req) 198 if err != nil { 199 return req, res, err 200 } 201 if res == nil { 202 return req, res, fmt.Errorf("res is nil pointer") 203 } 204 if res.ETag == nil { 205 return req, res, fmt.Errorf("res.ETag is nil pointer") 206 } 207 if len(*res.ETag) < 2 { 208 return req, res, fmt.Errorf("*res.ETag is too short. It should have 2 characters or more") 209 } 210 etag := (*res.ETag)[1 : len(*res.ETag)-1] 211 if md5hex != etag { 212 return req, res, fmt.Errorf("md5 and ETag does not match. md5:%s ETag:%s", md5hex, etag) 213 } 214 return req, res, err 215 } 216 217 func (s *S3) ZipUploadReaderD(S3 *s3.S3, key string, data io.Reader, contentType, perm string) (*s3.PutObjectInput, *s3.PutObjectOutput, error) { 218 219 b := &bytes.Buffer{} 220 gz := gzip.NewWriter(b) 221 _, err := io.Copy(gz, data) 222 if err != nil { 223 return nil, nil, err 224 } 225 gz.Close() 226 227 req := &s3.PutObjectInput{ 228 ACL: &perm, // aws.StringValue `xml:"-"` private | public-read | public-read-write | authenticated-read | bucket-owner-read | bucket-owner-full-control 229 Body: bytes.NewReader(b.Bytes()), // io.ReadSeeker `xml:"-"` 230 Bucket: &s.Bucket, // aws.StringValue `xml:"-"` 231 ContentType: &contentType, // aws.StringValue `xml:"-"` 232 Key: &key, // aws.StringValue `xml:"-"` 233 } 234 res, err := S3.PutObject(req) 235 return req, res, err 236 } 237 238 func Md5sumBase64(data []byte) string { 239 md5sum := md5.Sum(data) 240 return base64.StdEncoding.EncodeToString(md5sum[:]) 241 } 242 func Md5sumBase64File(f *os.File) (md5sum string, size int64, err error) { 243 var offset int64 244 offset, err = f.Seek(0, os.SEEK_CUR) 245 if err != nil { 246 return 247 } 248 defer f.Seek(offset, os.SEEK_SET) 249 h := md5.New() 250 size, err = io.Copy(h, f) 251 if err != nil { 252 return 253 } 254 md5sumRaw := h.Sum(nil) 255 md5sum = base64.StdEncoding.EncodeToString(md5sumRaw[:]) 256 return 257 } 258 func Md5Sum(r io.ReadSeeker) (md5hex string, md5b64 string, size int64, err error) { 259 var offset int64 260 offset, err = r.Seek(0, os.SEEK_CUR) 261 if err != nil { 262 return 263 } 264 defer r.Seek(offset, os.SEEK_SET) 265 digest := md5.New() 266 size, err = io.Copy(digest, r) 267 if err != nil { 268 return 269 } 270 sum := digest.Sum(nil) 271 md5hex = hex.EncodeToString(sum) 272 md5b64 = base64.StdEncoding.EncodeToString(sum) 273 return 274 }