github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/compute/pack.go (about)

     1  package compute
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/mholt/archiver/v3"
    10  
    11  	"github.com/fastly/cli/pkg/argparser"
    12  	fsterr "github.com/fastly/cli/pkg/errors"
    13  	"github.com/fastly/cli/pkg/filesystem"
    14  	"github.com/fastly/cli/pkg/global"
    15  	"github.com/fastly/cli/pkg/manifest"
    16  	"github.com/fastly/cli/pkg/text"
    17  )
    18  
    19  // PackCommand takes a .wasm and builds the required tar/gzip package ready to be uploaded.
    20  type PackCommand struct {
    21  	argparser.Base
    22  	wasmBinary string
    23  }
    24  
    25  // NewPackCommand returns a usable command registered under the parent.
    26  func NewPackCommand(parent argparser.Registerer, g *global.Data) *PackCommand {
    27  	var c PackCommand
    28  	c.Globals = g
    29  	c.CmdClause = parent.Command("pack", "Package a pre-compiled Wasm binary for a Fastly Compute service")
    30  	c.CmdClause.Flag("wasm-binary", "Path to a pre-compiled Wasm binary").Short('w').Required().StringVar(&c.wasmBinary)
    31  
    32  	return &c
    33  }
    34  
    35  // Exec implements the command interface.
    36  //
    37  // NOTE: The bin/manifest is placed in a 'package' folder within the tar.gz.
    38  func (c *PackCommand) Exec(_ io.Reader, out io.Writer) (err error) {
    39  	spinner, err := text.NewSpinner(out)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	defer func(errLog fsterr.LogInterface) {
    45  		_ = os.RemoveAll("pkg/package")
    46  		if err != nil {
    47  			errLog.Add(err)
    48  		}
    49  	}(c.Globals.ErrLog)
    50  
    51  	if err = c.Globals.Manifest.File.ReadError(); err != nil {
    52  		return err
    53  	}
    54  
    55  	bin := "pkg/package/bin/main.wasm"
    56  	bindir := filepath.Dir(bin)
    57  
    58  	err = filesystem.MakeDirectoryIfNotExists(bindir)
    59  	if err != nil {
    60  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
    61  			"Wasm directory (relative)": bindir,
    62  		})
    63  		return err
    64  	}
    65  
    66  	src, err := filepath.Abs(c.wasmBinary)
    67  	if err != nil {
    68  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
    69  			"Path (absolute)": src,
    70  		})
    71  		return err
    72  	}
    73  
    74  	dst, err := filepath.Abs(bin)
    75  	if err != nil {
    76  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
    77  			"Wasm destination (relative)": bin,
    78  		})
    79  		return err
    80  	}
    81  
    82  	err = spinner.Process("Copying wasm binary", func(_ *text.SpinnerWrapper) error {
    83  		if err := filesystem.CopyFile(src, dst); err != nil {
    84  			c.Globals.ErrLog.AddWithContext(err, map[string]any{
    85  				"Path (absolute)":             src,
    86  				"Wasm destination (absolute)": dst,
    87  			})
    88  			return fmt.Errorf("error copying wasm binary to '%s': %w", dst, err)
    89  		}
    90  
    91  		if !filesystem.FileExists(bin) {
    92  			return fsterr.RemediationError{
    93  				Inner:       fmt.Errorf("no wasm binary found"),
    94  				Remediation: "Run `fastly compute pack --path </path/to/wasm/binary>` to copy your wasm binary to the required location",
    95  			}
    96  		}
    97  		return nil
    98  	})
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	err = spinner.Process("Copying manifest", func(_ *text.SpinnerWrapper) error {
   104  		src = manifest.Filename
   105  		dst = fmt.Sprintf("pkg/package/%s", manifest.Filename)
   106  		if err := filesystem.CopyFile(src, dst); err != nil {
   107  			c.Globals.ErrLog.AddWithContext(err, map[string]any{
   108  				"Manifest (destination)": dst,
   109  				"Manifest (source)":      src,
   110  			})
   111  			return fmt.Errorf("error copying manifest to '%s': %w", dst, err)
   112  		}
   113  		return nil
   114  	})
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	return spinner.Process("Creating package.tar.gz file", func(_ *text.SpinnerWrapper) error {
   120  		tar := archiver.NewTarGz()
   121  		tar.OverwriteExisting = true
   122  		{
   123  			dir := "pkg/package"
   124  			src := []string{dir}
   125  			dst := fmt.Sprintf("%s.tar.gz", dir)
   126  			if err = tar.Archive(src, dst); err != nil {
   127  				c.Globals.ErrLog.AddWithContext(err, map[string]any{
   128  					"Tar source":      dir,
   129  					"Tar destination": dst,
   130  				})
   131  				return err
   132  			}
   133  		}
   134  		return nil
   135  	})
   136  }