github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/runtime/executors/executors.go (about)

     1  package executors
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	rt "runtime"
     7  	"strings"
     8  
     9  	"github.com/ActiveState/cli/pkg/project"
    10  
    11  	"github.com/ActiveState/cli/internal/installation"
    12  	"github.com/ActiveState/cli/internal/osutils"
    13  	"github.com/ActiveState/cli/pkg/platform/runtime/envdef"
    14  	"github.com/ActiveState/cli/pkg/platform/runtime/executors/execmeta"
    15  	"github.com/go-openapi/strfmt"
    16  
    17  	"github.com/ActiveState/cli/internal/errs"
    18  	"github.com/ActiveState/cli/internal/fileutils"
    19  	"github.com/ActiveState/cli/internal/locale"
    20  	"github.com/ActiveState/cli/internal/logging"
    21  )
    22  
    23  type Targeter interface {
    24  	CommitUUID() strfmt.UUID
    25  	Name() string
    26  	Owner() string
    27  	Dir() string
    28  }
    29  
    30  type Executors struct {
    31  	executorPath string // The location to store the executors
    32  
    33  	altExecSrcPath string // Path to alternate executor for testing. Executor() will use global func if not set.
    34  }
    35  
    36  func New(executorPath string) *Executors {
    37  	return &Executors{
    38  		executorPath: executorPath,
    39  	}
    40  }
    41  
    42  func (es *Executors) ExecutorSrc() (string, error) {
    43  	if es.altExecSrcPath != "" {
    44  		return es.altExecSrcPath, nil
    45  	}
    46  	return installation.ExecutorExec()
    47  }
    48  
    49  func (es *Executors) Apply(sockPath string, targeter Targeter, env map[string]string, exes envdef.ExecutablePaths) error {
    50  	logging.Debug("Creating executors at %s, exes: %v", es.executorPath, exes)
    51  
    52  	executors := make(map[string]string) // map[alias]dest
    53  	for _, dest := range exes {
    54  		executors[makeAlias(dest)] = dest
    55  	}
    56  
    57  	if err := es.Clean(); err != nil {
    58  		return errs.Wrap(err, "Could not clean up old executors")
    59  	}
    60  
    61  	if err := fileutils.MkdirUnlessExists(es.executorPath); err != nil {
    62  		return locale.WrapError(err, "err_mkdir", "Could not create directory: {{.V0}}", es.executorPath)
    63  	}
    64  
    65  	ns := project.NewNamespace(targeter.Owner(), targeter.Name(), "")
    66  	t := execmeta.Target{
    67  		CommitUUID: targeter.CommitUUID().String(),
    68  		Namespace:  ns.String(),
    69  		Dir:        targeter.Dir(),
    70  	}
    71  	m := execmeta.New(sockPath, osutils.EnvMapToSlice(env), t, executors)
    72  	if err := m.WriteToDisk(es.executorPath); err != nil {
    73  		return err
    74  	}
    75  
    76  	executorSrc, err := es.ExecutorSrc()
    77  	if err != nil {
    78  		return locale.WrapError(err, "err_state_exec")
    79  	}
    80  
    81  	for executor := range executors {
    82  		if err := copyExecutor(es.executorPath, executor, executorSrc); err != nil {
    83  			return locale.WrapError(err, "err_createexecutor", "Could not create executor for {{.V0}}.", executor)
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func makeAlias(destination string) string {
    91  	alias := filepath.Base(destination)
    92  
    93  	if rt.GOOS == "windows" {
    94  		ext := filepath.Ext(alias)
    95  		if ext != "" && ext != osutils.ExeExtension { // for non-.exe executables like pip.bat
    96  			alias = strings.TrimSuffix(alias, ext) + osutils.ExeExtension // setup alias pip.exe -> pip.bat
    97  		}
    98  	}
    99  
   100  	return alias
   101  }
   102  
   103  func (es *Executors) Clean() error {
   104  	if !fileutils.DirExists(es.executorPath) {
   105  		return nil
   106  	}
   107  
   108  	files, err := os.ReadDir(es.executorPath)
   109  	if err != nil {
   110  		return errs.Wrap(err, "Could not read dir: %s", es.executorPath)
   111  	}
   112  
   113  	for _, file := range files {
   114  		if file.IsDir() {
   115  			continue
   116  		}
   117  
   118  		filePath := filepath.Join(es.executorPath, file.Name())
   119  		b, err := fileutils.ReadFile(filePath)
   120  		if err != nil {
   121  			return locale.WrapError(err, "err_cleanexecutor_noread", "Could not read potential executor file: {{.V0}}.", file.Name())
   122  		}
   123  
   124  		if !isOwnedByUs(b) {
   125  			continue
   126  		}
   127  
   128  		if err := os.Remove(filePath); err != nil {
   129  			return locale.WrapError(err, "err_cleanexecutor_remove", "Could not remove executor: {{.V0}}", file.Name())
   130  		}
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func IsExecutor(filePath string) (bool, error) {
   137  	if fileutils.IsDir(filePath) {
   138  		return false, nil
   139  	}
   140  
   141  	b, err := fileutils.ReadFile(filePath)
   142  	if err != nil {
   143  		return false, locale.WrapError(err, "err_cleanexecutor_noread", "Could not read potential executor file: {{.V0}}.", filePath)
   144  	}
   145  	return isOwnedByUs(b), nil
   146  }
   147  
   148  func isOwnedByUs(fileContents []byte) bool {
   149  	return strings.Contains(string(fileContents), "state-exec") ||
   150  		execmeta.IsMetaFile(fileContents) ||
   151  		legacyIsOwnedByUs(fileContents)
   152  }
   153  
   154  func copyExecutor(destDir, executor, srcExec string) error {
   155  	name := filepath.Base(executor)
   156  	target := filepath.Clean(filepath.Join(destDir, name))
   157  
   158  	logging.Debug("Creating executor for %s at %s", name, target)
   159  
   160  	if fileutils.TargetExists(target) {
   161  		b, err := fileutils.ReadFile(target)
   162  		if err != nil {
   163  			return locale.WrapError(err, "err_createexecutor_exists_noread", "Could not create executor as target already exists and could not be read: {{.V0}}.", target)
   164  		}
   165  		if !isOwnedByUs(b) {
   166  			return locale.WrapError(err, "err_createexecutor_exists", "Could not create executor as target already exists: {{.V0}}.", target)
   167  		}
   168  	}
   169  
   170  	if err := fileutils.CopyFile(srcExec, target); err != nil {
   171  		return locale.WrapError(err, "err_copyexecutor_fail", "Could not copy {{.V0}} to {{.V1}}", srcExec, target)
   172  	}
   173  
   174  	if err := os.Chmod(target, 0755); err != nil {
   175  		return locale.WrapError(err, "err_setexecmode_fail", "Could not set mode of {{.V0}}", target)
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // denoter constants are to ensure we clean up old executors, but are deprecated as of this comment
   182  const (
   183  	legacyExecutorDenoter = "!DO NOT EDIT! State Tool Executor !DO NOT EDIT!"
   184  	legacyShimDenoter     = "!DO NOT EDIT! State Tool Shim !DO NOT EDIT!"
   185  )
   186  
   187  func legacyIsOwnedByUs(fileContents []byte) bool {
   188  	s := string(fileContents)
   189  	return strings.Contains(s, legacyExecutorDenoter) || strings.Contains(s, legacyShimDenoter)
   190  }