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  }