get.porter.sh/porter@v1.3.0/magefile.go (about)

     1  //go:build mage
     2  
     3  // This is a magefile, and is a "makefile for go".
     4  // See https://magefile.org/
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/build"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"get.porter.sh/magefiles/ci"
    19  	"get.porter.sh/magefiles/git"
    20  	"get.porter.sh/magefiles/releases"
    21  	"get.porter.sh/magefiles/tools"
    22  	"get.porter.sh/porter/mage/setup"
    23  	"get.porter.sh/porter/pkg"
    24  	"get.porter.sh/porter/tests/tester"
    25  	"github.com/magefile/mage/mg"
    26  	"github.com/magefile/mage/sh"
    27  	mageci "github.com/uwu-tools/magex/ci"
    28  	"github.com/uwu-tools/magex/mgx"
    29  	magepkg "github.com/uwu-tools/magex/pkg"
    30  	"github.com/uwu-tools/magex/pkg/archive"
    31  	"github.com/uwu-tools/magex/pkg/downloads"
    32  	"github.com/uwu-tools/magex/shx"
    33  	"github.com/uwu-tools/magex/xplat"
    34  	"golang.org/x/sync/errgroup"
    35  
    36  	// mage:import
    37  	"get.porter.sh/magefiles/docker"
    38  
    39  	// mage:import
    40  	"get.porter.sh/magefiles/tests"
    41  
    42  	// mage:import
    43  	_ "get.porter.sh/porter/mage/docs"
    44  )
    45  
    46  // Default target to run when none is specified
    47  // If not set, running mage will list available targets
    48  // var Default = Build
    49  
    50  const (
    51  	PKG       = "get.porter.sh/porter"
    52  	GoVersion = ">=1.17"
    53  )
    54  
    55  var must = shx.CommandBuilder{StopOnError: true}
    56  
    57  // Check if we have the right version of Go
    58  func CheckGoVersion() {
    59  	tools.EnforceGoVersion(GoVersion)
    60  }
    61  
    62  func GenerateGRPCProtobufs() {
    63  	mg.Deps(setup.EnsureBufBuild)
    64  	must.Command("buf", "build").In("proto").RunV()
    65  	must.Command("buf", "generate").In("proto").RunV()
    66  }
    67  
    68  // Builds all code artifacts in the repository
    69  func Build() {
    70  	mg.SerialDeps(InstallBuildTools, BuildPorter, BuildExecMixin, BuildAgent, DocsGen)
    71  	mg.Deps(GetMixins)
    72  }
    73  
    74  func InstallBuildTools() {
    75  	mg.Deps(setup.EnsureProtobufTools, setup.EnsureGRPCurl, setup.EnsureBufBuild)
    76  }
    77  
    78  // Build the porter client and runtime
    79  func BuildPorter() {
    80  	mg.Deps(Tidy, copySchema)
    81  
    82  	mgx.Must(releases.BuildAll(PKG, "porter", "bin"))
    83  }
    84  
    85  // TODO: add support to decouple dir and command name to magefile repo
    86  // TODO: add support for additional ldflags to magefile repo
    87  // Build the porter client and runtime with gRPC server enabled
    88  func XBuildPorterGRPCServer() {
    89  	var g errgroup.Group
    90  	supportedClientGOOS := []string{"linux", "darwin", "windows"}
    91  	supportedClientGOARCH := []string{"amd64", "arm64"}
    92  	srcCmd := "porter"
    93  	srcPath := "./cmd/" + srcCmd
    94  	outPath := "bin/dev"
    95  	info := releases.LoadMetadata()
    96  	ldflags := fmt.Sprintf("-w -X main.includeGRPCServer=true -X %s/pkg.Version=%s -X %s/pkg.Commit=%s", PKG, info.Version, PKG, info.Commit)
    97  	os.MkdirAll(filepath.Dir(outPath), 0770)
    98  	for _, goos := range supportedClientGOOS {
    99  		goos := goos
   100  		for _, goarch := range supportedClientGOARCH {
   101  			goarch := goarch
   102  			g.Go(func() error {
   103  				cmdName := fmt.Sprintf("%s-api-server-%s-%s", srcCmd, goos, goarch)
   104  				if goos == "windows" {
   105  					cmdName = cmdName + ".exe"
   106  				}
   107  				out := filepath.Join(outPath, cmdName)
   108  				return shx.Command("go", "build", "-ldflags", ldflags, "-o", out, srcPath).
   109  					Env("CGO_ENABLED=0", "GO111MODULE=on", "GOOS="+goos, "GOARCH="+goarch).
   110  					RunV()
   111  			})
   112  		}
   113  	}
   114  	mgx.Must(g.Wait())
   115  }
   116  
   117  func copySchema() {
   118  	// Copy the porter manifest schema into our templates directory with the other schema
   119  	// We can't use symbolic links because that doesn't work on windows
   120  	mgx.Must(shx.Copy("pkg/schema/manifest.schema.json", "pkg/templates/templates/schema.json"))
   121  	mgx.Must(shx.Copy("pkg/schema/manifest.v1.1.0.schema.json", "pkg/templates/templates/v1.1.0.schema.json"))
   122  }
   123  
   124  func Tidy() error {
   125  	return shx.Run("go", "mod", "tidy")
   126  }
   127  
   128  // Build the exec mixin client and runtime
   129  func BuildExecMixin() {
   130  	mgx.Must(releases.BuildAll(PKG, "exec", "bin/mixins/exec"))
   131  }
   132  
   133  // Build the porter agent
   134  func BuildAgent() {
   135  	// the agent is only used embedded in a docker container, so we only build for linux
   136  	releases.XBuild(PKG, "agent", "bin", "linux", "amd64")
   137  }
   138  
   139  // Cross compile agent for multiple archs in linux
   140  func XBuildAgent() {
   141  	mg.Deps(BuildAgent)
   142  	releases.XBuild(PKG, "agent", "bin", "linux", "arm64")
   143  }
   144  
   145  // Cross-compile porter and the exec mixin
   146  func XBuildAll() {
   147  	mg.Deps(XBuildPorter, XBuildMixins, XBuildAgent)
   148  	mg.SerialDeps(XBuildPorterGRPCServer)
   149  }
   150  
   151  // Cross-compile porter
   152  func XBuildPorter() {
   153  	mg.Deps(copySchema)
   154  	releases.XBuildAll(PKG, "porter", "bin")
   155  	releases.PrepareMixinForPublish("porter")
   156  }
   157  
   158  // Cross-compile the exec mixin
   159  func XBuildMixins() {
   160  	releases.XBuildAll(PKG, "exec", "bin/mixins/exec")
   161  	releases.PrepareMixinForPublish("exec")
   162  }
   163  
   164  // Generate cli documentation for the website
   165  func DocsGen() {
   166  	// Remove the generated cli directory so that it can detect deleted files
   167  	os.RemoveAll("docs/content/docs/references/cli")
   168  	os.Mkdir("docs/content/docs/references/cli", pkg.FileModeDirectory)
   169  
   170  	must.RunV("go", "run", "--tags=docs", "./cmd/porter", "docs")
   171  }
   172  
   173  // Cleanup workspace after building or running tests.
   174  func Clean() {
   175  	mg.Deps(tests.DeleteTestCluster)
   176  	mgx.Must(os.RemoveAll("bin"))
   177  }
   178  
   179  // Ensure EnsureMage is installed and on the PATH.
   180  func EnsureMage() error {
   181  	return tools.EnsureMage()
   182  }
   183  
   184  func Debug() {
   185  	releases.LoadMetadata()
   186  }
   187  
   188  // ConfigureAgent sets up an Azure DevOps agent with EnsureMage and ensures
   189  // that GOPATH/bin is in PATH.
   190  func ConfigureAgent() error {
   191  	return ci.ConfigureAgent()
   192  }
   193  
   194  // Install mixins used by tests and example bundles, if not already installed
   195  func GetMixins() error {
   196  	defaultMixinVersion := os.Getenv("MIXIN_TAG")
   197  	if defaultMixinVersion == "" {
   198  		defaultMixinVersion = "canary"
   199  	}
   200  
   201  	mixins := []struct {
   202  		name    string
   203  		url     string
   204  		feed    string
   205  		version string
   206  	}{
   207  		{name: "docker"},
   208  		{name: "docker-compose"},
   209  		{name: "arm"},
   210  		{name: "terraform"},
   211  		{name: "kubernetes"},
   212  		{name: "helm3", feed: "https://mchorfa.github.io/porter-helm3/atom.xml", version: "v0.1.16"},
   213  	}
   214  	var errG errgroup.Group
   215  	for _, mixin := range mixins {
   216  		mixin := mixin
   217  		mixinDir := filepath.Join("bin/mixins/", mixin.name)
   218  		if _, err := os.Stat(mixinDir); err == nil {
   219  			log.Println("Mixin already installed into bin:", mixin.name)
   220  			continue
   221  		}
   222  
   223  		errG.Go(func() error {
   224  			log.Println("Installing mixin:", mixin.name)
   225  			if mixin.version == "" {
   226  				mixin.version = defaultMixinVersion
   227  			}
   228  			var source string
   229  			if mixin.feed != "" {
   230  				source = "--feed-url=" + mixin.feed
   231  			} else {
   232  				source = "--url=" + mixin.url
   233  			}
   234  			return porter("mixin", "install", mixin.name, "--version", mixin.version, source).Run()
   235  		})
   236  	}
   237  
   238  	return errG.Wait()
   239  }
   240  
   241  // Run a porter command from the bin
   242  func porter(args ...string) shx.PreparedCommand {
   243  	porterPath := filepath.Join("bin", "porter")
   244  	p := shx.Command(porterPath, args...)
   245  
   246  	porterHome, _ := filepath.Abs("bin")
   247  	p.Cmd.Env = []string{"PORTER_HOME=" + porterHome}
   248  
   249  	return p
   250  }
   251  
   252  // Update golden test files (unit tests only) to match the new test outputs and re-run the unit tests
   253  func UpdateTestfiles() {
   254  	os.Setenv("PORTER_UPDATE_TEST_FILES", "true")
   255  	defer os.Unsetenv("PORTER_UPDATE_TEST_FILES")
   256  
   257  	// Run tests and update any golden files
   258  	TestUnit()
   259  
   260  	// Re-run the tests with the golden files locked in to make sure everything passes now
   261  	os.Unsetenv("PORTER_UPDATE_TEST_FILES")
   262  	TestUnit()
   263  }
   264  
   265  // Run all tests known to human-kind
   266  func Test() {
   267  	mg.Deps(TestUnit, TestSmoke, TestIntegration)
   268  }
   269  
   270  // Run unit tests and verify integration tests compile
   271  func TestUnit() {
   272  	mg.Deps(copySchema)
   273  
   274  	// Only do verbose output of tests when called with `mage -v TestSmoke`
   275  	v := ""
   276  	if mg.Verbose() {
   277  		v = "-v"
   278  	}
   279  
   280  	must.Command("go", "test", v, "./...", "-coverprofile", "coverage-unit.out").CollapseArgs().RunV()
   281  
   282  	// Verify integration tests compile since we don't run them automatically on pull requests
   283  	must.Run("go", "test", "-run=non", "-tags=integration", "./...")
   284  
   285  	TestInitWarnings()
   286  }
   287  
   288  // Run smoke tests to quickly check if Porter is broken
   289  func TestSmoke() error {
   290  	mg.Deps(copySchema, TryRegisterLocalHostAlias, docker.RestartDockerRegistry, BuildTestMixin)
   291  
   292  	// Only do verbose output of tests when called with `mage -v TestSmoke`
   293  	v := ""
   294  	if mg.Verbose() {
   295  		v = "-v"
   296  	}
   297  
   298  	// Adding -count to prevent go from caching the test results.
   299  	return shx.Command("go", "test", "-count=1", "-timeout=20m", "-tags", "smoke", v, "./tests/smoke/...").CollapseArgs().RunV()
   300  }
   301  
   302  // Run grpc service tests
   303  func TestGRPCService() {
   304  	var run string
   305  	runTest := os.Getenv("PORTER_RUN_TEST")
   306  	if runTest != "" {
   307  		run = "-run=" + runTest
   308  	}
   309  
   310  	verbose := ""
   311  	if mg.Verbose() {
   312  		verbose = "-v"
   313  	}
   314  	must.Command("go", "test", verbose, "-timeout=5m", run, "./tests/grpc/...").CollapseArgs().RunV()
   315  }
   316  
   317  func getRegistry() string {
   318  	registry := os.Getenv("PORTER_REGISTRY")
   319  	if registry == "" {
   320  		registry = "localhost:5000"
   321  	}
   322  	return registry
   323  }
   324  
   325  func BuildImages() {
   326  	info := releases.LoadMetadata()
   327  	registry := getRegistry()
   328  
   329  	buildImages(registry, info)
   330  }
   331  
   332  func buildGRPCProtocImage() {
   333  	var g errgroup.Group
   334  
   335  	enableBuildKit := "DOCKER_BUILDKIT=1"
   336  	g.Go(func() error {
   337  		img := "protoc:local"
   338  		err := shx.Command("docker", "build", "-t", img, "-f", "build/protoc.Dockerfile", ".").
   339  			Env(enableBuildKit).RunV()
   340  		if err != nil {
   341  			return err
   342  		}
   343  		return nil
   344  	})
   345  	mgx.Must(g.Wait())
   346  }
   347  
   348  func buildImages(registry string, info releases.GitMetadata) {
   349  	var g errgroup.Group
   350  
   351  	enableBuildKit := "DOCKER_BUILDKIT=1"
   352  	g.Go(func() error {
   353  		img := fmt.Sprintf("%s/porter:%s", registry, info.Version)
   354  		err := shx.Command("docker", "build", "-t", img, "-f", "build/images/client/Dockerfile", ".").
   355  			Env(enableBuildKit).RunV()
   356  		if err != nil {
   357  			return err
   358  		}
   359  
   360  		err = shx.Run("docker", "tag", img, fmt.Sprintf("%s/porter:%s", registry, info.Permalink))
   361  		if err != nil {
   362  			return err
   363  		}
   364  
   365  		// porter-agent does a FROM porter so they can't go in parallel
   366  		img = fmt.Sprintf("%s/porter-agent:%s", registry, info.Version)
   367  		err = shx.Command("docker", "build", "-t", img, "--build-arg", "PORTER_VERSION="+info.Version, "--build-arg", "REGISTRY="+registry, "-f", "build/images/agent/Dockerfile", ".").
   368  			Env(enableBuildKit).RunV()
   369  		if err != nil {
   370  			return err
   371  		}
   372  
   373  		return shx.Run("docker", "tag", img, fmt.Sprintf("%s/porter-agent:%s", registry, info.Permalink))
   374  	})
   375  
   376  	mgx.Must(g.Wait())
   377  }
   378  
   379  func PublishImages() {
   380  	mg.Deps(BuildImages)
   381  
   382  	info := releases.LoadMetadata()
   383  
   384  	pushImagesTo(getRegistry(), info)
   385  }
   386  
   387  // Builds the porter-agent image and publishes it to a local test cluster with the Porter Operator.
   388  func LocalPorterAgentBuild() {
   389  	// Publish to the local registry/cluster setup by the Porter Operator.
   390  	os.Setenv("REGISTRY", "localhost:5000")
   391  	// Force the image to be pushed to the registry even though it's a local dev build.
   392  	os.Setenv("PORTER_FORCE_PUBLISH", "true")
   393  
   394  	mg.SerialDeps(XBuildPorter, BuildAgent, PublishImages)
   395  }
   396  
   397  // Only push tagged versions, canary and latest
   398  func pushImagesTo(registry string, info releases.GitMetadata) {
   399  	if info.IsTaggedRelease {
   400  		pushImages(registry, info.Version)
   401  	}
   402  
   403  	force, _ := strconv.ParseBool(os.Getenv("PORTER_FORCE_PUBLISH"))
   404  	if info.ShouldPublishPermalink() || force {
   405  		pushImages(registry, info.Permalink)
   406  	} else {
   407  		fmt.Println("Skipping image publish for permalink", info.Permalink)
   408  	}
   409  }
   410  
   411  func PublishServerMultiArchImages() {
   412  	registry := getRegistry()
   413  	info := releases.LoadMetadata()
   414  
   415  	if info.IsTaggedRelease {
   416  		buildAndPushServerMultiArch(registry, info.Version)
   417  	} else {
   418  		fmt.Println("Skipping server image publish for not tagged release", info.Version)
   419  	}
   420  
   421  	if info.ShouldPublishPermalink() {
   422  		buildAndPushServerMultiArch(registry, info.Permalink)
   423  	} else {
   424  		fmt.Println("Skipping server image publish for permalink", info.Permalink)
   425  	}
   426  }
   427  
   428  func buildAndPushServerMultiArch(registry string, tag string) {
   429  	img := fmt.Sprintf("%s/server:%s", registry, tag)
   430  	must.RunV("docker", "buildx", "create", "--use")
   431  	must.RunV("docker", "buildx", "bake", "-f", "docker-bake.json", "--push", "--set", "server.tags="+img, "server")
   432  }
   433  
   434  // Build a local image for the server based off of local architecture
   435  func BuildLocalServerImage() {
   436  	registry := getRegistry()
   437  	info := releases.LoadMetadata()
   438  	goarch := runtime.GOARCH
   439  	buildServerImage(registry, info, goarch)
   440  }
   441  
   442  // Builds an image for the server based off of the goarch
   443  func buildServerImage(registry string, info releases.GitMetadata, goarch string) {
   444  	var platform string
   445  	switch goarch {
   446  	case "arm64":
   447  		platform = "linux/arm64"
   448  	case "amd64":
   449  		platform = "linux/amd64"
   450  	default:
   451  		platform = "linux/amd64"
   452  	}
   453  	img := fmt.Sprintf("%s/server:%s", registry, info.Version)
   454  	must.RunV("docker", "build", "-f", "build/images/server/Dockerfile", "-t", img, "--platform="+platform, ".")
   455  }
   456  
   457  func pushImages(registry string, tag string) {
   458  	pushImage(fmt.Sprintf("%s/porter:%s", registry, tag))
   459  	pushImage(fmt.Sprintf("%s/porter-agent:%s", registry, tag))
   460  }
   461  
   462  func pushImage(img string) {
   463  	must.RunV("docker", "push", img)
   464  }
   465  
   466  // Publish the porter binaries and install scripts.
   467  func PublishPorter() {
   468  	mg.Deps(tools.EnsureGitHubClient, releases.ConfigureGitBot)
   469  
   470  	info := releases.LoadMetadata()
   471  
   472  	// Copy install scripts into version directory
   473  	// Rewrites the version number in the script uploaded to the github release
   474  	// If it's a tagged version, we reference that in the script
   475  	// Otherwise reference the name of the build, e.g. "canary"
   476  	scriptVersion := info.Version
   477  	if !info.IsTaggedRelease {
   478  		scriptVersion = info.Permalink
   479  	}
   480  	must.Command("./scripts/prep-install-scripts.sh").Env("VERSION=" + scriptVersion).RunV()
   481  
   482  	porterVersionDir := filepath.Join("bin", info.Version)
   483  	execVersionDir := filepath.Join("bin/mixins/exec", info.Version)
   484  	var repo = os.Getenv("PORTER_RELEASE_REPOSITORY")
   485  	if repo == "" {
   486  		repo = "github.com/getporter/porter"
   487  	}
   488  	remote := fmt.Sprintf("https://%s.git", repo)
   489  
   490  	// Create or update GitHub release for the permalink (canary/latest) with the version's assets (porter binaries, exec binaries and install scripts)
   491  	if info.ShouldPublishPermalink() {
   492  		// Move the permalink tag. The existing release automatically points to the tag.
   493  		must.RunV("git", "tag", "-f", info.Permalink, info.Version)
   494  		must.RunV("git", "push", "-f", remote, info.Permalink)
   495  
   496  		releases.AddFilesToRelease(repo, info.Permalink, porterVersionDir)
   497  		releases.AddFilesToRelease(repo, info.Permalink, execVersionDir)
   498  	} else {
   499  		fmt.Println("Skipping publish binaries for permalink", info.Permalink)
   500  	}
   501  
   502  	if info.IsTaggedRelease {
   503  		// Create GitHub release for the exact version (v1.2.3) and attach assets
   504  		releases.AddFilesToRelease(repo, info.Version, porterVersionDir)
   505  		releases.AddFilesToRelease(repo, info.Version, execVersionDir)
   506  	} else {
   507  		fmt.Println("Skipping publish binaries for not tagged release", info.Version)
   508  	}
   509  }
   510  
   511  // Publish internal porter mixins, like exec.
   512  func PublishMixins() {
   513  	releases.PublishMixinFeed("exec")
   514  }
   515  
   516  // Copy the cross-compiled binaries from xbuild into bin.
   517  func UseXBuildBinaries() error {
   518  	pwd, _ := os.Getwd()
   519  	goos := build.Default.GOOS
   520  	ext := ""
   521  	if runtime.GOOS == "windows" {
   522  		ext = ".exe"
   523  	}
   524  
   525  	copies := map[string]string{
   526  		"bin/dev/porter-$GOOS-amd64$EXT":           "bin/porter$EXT",
   527  		"bin/dev/porter-linux-amd64":               "bin/runtimes/porter-runtime",
   528  		"bin/mixins/exec/dev/exec-$GOOS-amd64$EXT": "bin/mixins/exec/exec$EXT",
   529  		"bin/mixins/exec/dev/exec-linux-amd64":     "bin/mixins/exec/runtimes/exec-runtime",
   530  	}
   531  
   532  	r := strings.NewReplacer("$GOOS", goos, "$EXT", ext, "$PWD", pwd)
   533  	for src, dest := range copies {
   534  		src = r.Replace(src)
   535  		dest = r.Replace(dest)
   536  		log.Printf("Copying %s to %s", src, dest)
   537  
   538  		destDir := filepath.Dir(dest)
   539  		os.MkdirAll(destDir, pkg.FileModeDirectory)
   540  
   541  		err := sh.Copy(dest, src)
   542  		if err != nil {
   543  			return err
   544  		}
   545  	}
   546  
   547  	return SetBinExecutable()
   548  }
   549  
   550  // Run `chmod +x -R bin`.
   551  func SetBinExecutable() error {
   552  	err := chmodRecursive("bin", pkg.FileModeExecutable)
   553  	if err != nil {
   554  		return fmt.Errorf("could not set +x on the test bin: %w", err)
   555  	}
   556  
   557  	return nil
   558  }
   559  
   560  func chmodRecursive(name string, mode os.FileMode) error {
   561  	return filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
   562  		if err != nil {
   563  			return err
   564  		}
   565  
   566  		log.Println("chmod +x ", path)
   567  		return os.Chmod(path, mode)
   568  	})
   569  }
   570  
   571  // Run integration tests (slow).
   572  func TestIntegration() {
   573  	mg.Deps(tests.EnsureTestCluster, copySchema, TryRegisterLocalHostAlias, BuildTestMixin, BuildTestPlugin, EnsureCosign, EnsureNotation)
   574  
   575  	var run string
   576  	runTest := os.Getenv("PORTER_RUN_TEST")
   577  	if runTest != "" {
   578  		run = "-run=" + runTest
   579  	}
   580  
   581  	verbose := ""
   582  	if mg.Verbose() {
   583  		verbose = "-v"
   584  	}
   585  
   586  	var path string
   587  	filename := os.Getenv("PORTER_INTEG_FILE")
   588  	if filename == "" {
   589  		path = "./..."
   590  	} else {
   591  		path = "./tests/integration/" + filename
   592  	}
   593  
   594  	must.Command("go", "test", verbose, "-timeout=30m", run, "-tags=integration", path, "-coverprofile", "coverage-integration.out").CollapseArgs().RunV()
   595  }
   596  
   597  func TestInitWarnings() {
   598  	// This is hard to test in a normal unit test because we need to build porter with custom build tags,
   599  	// so I'm testing it in the magefile directly in a way that doesn't leave around an unsafe Porter binary.
   600  
   601  	// Verify that running Porter with traceSensitiveAttributes set that a warning is printed
   602  	fmt.Println("Validating traceSensitiveAttributes warning")
   603  	output := &bytes.Buffer{}
   604  	must.Command("go", "run", "-tags=traceSensitiveAttributes", "./cmd/porter", "schema").
   605  		Stderr(output).Stdout(output).Exec()
   606  	if !strings.Contains(output.String(), "WARNING! This is a custom developer build of Porter with the traceSensitiveAttributes build flag set") {
   607  		fmt.Printf("Got output: %s\n", output.String())
   608  		panic("Expected a build of Porter with traceSensitiveAttributes build tag set to warn at startup but it didn't")
   609  	}
   610  }
   611  
   612  // TryRegisterLocalHostAlias edits /etc/hosts to use porter-test-registry hostname alias
   613  // This is not safe to call more than once and is intended for use on the CI server only
   614  func TryRegisterLocalHostAlias() {
   615  	if _, isCI := mageci.DetectBuildProvider(); !isCI {
   616  		return
   617  	}
   618  
   619  	err := shx.RunV("sudo", "bash", "-c", "echo 127.0.0.1 porter-test-registry >> /etc/hosts")
   620  	if err != nil {
   621  		fmt.Println("skipping registering the porter-test-registry hostname alias: could not write to /etc/hosts")
   622  		return
   623  	}
   624  
   625  	fmt.Println("Added host alias porter-test-registry to /etc/hosts")
   626  	os.Setenv(tester.TestRegistryAlias, "porter-test-registry")
   627  }
   628  
   629  func BuildTestPlugin() {
   630  	must.RunV("go", "build", "-o", "bin/testplugin", "./cmd/testplugin")
   631  }
   632  
   633  func BuildTestMixin() {
   634  	os.MkdirAll("bin/mixins/testmixin", 0770)
   635  	must.RunV("go", "build", "-o", "bin/mixins/testmixin/testmixin"+xplat.FileExt(), "./cmd/testmixin")
   636  }
   637  
   638  // Copy the locally built porter and exec binaries to PORTER_HOME
   639  func Install() {
   640  	porterHome := getPorterHome()
   641  	fmt.Println("installing Porter from bin to", porterHome)
   642  
   643  	// Copy porter binaries
   644  	mgx.Must(os.MkdirAll(porterHome, pkg.FileModeDirectory))
   645  
   646  	// HACK: Works around a codesigning problem on Apple Silicon where overwriting a binary that has already been executed doesn't cause the corresponding codesign entry in the OS cache to update
   647  	// Mac then prevents the updated binary from running because the signature doesn't match
   648  	// Removing the file first clears the cache so that we don't run into "zsh: killed porter..."
   649  	// See https://stackoverflow.com/questions/67378106/mac-m1-cping-binary-over-another-results-in-crash
   650  	// See https://openradar.appspot.com/FB8914231
   651  	removeError := os.Remove(filepath.Join(porterHome, "porter"+xplat.FileExt()))
   652  	if !os.IsNotExist(removeError) {
   653  		mgx.Must(removeError)
   654  	}
   655  	removeError = os.RemoveAll(filepath.Join(porterHome, "runtimes"))
   656  	if !os.IsNotExist(removeError) {
   657  		mgx.Must(removeError)
   658  	}
   659  
   660  	// Okay now it's safe to copy these files over
   661  	mgx.Must(shx.Copy(filepath.Join("bin", "porter"+xplat.FileExt()), porterHome))
   662  	mgx.Must(shx.Copy(filepath.Join("bin", "runtimes"), porterHome, shx.CopyRecursive))
   663  
   664  	// Copy mixin binaries
   665  	mixinsDir := filepath.Join("bin", "mixins")
   666  	mixinsDirItems, err := os.ReadDir(mixinsDir)
   667  	if err != nil {
   668  		mgx.Must(fmt.Errorf("could not list mixins in bin: %w", err))
   669  	}
   670  
   671  	for _, fi := range mixinsDirItems {
   672  		// do not install the test mixins
   673  		if fi.Name() == "testmixin" {
   674  			continue
   675  		}
   676  
   677  		if !fi.IsDir() {
   678  			continue
   679  		}
   680  
   681  		mixin := fi.Name()
   682  		srcDir := filepath.Join(mixinsDir, mixin)
   683  		destDir := filepath.Join(porterHome, "mixins", mixin)
   684  		mgx.Must(os.MkdirAll(destDir, pkg.FileModeDirectory))
   685  
   686  		// HACK: Works around a codesigning problem on Apple Silicon where overwriting a binary that has already been executed doesn't cause the corresponding codesign entry in the OS cache to update
   687  		// Mac then prevents the updated binary from running because the signature doesn't match
   688  		// Removing the file first clears the cache so that we don't run into "zsh: killed MIXIN..."
   689  		// See https://stackoverflow.com/questions/67378106/mac-m1-cping-binary-over-another-results-in-crash
   690  		// See https://openradar.appspot.com/FB8914231
   691  
   692  		// Copy the mixin client binary
   693  		mgx.Must(shx.Copy(filepath.Join(srcDir, mixin+xplat.FileExt()), destDir))
   694  
   695  		// Copy the mixin runtimes
   696  		mgx.Must(shx.Copy(filepath.Join(srcDir, "runtimes"), destDir, shx.CopyRecursive))
   697  	}
   698  }
   699  
   700  // Run Go Vet on the project
   701  func Vet() {
   702  	must.RunV("go", "vet", "./...")
   703  }
   704  
   705  // Run golangci-lint on the project
   706  func Lint() {
   707  	mg.Deps(tools.EnsureGolangCILint)
   708  	must.RunV("golangci-lint", "run", "--max-issues-per-linter", "0", "--max-same-issues", "0", "./...")
   709  }
   710  
   711  func getPorterHome() string {
   712  	porterHome := os.Getenv("PORTER_HOME")
   713  	if porterHome == "" {
   714  		home, err := os.UserHomeDir()
   715  		if err != nil {
   716  			mgx.Must(fmt.Errorf("could not determine home directory: %w", err))
   717  		}
   718  
   719  		porterHome = filepath.Join(home, ".porter")
   720  	}
   721  	return porterHome
   722  }
   723  
   724  // SetupDCO configures your git repository to automatically sign your commits
   725  // to comply with our DCO
   726  func SetupDCO() error {
   727  	return git.SetupDCO()
   728  }
   729  
   730  func EnsureCosign() {
   731  	if ok, _ := magepkg.IsCommandAvailable("cosign", "version", "v2.2.2"); ok {
   732  		return
   733  	}
   734  
   735  	opts := downloads.DownloadOptions{
   736  		UrlTemplate: "https://github.com/sigstore/cosign/releases/download/v{{.VERSION}}/cosign-{{.GOOS}}-{{.GOARCH}}{{.EXT}}",
   737  		Name:        "cosign",
   738  		Version:     "2.2.2",
   739  	}
   740  
   741  	if runtime.GOOS == "windows" {
   742  		opts.Ext = ".exe"
   743  	}
   744  
   745  	err := downloads.DownloadToGopathBin(opts)
   746  	mgx.Must(err)
   747  }
   748  
   749  func EnsureNotation() {
   750  	if ok, _ := magepkg.IsCommandAvailable("notation", "version", "1.1.0"); ok {
   751  		return
   752  	}
   753  
   754  	target := "notation{{.EXT}}"
   755  	if runtime.GOOS == "windows" {
   756  		target = "notation.exe"
   757  	}
   758  
   759  	opts := archive.DownloadArchiveOptions{
   760  		DownloadOptions: downloads.DownloadOptions{
   761  			UrlTemplate: "https://github.com/notaryproject/notation/releases/download/v{{.VERSION}}/notation_{{.VERSION}}_{{.GOOS}}_{{.GOARCH}}{{.EXT}}",
   762  			Name:        "notation",
   763  			Version:     "1.1.0",
   764  		},
   765  		ArchiveExtensions: map[string]string{
   766  			"linux":   ".tar.gz",
   767  			"darwin":  ".tar.gz",
   768  			"windows": ".zip",
   769  		},
   770  		TargetFileTemplate: target,
   771  	}
   772  	err := archive.DownloadToGopathBin(opts)
   773  	mgx.Must(err)
   774  }