github.com/triarius/goreleaser@v1.12.5/internal/pipe/blob/blob_minio_test.go (about)

     1  package blob
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/triarius/goreleaser/internal/artifact"
    16  	"github.com/triarius/goreleaser/pkg/config"
    17  	"github.com/triarius/goreleaser/pkg/context"
    18  	"github.com/stretchr/testify/require"
    19  	"gocloud.dev/blob"
    20  )
    21  
    22  const (
    23  	minioUser     = "minio"
    24  	minioPwd      = "miniostorage"
    25  	containerName = "goreleaserTestMinio"
    26  )
    27  
    28  var listen string
    29  
    30  func TestMain(m *testing.M) {
    31  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    32  	if err != nil {
    33  		fmt.Println(err)
    34  		os.Exit(1)
    35  	}
    36  	listener.Close()
    37  	listen = listener.Addr().String()
    38  
    39  	cleanup, err := start(listen)
    40  	if err != nil {
    41  		fmt.Println(err)
    42  		os.Exit(1)
    43  	}
    44  	prepareEnv()
    45  
    46  	code := m.Run()
    47  	if err := cleanup(); err != nil {
    48  		fmt.Println(err)
    49  		os.Exit(1)
    50  	}
    51  	os.Exit(code)
    52  }
    53  
    54  func TestMinioUpload(t *testing.T) {
    55  	name := "basic"
    56  	folder := t.TempDir()
    57  	srcpath := filepath.Join(folder, "source.tar.gz")
    58  	tgzpath := filepath.Join(folder, "bin.tar.gz")
    59  	debpath := filepath.Join(folder, "bin.deb")
    60  	checkpath := filepath.Join(folder, "check.txt")
    61  	sigpath := filepath.Join(folder, "f.sig")
    62  	certpath := filepath.Join(folder, "f.pem")
    63  	require.NoError(t, os.WriteFile(checkpath, []byte("fake checksums"), 0o744))
    64  	require.NoError(t, os.WriteFile(srcpath, []byte("fake\nsrc"), 0o744))
    65  	require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
    66  	require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
    67  	require.NoError(t, os.WriteFile(sigpath, []byte("fake\nsig"), 0o744))
    68  	require.NoError(t, os.WriteFile(certpath, []byte("fake\ncert"), 0o744))
    69  	ctx := context.New(config.Project{
    70  		Dist:        folder,
    71  		ProjectName: "testupload",
    72  		Blobs: []config.Blob{
    73  			{
    74  				Provider: "s3",
    75  				Bucket:   name,
    76  				Region:   "us-east",
    77  				Endpoint: "http://" + listen,
    78  				IDs:      []string{"foo", "bar"},
    79  				ExtraFiles: []config.ExtraFile{
    80  					{
    81  						Glob: "./testdata/*.golden",
    82  					},
    83  				},
    84  			},
    85  		},
    86  	})
    87  	ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
    88  	ctx.Artifacts.Add(&artifact.Artifact{
    89  		Type: artifact.Checksum,
    90  		Name: "checksum.txt",
    91  		Path: checkpath,
    92  	})
    93  	ctx.Artifacts.Add(&artifact.Artifact{
    94  		Type: artifact.Signature,
    95  		Name: "checksum.txt.sig",
    96  		Path: sigpath,
    97  		Extra: map[string]interface{}{
    98  			artifact.ExtraID: "foo",
    99  		},
   100  	})
   101  	ctx.Artifacts.Add(&artifact.Artifact{
   102  		Type: artifact.Certificate,
   103  		Name: "checksum.pem",
   104  		Path: certpath,
   105  		Extra: map[string]interface{}{
   106  			artifact.ExtraID: "foo",
   107  		},
   108  	})
   109  	ctx.Artifacts.Add(&artifact.Artifact{
   110  		Type: artifact.UploadableSourceArchive,
   111  		Name: "source.tar.gz",
   112  		Path: srcpath,
   113  		Extra: map[string]interface{}{
   114  			artifact.ExtraFormat: "tar.gz",
   115  		},
   116  	})
   117  	ctx.Artifacts.Add(&artifact.Artifact{
   118  		Type: artifact.UploadableArchive,
   119  		Name: "bin.tar.gz",
   120  		Path: tgzpath,
   121  		Extra: map[string]interface{}{
   122  			artifact.ExtraID: "foo",
   123  		},
   124  	})
   125  	ctx.Artifacts.Add(&artifact.Artifact{
   126  		Type: artifact.LinuxPackage,
   127  		Name: "bin.deb",
   128  		Path: debpath,
   129  		Extra: map[string]interface{}{
   130  			artifact.ExtraID: "bar",
   131  		},
   132  	})
   133  
   134  	setupBucket(t, name)
   135  	require.NoError(t, Pipe{}.Default(ctx))
   136  	require.NoError(t, Pipe{}.Publish(ctx))
   137  
   138  	require.Subset(t, getFiles(t, ctx, ctx.Config.Blobs[0]), []string{
   139  		"testupload/v1.0.0/bin.deb",
   140  		"testupload/v1.0.0/bin.tar.gz",
   141  		"testupload/v1.0.0/checksum.txt",
   142  		"testupload/v1.0.0/checksum.txt.sig",
   143  		"testupload/v1.0.0/checksum.pem",
   144  		"testupload/v1.0.0/source.tar.gz",
   145  		"testupload/v1.0.0/file.golden",
   146  	})
   147  }
   148  
   149  func TestMinioUploadCustomBucketID(t *testing.T) {
   150  	name := "fromenv"
   151  	folder := t.TempDir()
   152  	tgzpath := filepath.Join(folder, "bin.tar.gz")
   153  	debpath := filepath.Join(folder, "bin.deb")
   154  	require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
   155  	require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
   156  	// Set custom BUCKET_ID env variable.
   157  	require.NoError(t, os.Setenv("BUCKET_ID", name))
   158  	ctx := context.New(config.Project{
   159  		Dist:        folder,
   160  		ProjectName: "testupload",
   161  		Blobs: []config.Blob{
   162  			{
   163  				Provider: "s3",
   164  				Bucket:   "{{.Env.BUCKET_ID}}",
   165  				Endpoint: "http://" + listen,
   166  			},
   167  		},
   168  	})
   169  	ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
   170  	ctx.Artifacts.Add(&artifact.Artifact{
   171  		Type: artifact.UploadableArchive,
   172  		Name: "bin.tar.gz",
   173  		Path: tgzpath,
   174  	})
   175  	ctx.Artifacts.Add(&artifact.Artifact{
   176  		Type: artifact.LinuxPackage,
   177  		Name: "bin.deb",
   178  		Path: debpath,
   179  	})
   180  
   181  	setupBucket(t, name)
   182  	require.NoError(t, Pipe{}.Default(ctx))
   183  	require.NoError(t, Pipe{}.Publish(ctx))
   184  }
   185  
   186  func TestMinioUploadRootFolder(t *testing.T) {
   187  	name := "rootdir"
   188  	folder := t.TempDir()
   189  	tgzpath := filepath.Join(folder, "bin.tar.gz")
   190  	debpath := filepath.Join(folder, "bin.deb")
   191  	require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
   192  	require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
   193  	ctx := context.New(config.Project{
   194  		Dist:        folder,
   195  		ProjectName: "testupload",
   196  		Blobs: []config.Blob{
   197  			{
   198  				Provider: "s3",
   199  				Bucket:   name,
   200  				Folder:   "/",
   201  				Endpoint: "http://" + listen,
   202  			},
   203  		},
   204  	})
   205  	ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
   206  	ctx.Artifacts.Add(&artifact.Artifact{
   207  		Type: artifact.UploadableArchive,
   208  		Name: "bin.tar.gz",
   209  		Path: tgzpath,
   210  	})
   211  	ctx.Artifacts.Add(&artifact.Artifact{
   212  		Type: artifact.LinuxPackage,
   213  		Name: "bin.deb",
   214  		Path: debpath,
   215  	})
   216  
   217  	setupBucket(t, name)
   218  	require.NoError(t, Pipe{}.Default(ctx))
   219  	require.NoError(t, Pipe{}.Publish(ctx))
   220  }
   221  
   222  func TestMinioUploadInvalidCustomBucketID(t *testing.T) {
   223  	folder := t.TempDir()
   224  	tgzpath := filepath.Join(folder, "bin.tar.gz")
   225  	debpath := filepath.Join(folder, "bin.deb")
   226  	require.NoError(t, os.WriteFile(tgzpath, []byte("fake\ntargz"), 0o744))
   227  	require.NoError(t, os.WriteFile(debpath, []byte("fake\ndeb"), 0o744))
   228  	ctx := context.New(config.Project{
   229  		Dist:        folder,
   230  		ProjectName: "testupload",
   231  		Blobs: []config.Blob{
   232  			{
   233  				Provider: "s3",
   234  				Bucket:   "{{.Bad}}",
   235  				Endpoint: "http://" + listen,
   236  			},
   237  		},
   238  	})
   239  	ctx.Git = context.GitInfo{CurrentTag: "v1.1.0"}
   240  	ctx.Artifacts.Add(&artifact.Artifact{
   241  		Type: artifact.UploadableArchive,
   242  		Name: "bin.tar.gz",
   243  		Path: tgzpath,
   244  	})
   245  	ctx.Artifacts.Add(&artifact.Artifact{
   246  		Type: artifact.LinuxPackage,
   247  		Name: "bin.deb",
   248  		Path: debpath,
   249  	})
   250  
   251  	require.NoError(t, Pipe{}.Default(ctx))
   252  	require.Error(t, Pipe{}.Publish(ctx))
   253  }
   254  
   255  func prepareEnv() {
   256  	os.Setenv("AWS_ACCESS_KEY_ID", minioUser)
   257  	os.Setenv("AWS_SECRET_ACCESS_KEY", minioPwd)
   258  	os.Setenv("AWS_REGION", "us-east-1")
   259  }
   260  
   261  func start(listen string) (func() error, error) {
   262  	data := filepath.Join(os.TempDir(), containerName)
   263  
   264  	fn := func() error {
   265  		if out, err := exec.Command("docker", "stop", containerName).CombinedOutput(); err != nil {
   266  			return fmt.Errorf("failed to stop minio: %s: %w", out, err)
   267  		}
   268  		if err := os.RemoveAll(data); err != nil {
   269  			log.Println("failed to remove", data)
   270  		}
   271  		return nil
   272  	}
   273  
   274  	// stop container if it is running (likely from previous test)
   275  	_, _ = exec.Command("docker", "stop", containerName).CombinedOutput()
   276  
   277  	if out, err := exec.Command(
   278  		"docker", "run", "-d", "--rm",
   279  		"-v", data+":/data",
   280  		"--name", containerName,
   281  		"-p", listen+":9000",
   282  		"-e", "MINIO_ROOT_USER="+minioUser,
   283  		"-e", "MINIO_ROOT_PASSWORD="+minioPwd,
   284  		"--health-interval", "1s",
   285  		"--health-cmd=curl --silent --fail http://localhost:9000/minio/health/ready || exit 1",
   286  		"minio/minio",
   287  		"server", "/data", "--console-address", ":9001",
   288  	).CombinedOutput(); err != nil {
   289  		return fn, fmt.Errorf("failed to start minio: %s: %w", out, err)
   290  	}
   291  
   292  	for range time.Tick(time.Second) {
   293  		out, err := exec.Command("docker", "inspect", "--format='{{json .State.Health}}'", containerName).CombinedOutput()
   294  		if err != nil {
   295  			return fn, fmt.Errorf("failed to check minio status: %s: %w", string(out), err)
   296  		}
   297  		if strings.Contains(string(out), `"Status":"healthy"`) {
   298  			log.Println("minio is healthy")
   299  			break
   300  		}
   301  		log.Println("waiting for minio to be healthy")
   302  	}
   303  
   304  	return fn, nil
   305  }
   306  
   307  func setupBucket(tb testing.TB, name string) {
   308  	tb.Helper()
   309  	mc(tb, "mc mb local/"+name)
   310  	tb.Cleanup(func() {
   311  		mc(tb, "mc rb --force local/"+name)
   312  	})
   313  }
   314  
   315  func mc(tb testing.TB, cmd string) {
   316  	tb.Helper()
   317  
   318  	if out, err := exec.Command(
   319  		"docker", "run", "--rm",
   320  		"--link", containerName,
   321  		"--entrypoint", "sh",
   322  		"minio/mc",
   323  		"-c", fmt.Sprintf(
   324  			"mc config host add local http://%s:9000 %s %s; %s",
   325  			containerName, minioUser, minioPwd, cmd,
   326  		),
   327  	).CombinedOutput(); err != nil {
   328  		tb.Fatalf("failed to create test bucket: %s", string(out))
   329  	}
   330  }
   331  
   332  func getFiles(t *testing.T, ctx *context.Context, cfg config.Blob) []string {
   333  	t.Helper()
   334  	url, err := urlFor(ctx, cfg)
   335  	require.NoError(t, err)
   336  	conn, err := blob.OpenBucket(ctx, url)
   337  	require.NoError(t, err)
   338  	defer conn.Close()
   339  	iter := conn.List(nil)
   340  	var files []string
   341  	for {
   342  		file, err := iter.Next(ctx)
   343  		if err != nil && err == io.EOF {
   344  			break
   345  		}
   346  		require.NoError(t, err)
   347  		files = append(files, file.Key)
   348  	}
   349  	return files
   350  }