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  }