github.com/kubeshop/testkube@v1.17.23/pkg/executor/content/fetcher.go (about)

     1  package content
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"net/url"
    13  
    14  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    15  	"github.com/kubeshop/testkube/pkg/executor/output"
    16  	"github.com/kubeshop/testkube/pkg/git"
    17  	"github.com/kubeshop/testkube/pkg/ui"
    18  
    19  	"github.com/kubeshop/testkube/pkg/http"
    20  )
    21  
    22  // NewFetcher returns new file/dir fetcher based on given directory path
    23  func NewFetcher(path string) Fetcher {
    24  	return Fetcher{
    25  		path: path,
    26  	}
    27  }
    28  
    29  type Fetcher struct {
    30  	path string
    31  }
    32  
    33  func (f Fetcher) Fetch(content *testkube.TestContent) (path string, err error) {
    34  	if content == nil {
    35  		output.PrintLog(fmt.Sprintf("%s Fetch - empty content, make sure test content has valid data structure and is not nil", ui.IconCross))
    36  		return "", fmt.Errorf("fetch - empty content, make sure test content has valid data structure and is not nil")
    37  	}
    38  
    39  	if content.Repository != nil && content.Repository.CertificateSecret != "" {
    40  		output.PrintLog(fmt.Sprintf("%s Using certificate for git authentication from secret: %s", ui.IconBox, content.Repository.CertificateSecret))
    41  		f.configureUseOfCertificate()
    42  	}
    43  
    44  	output.PrintLog(fmt.Sprintf("%s Fetching test content from %s...", ui.IconBox, content.Type_))
    45  
    46  	switch testkube.TestContentType(content.Type_) {
    47  	case testkube.TestContentTypeFileURI:
    48  		return f.FetchURI(content.Uri)
    49  	case testkube.TestContentTypeString:
    50  		return f.FetchString(content.Data)
    51  	case testkube.TestContentTypeGitFile:
    52  		return f.FetchGitFile(content.Repository)
    53  	case testkube.TestContentTypeGitDir:
    54  		return f.FetchGitDir(content.Repository)
    55  	case testkube.TestContentTypeGit:
    56  		return f.FetchGit(content.Repository)
    57  	case testkube.TestContentTypeEmpty:
    58  		output.PrintLog(fmt.Sprintf("%s Empty content type", ui.IconCross))
    59  		return path, nil
    60  	default:
    61  		output.PrintLog(fmt.Sprintf("%s Unhandled content type: '%s'", ui.IconCross, content.Type_))
    62  		return path, fmt.Errorf("unhandled content type: '%s'", content.Type_)
    63  	}
    64  }
    65  
    66  // FetchString stores string content as file
    67  func (f Fetcher) FetchString(str string) (path string, err error) {
    68  	return f.saveTempFile(strings.NewReader(str))
    69  }
    70  
    71  // FetchURI stores uri as local file
    72  func (f Fetcher) FetchURI(uri string) (path string, err error) {
    73  	client := http.NewClient()
    74  	resp, err := client.Get(uri)
    75  	if err != nil {
    76  		output.PrintLog(fmt.Sprintf("%s Failed to fetch test content: %s", ui.IconCross, err.Error()))
    77  		return path, err
    78  	}
    79  	defer resp.Body.Close()
    80  
    81  	return f.saveTempFile(resp.Body)
    82  }
    83  
    84  // FetchGitDir returns path to locally checked out git repo with partial path
    85  func (f Fetcher) FetchGitDir(repo *testkube.Repository) (path string, err error) {
    86  	uri, authHeader, err := f.gitURI(repo)
    87  	if err != nil {
    88  		output.PrintLog(fmt.Sprintf("%s Failed to fetch git dir: %s", ui.IconCross, err.Error()))
    89  		return path, err
    90  	}
    91  
    92  	// if path not set make full repo checkout
    93  	if repo.Path == "" || repo.WorkingDir != "" {
    94  		path, err := git.Checkout(uri, authHeader, repo.Branch, repo.Commit, f.path)
    95  		if err != nil {
    96  			output.PrintLog(fmt.Sprintf("%s Failed to fetch git dir: %s", ui.IconCross, err.Error()))
    97  			return path, fmt.Errorf("failed to fetch git dir: %w", err)
    98  		}
    99  		output.PrintLog(fmt.Sprintf("%s Test content fetched to path %s", ui.IconCheckMark, path))
   100  		return path, nil
   101  	}
   102  
   103  	path, err = git.PartialCheckout(uri, authHeader, repo.Path, repo.Branch, repo.Commit, f.path)
   104  	if err != nil {
   105  		output.PrintLog(fmt.Sprintf("%s Failed to do partial checkout on git dir: %s", ui.IconCross, err.Error()))
   106  		return path, fmt.Errorf("failed to do partial checkout on git dir: %w", err)
   107  	}
   108  	output.PrintLog(fmt.Sprintf("%s Test content fetched to path %s", ui.IconCheckMark, path))
   109  	return path, nil
   110  }
   111  
   112  // FetchGitFile returns path to git based file saved in local temp directory
   113  func (f Fetcher) FetchGitFile(repo *testkube.Repository) (path string, err error) {
   114  	uri, authHeader, err := f.gitURI(repo)
   115  	if err != nil {
   116  		output.PrintLog(fmt.Sprintf("%s Failed to fetch git file: %s", ui.IconCross, err.Error()))
   117  		return path, err
   118  	}
   119  
   120  	repoPath, err := git.Checkout(uri, authHeader, repo.Branch, repo.Commit, f.path)
   121  	if err != nil {
   122  		output.PrintLog(fmt.Sprintf("%s Failed to checkout git file: %s", ui.IconCross, err.Error()))
   123  		return path, err
   124  	}
   125  
   126  	path = filepath.Join(repoPath, repo.Path)
   127  	output.PrintLog(fmt.Sprintf("%s Test content fetched to path %s", ui.IconCheckMark, path))
   128  	return path, nil
   129  }
   130  
   131  // FetchGit returns path to git based file or dir saved in local temp directory
   132  func (f Fetcher) FetchGit(repo *testkube.Repository) (path string, err error) {
   133  	uri, authHeader, err := f.gitURI(repo)
   134  	if err != nil {
   135  		output.PrintLog(fmt.Sprintf("%s Failed to fetch git: %s", ui.IconCross, err.Error()))
   136  		return path, err
   137  	}
   138  
   139  	// if path not set make full repo checkout
   140  	if repo.Path == "" || repo.WorkingDir != "" {
   141  		path, err := git.Checkout(uri, authHeader, repo.Branch, repo.Commit, f.path)
   142  		if err != nil {
   143  			output.PrintLog(fmt.Sprintf("%s Failed to fetch git: %s", ui.IconCross, err.Error()))
   144  			return path, fmt.Errorf("failed to fetch git: %w", err)
   145  		}
   146  
   147  		if repo.Path != "" {
   148  			fileInfo, err := os.Stat(filepath.Join(path, repo.Path))
   149  			if err != nil {
   150  				output.PrintLog(fmt.Sprintf("%s Failed to get file stat: %s", ui.IconCross, err.Error()))
   151  				return path, fmt.Errorf("failed to get file stat: %w", err)
   152  			}
   153  
   154  			if !fileInfo.IsDir() {
   155  				path = filepath.Join(path, repo.Path)
   156  			}
   157  		}
   158  
   159  		output.PrintLog(fmt.Sprintf("%s Test content fetched to path %s", ui.IconCheckMark, path))
   160  		return path, nil
   161  	}
   162  
   163  	path, err = git.PartialCheckout(uri, authHeader, repo.Path, repo.Branch, repo.Commit, f.path)
   164  	if err != nil {
   165  		output.PrintLog(fmt.Sprintf("%s Failed to do partial checkout on git: %s", ui.IconCross, err.Error()))
   166  		return path, fmt.Errorf("failed to do partial checkout on git: %w", err)
   167  	}
   168  
   169  	output.PrintLog(fmt.Sprintf("%s Test content fetched to path %s", ui.IconCheckMark, path))
   170  	return path, nil
   171  }
   172  
   173  // gitUri merge creds with git uri
   174  func (f Fetcher) gitURI(repo *testkube.Repository) (uri, authHeader string, err error) {
   175  	if repo.AuthType == string(testkube.GitAuthTypeHeader) {
   176  		if repo.Token != "" {
   177  			authHeader = fmt.Sprintf("Authorization: Bearer %s", repo.Token)
   178  			return repo.Uri, authHeader, nil
   179  		}
   180  	}
   181  
   182  	if repo.AuthType == string(testkube.GitAuthTypeBasic) || repo.AuthType == "" {
   183  		if repo.Username != "" || repo.Token != "" {
   184  			gitURI, err := url.Parse(repo.Uri)
   185  			if err != nil {
   186  				return "", "", err
   187  			}
   188  
   189  			gitURI.User = url.UserPassword(repo.Username, repo.Token)
   190  			return gitURI.String(), "", nil
   191  		}
   192  	}
   193  
   194  	return repo.Uri, "", nil
   195  }
   196  
   197  func (f Fetcher) saveTempFile(reader io.Reader) (path string, err error) {
   198  	var tmpFile *os.File
   199  	filename := "test-content"
   200  	if f.path == "" {
   201  		tmpFile, err = os.CreateTemp("", filename)
   202  	} else {
   203  		tmpFile, err = os.Create(filepath.Join(f.path, filename))
   204  	}
   205  	if err != nil {
   206  		output.PrintLog(fmt.Sprintf("%s Failed to save test content: %s", ui.IconCross, err.Error()))
   207  		return "", err
   208  	}
   209  	defer tmpFile.Close()
   210  	_, err = io.Copy(tmpFile, reader)
   211  
   212  	output.PrintLog(fmt.Sprintf("%s Content saved to path %s", ui.IconCheckMark, tmpFile.Name()))
   213  	return tmpFile.Name(), err
   214  }
   215  
   216  func (f Fetcher) configureUseOfCertificate() error {
   217  	const certsPath = "/etc/certs"
   218  	const certExtension = ".crt"
   219  	var certificatePath string
   220  	output.PrintLog(fmt.Sprintf("%s Fetching certificate from path %s", ui.IconCheckMark, certsPath))
   221  	err := filepath.Walk(certsPath, func(path string, info os.FileInfo, err error) error {
   222  		if err != nil {
   223  			output.PrintLog(fmt.Sprintf("%s Failed to walk through %s to find certificates: %s", ui.IconCross, certsPath, err.Error()))
   224  			return err
   225  		}
   226  		if filepath.Ext(path) == certExtension {
   227  			output.PrintLog(fmt.Sprintf("%s Found certificate %s", ui.IconCheckMark, path))
   228  			certificatePath = path
   229  		}
   230  		return nil
   231  	})
   232  	if err != nil || certificatePath == "" {
   233  		output.PrintLog(fmt.Sprintf("%s Failed to find certificate in %s: %s", ui.IconCross, certsPath, err.Error()))
   234  		return err
   235  	}
   236  	gitConfigCommand := fmt.Sprintf("git config --global http.sslCAInfo %s", certificatePath)
   237  	out, err := exec.Command("/bin/sh", "-c", gitConfigCommand).Output()
   238  	if err != nil {
   239  		output.PrintLog(fmt.Sprintf("%s Failed to configure git to use certificate %s, output:%s \n Error: %s", ui.IconCross, certificatePath, out, err.Error()))
   240  		return err
   241  	}
   242  	output.PrintLog(fmt.Sprintf("%s Configured git to use certificate: %s", ui.IconCheckMark, certificatePath))
   243  
   244  	return nil
   245  }
   246  
   247  // CalculateGitContentType returns the type of the git test source
   248  // Deprecated: use git instead
   249  func (f Fetcher) CalculateGitContentType(repo testkube.Repository) (string, error) {
   250  	if repo.Uri == "" || repo.Path == "" {
   251  		return "", errors.New("repository uri and path should be populated")
   252  	}
   253  
   254  	dir, err := os.MkdirTemp("", "temp-git-files")
   255  	if err != nil {
   256  		return "", fmt.Errorf("could not create temporary directory for CalculateGitContentType: %s", err.Error())
   257  	}
   258  	// this will not overwrite the original path given how we used a value receiver for this function
   259  	f.path = dir
   260  	defer func() {
   261  		if err := os.RemoveAll(dir); err != nil {
   262  			output.PrintLog(fmt.Sprintf("%s Could not clean up after CalculateGitContentType: %s", ui.IconWarning, err.Error()))
   263  		}
   264  	}()
   265  
   266  	path, err := f.FetchGitFile(&repo)
   267  	if err != nil {
   268  		return "", fmt.Errorf("could not fetch from git: %w", err)
   269  	}
   270  
   271  	fileInfo, err := os.Stat(path)
   272  	if err != nil {
   273  		return "", fmt.Errorf("could not check path %s: %w", path, err)
   274  	}
   275  
   276  	if fileInfo.IsDir() {
   277  		return string(testkube.TestContentTypeGitDir), nil
   278  	}
   279  	return string(testkube.TestContentTypeGitFile), nil
   280  }
   281  
   282  // GetPathAndWorkingDir returns path to git based file or dir saved in local temp directory and working dir
   283  func GetPathAndWorkingDir(content *testkube.TestContent, dataDir string) (path, workingDir string, err error) {
   284  	basePath, err := filepath.Abs(dataDir)
   285  	if err != nil {
   286  		basePath = dataDir
   287  	}
   288  	if content != nil {
   289  		switch content.Type_ {
   290  		case string(testkube.TestContentTypeString), string(testkube.TestContentTypeFileURI):
   291  			path = filepath.Join(basePath, "test-content")
   292  		case string(testkube.TestContentTypeGitFile), string(testkube.TestContentTypeGitDir), string(testkube.TestContentTypeGit):
   293  			path = filepath.Join(basePath, "repo")
   294  			if content.Repository != nil {
   295  				if content.Repository.WorkingDir != "" {
   296  					workingDir = filepath.Join(path, content.Repository.WorkingDir)
   297  				}
   298  				path = filepath.Join(path, content.Repository.Path)
   299  			}
   300  		}
   301  	}
   302  
   303  	return path, workingDir, err
   304  }