github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/getter/getter_test.go (about)

     1  package getter
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/hashicorp/nomad/client/taskenv"
    16  	"github.com/hashicorp/nomad/nomad/mock"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  // fakeReplacer is a noop version of taskenv.TaskEnv.ReplaceEnv
    22  type fakeReplacer struct{}
    23  
    24  func (fakeReplacer) ReplaceEnv(s string) string {
    25  	return s
    26  }
    27  
    28  var taskEnv = fakeReplacer{}
    29  
    30  func TestGetArtifact_FileAndChecksum(t *testing.T) {
    31  	// Create the test server hosting the file to download
    32  	ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/"))))
    33  	defer ts.Close()
    34  
    35  	// Create a temp directory to download into
    36  	taskDir, err := ioutil.TempDir("", "nomad-test")
    37  	if err != nil {
    38  		t.Fatalf("failed to make temp directory: %v", err)
    39  	}
    40  	defer os.RemoveAll(taskDir)
    41  
    42  	// Create the artifact
    43  	file := "test.sh"
    44  	artifact := &structs.TaskArtifact{
    45  		GetterSource: fmt.Sprintf("%s/%s", ts.URL, file),
    46  		GetterOptions: map[string]string{
    47  			"checksum": "md5:bce963762aa2dbfed13caf492a45fb72",
    48  		},
    49  	}
    50  
    51  	// Download the artifact
    52  	if err := GetArtifact(taskEnv, artifact, taskDir); err != nil {
    53  		t.Fatalf("GetArtifact failed: %v", err)
    54  	}
    55  
    56  	// Verify artifact exists
    57  	if _, err := os.Stat(filepath.Join(taskDir, file)); err != nil {
    58  		t.Fatalf("file not found: %s", err)
    59  	}
    60  }
    61  
    62  func TestGetArtifact_File_RelativeDest(t *testing.T) {
    63  	// Create the test server hosting the file to download
    64  	ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/"))))
    65  	defer ts.Close()
    66  
    67  	// Create a temp directory to download into
    68  	taskDir, err := ioutil.TempDir("", "nomad-test")
    69  	if err != nil {
    70  		t.Fatalf("failed to make temp directory: %v", err)
    71  	}
    72  	defer os.RemoveAll(taskDir)
    73  
    74  	// Create the artifact
    75  	file := "test.sh"
    76  	relative := "foo/"
    77  	artifact := &structs.TaskArtifact{
    78  		GetterSource: fmt.Sprintf("%s/%s", ts.URL, file),
    79  		GetterOptions: map[string]string{
    80  			"checksum": "md5:bce963762aa2dbfed13caf492a45fb72",
    81  		},
    82  		RelativeDest: relative,
    83  	}
    84  
    85  	// Download the artifact
    86  	if err := GetArtifact(taskEnv, artifact, taskDir); err != nil {
    87  		t.Fatalf("GetArtifact failed: %v", err)
    88  	}
    89  
    90  	// Verify artifact was downloaded to the correct path
    91  	if _, err := os.Stat(filepath.Join(taskDir, relative, file)); err != nil {
    92  		t.Fatalf("file not found: %s", err)
    93  	}
    94  }
    95  
    96  func TestGetGetterUrl_Interpolation(t *testing.T) {
    97  	// Create the artifact
    98  	artifact := &structs.TaskArtifact{
    99  		GetterSource: "${NOMAD_META_ARTIFACT}",
   100  	}
   101  
   102  	url := "foo.com"
   103  	alloc := mock.Alloc()
   104  	task := alloc.Job.TaskGroups[0].Tasks[0]
   105  	task.Meta = map[string]string{"artifact": url}
   106  	taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build()
   107  
   108  	act, err := getGetterUrl(taskEnv, artifact)
   109  	if err != nil {
   110  		t.Fatalf("getGetterUrl() failed: %v", err)
   111  	}
   112  
   113  	if act != url {
   114  		t.Fatalf("getGetterUrl() returned %q; want %q", act, url)
   115  	}
   116  }
   117  
   118  func TestGetArtifact_InvalidChecksum(t *testing.T) {
   119  	// Create the test server hosting the file to download
   120  	ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/"))))
   121  	defer ts.Close()
   122  
   123  	// Create a temp directory to download into
   124  	taskDir, err := ioutil.TempDir("", "nomad-test")
   125  	if err != nil {
   126  		t.Fatalf("failed to make temp directory: %v", err)
   127  	}
   128  	defer os.RemoveAll(taskDir)
   129  
   130  	// Create the artifact with an incorrect checksum
   131  	file := "test.sh"
   132  	artifact := &structs.TaskArtifact{
   133  		GetterSource: fmt.Sprintf("%s/%s", ts.URL, file),
   134  		GetterOptions: map[string]string{
   135  			"checksum": "md5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
   136  		},
   137  	}
   138  
   139  	// Download the artifact and expect an error
   140  	if err := GetArtifact(taskEnv, artifact, taskDir); err == nil {
   141  		t.Fatalf("GetArtifact should have failed")
   142  	}
   143  }
   144  
   145  func createContents(basedir string, fileContents map[string]string, t *testing.T) {
   146  	for relPath, content := range fileContents {
   147  		folder := basedir
   148  		if strings.Index(relPath, "/") != -1 {
   149  			// Create the folder.
   150  			folder = filepath.Join(basedir, filepath.Dir(relPath))
   151  			if err := os.Mkdir(folder, 0777); err != nil {
   152  				t.Fatalf("failed to make directory: %v", err)
   153  			}
   154  		}
   155  
   156  		// Create a file in the existing folder.
   157  		file := filepath.Join(folder, filepath.Base(relPath))
   158  		if err := ioutil.WriteFile(file, []byte(content), 0777); err != nil {
   159  			t.Fatalf("failed to write data to file %v: %v", file, err)
   160  		}
   161  	}
   162  }
   163  
   164  func checkContents(basedir string, fileContents map[string]string, t *testing.T) {
   165  	for relPath, content := range fileContents {
   166  		path := filepath.Join(basedir, relPath)
   167  		actual, err := ioutil.ReadFile(path)
   168  		if err != nil {
   169  			t.Fatalf("failed to read file %q: %v", path, err)
   170  		}
   171  
   172  		if !reflect.DeepEqual(actual, []byte(content)) {
   173  			t.Fatalf("%q: expected %q; got %q", path, content, string(actual))
   174  		}
   175  	}
   176  }
   177  
   178  func TestGetArtifact_Archive(t *testing.T) {
   179  	// Create the test server hosting the file to download
   180  	ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/"))))
   181  	defer ts.Close()
   182  
   183  	// Create a temp directory to download into and create some of the same
   184  	// files that exist in the artifact to ensure they are overridden
   185  	taskDir, err := ioutil.TempDir("", "nomad-test")
   186  	if err != nil {
   187  		t.Fatalf("failed to make temp directory: %v", err)
   188  	}
   189  	defer os.RemoveAll(taskDir)
   190  
   191  	create := map[string]string{
   192  		"exist/my.config": "to be replaced",
   193  		"untouched":       "existing top-level",
   194  	}
   195  	createContents(taskDir, create, t)
   196  
   197  	file := "archive.tar.gz"
   198  	artifact := &structs.TaskArtifact{
   199  		GetterSource: fmt.Sprintf("%s/%s", ts.URL, file),
   200  		GetterOptions: map[string]string{
   201  			"checksum": "sha1:20bab73c72c56490856f913cf594bad9a4d730f6",
   202  		},
   203  	}
   204  
   205  	if err := GetArtifact(taskEnv, artifact, taskDir); err != nil {
   206  		t.Fatalf("GetArtifact failed: %v", err)
   207  	}
   208  
   209  	// Verify the unarchiving overrode files properly.
   210  	expected := map[string]string{
   211  		"untouched":       "existing top-level",
   212  		"exist/my.config": "hello world\n",
   213  		"new/my.config":   "hello world\n",
   214  		"test.sh":         "sleep 1\n",
   215  	}
   216  	checkContents(taskDir, expected, t)
   217  }
   218  
   219  func TestGetArtifact_Setuid(t *testing.T) {
   220  	// Create the test server hosting the file to download
   221  	ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/"))))
   222  	defer ts.Close()
   223  
   224  	// Create a temp directory to download into and create some of the same
   225  	// files that exist in the artifact to ensure they are overridden
   226  	taskDir, err := ioutil.TempDir("", "nomad-test")
   227  	require.NoError(t, err)
   228  	defer os.RemoveAll(taskDir)
   229  
   230  	file := "setuid.tgz"
   231  	artifact := &structs.TaskArtifact{
   232  		GetterSource: fmt.Sprintf("%s/%s", ts.URL, file),
   233  		GetterOptions: map[string]string{
   234  			"checksum": "sha1:e892194748ecbad5d0f60c6c6b2db2bdaa384a90",
   235  		},
   236  	}
   237  
   238  	require.NoError(t, GetArtifact(taskEnv, artifact, taskDir))
   239  
   240  	var expected map[string]int
   241  
   242  	if runtime.GOOS == "windows" {
   243  		// windows doesn't support Chmod changing file permissions.
   244  		expected = map[string]int{
   245  			"public":  0666,
   246  			"private": 0666,
   247  			"setuid":  0666,
   248  		}
   249  	} else {
   250  		// Verify the unarchiving masked files properly.
   251  		expected = map[string]int{
   252  			"public":  0666,
   253  			"private": 0600,
   254  			"setuid":  0755,
   255  		}
   256  	}
   257  
   258  	for file, perm := range expected {
   259  		path := filepath.Join(taskDir, "setuid", file)
   260  		s, err := os.Stat(path)
   261  		require.NoError(t, err)
   262  		p := os.FileMode(perm)
   263  		o := s.Mode()
   264  		require.Equalf(t, p, o, "%s expected %o found %o", file, p, o)
   265  	}
   266  }
   267  
   268  func TestGetGetterUrl_Queries(t *testing.T) {
   269  	cases := []struct {
   270  		name     string
   271  		artifact *structs.TaskArtifact
   272  		output   string
   273  	}{
   274  		{
   275  			name: "adds query parameters",
   276  			artifact: &structs.TaskArtifact{
   277  				GetterSource: "https://foo.com?test=1",
   278  				GetterOptions: map[string]string{
   279  					"foo": "bar",
   280  					"bam": "boom",
   281  				},
   282  			},
   283  			output: "https://foo.com?bam=boom&foo=bar&test=1",
   284  		},
   285  		{
   286  			name: "git without http",
   287  			artifact: &structs.TaskArtifact{
   288  				GetterSource: "github.com/hashicorp/nomad",
   289  				GetterOptions: map[string]string{
   290  					"ref": "abcd1234",
   291  				},
   292  			},
   293  			output: "github.com/hashicorp/nomad?ref=abcd1234",
   294  		},
   295  		{
   296  			name: "git using ssh",
   297  			artifact: &structs.TaskArtifact{
   298  				GetterSource: "git@github.com:hashicorp/nomad?sshkey=1",
   299  				GetterOptions: map[string]string{
   300  					"ref": "abcd1234",
   301  				},
   302  			},
   303  			output: "git@github.com:hashicorp/nomad?ref=abcd1234&sshkey=1",
   304  		},
   305  		{
   306  			name: "s3 scheme 1",
   307  			artifact: &structs.TaskArtifact{
   308  				GetterSource: "s3::https://s3.amazonaws.com/bucket/foo",
   309  				GetterOptions: map[string]string{
   310  					"aws_access_key_id": "abcd1234",
   311  				},
   312  			},
   313  			output: "s3::https://s3.amazonaws.com/bucket/foo?aws_access_key_id=abcd1234",
   314  		},
   315  		{
   316  			name: "s3 scheme 2",
   317  			artifact: &structs.TaskArtifact{
   318  				GetterSource: "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo",
   319  				GetterOptions: map[string]string{
   320  					"aws_access_key_id": "abcd1234",
   321  				},
   322  			},
   323  			output: "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo?aws_access_key_id=abcd1234",
   324  		},
   325  		{
   326  			name: "s3 scheme 3",
   327  			artifact: &structs.TaskArtifact{
   328  				GetterSource: "bucket.s3.amazonaws.com/foo",
   329  				GetterOptions: map[string]string{
   330  					"aws_access_key_id": "abcd1234",
   331  				},
   332  			},
   333  			output: "bucket.s3.amazonaws.com/foo?aws_access_key_id=abcd1234",
   334  		},
   335  		{
   336  			name: "s3 scheme 4",
   337  			artifact: &structs.TaskArtifact{
   338  				GetterSource: "bucket.s3-eu-west-1.amazonaws.com/foo/bar",
   339  				GetterOptions: map[string]string{
   340  					"aws_access_key_id": "abcd1234",
   341  				},
   342  			},
   343  			output: "bucket.s3-eu-west-1.amazonaws.com/foo/bar?aws_access_key_id=abcd1234",
   344  		},
   345  		{
   346  			name: "gcs",
   347  			artifact: &structs.TaskArtifact{
   348  				GetterSource: "gcs::https://www.googleapis.com/storage/v1/b/d/f",
   349  			},
   350  			output: "gcs::https://www.googleapis.com/storage/v1/b/d/f",
   351  		},
   352  		{
   353  			name: "local file",
   354  			artifact: &structs.TaskArtifact{
   355  				GetterSource: "/foo/bar",
   356  			},
   357  			output: "/foo/bar",
   358  		},
   359  	}
   360  
   361  	for _, c := range cases {
   362  		t.Run(c.name, func(t *testing.T) {
   363  			act, err := getGetterUrl(taskEnv, c.artifact)
   364  			if err != nil {
   365  				t.Fatalf("want %q; got err %v", c.output, err)
   366  			} else if act != c.output {
   367  				t.Fatalf("want %q; got %q", c.output, act)
   368  			}
   369  		})
   370  	}
   371  }