github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/builtin/s3/s3_plugin_test.go (about)

     1  package s3
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/evergreen-ci/evergreen/agent/comm"
    12  	"github.com/evergreen-ci/evergreen/command"
    13  	"github.com/evergreen-ci/evergreen/db"
    14  	"github.com/evergreen-ci/evergreen/model"
    15  	"github.com/evergreen-ci/evergreen/model/artifact"
    16  	"github.com/evergreen-ci/evergreen/model/host"
    17  	"github.com/evergreen-ci/evergreen/model/task"
    18  	modelutil "github.com/evergreen-ci/evergreen/model/testutil"
    19  	"github.com/evergreen-ci/evergreen/plugin"
    20  	"github.com/evergreen-ci/evergreen/plugin/plugintest"
    21  	"github.com/evergreen-ci/evergreen/service"
    22  	"github.com/evergreen-ci/evergreen/testutil"
    23  	"github.com/evergreen-ci/evergreen/util"
    24  	. "github.com/smartystreets/goconvey/convey"
    25  	"github.com/smartystreets/goconvey/convey/reporting"
    26  )
    27  
    28  func init() {
    29  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(testutil.TestConfig()))
    30  	reporting.QuietMode()
    31  }
    32  
    33  func reset(t *testing.T) {
    34  	testutil.HandleTestingErr(db.ClearCollections(task.Collection, artifact.Collection, host.Collection),
    35  		t, "error clearing test collections")
    36  }
    37  
    38  func TestValidateS3BucketName(t *testing.T) {
    39  	Convey("When validating s3 bucket names", t, func() {
    40  
    41  		Convey("a bucket name that is too short should be rejected", func() {
    42  			So(validateS3BucketName("a"), ShouldNotBeNil)
    43  		})
    44  
    45  		Convey("a bucket name that is too long should be rejected", func() {
    46  			So(validateS3BucketName(strings.Repeat("a", 64)), ShouldNotBeNil)
    47  		})
    48  
    49  		Convey("a bucket name that does not start with a label should be rejected", func() {
    50  			So(validateS3BucketName(".xx"), ShouldNotBeNil)
    51  		})
    52  
    53  		Convey("a bucket name that does not end with a label should be rejected", func() {
    54  			So(validateS3BucketName("xx."), ShouldNotBeNil)
    55  		})
    56  
    57  		Convey("a bucket name with two consecutive periods should be rejected", func() {
    58  			So(validateS3BucketName("xx..xx"), ShouldNotBeNil)
    59  		})
    60  
    61  		Convey("a bucket name with invalid characters should be rejected", func() {
    62  			So(validateS3BucketName("a*a"), ShouldNotBeNil)
    63  			So(validateS3BucketName("a'a"), ShouldNotBeNil)
    64  			So(validateS3BucketName("a?a"), ShouldNotBeNil)
    65  		})
    66  
    67  		Convey("valid bucket names should be accepted", func() {
    68  			So(validateS3BucketName("aaa"), ShouldBeNil)
    69  			So(validateS3BucketName("aa.aa.aa"), ShouldBeNil)
    70  			So(validateS3BucketName("a12.a-a"), ShouldBeNil)
    71  		})
    72  	})
    73  
    74  }
    75  
    76  func TestS3PutAndGetSingleFile(t *testing.T) {
    77  	reset(t)
    78  
    79  	conf := testutil.TestConfig()
    80  	testutil.ConfigureIntegrationTest(t, conf, "TestS3PutAndGet")
    81  
    82  	Convey("When putting to and retrieving from an s3 bucket", t, func() {
    83  
    84  		var putCmd *S3PutCommand
    85  		var getCmd *S3GetCommand
    86  
    87  		testDataDir := "testdata"
    88  		remoteFile := "remote_mci_put_test.tgz"
    89  		bucket := "mci-test-uploads"
    90  		permissions := "private"
    91  		contentType := "application/x-tar"
    92  		displayName := "testfile"
    93  
    94  		// create the local directory to be tarred
    95  		localDirToTar := filepath.Join(testDataDir, "put_test")
    96  		localFileToTar := filepath.Join(localDirToTar, "put_test_file.txt")
    97  		testutil.HandleTestingErr(os.RemoveAll(localDirToTar), t, "Error removing"+
    98  			" directory")
    99  		testutil.HandleTestingErr(os.MkdirAll(localDirToTar, 0755), t,
   100  			"Error creating directory")
   101  		randStr := util.RandomString()
   102  		So(ioutil.WriteFile(localFileToTar, []byte(randStr), 0755), ShouldBeNil)
   103  
   104  		// tar it
   105  		tarCmd := &command.LocalCommand{
   106  			CmdString:        "tar czf put_test.tgz put_test",
   107  			WorkingDirectory: testDataDir,
   108  			Stdout:           ioutil.Discard,
   109  			Stderr:           ioutil.Discard,
   110  		}
   111  		testutil.HandleTestingErr(tarCmd.Run(), t, "Error tarring directories")
   112  		tarballSource := filepath.Join(testDataDir, "put_test.tgz")
   113  
   114  		// remove the untarred version
   115  		testutil.HandleTestingErr(os.RemoveAll(localDirToTar), t, "Error removing directories")
   116  
   117  		Convey("the file retrieved should be the exact same as the file put", func() {
   118  
   119  			// load params into the put command
   120  			putCmd = &S3PutCommand{}
   121  			putParams := map[string]interface{}{
   122  				"aws_key":      conf.Providers.AWS.Id,
   123  				"aws_secret":   conf.Providers.AWS.Secret,
   124  				"local_file":   tarballSource,
   125  				"remote_file":  remoteFile,
   126  				"bucket":       bucket,
   127  				"permissions":  permissions,
   128  				"content_type": contentType,
   129  				"display_name": displayName,
   130  			}
   131  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   132  			_, err := putCmd.Put()
   133  			So(err, ShouldBeNil)
   134  
   135  			// next, get the file, untarring it
   136  			getCmd = &S3GetCommand{}
   137  			getParams := map[string]interface{}{
   138  				"aws_key":      conf.Providers.AWS.Id,
   139  				"aws_secret":   conf.Providers.AWS.Secret,
   140  				"remote_file":  remoteFile,
   141  				"bucket":       bucket,
   142  				"extract_to":   testDataDir,
   143  				"display_name": displayName,
   144  			}
   145  			So(getCmd.ParseParams(getParams), ShouldBeNil)
   146  			So(getCmd.Get(), ShouldBeNil)
   147  			// read in the file that was pulled down
   148  			fileContents, err := ioutil.ReadFile(localFileToTar)
   149  			So(err, ShouldBeNil)
   150  			So(string(fileContents), ShouldEqual, randStr)
   151  
   152  			// now, get the tarball without untarring it
   153  			getCmd = &S3GetCommand{}
   154  			localDlTarget := filepath.Join(testDataDir, "put_test_dl.tgz")
   155  			getParams = map[string]interface{}{
   156  				"aws_key":      conf.Providers.AWS.Id,
   157  				"aws_secret":   conf.Providers.AWS.Secret,
   158  				"remote_file":  remoteFile,
   159  				"bucket":       bucket,
   160  				"local_file":   localDlTarget,
   161  				"display_name": displayName,
   162  			}
   163  			So(getCmd.ParseParams(getParams), ShouldBeNil)
   164  			So(getCmd.Get(), ShouldBeNil)
   165  			exists, err := util.FileExists(localDlTarget)
   166  			So(err, ShouldBeNil)
   167  			So(exists, ShouldBeTrue)
   168  
   169  		})
   170  
   171  		Convey("the put command should always run if there is no variants filter", func() {
   172  			// load params into the put command
   173  			putCmd = &S3PutCommand{}
   174  			putParams := map[string]interface{}{
   175  				"aws_key":      conf.Providers.AWS.Id,
   176  				"aws_secret":   conf.Providers.AWS.Secret,
   177  				"local_file":   tarballSource,
   178  				"remote_file":  remoteFile,
   179  				"bucket":       bucket,
   180  				"permissions":  permissions,
   181  				"content_type": contentType,
   182  				"display_name": displayName,
   183  			}
   184  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   185  			So(putCmd.shouldRunForVariant("linux-64"), ShouldBeTrue)
   186  		})
   187  
   188  		Convey("put cmd with variants filter should only run if variant is in list", func() {
   189  			// load params into the put command
   190  			putCmd = &S3PutCommand{}
   191  			putParams := map[string]interface{}{
   192  				"aws_key":        conf.Providers.AWS.Id,
   193  				"aws_secret":     conf.Providers.AWS.Secret,
   194  				"local_file":     tarballSource,
   195  				"remote_file":    remoteFile,
   196  				"bucket":         bucket,
   197  				"permissions":    permissions,
   198  				"content_type":   contentType,
   199  				"display_name":   displayName,
   200  				"build_variants": []string{"linux-64", "windows-64"},
   201  			}
   202  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   203  
   204  			So(putCmd.shouldRunForVariant("linux-64"), ShouldBeTrue)
   205  			So(putCmd.shouldRunForVariant("osx-108"), ShouldBeFalse)
   206  		})
   207  
   208  		Convey("put cmd with 'optional' and missing file should not throw an error", func() {
   209  			// load params into the put command
   210  			putCmd = &S3PutCommand{}
   211  			putParams := map[string]interface{}{
   212  				"aws_key":      conf.Providers.AWS.Id,
   213  				"aws_secret":   conf.Providers.AWS.Secret,
   214  				"optional":     true,
   215  				"local_file":   "this_file_does_not_exist.txt",
   216  				"remote_file":  "remote_file",
   217  				"bucket":       "test_bucket",
   218  				"permissions":  "private",
   219  				"content_type": "text/plain",
   220  			}
   221  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   222  			server, err := service.CreateTestServer(conf, nil, plugin.APIPlugins)
   223  			testutil.HandleTestingErr(err, t, "problem setting up server")
   224  			defer server.Close()
   225  
   226  			httpCom := plugintest.TestAgentCommunicator(&modelutil.TestModelData{}, server.URL)
   227  			pluginCom := &comm.TaskJSONCommunicator{"s3", httpCom}
   228  
   229  			So(err, ShouldBeNil)
   230  
   231  			err = putCmd.Execute(&plugintest.MockLogger{}, pluginCom,
   232  				&model.TaskConfig{nil, nil, nil, nil, nil, &model.BuildVariant{Name: "linux"}, &command.Expansions{}, "."}, make(chan bool))
   233  			So(err, ShouldBeNil)
   234  		})
   235  		Convey("put cmd without 'optional' and missing file should throw an error", func() {
   236  			// load params into the put command
   237  			putCmd = &S3PutCommand{}
   238  			putParams := map[string]interface{}{
   239  				"aws_key":      conf.Providers.AWS.Id,
   240  				"aws_secret":   conf.Providers.AWS.Secret,
   241  				"local_file":   "this_file_does_not_exist.txt",
   242  				"remote_file":  "remote_file",
   243  				"bucket":       "test_bucket",
   244  				"permissions":  "private",
   245  				"content_type": "text/plain",
   246  			}
   247  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   248  			server, err := service.CreateTestServer(conf, nil, plugin.APIPlugins)
   249  			testutil.HandleTestingErr(err, t, "problem setting up server")
   250  			defer server.Close()
   251  			httpCom := plugintest.TestAgentCommunicator(&modelutil.TestModelData{}, server.URL)
   252  			pluginCom := &comm.TaskJSONCommunicator{"s3", httpCom}
   253  
   254  			So(err, ShouldBeNil)
   255  
   256  			err = putCmd.Execute(&plugintest.MockLogger{}, pluginCom,
   257  				&model.TaskConfig{nil, nil, nil, nil, nil, &model.BuildVariant{Name: "linux"}, &command.Expansions{}, "."}, make(chan bool))
   258  			So(err, ShouldNotBeNil)
   259  		})
   260  	})
   261  }
   262  
   263  type multiPutFileInfo struct {
   264  	remoteName  string
   265  	localPath   string
   266  	displayName string
   267  	data        string
   268  }
   269  
   270  func TestS3PutAndGetMultiFile(t *testing.T) {
   271  	reset(t)
   272  
   273  	conf := testutil.TestConfig()
   274  	testutil.ConfigureIntegrationTest(t, conf, "TestS3PutAndGet")
   275  
   276  	Convey("When putting to and retrieving from an s3 bucket", t, func() {
   277  
   278  		testDataDir := "testdata"
   279  		bucket := "mci-test-uploads"
   280  		permissions := "private"
   281  		contentType := "application/x-tar"
   282  		remoteFilePrefix := "remote_mci_put_test-"
   283  		displayNamePrefix := "testfile-"
   284  
   285  		// create the local directory
   286  		localDir := filepath.Join(testDataDir, "put_test")
   287  		localFilePrefix := filepath.Join(localDir, "put_test_file")
   288  		defer func() {
   289  			testutil.HandleTestingErr(os.RemoveAll(testDataDir), t, "error removing test dir")
   290  		}()
   291  		testutil.HandleTestingErr(os.RemoveAll(localDir), t,
   292  			"Error removing directory")
   293  		testutil.HandleTestingErr(os.MkdirAll(localDir, 0755), t,
   294  			"Error creating directory")
   295  		fileInfos := []multiPutFileInfo{}
   296  		for i := 0; i < 5; i++ {
   297  			randStr := util.RandomString()
   298  			localPath := fmt.Sprintf("%s%d.txt", localFilePrefix, i)
   299  			localName := filepath.Base(localPath)
   300  			remoteName := fmt.Sprintf("%s%s", remoteFilePrefix, localName)
   301  			displayName := fmt.Sprintf("%s%s", displayNamePrefix, localName)
   302  			So(ioutil.WriteFile(localPath, []byte(randStr), 0755), ShouldBeNil)
   303  			fileInfo := multiPutFileInfo{
   304  				remoteName:  remoteName,
   305  				localPath:   localPath,
   306  				displayName: displayName,
   307  				data:        randStr,
   308  			}
   309  			fileInfos = append(fileInfos, fileInfo)
   310  		}
   311  
   312  		Convey("the files should be put without error", func() {
   313  			includesFilter := []string{fmt.Sprintf("%s/*.txt", localDir)}
   314  
   315  			// load params into the put command
   316  			putCmd := &S3PutCommand{}
   317  			putParams := map[string]interface{}{
   318  				"aws_key":                    conf.Providers.AWS.Id,
   319  				"aws_secret":                 conf.Providers.AWS.Secret,
   320  				"local_files_include_filter": includesFilter,
   321  				"remote_file":                remoteFilePrefix,
   322  				"bucket":                     bucket,
   323  				"permissions":                permissions,
   324  				"content_type":               contentType,
   325  				"display_name":               displayNamePrefix,
   326  			}
   327  			So(putCmd.ParseParams(putParams), ShouldBeNil)
   328  			_, err := putCmd.Put()
   329  			So(err, ShouldBeNil)
   330  			Convey("the files should each be gotten without error", func() {
   331  				for _, f := range fileInfos {
   332  					// next, get the file
   333  					getCmd := &S3GetCommand{}
   334  					getParams := map[string]interface{}{
   335  						"aws_key":      conf.Providers.AWS.Id,
   336  						"aws_secret":   conf.Providers.AWS.Secret,
   337  						"remote_file":  f.remoteName,
   338  						"bucket":       bucket,
   339  						"local_file":   f.localPath,
   340  						"display_name": f.displayName,
   341  					}
   342  					So(getCmd.ParseParams(getParams), ShouldBeNil)
   343  					So(getCmd.Get(), ShouldBeNil)
   344  
   345  					// read in the file that was pulled down
   346  					fileContents, err := ioutil.ReadFile(f.localPath)
   347  					So(err, ShouldBeNil)
   348  					So(string(fileContents), ShouldEqual, f.data)
   349  
   350  				}
   351  			})
   352  		})
   353  
   354  	})
   355  }
   356  
   357  func TestAttachResults(t *testing.T) {
   358  	remoteFname := "remote_file"
   359  	localFpath := "path/to/local_file"
   360  	taskId := "testTask"
   361  	displayName := "display_name"
   362  	testBucket := "testBucket"
   363  	Convey("When putting to an s3 bucket", t, func() {
   364  		reset(t)
   365  
   366  		testTask := &task.Task{
   367  			Id:     taskId,
   368  			Secret: "secret",
   369  		}
   370  		So(testTask.Insert(), ShouldBeNil)
   371  
   372  		testHost := &host.Host{
   373  			Id:          "hostId",
   374  			RunningTask: testTask.Id,
   375  			Secret:      "secret",
   376  		}
   377  		So(testHost.Insert(), ShouldBeNil)
   378  
   379  		conf := testutil.TestConfig()
   380  		testutil.ConfigureIntegrationTest(t, conf, "TestAttachResults")
   381  		server, err := service.CreateTestServer(conf, nil, plugin.APIPlugins)
   382  		testutil.HandleTestingErr(err, t, "problem setting up server")
   383  		defer server.Close()
   384  
   385  		httpCom := plugintest.TestAgentCommunicator(&modelutil.TestModelData{Task: testTask, Host: testHost}, server.URL)
   386  		pluginCom := &comm.TaskJSONCommunicator{"s3", httpCom}
   387  
   388  		s3pc := S3PutCommand{
   389  			LocalFile:   localFpath,
   390  			RemoteFile:  remoteFname,
   391  			DisplayName: displayName,
   392  			Visibility:  "visible",
   393  			Bucket:      testBucket,
   394  		}
   395  		Convey("and attach is multi", func() {
   396  			s3pc.LocalFilesIncludeFilter = []string{"one", "two"}
   397  			So(s3pc.AttachTaskFiles(&plugintest.MockLogger{}, pluginCom,
   398  				s3pc.LocalFile, s3pc.RemoteFile), ShouldBeNil)
   399  			Convey("files should each be added properly", func() {
   400  				entry, err := artifact.FindOne(artifact.ByTaskId(taskId))
   401  				So(err, ShouldBeNil)
   402  				So(entry, ShouldNotBeNil)
   403  				So(len(entry.Files), ShouldEqual, 1)
   404  				file := entry.Files[0]
   405  
   406  				So(file.Link, ShouldEqual, fmt.Sprintf("%s%s/%s%s", s3baseURL, testBucket, remoteFname, filepath.Base(localFpath)))
   407  				So(file.Name, ShouldEqual, fmt.Sprintf("%s %s", displayName, filepath.Base(localFpath)))
   408  			})
   409  		})
   410  		Convey("and attaching is singular", func() {
   411  			s3pc.LocalFilesIncludeFilter = []string{}
   412  			So(s3pc.AttachTaskFiles(&plugintest.MockLogger{}, pluginCom,
   413  				s3pc.LocalFile, s3pc.RemoteFile), ShouldBeNil)
   414  			Convey("file should be added properly", func() {
   415  				entry, err := artifact.FindOne(artifact.ByTaskId(taskId))
   416  				So(err, ShouldBeNil)
   417  				So(entry, ShouldNotBeNil)
   418  				So(len(entry.Files), ShouldEqual, 1)
   419  				file := entry.Files[0]
   420  
   421  				So(file.Link, ShouldEqual, fmt.Sprintf("%s%s/%s", s3baseURL, testBucket, remoteFname))
   422  				So(file.Name, ShouldEqual, fmt.Sprintf("%s", displayName))
   423  			})
   424  		})
   425  		Convey("and attach is called many times", func() {
   426  			filesList := make([][]string, 10)
   427  			for i := 0; i < 10; i++ {
   428  				filesList[i] = []string{fmt.Sprintf("remote%d-", i), fmt.Sprintf("local%d", i)}
   429  			}
   430  			s3pc.LocalFilesIncludeFilter = []string{"one", "two"}
   431  			for _, fileData := range filesList {
   432  				So(s3pc.AttachTaskFiles(&plugintest.MockLogger{}, pluginCom,
   433  					fileData[1], fileData[0]), ShouldBeNil)
   434  			}
   435  			Convey("files should each be added properly", func() {
   436  				entry, err := artifact.FindOne(artifact.ByTaskId(taskId))
   437  				So(err, ShouldBeNil)
   438  				So(len(entry.Files), ShouldEqual, 10)
   439  				for _, file := range entry.Files {
   440  					entryIndex := fetchFileIndex(file.Name, displayName, filesList)
   441  					So(entryIndex, ShouldNotEqual, -1)
   442  					So(file.Link, ShouldEqual, fmt.Sprintf("%s%s/%s%s", s3baseURL,
   443  						testBucket, filesList[entryIndex][0], filesList[entryIndex][1]))
   444  					So(file.Name, ShouldEqual, fmt.Sprintf("%s %s", displayName,
   445  						filesList[entryIndex][1]))
   446  					filesList = append(filesList[:entryIndex], filesList[entryIndex+1:]...)
   447  				}
   448  				So(len(filesList), ShouldEqual, 0)
   449  			})
   450  		})
   451  	})
   452  }
   453  
   454  func fetchFileIndex(fName, displayName string, filesList [][]string) int {
   455  	for index, fileData := range filesList {
   456  		fullDisplayName := fmt.Sprintf("%s %s", displayName, fileData[1])
   457  		if fName == fullDisplayName {
   458  			return index
   459  		}
   460  	}
   461  	return -1
   462  }