github.com/jfrog/frogbot/v2@v2.21.0/utils/params.go (about) 1 package utils 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "github.com/jfrog/jfrog-cli-security/commands/audit/jas" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 15 "github.com/jfrog/frogbot/v2/utils/outputwriter" 16 xrutils "github.com/jfrog/jfrog-cli-security/utils" 17 18 "github.com/jfrog/build-info-go/utils" 19 "github.com/jfrog/froggit-go/vcsclient" 20 "github.com/jfrog/froggit-go/vcsutils" 21 coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" 22 "github.com/jfrog/jfrog-client-go/utils/log" 23 "gopkg.in/yaml.v3" 24 ) 25 26 const ( 27 frogbotConfigDir = ".frogbot" 28 FrogbotConfigFile = "frogbot-config.yml" 29 ) 30 31 var ( 32 errFrogbotConfigNotFound = fmt.Errorf("%s wasn't found in the Frogbot directory and its subdirectories. Assuming all the configuration is stored as environment variables", FrogbotConfigFile) 33 // Possible Config file path's to Frogbot Management repository 34 osFrogbotConfigPath = filepath.Join(frogbotConfigDir, FrogbotConfigFile) 35 ) 36 37 type FrogbotDetails struct { 38 Repositories RepoAggregator 39 ServerDetails *coreconfig.ServerDetails 40 GitClient vcsclient.VcsClient 41 ReleasesRepo string 42 } 43 44 type RepoAggregator []Repository 45 46 // NewRepoAggregator returns an initialized RepoAggregator with an empty repository 47 func newRepoAggregator() RepoAggregator { 48 return RepoAggregator{{Params: Params{Scan: Scan{Projects: []Project{{}}}}}} 49 } 50 51 type Repository struct { 52 Params `yaml:"params,omitempty"` 53 OutputWriter outputwriter.OutputWriter 54 Server coreconfig.ServerDetails 55 } 56 57 func (r *Repository) setOutputWriterDetails() { 58 r.OutputWriter = outputwriter.GetCompatibleOutputWriter(r.Params.GitProvider) 59 r.OutputWriter.SetAvoidExtraMessages(r.Params.AvoidExtraMessages) 60 r.OutputWriter.SetPullRequestCommentTitle(r.Params.PullRequestCommentTitle) 61 } 62 63 type Params struct { 64 Scan `yaml:"scan,omitempty"` 65 Git `yaml:"git,omitempty"` 66 JFrogPlatform `yaml:"jfrogPlatform,omitempty"` 67 } 68 69 func (p *Params) setDefaultsIfNeeded(gitParamsFromEnv *Git, commandName string) error { 70 if err := p.Git.setDefaultsIfNeeded(gitParamsFromEnv, commandName); err != nil { 71 return err 72 } 73 if err := p.JFrogPlatform.setDefaultsIfNeeded(); err != nil { 74 return err 75 } 76 return p.Scan.setDefaultsIfNeeded() 77 } 78 79 type Project struct { 80 InstallCommand string `yaml:"installCommand,omitempty"` 81 PipRequirementsFile string `yaml:"pipRequirementsFile,omitempty"` 82 WorkingDirs []string `yaml:"workingDirs,omitempty"` 83 PathExclusions []string `yaml:"pathExclusions,omitempty"` 84 UseWrapper *bool `yaml:"useWrapper,omitempty"` 85 DepsRepo string `yaml:"repository,omitempty"` 86 InstallCommandName string 87 InstallCommandArgs []string 88 IsRecursiveScan bool 89 } 90 91 func (p *Project) setDefaultsIfNeeded() error { 92 if len(p.WorkingDirs) == 0 { 93 workingDir := getTrimmedEnv(WorkingDirectoryEnv) 94 if workingDir == "" { 95 96 // If no working directories are provided, and none exist in the environment variable, we designate the project's root directory as our sole working directory. 97 // We then execute a recursive scan across the entire project, commencing from the root. 98 workingDir = RootDir 99 p.IsRecursiveScan = true 100 } 101 p.WorkingDirs = append(p.WorkingDirs, workingDir) 102 } 103 if len(p.PathExclusions) == 0 { 104 if p.PathExclusions, _ = readArrayParamFromEnv(PathExclusionsEnv, ";"); len(p.PathExclusions) == 0 { 105 p.PathExclusions = jas.DefaultExcludePatterns 106 } 107 } 108 if p.UseWrapper == nil { 109 useWrapper, err := getBoolEnv(UseWrapperEnv, true) 110 if err != nil { 111 return err 112 } 113 p.UseWrapper = &useWrapper 114 } 115 if p.InstallCommand == "" { 116 p.InstallCommand = getTrimmedEnv(InstallCommandEnv) 117 } 118 if p.InstallCommand != "" { 119 setProjectInstallCommand(p.InstallCommand, p) 120 } 121 if p.PipRequirementsFile == "" { 122 p.PipRequirementsFile = getTrimmedEnv(RequirementsFileEnv) 123 } 124 if p.DepsRepo == "" { 125 p.DepsRepo = getTrimmedEnv(DepsRepoEnv) 126 } 127 return nil 128 } 129 130 type Scan struct { 131 IncludeAllVulnerabilities bool `yaml:"includeAllVulnerabilities,omitempty"` 132 FixableOnly bool `yaml:"fixableOnly,omitempty"` 133 FailOnSecurityIssues *bool `yaml:"failOnSecurityIssues,omitempty"` 134 AvoidPreviousPrCommentsDeletion bool `yaml:"avoidPreviousPrCommentsDeletion,omitempty"` 135 MinSeverity string `yaml:"minSeverity,omitempty"` 136 AllowedLicenses []string `yaml:"allowedLicenses,omitempty"` 137 Projects []Project `yaml:"projects,omitempty"` 138 EmailDetails `yaml:",inline"` 139 } 140 141 type EmailDetails struct { 142 SmtpServer string 143 SmtpPort string 144 SmtpUser string 145 SmtpPassword string 146 EmailReceivers []string `yaml:"emailReceivers,omitempty"` 147 } 148 149 func (s *Scan) SetEmailDetails() error { 150 smtpServerAndPort := getTrimmedEnv(SmtpServerEnv) 151 if smtpServerAndPort == "" { 152 return nil 153 } 154 splittedServerAndPort := strings.Split(smtpServerAndPort, ":") 155 if len(splittedServerAndPort) < 2 { 156 return fmt.Errorf("failed while setting your email details. Could not extract the smtp server and its port from the %s environment variable. Expected format: `smtp.server.com:port`, received: %s", SmtpServerEnv, smtpServerAndPort) 157 } 158 s.SmtpServer = splittedServerAndPort[0] 159 s.SmtpPort = splittedServerAndPort[1] 160 s.SmtpUser = getTrimmedEnv(SmtpUserEnv) 161 s.SmtpPassword = getTrimmedEnv(SmtpPasswordEnv) 162 if s.SmtpUser == "" { 163 return fmt.Errorf("failed while setting your email details. SMTP username is expected, but the %s environment variable is empty", SmtpUserEnv) 164 } 165 if s.SmtpPassword == "" { 166 return fmt.Errorf("failed while setting your email details. SMTP password is expected, but the %s environment variable is empty", SmtpPasswordEnv) 167 } 168 if len(s.EmailReceivers) == 0 { 169 if emailReceiversEnv := getTrimmedEnv(EmailReceiversEnv); emailReceiversEnv != "" { 170 s.EmailReceivers = strings.Split(emailReceiversEnv, ",") 171 } 172 } 173 return nil 174 } 175 176 func (s *Scan) setDefaultsIfNeeded() (err error) { 177 e := &ErrMissingEnv{} 178 if !s.IncludeAllVulnerabilities { 179 if s.IncludeAllVulnerabilities, err = getBoolEnv(IncludeAllVulnerabilitiesEnv, false); err != nil { 180 return 181 } 182 } 183 if !s.AvoidPreviousPrCommentsDeletion { 184 if s.AvoidPreviousPrCommentsDeletion, err = getBoolEnv(AvoidPreviousPrCommentsDeletionEnv, false); err != nil { 185 return 186 } 187 } 188 if !s.FixableOnly { 189 if s.FixableOnly, err = getBoolEnv(FixableOnlyEnv, false); err != nil { 190 return 191 } 192 } 193 if s.FailOnSecurityIssues == nil { 194 var failOnSecurityIssues bool 195 if failOnSecurityIssues, err = getBoolEnv(FailOnSecurityIssuesEnv, true); err != nil { 196 return 197 } 198 s.FailOnSecurityIssues = &failOnSecurityIssues 199 } 200 if s.MinSeverity == "" { 201 if err = readParamFromEnv(MinSeverityEnv, &s.MinSeverity); err != nil && !e.IsMissingEnvErr(err) { 202 return 203 } 204 } 205 if s.MinSeverity, err = xrutils.GetSeveritiesFormat(s.MinSeverity); err != nil { 206 return 207 } 208 if len(s.Projects) == 0 { 209 s.Projects = append(s.Projects, Project{}) 210 } 211 if len(s.AllowedLicenses) == 0 { 212 if s.AllowedLicenses, err = readArrayParamFromEnv(AllowedLicensesEnv, ","); err != nil && !e.IsMissingEnvErr(err) { 213 return 214 } 215 } 216 for i := range s.Projects { 217 if err = s.Projects[i].setDefaultsIfNeeded(); err != nil { 218 return 219 } 220 } 221 err = s.SetEmailDetails() 222 return 223 } 224 225 type JFrogPlatform struct { 226 Watches []string `yaml:"watches,omitempty"` 227 JFrogProjectKey string `yaml:"jfrogProjectKey,omitempty"` 228 } 229 230 func (jp *JFrogPlatform) setDefaultsIfNeeded() (err error) { 231 e := &ErrMissingEnv{} 232 if len(jp.Watches) == 0 { 233 if jp.Watches, err = readArrayParamFromEnv(jfrogWatchesEnv, WatchesDelimiter); err != nil && !e.IsMissingEnvErr(err) { 234 return 235 } 236 } 237 238 if jp.JFrogProjectKey == "" { 239 if err = readParamFromEnv(jfrogProjectEnv, &jp.JFrogProjectKey); err != nil && !e.IsMissingEnvErr(err) { 240 return 241 } 242 // We don't want to return an error from this function if the error is of type ErrMissingEnv because JFrogPlatform environment variables are not mandatory. 243 err = nil 244 } 245 return 246 } 247 248 type Git struct { 249 GitProvider vcsutils.VcsProvider 250 vcsclient.VcsInfo 251 RepoOwner string 252 RepoName string `yaml:"repoName,omitempty"` 253 Branches []string `yaml:"branches,omitempty"` 254 BranchNameTemplate string `yaml:"branchNameTemplate,omitempty"` 255 CommitMessageTemplate string `yaml:"commitMessageTemplate,omitempty"` 256 PullRequestTitleTemplate string `yaml:"pullRequestTitleTemplate,omitempty"` 257 PullRequestCommentTitle string `yaml:"pullRequestCommentTitle,omitempty"` 258 AvoidExtraMessages bool `yaml:"avoidExtraMessages,omitempty"` 259 EmailAuthor string `yaml:"emailAuthor,omitempty"` 260 AggregateFixes bool `yaml:"aggregateFixes,omitempty"` 261 PullRequestDetails vcsclient.PullRequestInfo 262 RepositoryCloneUrl string 263 } 264 265 func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git, commandName string) (err error) { 266 g.RepoOwner = gitParamsFromEnv.RepoOwner 267 g.GitProvider = gitParamsFromEnv.GitProvider 268 g.VcsInfo = gitParamsFromEnv.VcsInfo 269 g.PullRequestDetails = gitParamsFromEnv.PullRequestDetails 270 if g.RepoName == "" { 271 if gitParamsFromEnv.RepoName == "" { 272 return fmt.Errorf("repository name is missing. please set the repository name in your %s file or as the %s environment variable", FrogbotConfigFile, GitRepoEnv) 273 } 274 g.RepoName = gitParamsFromEnv.RepoName 275 } 276 if g.EmailAuthor == "" { 277 if g.EmailAuthor = getTrimmedEnv(GitEmailAuthorEnv); g.EmailAuthor == "" { 278 g.EmailAuthor = frogbotAuthorEmail 279 } 280 } 281 if commandName == ScanPullRequest { 282 if err = g.extractScanPullRequestEnvParams(gitParamsFromEnv); err != nil { 283 return 284 } 285 } 286 if commandName == ScanRepository || commandName == ScanMultipleRepositories { 287 if err = g.extractScanRepositoryEnvParams(gitParamsFromEnv); err != nil { 288 return 289 } 290 } 291 return 292 } 293 294 func (g *Git) extractScanPullRequestEnvParams(gitParamsFromEnv *Git) (err error) { 295 // The Pull Request ID is a mandatory requirement for Frogbot to properly identify and scan the relevant pull request 296 if gitParamsFromEnv.PullRequestDetails.ID == 0 { 297 return errors.New("no Pull Request ID has been provided. Please configure it by using the `JF_GIT_PULL_REQUEST_ID` environment variable") 298 } 299 if g.PullRequestCommentTitle == "" { 300 g.PullRequestCommentTitle = getTrimmedEnv(PullRequestCommentTitleEnv) 301 } 302 g.AvoidExtraMessages, err = getBoolEnv(AvoidExtraMessages, false) 303 return 304 } 305 306 func (g *Git) extractScanRepositoryEnvParams(gitParamsFromEnv *Git) (err error) { 307 // Continue to extract ScanRepository related env params 308 noBranchesProvidedViaConfig := len(g.Branches) == 0 309 noBranchesProvidedViaEnv := len(gitParamsFromEnv.Branches) == 0 310 if noBranchesProvidedViaConfig { 311 if noBranchesProvidedViaEnv { 312 return errors.New("no branches were provided. Please set your branches using the `JF_GIT_BASE_BRANCH` environment variable or by configuring them in the frogbot-config.yml file") 313 } 314 g.Branches = gitParamsFromEnv.Branches 315 } 316 if g.BranchNameTemplate == "" { 317 branchTemplate := getTrimmedEnv(BranchNameTemplateEnv) 318 if err = validateHashPlaceHolder(branchTemplate); err != nil { 319 return 320 } 321 g.BranchNameTemplate = branchTemplate 322 } 323 if g.CommitMessageTemplate == "" { 324 g.CommitMessageTemplate = getTrimmedEnv(CommitMessageTemplateEnv) 325 } 326 if g.PullRequestTitleTemplate == "" { 327 g.PullRequestTitleTemplate = getTrimmedEnv(PullRequestTitleTemplateEnv) 328 } 329 if !g.AggregateFixes { 330 if g.AggregateFixes, err = getBoolEnv(GitAggregateFixesEnv, false); err != nil { 331 return 332 } 333 } 334 return 335 } 336 337 func validateHashPlaceHolder(template string) error { 338 if template == "" { 339 return nil 340 } 341 if !strings.Contains(template, BranchHashPlaceHolder) { 342 return fmt.Errorf("branch name template must contain %s, provided: %s", BranchHashPlaceHolder, template) 343 } 344 return nil 345 } 346 347 func GetFrogbotDetails(commandName string) (frogbotDetails *FrogbotDetails, err error) { 348 // Get server and git details 349 jfrogServer, err := extractJFrogCredentialsFromEnvs() 350 if err != nil { 351 return 352 } 353 gitParamsFromEnv, err := extractGitParamsFromEnvs(commandName) 354 if err != nil { 355 return 356 } 357 358 defer func() { 359 err = errors.Join(err, SanitizeEnv()) 360 }() 361 362 // Build a version control client for REST API requests 363 client, err := vcsclient. 364 NewClientBuilder(gitParamsFromEnv.GitProvider). 365 ApiEndpoint(strings.TrimSuffix(gitParamsFromEnv.APIEndpoint, "/")). 366 Token(gitParamsFromEnv.Token). 367 Project(gitParamsFromEnv.Project). 368 Logger(log.GetLogger()). 369 Username(gitParamsFromEnv.Username). 370 Build() 371 if err != nil { 372 return 373 } 374 375 configAggregator, err := getConfigAggregator(client, gitParamsFromEnv, jfrogServer, commandName) 376 if err != nil { 377 return 378 } 379 380 frogbotDetails = &FrogbotDetails{Repositories: configAggregator, GitClient: client, ServerDetails: jfrogServer, ReleasesRepo: os.Getenv(jfrogReleasesRepoEnv)} 381 return 382 } 383 384 // getConfigAggregator returns a RepoAggregator based on frogbot-config.yml and environment variables. 385 func getConfigAggregator(gitClient vcsclient.VcsClient, gitParamsFromEnv *Git, jfrogServer *coreconfig.ServerDetails, commandName string) (RepoAggregator, error) { 386 configFileContent, err := getConfigFileContent(gitClient, gitParamsFromEnv, commandName) 387 if err != nil { 388 return nil, err 389 } 390 if configFileContent != nil { 391 log.Debug(fmt.Sprintf("The content of %s that will be used is:\n%s", FrogbotConfigFile, string(configFileContent))) 392 } 393 return BuildRepoAggregator(gitClient, configFileContent, gitParamsFromEnv, jfrogServer, commandName) 394 } 395 396 // getConfigFileContent retrieves the content of the frogbot-config.yml file 397 func getConfigFileContent(gitClient vcsclient.VcsClient, gitParamsFromEnv *Git, commandName string) ([]byte, error) { 398 var errMissingConfig *ErrMissingConfig 399 400 if commandName == ScanRepository || commandName == ScanMultipleRepositories { 401 configFileContent, err := ReadConfigFromFileSystem(osFrogbotConfigPath) 402 if err != nil && !errors.As(err, &errMissingConfig) { 403 return nil, err 404 } 405 if configFileContent != nil { 406 return configFileContent, nil 407 } 408 } 409 410 configFileContent, err := readConfigFromTarget(gitClient, gitParamsFromEnv) 411 if errors.As(err, &errMissingConfig) { 412 // Avoid returning an error if the frogbot-config.yml file is missing. 413 // If an error occurs because the file is missing, we will create an environment variable-based configuration aggregator instead. 414 return nil, nil 415 } 416 return configFileContent, err 417 } 418 419 // BuildRepoAggregator receives the content of a frogbot-config.yml file, along with the Git (built from environment variables) and ServerDetails parameters. 420 // Returns a RepoAggregator instance with all the defaults and necessary fields. 421 func BuildRepoAggregator(gitClient vcsclient.VcsClient, configFileContent []byte, gitParamsFromEnv *Git, server *coreconfig.ServerDetails, commandName string) (resultAggregator RepoAggregator, err error) { 422 var cleanAggregator RepoAggregator 423 // Unmarshal the frogbot-config.yml file if exists 424 if cleanAggregator, err = unmarshalFrogbotConfigYaml(configFileContent); err != nil { 425 return 426 } 427 for _, repository := range cleanAggregator { 428 repository.Server = *server 429 if err = repository.Params.setDefaultsIfNeeded(gitParamsFromEnv, commandName); err != nil { 430 return 431 } 432 repository.setOutputWriterDetails() 433 repository.OutputWriter.SetSizeLimit(gitClient) 434 resultAggregator = append(resultAggregator, repository) 435 } 436 437 return 438 } 439 440 // unmarshalFrogbotConfigYaml uses the yaml.Unmarshaler interface to parse the yamlContent. 441 // If there is no config file, the function returns a RepoAggregator with an empty repository. 442 func unmarshalFrogbotConfigYaml(yamlContent []byte) (result RepoAggregator, err error) { 443 if len(yamlContent) == 0 { 444 result = newRepoAggregator() 445 return 446 } 447 err = yaml.Unmarshal(yamlContent, &result) 448 return 449 } 450 451 func extractJFrogCredentialsFromEnvs() (*coreconfig.ServerDetails, error) { 452 server := coreconfig.ServerDetails{} 453 platformUrl := strings.TrimSuffix(getTrimmedEnv(JFrogUrlEnv), "/") 454 xrUrl := strings.TrimSuffix(getTrimmedEnv(jfrogXrayUrlEnv), "/") 455 rtUrl := strings.TrimSuffix(getTrimmedEnv(jfrogArtifactoryUrlEnv), "/") 456 if xrUrl != "" && rtUrl != "" { 457 server.XrayUrl = xrUrl + "/" 458 server.ArtifactoryUrl = rtUrl + "/" 459 } else { 460 if platformUrl == "" { 461 return nil, fmt.Errorf("%s or %s and %s environment variables are missing", JFrogUrlEnv, jfrogXrayUrlEnv, jfrogArtifactoryUrlEnv) 462 } 463 server.Url = platformUrl + "/" 464 server.XrayUrl = platformUrl + "/xray/" 465 server.ArtifactoryUrl = platformUrl + "/artifactory/" 466 } 467 468 password := getTrimmedEnv(JFrogPasswordEnv) 469 user := getTrimmedEnv(JFrogUserEnv) 470 if password != "" && user != "" { 471 server.User = user 472 server.Password = password 473 } else if accessToken := getTrimmedEnv(JFrogTokenEnv); accessToken != "" { 474 server.AccessToken = accessToken 475 } else { 476 return nil, fmt.Errorf("%s and %s or %s environment variables are missing", JFrogUserEnv, JFrogPasswordEnv, JFrogTokenEnv) 477 } 478 return &server, nil 479 } 480 481 func extractGitParamsFromEnvs(commandName string) (*Git, error) { 482 e := &ErrMissingEnv{} 483 var err error 484 gitEnvParams := &Git{} 485 // Branch & Repo names are mandatory variables. 486 // Must be set in the frogbot-config.yml or as an environment variables. 487 // Validation performed later 488 // Set the base branch name 489 var branch string 490 if err = readParamFromEnv(GitBaseBranchEnv, &branch); err != nil && !e.IsMissingEnvErr(err) { 491 return nil, err 492 } 493 if branch != "" { 494 gitEnvParams.Branches = []string{branch} 495 } 496 // Non-mandatory Git Api Endpoint, if not set, default values will be used. 497 if err = readParamFromEnv(GitApiEndpointEnv, &gitEnvParams.APIEndpoint); err != nil && !e.IsMissingEnvErr(err) { 498 return nil, err 499 } 500 if err = verifyValidApiEndpoint(gitEnvParams.APIEndpoint); err != nil { 501 return nil, err 502 } 503 // [Mandatory] Set the Git provider 504 if gitEnvParams.GitProvider, err = extractVcsProviderFromEnv(); err != nil { 505 return nil, err 506 } 507 // [Mandatory] Set the git repository owner name (organization) 508 if err = readParamFromEnv(GitRepoOwnerEnv, &gitEnvParams.RepoOwner); err != nil { 509 return nil, err 510 } 511 // [Mandatory] Set the access token to the git provider 512 if err = readParamFromEnv(GitTokenEnv, &gitEnvParams.Token); err != nil { 513 return nil, err 514 } 515 516 // [Mandatory] Set the repository name, except for multi repository. 517 if err = readParamFromEnv(GitRepoEnv, &gitEnvParams.RepoName); err != nil && commandName != ScanMultipleRepositories { 518 return nil, err 519 } 520 521 // Set Bitbucket Server username 522 // Mandatory only for Bitbucket Server, this authentication detail is required for performing git operations. 523 if err = readParamFromEnv(GitUsernameEnv, &gitEnvParams.Username); err != nil && !e.IsMissingEnvErr(err) { 524 return nil, err 525 } 526 // Set Azure Repos Project name 527 // Mandatory for Azure Repos only 528 if err = readParamFromEnv(GitProjectEnv, &gitEnvParams.Project); err != nil && gitEnvParams.GitProvider == vcsutils.AzureRepos { 529 return nil, err 530 } 531 if envPrId := getTrimmedEnv(GitPullRequestIDEnv); envPrId != "" { 532 var convertedPrId int 533 if convertedPrId, err = strconv.Atoi(envPrId); err != nil { 534 return nil, fmt.Errorf("failed parsing %s environment variable as a number. The received environment is : %s", GitPullRequestIDEnv, envPrId) 535 } 536 gitEnvParams.PullRequestDetails = vcsclient.PullRequestInfo{ID: int64(convertedPrId)} 537 } 538 539 return gitEnvParams, nil 540 } 541 542 func verifyValidApiEndpoint(apiEndpoint string) error { 543 // Empty string will resolve to default values. 544 if apiEndpoint == "" { 545 return nil 546 } 547 parsedUrl, err := url.Parse(apiEndpoint) 548 if err != nil { 549 return err 550 } 551 if parsedUrl.Scheme == "" { 552 return errors.New("the given API endpoint is invalid. Please note that the API endpoint format should be provided with the 'HTTPS' protocol as a prefix") 553 } 554 return nil 555 } 556 557 func readArrayParamFromEnv(envKey, delimiter string) ([]string, error) { 558 var envValue string 559 var err error 560 e := &ErrMissingEnv{} 561 if err = readParamFromEnv(envKey, &envValue); err != nil && !e.IsMissingEnvErr(err) { 562 return nil, err 563 } 564 if envValue == "" { 565 return nil, &ErrMissingEnv{VariableName: envKey} 566 } 567 // Remove spaces if exists 568 envValue = strings.ReplaceAll(envValue, " ", "") 569 return strings.Split(envValue, delimiter), nil 570 } 571 572 func readParamFromEnv(envKey string, paramValue *string) error { 573 *paramValue = getTrimmedEnv(envKey) 574 if *paramValue == "" { 575 return &ErrMissingEnv{VariableName: envKey} 576 } 577 return nil 578 } 579 580 func getTrimmedEnv(envKey string) string { 581 return strings.TrimSpace(os.Getenv(envKey)) 582 } 583 584 func extractVcsProviderFromEnv() (vcsutils.VcsProvider, error) { 585 vcsProvider := getTrimmedEnv(GitProvider) 586 switch vcsProvider { 587 case string(GitHub): 588 return vcsutils.GitHub, nil 589 case string(GitLab): 590 return vcsutils.GitLab, nil 591 // For backward compatibility, we are accepting also "bitbucket server" 592 case string(BitbucketServer), "bitbucket server": 593 return vcsutils.BitbucketServer, nil 594 case string(AzureRepos): 595 return vcsutils.AzureRepos, nil 596 } 597 return 0, fmt.Errorf("%s should be one of: '%s', '%s', '%s' or '%s'", GitProvider, GitHub, GitLab, BitbucketServer, AzureRepos) 598 } 599 600 func SanitizeEnv() error { 601 for _, env := range os.Environ() { 602 if !strings.HasPrefix(env, "JF_") { 603 continue 604 } 605 envSplit := strings.Split(env, "=") 606 if err := os.Unsetenv(envSplit[0]); err != nil { 607 return err 608 } 609 } 610 return nil 611 } 612 613 // ReadConfigFromFileSystem looks for .frogbot/frogbot-config.yml from the given path and return its content. The path is relative and starts from the root of the project. 614 // If the config file is not found in the relative path, it will search in parent dirs. 615 func ReadConfigFromFileSystem(configRelativePath string) (configFileContent []byte, err error) { 616 log.Debug("Reading config from file system. Looking for", osFrogbotConfigPath) 617 fullConfigDirPath, err := filepath.Abs(configRelativePath) 618 if err != nil { 619 return nil, err 620 } 621 622 // Look for the frogbot-config.yml file in fullConfigPath 623 exist, err := utils.IsFileExists(fullConfigDirPath, false) 624 if !exist || err != nil { 625 // Look for the frogbot-config.yml in fullConfigPath parents dirs 626 log.Debug(FrogbotConfigFile, "wasn't found in "+fullConfigDirPath+". Searching for it in upstream directories") 627 if fullConfigDirPath, err = utils.FindFileInDirAndParents(fullConfigDirPath, configRelativePath); err != nil { 628 return nil, &ErrMissingConfig{errFrogbotConfigNotFound.Error()} 629 } 630 fullConfigDirPath = filepath.Join(fullConfigDirPath, configRelativePath) 631 } 632 633 log.Debug(FrogbotConfigFile, "found in", fullConfigDirPath) 634 configFileContent, err = os.ReadFile(filepath.Clean(fullConfigDirPath)) 635 if err != nil { 636 err = fmt.Errorf("an error occurd while reading the %s file at: %s\n%s", FrogbotConfigFile, configRelativePath, err.Error()) 637 } 638 return 639 } 640 641 func setProjectInstallCommand(installCommand string, project *Project) { 642 parts := strings.Fields(installCommand) 643 if len(parts) > 1 { 644 project.InstallCommandArgs = parts[1:] 645 } 646 project.InstallCommandName = parts[0] 647 } 648 649 func getBoolEnv(envKey string, defaultValue bool) (bool, error) { 650 envValue := getTrimmedEnv(envKey) 651 if envValue != "" { 652 parsedEnv, err := strconv.ParseBool(envValue) 653 if err != nil { 654 return false, fmt.Errorf("the value of the %s environment is expected to be either TRUE or FALSE. The value received however is %s", envKey, envValue) 655 } 656 return parsedEnv, nil 657 } 658 659 return defaultValue, nil 660 } 661 662 // readConfigFromTarget reads the .frogbot/frogbot-config.yml from the target repository 663 func readConfigFromTarget(client vcsclient.VcsClient, gitParamsFromEnv *Git) (configContent []byte, err error) { 664 // Extract repository details from Git parameters 665 repoName := gitParamsFromEnv.RepoName 666 repoOwner := gitParamsFromEnv.RepoOwner 667 branches := gitParamsFromEnv.Branches 668 669 if repoName == "" && repoOwner == "" { 670 return 671 } 672 673 log.Debug("Attempting to download", FrogbotConfigFile, "from", repoOwner+"/"+repoName) 674 675 var branch string 676 if len(branches) == 0 { 677 log.Debug(GitBaseBranchEnv, "is missing. Assuming that the", FrogbotConfigFile, "file exists on default branch") 678 } else { 679 // We encounter this scenario when the JF_GIT_BASE_BRANCH is defined. In this situation, we have only one branch. 680 branch = branches[0] 681 log.Debug("The", FrogbotConfigFile, "will be downloaded from", branch, "branch") 682 } 683 684 // Construct the path to the frogbot-config.yml file in the repository 685 gitFrogbotConfigPath := fmt.Sprintf("%s/%s", frogbotConfigDir, FrogbotConfigFile) 686 687 // Download the frogbot-config.yml file from the repository 688 var statusCode int 689 configContent, statusCode, err = client.DownloadFileFromRepo(context.Background(), repoOwner, repoName, branch, gitFrogbotConfigPath) 690 691 // Handle different HTTP status codes 692 switch statusCode { 693 case http.StatusOK: 694 log.Info(fmt.Sprintf("Successfully downloaded %s file from <%s/%s/%s>", FrogbotConfigFile, repoOwner, repoName, branch)) 695 case http.StatusNotFound: 696 log.Debug(fmt.Sprintf("The %s file wasn't recognized in <%s/%s>", gitFrogbotConfigPath, repoOwner, repoName)) 697 // If .frogbot/frogbot-config.yml isn't found, return an ErrMissingConfig 698 configContent = nil 699 err = &ErrMissingConfig{errFrogbotConfigNotFound.Error()} 700 case http.StatusUnauthorized: 701 log.Warn("Your credentials seem to be invalid. If you are using an on-premises Git provider, please set the API endpoint of your Git provider using the 'JF_GIT_API_ENDPOINT' environment variable (example: 'https://gitlab.example.com'). Additionally, make sure that the provided credentials have the required Git permissions.") 702 } 703 return 704 }