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 }