...

Source file src/github.com/redhat-developer/odo/pkg/util/util.go

Documentation: github.com/redhat-developer/odo/pkg/util

     1  package util
     2  
     3  import (
     4  	"archive/zip"
     5  	"bufio"
     6  	"bytes"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/fs"
    12  	"net"
    13  	"net/url"
    14  	"os"
    15  	"os/signal"
    16  	"path"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"sort"
    21  	"strings"
    22  	"syscall"
    23  	"time"
    24  
    25  	"github.com/fatih/color"
    26  	"github.com/go-git/go-git/v5"
    27  	gitignore "github.com/sabhiram/go-gitignore"
    28  
    29  	"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    30  	devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
    31  	dfutil "github.com/devfile/library/v2/pkg/util"
    32  
    33  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    34  
    35  	"k8s.io/klog"
    36  )
    37  
    38  var httpCacheDir = filepath.Join(os.TempDir(), "odohttpcache")
    39  
    40  // This value can be provided to set a seperate directory for users 'homedir' resolution
    41  // note for mocking purpose ONLY
    42  var customHomeDir = os.Getenv("CUSTOM_HOMEDIR")
    43  
    44  // ConvertLabelsToSelector converts the given labels to selector
    45  // To pass operands such as !=, append a ! prefix to the value.
    46  // For E.g. map[string]string{"app.kubernetes.io/managed-by": "!odo"}
    47  // Using != operators also means that resource will be filtered even if it doesn't have the key.
    48  // So a resource not labelled with key "app.kubernetes.io/managed-by" will also be returned.
    49  // TODO(feloy) sync with devfile library?
    50  func ConvertLabelsToSelector(labels map[string]string) string {
    51  	var selector string
    52  	isFirst := true
    53  	keys := make([]string, 0, len(labels))
    54  	for k := range labels {
    55  		keys = append(keys, k)
    56  	}
    57  	sort.Strings(keys)
    58  	for _, k := range keys {
    59  		v := labels[k]
    60  		if isFirst {
    61  			isFirst = false
    62  			if v == "" {
    63  				selector = selector + fmt.Sprintf("%v", k)
    64  			} else {
    65  				if strings.HasPrefix(v, "!") {
    66  					v = strings.Replace(v, "!", "", -1)
    67  					selector = fmt.Sprintf("%v!=%v", k, v)
    68  				} else {
    69  					selector = fmt.Sprintf("%v=%v", k, v)
    70  				}
    71  			}
    72  		} else {
    73  			if v == "" {
    74  				selector = selector + fmt.Sprintf(",%v", k)
    75  			} else {
    76  				if strings.HasPrefix(v, "!") {
    77  					v = strings.Replace(v, "!", "", -1)
    78  					selector = selector + fmt.Sprintf(",%v!=%v", k, v)
    79  				} else {
    80  					selector = selector + fmt.Sprintf(",%v=%v", k, v)
    81  				}
    82  			}
    83  		}
    84  	}
    85  	return selector
    86  }
    87  
    88  // NamespaceKubernetesObject hyphenates applicationName and componentName
    89  func NamespaceKubernetesObject(componentName string, applicationName string) (string, error) {
    90  
    91  	// Error if it's blank
    92  	if componentName == "" {
    93  		return "", errors.New("namespacing: component name cannot be blank")
    94  	}
    95  
    96  	// Error if it's blank
    97  	if applicationName == "" {
    98  		return "", errors.New("namespacing: application name cannot be blank")
    99  	}
   100  
   101  	// Return the hyphenated namespaced name
   102  	return fmt.Sprintf("%s-%s", strings.Replace(componentName, "/", "-", -1), applicationName), nil
   103  }
   104  
   105  // NamespaceKubernetesObjectWithTrim hyphenates applicationName and componentName
   106  // if the resultant name is greater than 63 characters
   107  // it trims app name then component name
   108  func NamespaceKubernetesObjectWithTrim(componentName, applicationName string, maxLen int) (string, error) {
   109  	value, err := NamespaceKubernetesObject(componentName, applicationName)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  
   114  	// doesn't require trim
   115  	if len(value) <= maxLen {
   116  		return value, nil
   117  	}
   118  
   119  	// trim app name to max 31 characters
   120  	appNameLen := len(applicationName)
   121  	if appNameLen > maxLen/2 {
   122  		appNameLen = maxLen / 2
   123  	}
   124  	applicationName = applicationName[:appNameLen]
   125  
   126  	// trim component name with remaining length
   127  	minComponentLen := len(componentName)
   128  	if minComponentLen > maxLen-appNameLen-1 {
   129  		minComponentLen = maxLen - appNameLen - 1
   130  	}
   131  	componentName = componentName[:minComponentLen]
   132  
   133  	value, err = NamespaceKubernetesObject(componentName, applicationName)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	return value, nil
   138  }
   139  
   140  // TruncateString truncates passed string to given length
   141  // Note: if -1 is passed, the original string is returned
   142  // if appendIfTrunicated is given, then it will be appended to trunicated
   143  // string
   144  // TODO(feloy) sync with devfile library?
   145  func TruncateString(str string, maxLen int, appendIfTrunicated ...string) string {
   146  	if maxLen == -1 {
   147  		return str
   148  	}
   149  	if len(str) > maxLen {
   150  		truncatedString := str[:maxLen]
   151  		for _, item := range appendIfTrunicated {
   152  			truncatedString = fmt.Sprintf("%s%s", truncatedString, item)
   153  		}
   154  		return truncatedString
   155  	}
   156  	return str
   157  }
   158  
   159  // GetDNS1123Name Converts passed string into DNS-1123 string
   160  // TODO(feloy) sync with devfile library?
   161  func GetDNS1123Name(str string) string {
   162  	nonAllowedCharsRegex := regexp.MustCompile(`[^a-zA-Z0-9_-]+`)
   163  	withReplacedChars := strings.Replace(
   164  		nonAllowedCharsRegex.ReplaceAllString(str, "-"),
   165  		"--", "-", -1)
   166  	name := strings.ToLower(removeNonAlphaSuffix(removeNonAlphaPrefix(withReplacedChars)))
   167  	// if the name is all numeric
   168  	if len(str) != 0 && len(name) == 0 {
   169  		name = strings.ToLower(removeNonAlphaSuffix(removeNonAlphaPrefix("x" + withReplacedChars)))
   170  	}
   171  	return name
   172  }
   173  
   174  func removeNonAlphaPrefix(input string) string {
   175  	regex := regexp.MustCompile("^[^a-zA-Z]+(.*)$")
   176  	return regex.ReplaceAllString(input, "$1")
   177  }
   178  
   179  func removeNonAlphaSuffix(input string) string {
   180  	suffixRegex := regexp.MustCompile("^(.*?)[^a-zA-Z0-9]+$") // regex that strips all trailing non alpha-numeric chars
   181  	matches := suffixRegex.FindStringSubmatch(input)
   182  	matchesLength := len(matches)
   183  	if matchesLength == 0 {
   184  		// in this case the string does not contain a non-alphanumeric suffix
   185  		return input
   186  	}
   187  	// in this case we return the smallest match which in the last element in the array
   188  	return matches[matchesLength-1]
   189  }
   190  
   191  // CheckPathExists checks if a path exists or not
   192  // TODO(feloy) use from devfile library?
   193  func CheckPathExists(fsys filesystem.Filesystem, path string) bool {
   194  	if _, err := fsys.Stat(path); !errors.Is(err, fs.ErrNotExist) {
   195  		// path to file does exist
   196  		return true
   197  	}
   198  	klog.V(4).Infof("path %s doesn't exist, skipping it", path)
   199  	return false
   200  }
   201  
   202  // IsValidProjectDir checks that the folder to download the project from devfile is
   203  // either empty or contains the devfile used.
   204  // TODO(feloy) sync with devfile library?
   205  func IsValidProjectDir(path string, devfilePath string, fs filesystem.Filesystem) error {
   206  	entries, err := fs.ReadDir(path)
   207  	if err != nil {
   208  		return err
   209  	}
   210  	if len(entries) >= 1 {
   211  		for _, entry := range entries {
   212  			fileName := entry.Name()
   213  			devfilePath = strings.TrimPrefix(devfilePath, "./")
   214  			if !entry.IsDir() && fileName == devfilePath {
   215  				return nil
   216  			}
   217  		}
   218  		return fmt.Errorf("folder %s doesn't contain the devfile used", path)
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // GetAndExtractZip downloads a zip file from a URL with a http prefix or
   225  // takes an absolute path prefixed with file:// and extracts it to a destination.
   226  // pathToUnzip specifies the path within the zip folder to extract
   227  // TODO(feloy) sync with devfile library?
   228  func GetAndExtractZip(zipURL string, destination string, pathToUnzip string, starterToken string, fsys filesystem.Filesystem) error {
   229  	if zipURL == "" {
   230  		return fmt.Errorf("empty zip url: %s", zipURL)
   231  	}
   232  
   233  	var pathToZip string
   234  	if strings.HasPrefix(zipURL, "file://") {
   235  		pathToZip = strings.TrimPrefix(zipURL, "file:/")
   236  		if runtime.GOOS == "windows" {
   237  			pathToZip = strings.Replace(pathToZip, "\\", "/", -1)
   238  		}
   239  	} else if strings.HasPrefix(zipURL, "http://") || strings.HasPrefix(zipURL, "https://") {
   240  		// Generate temporary zip file location
   241  		time := time.Now().Format(time.RFC3339)
   242  		time = strings.Replace(time, ":", "-", -1) // ":" is illegal char in windows
   243  		tempPath, err := fsys.TempDir("", "")
   244  		if err != nil {
   245  			return err
   246  		}
   247  		pathToZip = path.Join(tempPath, "_"+time+".zip")
   248  
   249  		params := dfutil.DownloadParams{
   250  			Request: dfutil.HTTPRequestParams{
   251  				URL:   zipURL,
   252  				Token: starterToken,
   253  			},
   254  			Filepath: pathToZip,
   255  		}
   256  		err = dfutil.DownloadFile(params)
   257  		if err != nil {
   258  			return err
   259  		}
   260  
   261  		defer func() {
   262  			if err = fsys.Remove(pathToZip); err != nil && !errors.Is(err, fs.ErrNotExist) {
   263  				klog.Errorf("Could not delete temporary directory for zip file. Error: %s", err)
   264  			}
   265  		}()
   266  	} else {
   267  		return fmt.Errorf("invalid Zip URL: %s . Should either be prefixed with file://, http:// or https://", zipURL)
   268  	}
   269  
   270  	filenames, err := Unzip(pathToZip, destination, pathToUnzip, fsys)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	if len(filenames) == 0 {
   276  		return errors.New("no files were unzipped, ensure that the project repo is not empty or that subDir has a valid path")
   277  	}
   278  
   279  	return nil
   280  }
   281  
   282  // Unzip will decompress a zip archive, moving specified files and folders
   283  // within the zip file (parameter 1) to an output directory (parameter 2)
   284  // Source: https://golangcode.com/unzip-files-in-go/
   285  // pathToUnzip (parameter 3) is the path within the zip folder to extract
   286  // TODO(feloy) sync with devfile library?
   287  func Unzip(src, dest, pathToUnzip string, fsys filesystem.Filesystem) ([]string, error) {
   288  	var filenames []string
   289  
   290  	r, err := zip.OpenReader(src)
   291  	if err != nil {
   292  		return filenames, err
   293  	}
   294  	defer r.Close()
   295  
   296  	// change path separator to correct character
   297  	pathToUnzip = filepath.FromSlash(pathToUnzip)
   298  
   299  	// removes first slash of pathToUnzip if present
   300  	pathToUnzip = strings.TrimPrefix(pathToUnzip, string(os.PathSeparator))
   301  
   302  	for _, f := range r.File {
   303  		// Store filename/path for returning and using later on
   304  		index := strings.Index(f.Name, "/")
   305  		filename := filepath.FromSlash(f.Name[index+1:])
   306  		if filename == "" {
   307  			continue
   308  		}
   309  
   310  		// if subDir has a pattern
   311  		match, err := filepath.Match(pathToUnzip, filename)
   312  		if err != nil {
   313  			return filenames, err
   314  		}
   315  
   316  		// destination filepath before trim
   317  		fpath := filepath.Join(dest, filename)
   318  
   319  		// used for pattern matching
   320  		fpathDir := filepath.Dir(fpath)
   321  
   322  		// check for prefix or match
   323  		if strings.HasPrefix(filename, pathToUnzip) {
   324  			filename = strings.TrimPrefix(filename, pathToUnzip)
   325  		} else if !strings.HasPrefix(filename, pathToUnzip) && !match && !sliceContainsString(fpathDir, filenames) {
   326  			continue
   327  		}
   328  		// adds trailing slash to destination if needed as filepath.Join removes it
   329  		if (len(filename) == 1 && os.IsPathSeparator(filename[0])) || filename == "" {
   330  			fpath = dest + string(os.PathSeparator)
   331  		} else {
   332  			fpath = filepath.Join(dest, filename)
   333  		}
   334  		// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
   335  		if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
   336  			return filenames, fmt.Errorf("%s: illegal file path", fpath)
   337  		}
   338  
   339  		filenames = append(filenames, fpath)
   340  
   341  		if f.FileInfo().IsDir() {
   342  			// Make Folder
   343  			if err = fsys.MkdirAll(fpath, os.ModePerm); err != nil {
   344  				return filenames, err
   345  			}
   346  			continue
   347  		}
   348  
   349  		// Make File
   350  		if err = fsys.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
   351  			return filenames, err
   352  		}
   353  
   354  		outFile, err := fsys.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
   355  		if err != nil {
   356  			return filenames, err
   357  		}
   358  
   359  		rc, err := f.Open()
   360  		if err != nil {
   361  			return filenames, err
   362  		}
   363  
   364  		// limit the number of bytes copied from a file
   365  		// This is set to the limit of file size in Github
   366  		// which is 100MB
   367  		limited := io.LimitReader(rc, 100*1024*1024)
   368  
   369  		_, err = io.Copy(outFile, limited)
   370  
   371  		// Close the file without defer to close before next iteration of loop
   372  		outFile.Close()
   373  		rc.Close()
   374  
   375  		if err != nil {
   376  			return filenames, err
   377  		}
   378  	}
   379  	return filenames, nil
   380  }
   381  
   382  // DownloadFileInMemory uses the url to download the file and return bytes
   383  // TODO(feloy): sync with devfile library?
   384  func DownloadFileInMemory(params dfutil.HTTPRequestParams) ([]byte, error) {
   385  	data, err := dfutil.HTTPGetRequest(params, 0)
   386  
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	return data, nil
   392  }
   393  
   394  // DownloadFileInMemoryWithCache uses the url to download the file and return bytes
   395  func DownloadFileInMemoryWithCache(params dfutil.HTTPRequestParams, cacheFor int) ([]byte, error) {
   396  	data, err := dfutil.HTTPGetRequest(params, cacheFor)
   397  
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  
   402  	return data, nil
   403  }
   404  
   405  // ValidateURL validates the URL
   406  // TODO(feloy) sync with devfile library?
   407  func ValidateURL(sourceURL string) error {
   408  	// Valid URL needs to satisfy the following requirements:
   409  	// 1. URL has scheme and host components
   410  	// 2. Scheme, host of the URL shouldn't contain reserved character
   411  	url, err := url.ParseRequestURI(sourceURL)
   412  	if err != nil {
   413  		return err
   414  	}
   415  	host := url.Host
   416  
   417  	re := regexp.MustCompile(`[\/\?#\[\]@]`)
   418  	if host == "" || re.MatchString(host) {
   419  		return errors.New("URL is invalid")
   420  	}
   421  
   422  	return nil
   423  }
   424  
   425  // GetDataFromURI gets the data from the given URI
   426  // if the uri is a local path, we use the componentContext to complete the local path
   427  func GetDataFromURI(uri, componentContext string, fs devfilefs.Filesystem) (string, error) {
   428  	parsedURL, err := url.Parse(uri)
   429  	if err != nil {
   430  		return "", err
   431  	}
   432  	if len(parsedURL.Host) != 0 && len(parsedURL.Scheme) != 0 {
   433  		params := dfutil.HTTPRequestParams{
   434  			URL: uri,
   435  		}
   436  		dataBytes, err := DownloadFileInMemoryWithCache(params, 1)
   437  		if err != nil {
   438  			return "", err
   439  		}
   440  		return string(dataBytes), nil
   441  	} else {
   442  		dataBytes, err := fs.ReadFile(filepath.Join(componentContext, uri))
   443  		if err != nil {
   444  			return "", err
   445  		}
   446  		return string(dataBytes), nil
   447  	}
   448  }
   449  
   450  // sliceContainsString checks for existence of given string in given slice
   451  func sliceContainsString(str string, slice []string) bool {
   452  	for _, b := range slice {
   453  		if b == str {
   454  			return true
   455  		}
   456  	}
   457  	return false
   458  }
   459  
   460  func addFileToIgnoreFile(gitIgnoreFile, filename string, fs filesystem.Filesystem) error {
   461  	filename = filepath.ToSlash(filename)
   462  
   463  	readFile, err := fs.Open(gitIgnoreFile)
   464  	if err != nil {
   465  		return err
   466  	}
   467  	defer readFile.Close()
   468  	fileScanner := bufio.NewScanner(readFile)
   469  	fileScanner.Split(bufio.ScanLines)
   470  	var fileLines []string
   471  	for fileScanner.Scan() {
   472  		fileLines = append(fileLines, fileScanner.Text())
   473  	}
   474  
   475  	// check whether filename is already in the .gitignore file
   476  	ignoreMatcher := gitignore.CompileIgnoreLines(fileLines...)
   477  	if !ignoreMatcher.MatchesPath(filename) {
   478  		file, err := fs.OpenFile(gitIgnoreFile, os.O_APPEND|os.O_RDWR, dfutil.ModeReadWriteFile)
   479  		if err != nil {
   480  			return fmt.Errorf("failed to open .gitignore file: %w", err)
   481  		}
   482  		defer file.Close()
   483  		if _, err := file.WriteString("\n" + filename); err != nil {
   484  			return fmt.Errorf("failed to add %v to %v file: %w", filepath.Base(filename), gitIgnoreFile, err)
   485  		}
   486  	}
   487  	return nil
   488  }
   489  
   490  // DisplayLog displays logs to user stdout with some color formatting
   491  // numberOfLastLines limits the number of lines from the output when we are not following it
   492  // TODO(feloy) sync with devfile library?
   493  func DisplayLog(followLog bool, rd io.ReadCloser, writer io.Writer, compName string, numberOfLastLines int) (err error) {
   494  
   495  	defer func() {
   496  		cErr := rd.Close()
   497  		if err == nil {
   498  			err = cErr
   499  		}
   500  	}()
   501  
   502  	// Copy to stdout (in yellow)
   503  	color.Set(color.FgYellow)
   504  	defer color.Unset()
   505  
   506  	// If we are going to followLog, we'll be copying it to stdout
   507  	// else, we copy it to a buffer
   508  	if followLog {
   509  
   510  		c := make(chan os.Signal, 1)
   511  		signal.Notify(c, os.Interrupt, syscall.SIGTERM)
   512  		go func() {
   513  			<-c
   514  			color.Unset()
   515  			os.Exit(1)
   516  		}()
   517  
   518  		if _, err = io.Copy(writer, rd); err != nil {
   519  			return fmt.Errorf("error followLoging logs for %s: %w", compName, err)
   520  		}
   521  
   522  	} else if numberOfLastLines == -1 {
   523  		// Copy to buffer (we aren't going to be followLoging the logs..)
   524  		buf := new(bytes.Buffer)
   525  		_, err = io.Copy(buf, rd)
   526  		if err != nil {
   527  			return fmt.Errorf("unable to copy followLog to buffer: %w", err)
   528  		}
   529  
   530  		// Copy to stdout
   531  		if _, err = io.Copy(writer, buf); err != nil {
   532  			return fmt.Errorf("error copying logs to stdout: %w", err)
   533  		}
   534  	} else {
   535  		reader := bufio.NewReader(rd)
   536  		var lines []string
   537  		var line string
   538  		for {
   539  			line, err = reader.ReadString('\n')
   540  			if err != nil {
   541  				if err != io.EOF {
   542  					return err
   543  				}
   544  				err = nil
   545  				break
   546  			}
   547  
   548  			lines = append(lines, line)
   549  		}
   550  
   551  		index := len(lines) - numberOfLastLines
   552  		if index < 0 {
   553  			index = 0
   554  		}
   555  
   556  		for i := index; i < len(lines); i++ {
   557  			_, err = fmt.Fprintf(writer, lines[i])
   558  			if err != nil {
   559  				return err
   560  			}
   561  		}
   562  	}
   563  	return err
   564  
   565  }
   566  
   567  // copyFileWithFs copies a single file from src to dst
   568  func copyFileWithFs(src, dst string, fs filesystem.Filesystem) error {
   569  	var err error
   570  	var srcinfo os.FileInfo
   571  
   572  	srcfd, err := fs.Open(src)
   573  	if err != nil {
   574  		return err
   575  	}
   576  	defer func() {
   577  		if e := srcfd.Close(); e != nil {
   578  			klog.V(4).Infof("err occurred while closing file: %v", e)
   579  		}
   580  	}()
   581  
   582  	dstfd, err := fs.Create(dst)
   583  	if err != nil {
   584  		return err
   585  	}
   586  	defer func() {
   587  		if e := dstfd.Close(); e != nil {
   588  			klog.V(4).Infof("err occurred while closing file: %v", e)
   589  		}
   590  	}()
   591  
   592  	if _, err = io.Copy(dstfd, srcfd); err != nil {
   593  		return err
   594  	}
   595  	if srcinfo, err = fs.Stat(src); err != nil {
   596  		return err
   597  	}
   598  	return fs.Chmod(dst, srcinfo.Mode())
   599  }
   600  
   601  // CopyDirWithFS copies a whole directory recursively
   602  func CopyDirWithFS(src string, dst string, fs filesystem.Filesystem) error {
   603  	var err error
   604  	var fds []os.FileInfo
   605  	var srcinfo os.FileInfo
   606  
   607  	if srcinfo, err = fs.Stat(src); err != nil {
   608  		return err
   609  	}
   610  
   611  	if err = fs.MkdirAll(dst, srcinfo.Mode()); err != nil {
   612  		return err
   613  	}
   614  
   615  	if fds, err = fs.ReadDir(src); err != nil {
   616  		return err
   617  	}
   618  	for _, fd := range fds {
   619  		srcfp := path.Join(src, fd.Name())
   620  		dstfp := path.Join(dst, fd.Name())
   621  
   622  		if fd.IsDir() {
   623  			if err = CopyDirWithFS(srcfp, dstfp, fs); err != nil {
   624  				return err
   625  			}
   626  		} else {
   627  			if err = copyFileWithFs(srcfp, dstfp, fs); err != nil {
   628  				return err
   629  			}
   630  		}
   631  	}
   632  	return nil
   633  }
   634  
   635  // StartSignalWatcher watches for signals and handles the situation before exiting the program
   636  func StartSignalWatcher(watchSignals []os.Signal, handle func(receivedSignal os.Signal)) {
   637  	signals := make(chan os.Signal, 1)
   638  	signal.Notify(signals, watchSignals...)
   639  	defer signal.Stop(signals)
   640  
   641  	receivedSignal := <-signals
   642  	handle(receivedSignal)
   643  }
   644  
   645  // cleanDir cleans the original folder during events like interrupted copy etc
   646  // it leaves the given files behind for later use
   647  func cleanDir(originalPath string, leaveBehindFiles map[string]bool, fs filesystem.Filesystem) error {
   648  	// Open the directory.
   649  	outputDirRead, err := fs.Open(originalPath)
   650  	if err != nil {
   651  		return err
   652  	}
   653  
   654  	// Call Readdir to get all files.
   655  	outputDirFiles, err := outputDirRead.Readdir(0)
   656  	if err != nil {
   657  		return err
   658  	}
   659  
   660  	// Loop over files.
   661  	for _, file := range outputDirFiles {
   662  		if value, ok := leaveBehindFiles[file.Name()]; ok && value {
   663  			continue
   664  		}
   665  		err = fs.RemoveAll(filepath.Join(originalPath, file.Name()))
   666  		if err != nil {
   667  			return err
   668  		}
   669  	}
   670  	return err
   671  }
   672  
   673  // GitSubDir handles subDir for git components using the default filesystem
   674  func GitSubDir(srcPath, destinationPath, subDir string) error {
   675  	return gitSubDir(srcPath, destinationPath, subDir, filesystem.DefaultFs{})
   676  }
   677  
   678  // gitSubDir handles subDir for git components
   679  func gitSubDir(srcPath, destinationPath, subDir string, fs filesystem.Filesystem) error {
   680  	go StartSignalWatcher([]os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt}, func(_ os.Signal) {
   681  		err := cleanDir(destinationPath, map[string]bool{
   682  			"devfile.yaml": true,
   683  		}, fs)
   684  		if err != nil {
   685  			klog.V(4).Infof("error %v occurred while calling handleInterruptedSubDir", err)
   686  		}
   687  		err = fs.RemoveAll(srcPath)
   688  		if err != nil {
   689  			klog.V(4).Infof("error %v occurred during temp folder clean up", err)
   690  		}
   691  	})
   692  
   693  	err := func() error {
   694  		// Open the directory.
   695  		outputDirRead, err := fs.Open(filepath.Join(srcPath, subDir))
   696  		if err != nil {
   697  			return err
   698  		}
   699  		defer func() {
   700  			if err1 := outputDirRead.Close(); err1 != nil {
   701  				klog.V(4).Infof("err occurred while closing temp dir: %v", err1)
   702  
   703  			}
   704  		}()
   705  		// Call Readdir to get all files.
   706  		outputDirFiles, err := outputDirRead.Readdir(0)
   707  		if err != nil {
   708  			return err
   709  		}
   710  
   711  		// Loop over files.
   712  		for outputIndex := range outputDirFiles {
   713  			outputFileHere := outputDirFiles[outputIndex]
   714  
   715  			// Get name of file.
   716  			fileName := outputFileHere.Name()
   717  
   718  			oldPath := filepath.Join(srcPath, subDir, fileName)
   719  
   720  			if outputFileHere.IsDir() {
   721  				err = CopyDirWithFS(oldPath, filepath.Join(destinationPath, fileName), fs)
   722  			} else {
   723  				err = copyFileWithFs(oldPath, filepath.Join(destinationPath, fileName), fs)
   724  			}
   725  
   726  			if err != nil {
   727  				return err
   728  			}
   729  		}
   730  		return nil
   731  	}()
   732  	if err != nil {
   733  		return err
   734  	}
   735  	return fs.RemoveAll(srcPath)
   736  }
   737  
   738  // GetCommandStringFromEnvs creates a string from the given environment variables
   739  func GetCommandStringFromEnvs(envVars []v1alpha2.EnvVar) string {
   740  	var setEnvVariable string
   741  	for i, envVar := range envVars {
   742  		if i == 0 {
   743  			setEnvVariable = "export"
   744  		}
   745  		setEnvVariable = setEnvVariable + fmt.Sprintf(" %v=\"%v\"", envVar.Name, envVar.Value)
   746  	}
   747  	return setEnvVariable
   748  }
   749  
   750  // GetGitOriginPath gets the remote fetch URL from the given git repo
   751  // if the repo is not a git repo, the error is ignored
   752  func GetGitOriginPath(path string) string {
   753  	open, err := git.PlainOpen(path)
   754  	if err != nil {
   755  		return ""
   756  	}
   757  
   758  	remotes, err := open.Remotes()
   759  	if err != nil {
   760  		return ""
   761  	}
   762  
   763  	for _, remote := range remotes {
   764  		if remote.Config().Name == "origin" {
   765  			if len(remote.Config().URLs) > 0 {
   766  				// https://github.com/go-git/go-git/blob/db4233e9e8b3b2e37259ed4e7952faaed16218b9/config/config.go#L549-L550
   767  				// the first URL is the fetch URL
   768  				return remote.Config().URLs[0]
   769  			}
   770  		}
   771  	}
   772  	return ""
   773  }
   774  
   775  // Bool returns pointer to passed boolean
   776  func GetBool(b bool) *bool {
   777  	return &b
   778  }
   779  
   780  // SafeGetBool returns the value of the bool pointer, or false if the pointer is nil
   781  func SafeGetBool(b *bool) bool {
   782  	if b == nil {
   783  		return false
   784  	}
   785  	return *b
   786  }
   787  
   788  // IsPortFree checks if the port on a given address is free to use
   789  func IsPortFree(port int, localAddress string) bool {
   790  	if localAddress == "" {
   791  		localAddress = "127.0.0.1"
   792  	}
   793  	address := fmt.Sprintf("%s:%d", localAddress, port)
   794  	listener, err := net.Listen("tcp", address)
   795  	if err != nil {
   796  		return false
   797  	}
   798  	err = listener.Close()
   799  	return err == nil
   800  }
   801  
   802  // NextFreePort returns the next free port on system, starting at start
   803  // end finishing at end.
   804  // If no port is found in the range [start, end], 0 is returned
   805  func NextFreePort(start, end int, usedPorts []int, address string) (int, error) {
   806  
   807  	port := start
   808  	for {
   809  		for _, usedPort := range usedPorts {
   810  			if usedPort == port {
   811  				return port, nil
   812  			}
   813  		}
   814  		if IsPortFree(port, address) {
   815  			return port, nil
   816  		}
   817  		port++
   818  		if port > end {
   819  			return 0, fmt.Errorf("no free port in range [%d-%d]", start, end)
   820  		}
   821  	}
   822  }
   823  
   824  // WriteToJSONFile writes a struct to json file
   825  func WriteToJSONFile(c interface{}, filename string) error {
   826  	data, err := json.Marshal(c)
   827  	if err != nil {
   828  		return fmt.Errorf("unable to marshal data: %w", err)
   829  	}
   830  
   831  	if err = CreateIfNotExists(filename); err != nil {
   832  		return err
   833  	}
   834  	err = os.WriteFile(filename, data, 0600)
   835  	if err != nil {
   836  		return fmt.Errorf("unable to write data to file %v: %w", c, err)
   837  	}
   838  
   839  	return nil
   840  }
   841  

View as plain text