github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/runtime/setup/implementations/camel/artifact.go (about) 1 package camel 2 3 import ( 4 "bytes" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 "github.com/ActiveState/cli/internal/errs" 12 "github.com/ActiveState/cli/internal/fileutils" 13 "github.com/ActiveState/cli/internal/locale" 14 "github.com/ActiveState/cli/internal/unarchiver" 15 "github.com/ActiveState/cli/pkg/platform/runtime/envdef" 16 "github.com/ActiveState/cli/pkg/platform/runtime/store" 17 "github.com/go-openapi/strfmt" 18 "github.com/thoas/go-funk" 19 ) 20 21 type ErrNotExecutable struct{ *locale.LocalizedError } 22 23 type ErrNoPrefixes struct{ *locale.LocalizedError } 24 25 type ArtifactSetup struct { 26 artifactID strfmt.UUID 27 store *store.Store 28 } 29 30 func NewArtifactSetup(artifactID strfmt.UUID, store *store.Store) *ArtifactSetup { 31 return &ArtifactSetup{artifactID, store} 32 } 33 34 func (as *ArtifactSetup) EnvDef(tmpDir string) (*envdef.EnvironmentDefinition, error) { 35 // camel archives are structured like this 36 // <archiveName>/ 37 // <relInstallDir>/ 38 // artifact contents ... 39 // metadata.json 40 41 // First: We need to identify the values for <archiveName> and <relInstallDir> 42 43 var archiveName string 44 fs, err := os.ReadDir(tmpDir) 45 if err != nil { 46 return nil, errs.Wrap(err, "Could not read temporary installation directory %s", tmpDir) 47 } 48 for _, f := range fs { 49 if f.IsDir() { 50 archiveName = f.Name() 51 } 52 } 53 if archiveName == "" { 54 return nil, errs.New("Expected sub-directory in extracted artifact tarball.") 55 } 56 57 tmpBaseDir := filepath.Join(tmpDir, archiveName) 58 59 // parse the legacy metadata 60 md, err := InitMetaData(tmpBaseDir) 61 if err != nil { 62 return nil, errs.Wrap(err, "Could not load meta data definitions for camel artifact.") 63 } 64 65 // convert file relocation commands into an envdef.FileTransform slice 66 transforms, err := convertToFileTransforms(tmpBaseDir, md.InstallDir, md) 67 if err != nil { 68 return nil, errs.Wrap(err, "Could not determine file transformations") 69 } 70 71 // convert environment variables into an envdef.EnvironmentVariable slice 72 vars := convertToEnvVars(md) 73 74 ed := &envdef.EnvironmentDefinition{ 75 InstallDir: filepath.Join(archiveName, md.InstallDir), 76 Transforms: transforms, 77 Env: vars, 78 } 79 80 return ed, nil 81 } 82 83 func convertToEnvVars(metadata *MetaData) []envdef.EnvironmentVariable { 84 var res []envdef.EnvironmentVariable 85 if metadata.AffectedEnv != "" { 86 res = append(res, envdef.EnvironmentVariable{ 87 Name: metadata.AffectedEnv, 88 Values: []string{}, 89 Inherit: false, 90 Join: envdef.Disallowed, 91 }) 92 } 93 for k, v := range metadata.Env { 94 res = append(res, envdef.EnvironmentVariable{ 95 Name: k, 96 Values: []string{v}, 97 Inherit: false, 98 }) 99 } 100 for k, v := range metadata.PathListEnv { 101 res = append(res, envdef.EnvironmentVariable{ 102 Name: k, 103 Values: []string{v}, 104 Join: envdef.Prepend, 105 Separator: string(os.PathListSeparator), 106 Inherit: true, 107 }) 108 } 109 var binPaths []string 110 111 // set up PATH according to binary locations 112 for _, v := range metadata.BinaryLocations { 113 path := v.Path 114 if v.Relative { 115 path = filepath.Join("${INSTALLDIR}", path) 116 } 117 binPaths = append(binPaths, path) 118 } 119 120 // Add DLL dir to PATH on Windows 121 if runtime.GOOS == "windows" && metadata.RelocationTargetBinaries != "" { 122 binPaths = append(binPaths, filepath.Join("${INSTALLDIR}", metadata.RelocationTargetBinaries)) 123 124 } 125 126 res = append(res, envdef.EnvironmentVariable{ 127 Name: "PATH", 128 Values: funk.ReverseStrings(binPaths), 129 Inherit: true, 130 Join: envdef.Prepend, 131 Separator: string(os.PathListSeparator), 132 }) 133 134 return res 135 } 136 137 func paddingForBinaryFile(isBinary bool) *string { 138 if !isBinary { 139 return nil 140 } 141 pad := "\000" 142 return &pad 143 } 144 145 func convertToFileTransforms(tmpBaseDir string, relInstDir string, metadata *MetaData) ([]envdef.FileTransform, error) { 146 var res []envdef.FileTransform 147 instDir := filepath.Join(tmpBaseDir, relInstDir) 148 for _, tr := range metadata.TargetedRelocations { 149 // walk through files in tr.InDir and find files that need replacements. For those we create a FileTransform element 150 trans, err := fileTransformsInDir(instDir, filepath.Join(instDir, tr.InDir), tr.SearchString, tr.Replacement, func(_ string, _ bool) bool { return true }) 151 if err != nil { 152 return res, errs.Wrap(err, "Failed convert targeted relocations") 153 } 154 res = append(res, trans...) 155 } 156 157 // metadata.RelocationDir is the string to search for and replace with ${INSTALLDIR} 158 if metadata.RelocationDir == "" { 159 return res, nil 160 } 161 binariesSeparate := runtime.GOOS == "linux" && metadata.RelocationTargetBinaries != "" 162 163 relocFilePath := filepath.Join(tmpBaseDir, "support", "reloc.txt") 164 relocMap := map[string]bool{} 165 if fileutils.FileExists(relocFilePath) { 166 relocMap = loadRelocationFile(relocFilePath) 167 } 168 169 trans, err := fileTransformsInDir(instDir, instDir, metadata.RelocationDir, "${INSTALLDIR}", func(path string, isBinary bool) bool { 170 return relocMap[path] || !binariesSeparate || !isBinary 171 }) 172 if err != nil { 173 return res, errs.Wrap(err, "Could not determine transformations in installation directory") 174 } 175 res = append(res, trans...) 176 177 if binariesSeparate { 178 trans, err := fileTransformsInDir(instDir, instDir, metadata.RelocationDir, "${INSTALLDIR}", func(_ string, isBinary bool) bool { 179 return isBinary 180 }) 181 if err != nil { 182 return res, errs.Wrap(err, "Could not determine separate binary transformations in installation directory") 183 } 184 res = append(res, trans...) 185 } 186 return res, nil 187 } 188 189 // fileTransformsInDir walks through all the files in searchDir and creates a FileTransform item for files that contain searchString and pass the filter function 190 func fileTransformsInDir(instDir string, searchDir string, searchString string, replacement string, filter func(string, bool) bool) ([]envdef.FileTransform, error) { 191 var res []envdef.FileTransform 192 193 err := filepath.Walk(searchDir, func(path string, info os.FileInfo, err error) error { 194 if err != nil { 195 return err 196 } 197 198 // skip symlinks 199 if (info.Mode() & fs.ModeSymlink) == fs.ModeSymlink { 200 return nil 201 } 202 203 if info.IsDir() { 204 return nil 205 } 206 207 b, err := os.ReadFile(path) 208 if err != nil { 209 return errs.Wrap(err, "Could not read file path %s", path) 210 } 211 212 // relativePath is the path relative to the installation directory 213 relativePath := strings.TrimPrefix(path, instDir) 214 isBinary := fileutils.IsBinary(b) 215 if !filter(relativePath, isBinary) { 216 return nil 217 } 218 if bytes.Contains(b, []byte(searchString)) { 219 res = append(res, envdef.FileTransform{ 220 In: []string{relativePath}, 221 Pattern: searchString, 222 With: replacement, 223 PadWith: paddingForBinaryFile(isBinary), 224 }) 225 } 226 227 return nil 228 }) 229 return res, err 230 } 231 232 func (as *ArtifactSetup) Unarchiver() unarchiver.Unarchiver { 233 if runtime.GOOS == "windows" { 234 return unarchiver.NewZip() 235 } 236 return unarchiver.NewTarGz() 237 }