github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/distgo/cmd/dist/dist_test.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dist_test
    16  
    17  import (
    18  	"archive/tar"
    19  	"compress/gzip"
    20  	"crypto/sha256"
    21  	"encoding/hex"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"regexp"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/nmiyake/pkg/dirs"
    33  	"github.com/palantir/pkg/matcher"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  
    37  	"github.com/palantir/godel/apps/distgo/cmd/build"
    38  	"github.com/palantir/godel/apps/distgo/cmd/dist"
    39  	"github.com/palantir/godel/apps/distgo/params"
    40  	"github.com/palantir/godel/apps/distgo/pkg/git"
    41  	"github.com/palantir/godel/apps/distgo/pkg/git/gittest"
    42  	"github.com/palantir/godel/apps/distgo/pkg/osarch"
    43  )
    44  
    45  const (
    46  	testMain = `package main
    47  
    48  import "fmt"
    49  
    50  var testVersionVar = "defaultVersion"
    51  
    52  func main() {
    53  	fmt.Println(testVersionVar)
    54  }
    55  `
    56  	expectManifest = `manifest-version: "1.0"
    57  product-group: com.test.group
    58  product-name: foo
    59  product-version: 0.1.0
    60  `
    61  	expectManifestWithOptionalFields = `manifest-version: "1.0"
    62  product-group: com.test.group
    63  product-name: foo
    64  product-version: 0.1.0
    65  product-type: service.v1
    66  extensions:
    67    bool-ext: true
    68    map-ext:
    69      hello: world
    70  `
    71  )
    72  
    73  func TestDist(t *testing.T) {
    74  	tmp, cleanup, err := dirs.TempDir("", "")
    75  	defer cleanup()
    76  	require.NoError(t, err)
    77  
    78  	for i, currCase := range []struct {
    79  		name            string
    80  		skip            func() bool
    81  		spec            func(projectDir string) params.ProductBuildSpecWithDeps
    82  		preDistAction   func(projectDir string, buildSpec params.ProductBuildSpec)
    83  		skipBuild       bool
    84  		wantErrorRegexp string
    85  		validate        func(caseNum int, name string, projectDir string)
    86  	}{
    87  		{
    88  			name: "builds product and creates distribution directory and tgz",
    89  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
    90  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
    91  					projectDir,
    92  					"foo",
    93  					git.ProjectInfo{
    94  						Version: "0.1.0",
    95  					},
    96  					params.Product{
    97  						Build: params.Build{
    98  							MainPkg: "./.",
    99  						},
   100  					},
   101  					params.Project{
   102  						GroupID: "com.test.group",
   103  					},
   104  				), nil)
   105  				require.NoError(t, err)
   106  				return specWithDeps
   107  			},
   108  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   109  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   110  			},
   111  			validate: func(caseNum int, name string, projectDir string) {
   112  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "deployment", "manifest.yml"))
   113  				require.NoError(t, err)
   114  				assert.Equal(t, expectManifest, string(bytes), "Case %d: %s", caseNum, name)
   115  
   116  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0.sls.tgz"))
   117  				require.NoError(t, err)
   118  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   119  
   120  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   121  				require.NoError(t, err)
   122  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   123  
   124  				info, err = os.Stat(path.Join(projectDir, "build", "0.1.0", osarch.Current().String(), "foo"))
   125  				require.NoError(t, err)
   126  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   127  
   128  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", osarch.Current().String(), "foo"))
   129  				require.NoError(t, err)
   130  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   131  			},
   132  		},
   133  		{
   134  			name: "SLS fails if GroupID is not specified",
   135  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   136  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   137  					projectDir,
   138  					"foo",
   139  					git.ProjectInfo{
   140  						Version: "0.1.0",
   141  					},
   142  					params.Product{
   143  						Build: params.Build{
   144  							MainPkg: "./.",
   145  						},
   146  					},
   147  					params.Project{},
   148  				), nil)
   149  				require.NoError(t, err)
   150  				return specWithDeps
   151  			},
   152  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   153  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   154  			},
   155  			wantErrorRegexp: "^failed to create manifest for SLS distribution: required properties were missing: group-id$",
   156  		},
   157  		{
   158  			name: "SLS fails if generated artifact does not conform to SLS specification (missing manifest.yml)",
   159  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   160  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   161  					projectDir,
   162  					"foo",
   163  					git.ProjectInfo{
   164  						Version: "0.1.0",
   165  					},
   166  					params.Product{
   167  						Build: params.Build{
   168  							MainPkg: "./.",
   169  						},
   170  						Dist: []params.Dist{{
   171  							Script: "rm $DIST_DIR/deployment/manifest.yml",
   172  						}},
   173  					},
   174  					params.Project{
   175  						GroupID: "com.test.group",
   176  					},
   177  				), nil)
   178  				require.NoError(t, err)
   179  				return specWithDeps
   180  			},
   181  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   182  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   183  			},
   184  			wantErrorRegexp: `(?s).+distribution directory failed SLS validation: foo-0.1.0/deployment/manifest.yml does not exist$`,
   185  		},
   186  		{
   187  			name: "SLS fails if configuration.yml contains invalid YML",
   188  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   189  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   190  					projectDir,
   191  					"foo",
   192  					git.ProjectInfo{
   193  						Version: "0.1.0",
   194  					},
   195  					params.Product{
   196  						Build: params.Build{
   197  							MainPkg: "./.",
   198  						},
   199  						Dist: []params.Dist{{
   200  							Script: `echo "{788=fads\n\tthis is invalid YML" > $DIST_DIR/deployment/configuration.yml`,
   201  						}},
   202  					},
   203  					params.Project{
   204  						GroupID: "com.test.group",
   205  					},
   206  				), nil)
   207  				require.NoError(t, err)
   208  				return specWithDeps
   209  			},
   210  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   211  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   212  			},
   213  			wantErrorRegexp: `(?s).+distribution directory failed SLS validation: invalid YML files: \[foo-0.1.0/deployment/configuration.yml\]
   214  If these files are known to be correct, exclude them from validation using the SLS YML validation exclude matcher.$`,
   215  		},
   216  		{
   217  			name: "SLS succeeds with invalid YML if it is excluded by matcher",
   218  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   219  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   220  					projectDir,
   221  					"foo",
   222  					git.ProjectInfo{
   223  						Version: "0.1.0",
   224  					},
   225  					params.Product{
   226  						Build: params.Build{
   227  							MainPkg: "./.",
   228  						},
   229  						Dist: []params.Dist{{
   230  							Script: `echo "{788=fads\n\tthis is invalid YML" > $DIST_DIR/deployment/configuration.yml`,
   231  							Info: &params.SLSDistInfo{
   232  								YMLValidationExclude: matcher.Path("deployment"),
   233  							},
   234  						}},
   235  					},
   236  					params.Project{
   237  						GroupID: "com.test.group",
   238  					},
   239  				), nil)
   240  				require.NoError(t, err)
   241  				return specWithDeps
   242  			},
   243  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   244  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   245  			},
   246  		},
   247  		{
   248  			name: "copies executable from build location if it already exists",
   249  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   250  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   251  					projectDir,
   252  					"foo",
   253  					git.ProjectInfo{
   254  						Version: "0.1.0",
   255  					},
   256  					params.Product{
   257  						Build: params.Build{
   258  							MainPkg: "./.",
   259  							OSArchs: []osarch.OSArch{
   260  								{
   261  									OS:   "fake",
   262  									Arch: "fake",
   263  								},
   264  							},
   265  						},
   266  					},
   267  					params.Project{
   268  						GroupID: "com.test.group",
   269  					},
   270  				), nil)
   271  				require.NoError(t, err)
   272  				return specWithDeps
   273  			},
   274  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   275  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   276  
   277  				// write fake executable
   278  				artifactPath, ok := build.ArtifactPaths(buildSpec)[osarch.OSArch{OS: "fake", Arch: "fake"}]
   279  				require.True(t, ok)
   280  
   281  				err := os.MkdirAll(path.Dir(artifactPath), 0755)
   282  				require.NoError(t, err)
   283  
   284  				err = ioutil.WriteFile(artifactPath, []byte("test-content"), 0755)
   285  				require.NoError(t, err)
   286  			},
   287  			validate: func(caseNum int, name string, projectDir string) {
   288  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "build", "0.1.0", "fake-fake", "foo"))
   289  				require.NoError(t, err)
   290  				assert.Equal(t, "test-content", string(bytes), "Case %d: %s", caseNum, name)
   291  			},
   292  		},
   293  		{
   294  			name: "re-builds executable if source files are newer than executable",
   295  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   296  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   297  					projectDir,
   298  					"foo",
   299  					git.ProjectInfo{
   300  						Version: "0.1.0",
   301  					},
   302  					params.Product{
   303  						Build: params.Build{
   304  							MainPkg: "./.",
   305  						},
   306  					},
   307  					params.Project{
   308  						GroupID: "com.test.group",
   309  					},
   310  				), nil)
   311  				require.NoError(t, err)
   312  				return specWithDeps
   313  			},
   314  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   315  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   316  
   317  				// write fake executable
   318  				artifactPath, ok := build.ArtifactPaths(buildSpec)[osarch.Current()]
   319  				require.True(t, ok)
   320  
   321  				err := os.MkdirAll(path.Dir(artifactPath), 0755)
   322  				require.NoError(t, err)
   323  				err = ioutil.WriteFile(artifactPath, []byte("test-content"), 0755)
   324  				require.NoError(t, err)
   325  
   326  				// write newer version of source file (sleep to ensure timestamp is later)
   327  				time.Sleep(time.Second)
   328  				err = ioutil.WriteFile(path.Join(projectDir, "main.go"), []byte(testMain+"\n"), 0644)
   329  				require.NoError(t, err)
   330  			},
   331  			validate: func(caseNum int, name string, projectDir string) {
   332  				// content should not be fake executable (build should be executed and overwrite content)
   333  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "build", "0.1.0", osarch.Current().String(), "foo"))
   334  				require.NoError(t, err)
   335  				assert.NotEqual(t, "test-content", string(bytes), "Case %d: %s", caseNum, name)
   336  			},
   337  		},
   338  		{
   339  			name: "copies layout from specified SLS input directory and ignores .gitkeep files",
   340  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   341  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   342  					projectDir,
   343  					"foo",
   344  					git.ProjectInfo{
   345  						Version: "0.1.0",
   346  					},
   347  					params.Product{
   348  						Build: params.Build{
   349  							MainPkg: "./.",
   350  						},
   351  						Dist: []params.Dist{{
   352  							InputDir: "sls",
   353  						}},
   354  						Publish: params.Publish{
   355  							GroupID: "com.test.group",
   356  						},
   357  					},
   358  					params.Project{},
   359  				), nil)
   360  				require.NoError(t, err)
   361  				return specWithDeps
   362  			},
   363  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   364  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   365  
   366  				// write manifest file that will be overwritten
   367  				err := os.MkdirAll(path.Join(projectDir, "sls", "deployment"), 0755)
   368  				require.NoError(t, err)
   369  				err = ioutil.WriteFile(path.Join(projectDir, "sls", "deployment", "manifest.yml"), []byte("test-content"), 0644)
   370  				require.NoError(t, err)
   371  
   372  				// write .gitkeep file that should be ignored in top-level directory
   373  				err = ioutil.WriteFile(path.Join(projectDir, "sls", ".gitkeep"), []byte(""), 0644)
   374  				require.NoError(t, err)
   375  
   376  				// write .gitkeep file that should be ignored in child directory
   377  				err = os.MkdirAll(path.Join(projectDir, "sls", "empty"), 0755)
   378  				require.NoError(t, err)
   379  				err = ioutil.WriteFile(path.Join(projectDir, "sls", "empty", ".gitkeep"), []byte(""), 0644)
   380  				require.NoError(t, err)
   381  
   382  				// write test file that will be copied
   383  				err = os.MkdirAll(path.Join(projectDir, "sls", "other"), 0755)
   384  				require.NoError(t, err)
   385  				err = ioutil.WriteFile(path.Join(projectDir, "sls", "other", "testfile"), []byte("test-content"), 0644)
   386  				require.NoError(t, err)
   387  			},
   388  			validate: func(caseNum int, name string, projectDir string) {
   389  				// manifest should be overwritten by dist
   390  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "deployment", "manifest.yml"))
   391  				require.NoError(t, err)
   392  				assert.Equal(t, expectManifest, string(bytes), "Case %d: %s", caseNum, name)
   393  
   394  				// top-level .gitkeep should not exist
   395  				fileInfo, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", ".gitkeep"))
   396  				assert.True(t, os.IsNotExist(err), "Case %d: %s", caseNum, name)
   397  
   398  				// empty directory should exist, but .gitkeep should not
   399  				fileInfo, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "empty"))
   400  				assert.NoError(t, err, "Case %d: %s", caseNum, name)
   401  				assert.True(t, fileInfo.IsDir(), "Case %d: %s", caseNum, name)
   402  				fileInfo, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "empty", ".gitkeep"))
   403  				assert.True(t, os.IsNotExist(err), "Case %d: %s", caseNum, name)
   404  
   405  				// test file should exist
   406  				bytes, err = ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "other", "testfile"))
   407  				require.NoError(t, err)
   408  				assert.Equal(t, "test-content", string(bytes), "Case %d: %s", caseNum, name)
   409  			},
   410  		},
   411  		{
   412  			name: "writes full SLS manifest with optional fields",
   413  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   414  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   415  					projectDir,
   416  					"foo",
   417  					git.ProjectInfo{
   418  						Version: "0.1.0",
   419  					},
   420  					params.Product{
   421  						Build: params.Build{
   422  							MainPkg: "./.",
   423  						},
   424  						Dist: []params.Dist{{
   425  							Info: &params.SLSDistInfo{
   426  								ProductType: "service.v1",
   427  								ManifestExtensions: map[string]interface{}{
   428  									"bool-ext": true,
   429  									"map-ext": map[string]string{
   430  										"hello": "world",
   431  									},
   432  								},
   433  							},
   434  						}},
   435  						Publish: params.Publish{
   436  							GroupID: "com.test.group",
   437  						},
   438  					},
   439  					params.Project{},
   440  				), nil)
   441  				require.NoError(t, err)
   442  				return specWithDeps
   443  			},
   444  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   445  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   446  			},
   447  			validate: func(caseNum int, name string, projectDir string) {
   448  				// manifest should be overwritten by dist
   449  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "deployment", "manifest.yml"))
   450  				require.NoError(t, err)
   451  				assert.Equal(t, expectManifestWithOptionalFields, string(bytes), "Case %d: %s", caseNum, name)
   452  			},
   453  		},
   454  		{
   455  			name: "copies Windows executables",
   456  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   457  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   458  					projectDir,
   459  					"foo",
   460  					git.ProjectInfo{
   461  						Version: "0.1.0",
   462  					},
   463  					params.Product{
   464  						Build: params.Build{
   465  							MainPkg: "./.",
   466  							OSArchs: []osarch.OSArch{
   467  								{
   468  									OS:   "windows",
   469  									Arch: "amd64",
   470  								},
   471  							},
   472  						},
   473  					},
   474  					params.Project{
   475  						GroupID: "com.test.group",
   476  					},
   477  				), nil)
   478  				require.NoError(t, err)
   479  				return specWithDeps
   480  			},
   481  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   482  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   483  			},
   484  			validate: func(caseNum int, name string, projectDir string) {
   485  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "windows-amd64", "foo.exe"))
   486  				require.NoError(t, err)
   487  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   488  			},
   489  		},
   490  		{
   491  			name: "runs custom dist script",
   492  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   493  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   494  					projectDir,
   495  					"foo",
   496  					git.ProjectInfo{
   497  						Version: "0.1.0",
   498  					},
   499  					params.Product{
   500  						Build: params.Build{
   501  							MainPkg: "./.",
   502  						},
   503  						Dist: []params.Dist{{
   504  							Script: "touch $DIST_DIR/test-file.txt",
   505  						}},
   506  					},
   507  					params.Project{
   508  						GroupID: "com.test.group",
   509  					},
   510  				), nil)
   511  				require.NoError(t, err)
   512  				return specWithDeps
   513  			},
   514  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   515  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   516  			},
   517  			validate: func(caseNum int, name string, projectDir string) {
   518  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "test-file.txt"))
   519  				require.NoError(t, err)
   520  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   521  			},
   522  		},
   523  		{
   524  			name: "supports creating TGZ files that contain long paths",
   525  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   526  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   527  					projectDir,
   528  					"foo",
   529  					git.ProjectInfo{
   530  						Version: "0.1.0",
   531  					},
   532  					params.Product{
   533  						Build: params.Build{
   534  							MainPkg: "./.",
   535  						},
   536  						Dist: []params.Dist{{
   537  							Script: `
   538  							mkdir -p $DIST_DIR/0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/
   539  							touch $DIST_DIR/0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt`,
   540  						}},
   541  					},
   542  					params.Project{
   543  						GroupID: "com.test.group",
   544  					},
   545  				), nil)
   546  				require.NoError(t, err)
   547  				return specWithDeps
   548  			},
   549  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   550  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   551  			},
   552  			validate: func(caseNum int, name string, projectDir string) {
   553  				dst, err := ioutil.TempDir(projectDir, "expandedTGZDir")
   554  				require.NoError(t, err)
   555  
   556  				cmd := exec.Command("tar", "-C", dst, "-xzvf", path.Join(projectDir, "dist", "foo-0.1.0.sls.tgz"))
   557  				output, err := cmd.CombinedOutput()
   558  				require.NoError(t, err, "Command %v failed: %v", cmd.Args, string(output))
   559  
   560  				// long file in tgz should be expanded properly
   561  				_, err = os.Stat(path.Join(dst, "foo-0.1.0", "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt"))
   562  				require.NoError(t, err, "Case %d: %s", caseNum, name)
   563  
   564  				// stray file should not exist
   565  				_, err = os.Stat(path.Join(dst, "file.txt"))
   566  				require.Error(t, err, fmt.Sprintf("Case %d: %s", caseNum, name))
   567  			},
   568  		},
   569  		{
   570  			name: "custom dist script inherits process environment variables",
   571  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   572  				err := os.Setenv("DIST_TEST_KEY", "distTestVal")
   573  				require.NoError(t, err)
   574  				err = os.Setenv("DIST_DIR", projectDir)
   575  				require.NoError(t, err)
   576  
   577  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   578  					projectDir,
   579  					"foo",
   580  					git.ProjectInfo{
   581  						Version: "0.1.0",
   582  					},
   583  					params.Product{
   584  						Build: params.Build{
   585  							MainPkg: "./.",
   586  						},
   587  						Dist: []params.Dist{{
   588  							Script: `touch $DIST_DIR/$DIST_TEST_KEY.txt
   589  							touch $DIST_DIR/product:$PRODUCT
   590  							touch $DIST_DIR/version:$VERSION
   591  							touch $DIST_DIR/snapshot:$IS_SNAPSHOT`,
   592  						}},
   593  					},
   594  					params.Project{
   595  						GroupID: "com.test.group",
   596  					},
   597  				), nil)
   598  				require.NoError(t, err)
   599  				return specWithDeps
   600  			},
   601  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   602  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   603  			},
   604  			validate: func(caseNum int, name string, projectDir string) {
   605  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "distTestVal.txt"))
   606  				require.NoError(t, err)
   607  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   608  
   609  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "product:foo"))
   610  				require.NoError(t, err)
   611  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   612  
   613  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "version:0.1.0"))
   614  				require.NoError(t, err)
   615  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   616  
   617  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "snapshot:0"))
   618  				require.NoError(t, err)
   619  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   620  
   621  				err = os.Unsetenv("DIST_TEST_KEY")
   622  				require.NoError(t, err)
   623  			},
   624  		},
   625  		{
   626  			name: "custom dist script inherits dist script include",
   627  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   628  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   629  					projectDir,
   630  					"foo",
   631  					git.ProjectInfo{
   632  						Version: "0.1.0",
   633  					},
   634  					params.Product{
   635  						Build: params.Build{
   636  							MainPkg: "./.",
   637  						},
   638  						Dist: []params.Dist{{
   639  							Script: `touch $DIST_DIR/$VERSION
   640  							helper_func`,
   641  						}},
   642  					},
   643  					params.Project{
   644  						DistScriptInclude: `touch $DIST_DIR/foo.txt
   645  						helper_func() {
   646  							touch $DIST_DIR/$IS_SNAPSHOT
   647  						}`,
   648  						GroupID: "com.test.group",
   649  					},
   650  				), nil)
   651  				require.NoError(t, err)
   652  				return specWithDeps
   653  			},
   654  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   655  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   656  			},
   657  			validate: func(caseNum int, name string, projectDir string) {
   658  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "foo.txt"))
   659  				require.NoError(t, err)
   660  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   661  
   662  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "0.1.0"))
   663  				require.NoError(t, err)
   664  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   665  
   666  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "0"))
   667  				require.NoError(t, err)
   668  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   669  			},
   670  		},
   671  		{
   672  			name: "custom dist script include does not run if script is not provided",
   673  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   674  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   675  					projectDir,
   676  					"foo",
   677  					git.ProjectInfo{
   678  						Version: "0.1.0",
   679  					},
   680  					params.Product{
   681  						Build: params.Build{
   682  							MainPkg: "./.",
   683  						},
   684  					},
   685  					params.Project{
   686  						DistScriptInclude: "touch $DIST_DIR/foo.txt",
   687  						GroupID:           "com.test.group",
   688  					},
   689  				), nil)
   690  				require.NoError(t, err)
   691  				return specWithDeps
   692  			},
   693  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   694  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   695  			},
   696  			validate: func(caseNum int, name string, projectDir string) {
   697  				_, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "foo.txt"))
   698  				assert.True(t, os.IsNotExist(err), "Case %d: %s", caseNum, name)
   699  			},
   700  		},
   701  		{
   702  			name: "copies dependent products",
   703  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   704  				osArchsMap := make(map[osarch.OSArch]bool)
   705  				osArchsMap[osarch.OSArch{
   706  					OS:   "darwin",
   707  					Arch: "amd64",
   708  				}] = true
   709  				osArchsMap[osarch.OSArch{
   710  					OS:   "linux",
   711  					Arch: "amd64",
   712  				}] = true
   713  				osArchsMap[osarch.Current()] = true
   714  
   715  				var osArchsSlice []osarch.OSArch
   716  				for osArch := range osArchsMap {
   717  					osArchsSlice = append(osArchsSlice, osArch)
   718  				}
   719  
   720  				barSpec := params.NewProductBuildSpec(
   721  					projectDir,
   722  					"bar",
   723  					git.ProjectInfo{
   724  						Version: "0.1.0",
   725  					},
   726  					params.Product{
   727  						Build: params.Build{
   728  							MainPkg: "./.",
   729  							OSArchs: osArchsSlice,
   730  						},
   731  					},
   732  					params.Project{
   733  						GroupID: "com.test.group",
   734  					},
   735  				)
   736  
   737  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   738  					projectDir,
   739  					"foo",
   740  					git.ProjectInfo{
   741  						Version: "0.1.0",
   742  					},
   743  					params.Product{
   744  						Build: params.Build{
   745  							MainPkg: "./.",
   746  						},
   747  						Dist: []params.Dist{{
   748  							InputProducts: []string{
   749  								"bar",
   750  							},
   751  						}},
   752  					},
   753  					params.Project{
   754  						GroupID: "com.test.group",
   755  					},
   756  				), map[string]params.ProductBuildSpec{
   757  					"bar": barSpec,
   758  				})
   759  				require.NoError(t, err)
   760  				return specWithDeps
   761  			},
   762  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   763  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   764  			},
   765  			validate: func(caseNum int, name string, projectDir string) {
   766  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", osarch.Current().String(), "foo"))
   767  				require.NoError(t, err)
   768  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   769  
   770  				info, err = os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", osarch.Current().String(), "bar"))
   771  				require.NoError(t, err)
   772  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
   773  			},
   774  		},
   775  		{
   776  			name: "uses custom manifest when provided",
   777  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   778  				manifestName := "test-manifest.yml"
   779  				err := ioutil.WriteFile(path.Join(projectDir, manifestName), []byte(`---
   780  manifestVersion: 1.0.0-alpha
   781  productGroup: {{.Publish.GroupID}}
   782  productName: {{.ProductName}}
   783  productVersion: {{.ProductVersion}}
   784  daemon: true
   785  `), 0644)
   786  				require.NoError(t, err)
   787  
   788  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   789  					projectDir,
   790  					"foo",
   791  					git.ProjectInfo{
   792  						Version: "0.1.0",
   793  					},
   794  					params.Product{
   795  						Build: params.Build{
   796  							MainPkg: "./.",
   797  						},
   798  						Dist: []params.Dist{{
   799  							Info: &params.SLSDistInfo{
   800  								ManifestTemplateFile: manifestName,
   801  							},
   802  						}},
   803  					},
   804  					params.Project{
   805  						GroupID: "com.test.group",
   806  					},
   807  				), nil)
   808  				require.NoError(t, err)
   809  				return specWithDeps
   810  			},
   811  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   812  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   813  			},
   814  			validate: func(caseNum int, name string, projectDir string) {
   815  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "deployment", "manifest.yml"))
   816  				require.NoError(t, err)
   817  				assert.Equal(t, `---
   818  manifestVersion: 1.0.0-alpha
   819  productGroup: com.test.group
   820  productName: foo
   821  productVersion: 0.1.0
   822  daemon: true
   823  `, string(bytes), "Case %d: %s", caseNum, name)
   824  			},
   825  		},
   826  		{
   827  			name: "uses custom init.sh when provided",
   828  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   829  				initShName := "test-init.sh"
   830  				err := ioutil.WriteFile(path.Join(projectDir, initShName), []byte(`init {{.ProductName}} {{.ProductVersion}} {{.Publish.GroupID}}`), 0644)
   831  				require.NoError(t, err)
   832  
   833  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   834  					projectDir,
   835  					"foo",
   836  					git.ProjectInfo{
   837  						Version: "0.1.0",
   838  					},
   839  					params.Product{
   840  						Build: params.Build{
   841  							MainPkg: "./.",
   842  						},
   843  						Dist: []params.Dist{{
   844  							Info: &params.SLSDistInfo{
   845  								InitShTemplateFile: initShName,
   846  							},
   847  						}},
   848  					},
   849  					params.Project{
   850  						GroupID: "com.test.group",
   851  					},
   852  				), nil)
   853  				require.NoError(t, err)
   854  				return specWithDeps
   855  			},
   856  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   857  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   858  			},
   859  			validate: func(caseNum int, name string, projectDir string) {
   860  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   861  				require.NoError(t, err)
   862  				assert.Equal(t, `init foo 0.1.0 com.test.group`, string(bytes), "Case %d: %s", caseNum, name)
   863  			},
   864  		},
   865  		{
   866  			name: "properly templatizes init.sh when ServiceArgs is empty",
   867  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   868  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   869  					projectDir,
   870  					"foo",
   871  					git.ProjectInfo{
   872  						Version: "0.1.0",
   873  					},
   874  					params.Product{
   875  						Build: params.Build{
   876  							MainPkg: "./.",
   877  						},
   878  						Dist: []params.Dist{{
   879  							Info: &params.SLSDistInfo{},
   880  						}},
   881  					},
   882  					params.Project{
   883  						GroupID: "com.test.group",
   884  					},
   885  				), nil)
   886  				require.NoError(t, err)
   887  				return specWithDeps
   888  			},
   889  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   890  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   891  			},
   892  			validate: func(caseNum int, name string, projectDir string) {
   893  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   894  				require.NoError(t, err)
   895  				assert.Regexp(t, `SERVICE_CMD="\$SERVICE_HOME/service/bin/\$OS_ARCH/\$SERVICE "\n`, string(bytes), "Case %d: %s", caseNum, name)
   896  			},
   897  		},
   898  		{
   899  			name: "properly templatizes init.sh with ServiceArgs",
   900  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   901  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   902  					projectDir,
   903  					"foo",
   904  					git.ProjectInfo{
   905  						Version: "0.1.0",
   906  					},
   907  					params.Product{
   908  						Build: params.Build{
   909  							MainPkg: "./.",
   910  						},
   911  						Dist: []params.Dist{{
   912  							Info: &params.SLSDistInfo{
   913  								ServiceArgs: "providedArgs arg2",
   914  							},
   915  						}},
   916  					},
   917  					params.Project{
   918  						GroupID: "com.test.group",
   919  					},
   920  				), nil)
   921  				require.NoError(t, err)
   922  				return specWithDeps
   923  			},
   924  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   925  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   926  			},
   927  			validate: func(caseNum int, name string, projectDir string) {
   928  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   929  				require.NoError(t, err)
   930  				assert.Regexp(t, `SERVICE_CMD="\$SERVICE_HOME/service/bin/\$OS_ARCH/\$SERVICE providedArgs arg2"\n`, string(bytes), "Case %d: %s", caseNum, name)
   931  			},
   932  		},
   933  		{
   934  			name: "properly templatizes init.sh with Reloadable: false",
   935  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   936  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   937  					projectDir,
   938  					"foo",
   939  					git.ProjectInfo{
   940  						Version: "0.1.0",
   941  					},
   942  					params.Product{
   943  						Build: params.Build{
   944  							MainPkg: "./.",
   945  						},
   946  						Dist: []params.Dist{{
   947  							Info: &params.SLSDistInfo{},
   948  						}},
   949  					},
   950  					params.Project{
   951  						GroupID: "com.test.group",
   952  					},
   953  				), nil)
   954  				require.NoError(t, err)
   955  				return specWithDeps
   956  			},
   957  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   958  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   959  			},
   960  			validate: func(caseNum int, name string, projectDir string) {
   961  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   962  				require.NoError(t, err)
   963  				assert.Regexp(t, `does not support reload`, string(bytes), "Case %d: %s", caseNum, name)
   964  			},
   965  		},
   966  		{
   967  			name: "properly templatizes init.sh with Reloadable: true",
   968  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
   969  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
   970  					projectDir,
   971  					"foo",
   972  					git.ProjectInfo{
   973  						Version: "0.1.0",
   974  					},
   975  					params.Product{
   976  						Build: params.Build{
   977  							MainPkg: "./.",
   978  						},
   979  						Dist: []params.Dist{{
   980  							Info: &params.SLSDistInfo{
   981  								Reloadable: true,
   982  							},
   983  						}},
   984  					},
   985  					params.Project{
   986  						GroupID: "com.test.group",
   987  					},
   988  				), nil)
   989  				require.NoError(t, err)
   990  				return specWithDeps
   991  			},
   992  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
   993  				gittest.CreateGitTag(t, projectDir, "0.1.0")
   994  			},
   995  			validate: func(caseNum int, name string, projectDir string) {
   996  				bytes, err := ioutil.ReadFile(path.Join(projectDir, "dist", "foo-0.1.0", "service", "bin", "init.sh"))
   997  				require.NoError(t, err)
   998  				assert.Regexp(t, `Reloading`, string(bytes), "Case %d: %s", caseNum, name)
   999  			},
  1000  		},
  1001  		{
  1002  			name: "creates outputs using bin mode",
  1003  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1004  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1005  					projectDir,
  1006  					"foo",
  1007  					git.ProjectInfo{
  1008  						Version: "0.1.0",
  1009  					},
  1010  					params.Product{
  1011  						Build: params.Build{
  1012  							MainPkg: "./.",
  1013  						},
  1014  						Dist: []params.Dist{{
  1015  							Info: &params.BinDistInfo{},
  1016  						}},
  1017  					},
  1018  					params.Project{},
  1019  				), nil)
  1020  				require.NoError(t, err)
  1021  				return specWithDeps
  1022  			},
  1023  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1024  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1025  			},
  1026  			validate: func(caseNum int, name string, projectDir string) {
  1027  				// bin directory exists in top-level directory
  1028  				fileInfo, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "bin"))
  1029  				require.NoError(t, err)
  1030  				assert.True(t, fileInfo.IsDir(), "Case %d: %s", caseNum, name)
  1031  
  1032  				// executable should exist in os-arch directory
  1033  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", "bin", osarch.Current().String(), "foo"))
  1034  				require.NoError(t, err)
  1035  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1036  			},
  1037  		},
  1038  		{
  1039  			name: "osarch dist produces archive that contains executable for current OS/Arch by default",
  1040  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1041  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1042  					projectDir,
  1043  					"foo",
  1044  					git.ProjectInfo{
  1045  						Version: "0.1.0",
  1046  					},
  1047  					params.Product{
  1048  						Build: params.Build{
  1049  							MainPkg: "./.",
  1050  						},
  1051  						Dist: []params.Dist{{
  1052  							Info: &params.OSArchsBinDistInfo{},
  1053  						}},
  1054  					},
  1055  					params.Project{},
  1056  				), nil)
  1057  				require.NoError(t, err)
  1058  				return specWithDeps
  1059  			},
  1060  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1061  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1062  			},
  1063  			validate: func(caseNum int, name string, projectDir string) {
  1064  				// executable should exist in dist directory
  1065  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", osarch.Current().String(), "foo"))
  1066  				require.NoError(t, err)
  1067  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1068  
  1069  				// tgz should contain executable
  1070  				tgzFiles, err := pathsInTGZ(path.Join(projectDir, "dist", fmt.Sprintf("foo-0.1.0-%v.tgz", osarch.Current())))
  1071  				require.NoError(t, err)
  1072  				assert.Equal(t, map[string]struct{}{"foo": {}}, tgzFiles)
  1073  			},
  1074  		},
  1075  		{
  1076  			name: "osarch dist produces archive that contains executable",
  1077  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1078  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1079  					projectDir,
  1080  					"foo",
  1081  					git.ProjectInfo{
  1082  						Version: "0.1.0",
  1083  					},
  1084  					params.Product{
  1085  						Build: params.Build{
  1086  							MainPkg: "./.",
  1087  						},
  1088  						Dist: []params.Dist{{
  1089  							Info: &params.OSArchsBinDistInfo{
  1090  								OSArchs: []osarch.OSArch{
  1091  									osarch.Current(),
  1092  								},
  1093  							},
  1094  						}},
  1095  					},
  1096  					params.Project{},
  1097  				), nil)
  1098  				require.NoError(t, err)
  1099  				return specWithDeps
  1100  			},
  1101  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1102  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1103  			},
  1104  			validate: func(caseNum int, name string, projectDir string) {
  1105  				// executable should exist in dist directory
  1106  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0", osarch.Current().String(), "foo"))
  1107  				require.NoError(t, err)
  1108  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1109  
  1110  				// tgz should contain executable
  1111  				tgzFiles, err := pathsInTGZ(path.Join(projectDir, "dist", fmt.Sprintf("foo-0.1.0-%v.tgz", osarch.Current())))
  1112  				require.NoError(t, err)
  1113  				assert.Equal(t, map[string]struct{}{"foo": {}}, tgzFiles)
  1114  			},
  1115  		},
  1116  		{
  1117  			name: "osarch dist produces archives that are different",
  1118  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1119  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1120  					projectDir,
  1121  					"foo",
  1122  					git.ProjectInfo{
  1123  						Version: "0.1.0",
  1124  					},
  1125  					params.Product{
  1126  						Build: params.Build{
  1127  							MainPkg: "./.",
  1128  							OSArchs: []osarch.OSArch{
  1129  								{
  1130  									OS:   "darwin",
  1131  									Arch: "amd64",
  1132  								},
  1133  								{
  1134  									OS:   "linux",
  1135  									Arch: "amd64",
  1136  								},
  1137  							},
  1138  						},
  1139  						Dist: []params.Dist{{
  1140  							Info: &params.OSArchsBinDistInfo{
  1141  								OSArchs: []osarch.OSArch{
  1142  									{
  1143  										OS:   "darwin",
  1144  										Arch: "amd64",
  1145  									},
  1146  									{
  1147  										OS:   "linux",
  1148  										Arch: "amd64",
  1149  									},
  1150  								},
  1151  							},
  1152  						}},
  1153  					},
  1154  					params.Project{},
  1155  				), nil)
  1156  				require.NoError(t, err)
  1157  				return specWithDeps
  1158  			},
  1159  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1160  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1161  			},
  1162  			validate: func(caseNum int, name string, projectDir string) {
  1163  				getHexChecksum := func(tgzPath string) string {
  1164  					bytes, err := ioutil.ReadFile(tgzPath)
  1165  					require.NoError(t, err)
  1166  					sha256Bytes := sha256.Sum256(bytes)
  1167  					return hex.EncodeToString(sha256Bytes[:])
  1168  				}
  1169  
  1170  				darwinChecksum := getHexChecksum(path.Join(projectDir, "dist", "foo-0.1.0-darwin-amd64.tgz"))
  1171  				linuxChecksum := getHexChecksum(path.Join(projectDir, "dist", "foo-0.1.0-linux-amd64.tgz"))
  1172  
  1173  				assert.NotEqual(t, darwinChecksum, linuxChecksum, "checksums should differ")
  1174  			},
  1175  		},
  1176  		{
  1177  			name: "osarch dist fails if OS/Arch specified by dist is not supported by build",
  1178  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1179  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1180  					projectDir,
  1181  					"foo",
  1182  					git.ProjectInfo{
  1183  						Version: "0.1.0",
  1184  					},
  1185  					params.Product{
  1186  						Build: params.Build{
  1187  							MainPkg: "./.",
  1188  							OSArchs: []osarch.OSArch{
  1189  								{
  1190  									OS:   "linux",
  1191  									Arch: "amd64",
  1192  								},
  1193  							},
  1194  						},
  1195  						Dist: []params.Dist{{
  1196  							Info: &params.OSArchsBinDistInfo{
  1197  								OSArchs: []osarch.OSArch{
  1198  									{
  1199  										OS:   "darwin",
  1200  										Arch: "amd64",
  1201  									},
  1202  								},
  1203  							},
  1204  						}},
  1205  					},
  1206  					params.Project{},
  1207  				), nil)
  1208  				require.NoError(t, err)
  1209  				return specWithDeps
  1210  			},
  1211  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1212  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1213  			},
  1214  			wantErrorRegexp: regexp.QuoteMeta(`The OS/Arch specified for the distribution of a product must be specified as a build target for the product, but product foo does not specify darwin-amd64 as one of its build targets. Current build targets: [linux-amd64]`),
  1215  		},
  1216  		{
  1217  			name: "manual dist produces output based on script",
  1218  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1219  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1220  					projectDir,
  1221  					"foo",
  1222  					git.ProjectInfo{
  1223  						Version: "0.1.0",
  1224  					},
  1225  					params.Product{
  1226  						Build: params.Build{
  1227  							Skip: true,
  1228  						},
  1229  						Dist: []params.Dist{{
  1230  							Script: `
  1231  echo "test-dist-contents" > "$DIST_DIR/$PRODUCT-$VERSION.tgz"
  1232  `,
  1233  							Info: &params.ManualDistInfo{
  1234  								Extension: "tgz",
  1235  							},
  1236  						}},
  1237  					},
  1238  					params.Project{},
  1239  				), nil)
  1240  				require.NoError(t, err)
  1241  				return specWithDeps
  1242  			},
  1243  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1244  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1245  			},
  1246  			validate: func(caseNum int, name string, projectDir string) {
  1247  				// output tgz should exist and contain test contents
  1248  				tgzFile := path.Join(projectDir, "dist", "foo-0.1.0", "foo-0.1.0.tgz")
  1249  				contents, err := ioutil.ReadFile(tgzFile)
  1250  				require.NoError(t, err)
  1251  				assert.Equal(t, "test-dist-contents\n", string(contents))
  1252  
  1253  				// dist output tgz should exist and contain test contents
  1254  				tgzFile = path.Join(projectDir, "dist", "foo-0.1.0.tgz")
  1255  				contents, err = ioutil.ReadFile(tgzFile)
  1256  				require.NoError(t, err)
  1257  				assert.Equal(t, "test-dist-contents\n", string(contents))
  1258  			},
  1259  		},
  1260  		{
  1261  			name: "builds rpm",
  1262  			skip: func() bool {
  1263  				// Run this case only if both fpm and rpmbuild are available
  1264  				_, fpmErr := exec.LookPath("fpm")
  1265  				_, rpmbuildErr := exec.LookPath("rpmbuild")
  1266  				return fpmErr != nil || rpmbuildErr != nil
  1267  			},
  1268  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1269  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1270  					projectDir,
  1271  					"foo",
  1272  					git.ProjectInfo{
  1273  						Version: "0.1.0",
  1274  					},
  1275  					params.Product{
  1276  						Build: params.Build{
  1277  							MainPkg: "./.",
  1278  							OSArchs: []osarch.OSArch{
  1279  								{
  1280  									OS:   "linux",
  1281  									Arch: "amd64",
  1282  								},
  1283  							},
  1284  						},
  1285  						Dist: []params.Dist{{
  1286  							InputDir: "root",
  1287  							Info: &params.RPMDistInfo{
  1288  								ConfigFiles: []string{"/usr/lib/systemd/system/orchestrator.service"},
  1289  								BeforeInstallScript: "" +
  1290  									"/usr/bin/getent group orchestrator || /usr/sbin/groupadd \\\n" +
  1291  									"    -g 380 orchestrator\n" +
  1292  									"/usr/bin/getent passwd orchestrator || /usr/sbin/useradd -r \\\n" +
  1293  									"    -d /var/lib/orchestrator -g orchestrator -u 380 -m \\\n" +
  1294  									"    -s /sbin/nologin orchestrator\n",
  1295  								AfterInstallScript: "systemctl daemon-reload\n",
  1296  								AfterRemoveScript:  "systemctl daemon-reload\n",
  1297  							},
  1298  						}},
  1299  					},
  1300  					params.Project{
  1301  						GroupID: "com.test.group",
  1302  					},
  1303  				), nil)
  1304  				require.NoError(t, err)
  1305  				return specWithDeps
  1306  			},
  1307  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1308  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1309  
  1310  				// write fake systemd config file
  1311  				err := os.MkdirAll(path.Join(projectDir, "root", "usr", "lib", "systemd", "system"), 0755)
  1312  				require.NoError(t, err)
  1313  
  1314  				err = ioutil.WriteFile(path.Join(projectDir, "root", "usr", "lib", "systemd", "system", "orchestrator.service"), []byte("configured"), 0600)
  1315  				require.NoError(t, err)
  1316  			},
  1317  			validate: func(caseNum int, name string, projectDir string) {
  1318  				info, err := os.Stat(path.Join(projectDir, "dist", "foo-0.1.0-1.x86_64.rpm"))
  1319  				require.NoError(t, err)
  1320  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1321  			},
  1322  		},
  1323  		{
  1324  			name: "more than one dist",
  1325  			spec: func(projectDir string) params.ProductBuildSpecWithDeps {
  1326  				specWithDeps, err := params.NewProductBuildSpecWithDeps(params.NewProductBuildSpec(
  1327  					projectDir,
  1328  					"foo",
  1329  					git.ProjectInfo{
  1330  						Version: "0.1.0",
  1331  					},
  1332  					params.Product{
  1333  						Build: params.Build{
  1334  							MainPkg: "./.",
  1335  							OSArchs: []osarch.OSArch{
  1336  								{
  1337  									OS:   "linux",
  1338  									Arch: "amd64",
  1339  								},
  1340  							},
  1341  						},
  1342  						Dist: []params.Dist{{
  1343  							Info:      &params.BinDistInfo{},
  1344  							OutputDir: "dist/bin",
  1345  							Script:    "touch $DIST_DIR/dist-1.txt",
  1346  						}, {
  1347  							Info:      &params.RPMDistInfo{},
  1348  							OutputDir: "dist/rpm",
  1349  							Script:    "touch $DIST_DIR/dist-2.txt",
  1350  						}},
  1351  					},
  1352  					params.Project{
  1353  						GroupID: "com.test.group",
  1354  					},
  1355  				), nil)
  1356  				require.NoError(t, err)
  1357  				return specWithDeps
  1358  			},
  1359  			preDistAction: func(projectDir string, buildSpec params.ProductBuildSpec) {
  1360  				gittest.CreateGitTag(t, projectDir, "0.1.0")
  1361  			},
  1362  			validate: func(caseNum int, name string, projectDir string) {
  1363  				info, err := os.Stat(path.Join(projectDir, "dist", "bin", "foo-0.1.0", "dist-1.txt"))
  1364  				require.NoError(t, err)
  1365  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1366  
  1367  				info, err = os.Stat(path.Join(projectDir, "dist", "rpm", "foo-0.1.0", "dist-2.txt"))
  1368  				require.NoError(t, err)
  1369  				assert.False(t, info.IsDir(), "Case %d: %s", caseNum, name)
  1370  			},
  1371  		},
  1372  	} {
  1373  		if currCase.skip != nil && currCase.skip() {
  1374  			fmt.Fprintln(os.Stderr, "SKIPPING CASE", i)
  1375  			continue
  1376  		}
  1377  
  1378  		currTmpDir, err := ioutil.TempDir(tmp, "")
  1379  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
  1380  
  1381  		gittest.InitGitDir(t, currTmpDir)
  1382  		err = ioutil.WriteFile(path.Join(currTmpDir, "main.go"), []byte(testMain), 0644)
  1383  		require.NoError(t, err, "Case %d: %s", i, currCase.name)
  1384  		gittest.CommitAllFiles(t, currTmpDir, "Commit")
  1385  
  1386  		if currCase.preDistAction != nil {
  1387  			currCase.preDistAction(currTmpDir, currCase.spec(currTmpDir).Spec)
  1388  		}
  1389  
  1390  		currSpecWithDeps := currCase.spec(currTmpDir)
  1391  		if !currCase.skipBuild {
  1392  			err = build.Run(build.RequiresBuild(currSpecWithDeps, nil).Specs(), nil, build.Context{}, ioutil.Discard)
  1393  			require.NoError(t, err, "Case %d: %s", i, currCase.name)
  1394  		}
  1395  
  1396  		err = dist.Run(currSpecWithDeps, ioutil.Discard)
  1397  		if currCase.wantErrorRegexp == "" {
  1398  			require.NoError(t, err, "Case %d: %s", i, currCase.name)
  1399  		} else {
  1400  			require.Error(t, err, fmt.Sprintf("Case %d: %s", i, currCase.name))
  1401  			assert.Regexp(t, regexp.MustCompile(currCase.wantErrorRegexp), err.Error(), "Case %d: %s", i, currCase.name)
  1402  		}
  1403  
  1404  		if currCase.validate != nil {
  1405  			currCase.validate(i, currCase.name, currTmpDir)
  1406  		}
  1407  	}
  1408  }
  1409  
  1410  func pathsInTGZ(tgzFile string) (rPaths map[string]struct{}, rErr error) {
  1411  	file, err := os.Open(tgzFile)
  1412  	if err != nil {
  1413  		return nil, err
  1414  	}
  1415  	defer func() {
  1416  		if err := file.Close(); err != nil && rErr == nil {
  1417  			rErr = err
  1418  		}
  1419  	}()
  1420  
  1421  	gzf, err := gzip.NewReader(file)
  1422  	if err != nil {
  1423  		return nil, err
  1424  	}
  1425  
  1426  	tarReader := tar.NewReader(gzf)
  1427  	dirs := make(map[string]struct{})
  1428  	for {
  1429  		header, err := tarReader.Next()
  1430  		if err == io.EOF {
  1431  			break
  1432  		} else if err != nil {
  1433  			return nil, err
  1434  		}
  1435  
  1436  		switch header.Typeflag {
  1437  		case tar.TypeReg:
  1438  			dirs[header.Name] = struct{}{}
  1439  		default:
  1440  		}
  1441  	}
  1442  	return dirs, nil
  1443  }