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

     1  package compute
     2  
     3  import (
     4  	"crypto/sha512"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/kennygrant/sanitize"
    12  
    13  	"github.com/fastly/cli/pkg/argparser"
    14  	fsterr "github.com/fastly/cli/pkg/errors"
    15  	"github.com/fastly/cli/pkg/global"
    16  	"github.com/fastly/cli/pkg/manifest"
    17  	"github.com/fastly/cli/pkg/text"
    18  )
    19  
    20  // HashsumCommand produces a deployable artifact from files on the local disk.
    21  type HashsumCommand struct {
    22  	argparser.Base
    23  
    24  	// Build fields
    25  	dir                   argparser.OptionalString
    26  	env                   argparser.OptionalString
    27  	includeSrc            argparser.OptionalBool
    28  	lang                  argparser.OptionalString
    29  	metadataDisable       argparser.OptionalBool
    30  	metadataFilterEnvVars argparser.OptionalString
    31  	metadataShow          argparser.OptionalBool
    32  	packageName           argparser.OptionalString
    33  	timeout               argparser.OptionalInt
    34  
    35  	buildCmd    *BuildCommand
    36  	PackagePath string
    37  	SkipBuild   bool
    38  }
    39  
    40  // NewHashsumCommand returns a usable command registered under the parent.
    41  // Deprecated: Use NewHashFilesCommand instead.
    42  func NewHashsumCommand(parent argparser.Registerer, g *global.Data, build *BuildCommand) *HashsumCommand {
    43  	var c HashsumCommand
    44  	c.buildCmd = build
    45  	c.Globals = g
    46  	c.CmdClause = parent.Command("hashsum", "Generate a SHA512 digest from a Compute package").Hidden()
    47  	c.CmdClause.Flag("dir", "Project directory to build (default: current directory)").Short('C').Action(c.dir.Set).StringVar(&c.dir.Value)
    48  	c.CmdClause.Flag("env", "The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')").Action(c.env.Set).StringVar(&c.env.Value)
    49  	c.CmdClause.Flag("include-source", "Include source code in built package").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value)
    50  	c.CmdClause.Flag("language", "Language type").Action(c.lang.Set).StringVar(&c.lang.Value)
    51  	c.CmdClause.Flag("metadata-disable", "Disable Wasm binary metadata annotations").Action(c.metadataDisable.Set).BoolVar(&c.metadataDisable.Value)
    52  	c.CmdClause.Flag("metadata-filter-envvars", "Redact specified environment variables from [scripts.env_vars] using comma-separated list").Action(c.metadataFilterEnvVars.Set).StringVar(&c.metadataFilterEnvVars.Value)
    53  	c.CmdClause.Flag("metadata-show", "Inspect the Wasm binary metadata").Action(c.metadataShow.Set).BoolVar(&c.metadataShow.Value)
    54  	c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').StringVar(&c.PackagePath)
    55  	c.CmdClause.Flag("package-name", "Package name").Action(c.packageName.Set).StringVar(&c.packageName.Value)
    56  	c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.SkipBuild)
    57  	c.CmdClause.Flag("timeout", "Timeout, in seconds, for the build compilation step").Action(c.timeout.Set).IntVar(&c.timeout.Value)
    58  
    59  	return &c
    60  }
    61  
    62  // Exec implements the command interface.
    63  func (c *HashsumCommand) Exec(in io.Reader, out io.Writer) (err error) {
    64  	if !c.Globals.Flags.Quiet {
    65  		// FIXME: Remove `hashsum` subcommand before v11.0.0 is released.
    66  		text.Warning(out, "This command is deprecated. Use `fastly compute hash-files` instead.")
    67  	}
    68  
    69  	// No point in building a package if the user provides a package path.
    70  	if !c.SkipBuild && c.PackagePath == "" {
    71  		err = c.Build(in, out)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		if !c.Globals.Flags.Quiet {
    76  			text.Break(out)
    77  		}
    78  	}
    79  
    80  	pkgPath := c.PackagePath
    81  	if pkgPath == "" {
    82  		manifestFilename := EnvironmentManifest(c.env.Value)
    83  		wd, err := os.Getwd()
    84  		if err != nil {
    85  			return fmt.Errorf("failed to get current working directory: %w", err)
    86  		}
    87  		defer func() {
    88  			_ = os.Chdir(wd)
    89  		}()
    90  		manifestPath := filepath.Join(wd, manifestFilename)
    91  
    92  		projectDir, err := ChangeProjectDirectory(c.dir.Value)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		if projectDir != "" {
    97  			if c.Globals.Verbose() {
    98  				text.Info(out, ProjectDirMsg, projectDir)
    99  			}
   100  			manifestPath = filepath.Join(projectDir, manifestFilename)
   101  		}
   102  
   103  		if projectDir != "" || c.env.WasSet {
   104  			err = c.Globals.Manifest.File.Read(manifestPath)
   105  		} else {
   106  			err = c.Globals.Manifest.File.ReadError()
   107  		}
   108  		if err != nil {
   109  			if errors.Is(err, os.ErrNotExist) {
   110  				err = fsterr.ErrReadingManifest
   111  			}
   112  			c.Globals.ErrLog.Add(err)
   113  			return err
   114  		}
   115  
   116  		projectName, source := c.Globals.Manifest.Name()
   117  		if source == manifest.SourceUndefined {
   118  			return fsterr.ErrReadingManifest
   119  		}
   120  		pkgPath = filepath.Join(projectDir, "pkg", fmt.Sprintf("%s.tar.gz", sanitize.BaseName(projectName)))
   121  	}
   122  
   123  	err = validatePackage(pkgPath)
   124  	if err != nil {
   125  		var skipBuildMsg string
   126  		if c.SkipBuild {
   127  			skipBuildMsg = " avoid using --skip-build, or"
   128  		}
   129  		return fsterr.RemediationError{
   130  			Inner:       fmt.Errorf("failed to validate package: %w", err),
   131  			Remediation: fmt.Sprintf("Run `fastly compute build` to produce a Compute package, alternatively%s use the --package flag to reference a package outside of the current project.", skipBuildMsg),
   132  		}
   133  	}
   134  
   135  	hashSum, err := getHashSum(pkgPath)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	text.Output(out, hashSum)
   141  	return nil
   142  }
   143  
   144  // Build constructs and executes the build logic.
   145  func (c *HashsumCommand) Build(in io.Reader, out io.Writer) error {
   146  	output := out
   147  	if !c.Globals.Verbose() && !c.metadataShow.WasSet {
   148  		output = io.Discard
   149  	} else {
   150  		text.Break(out)
   151  	}
   152  	if c.dir.WasSet {
   153  		c.buildCmd.Flags.Dir = c.dir.Value
   154  	}
   155  	if c.env.WasSet {
   156  		c.buildCmd.Flags.Env = c.env.Value
   157  	}
   158  	if c.includeSrc.WasSet {
   159  		c.buildCmd.Flags.IncludeSrc = c.includeSrc.Value
   160  	}
   161  	if c.lang.WasSet {
   162  		c.buildCmd.Flags.Lang = c.lang.Value
   163  	}
   164  	if c.packageName.WasSet {
   165  		c.buildCmd.Flags.PackageName = c.packageName.Value
   166  	}
   167  	if c.timeout.WasSet {
   168  		c.buildCmd.Flags.Timeout = c.timeout.Value
   169  	}
   170  	if c.metadataDisable.WasSet {
   171  		c.buildCmd.MetadataDisable = c.metadataDisable.Value
   172  	}
   173  	if c.metadataFilterEnvVars.WasSet {
   174  		c.buildCmd.MetadataFilterEnvVars = c.metadataFilterEnvVars.Value
   175  	}
   176  	if c.metadataShow.WasSet {
   177  		c.buildCmd.MetadataShow = c.metadataShow.Value
   178  	}
   179  	return c.buildCmd.Exec(in, output)
   180  }
   181  
   182  // getHashSum returns a hash of the package.
   183  func getHashSum(pkg string) (string, error) {
   184  	// gosec flagged this:
   185  	// G304 (CWE-22): Potential file inclusion via variable
   186  	// Disabling as we trust the source of the filepath variable.
   187  	/* #nosec */
   188  	f, err := os.Open(pkg)
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  
   193  	h := sha512.New()
   194  	if _, err := io.Copy(h, f); err != nil {
   195  		_ = f.Close()
   196  		return "", err
   197  	}
   198  
   199  	if err = f.Close(); err != nil {
   200  		return "", err
   201  	}
   202  
   203  	return fmt.Sprintf("%x", h.Sum(nil)), nil
   204  }