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 }