2021-09-22 18:48:01 +00:00
|
|
|
package shared
|
2019-06-04 13:04:18 +00:00
|
|
|
|
|
|
|
import (
|
2021-09-28 10:56:59 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2021-09-25 23:23:17 +00:00
|
|
|
"fmt"
|
2019-06-24 07:04:30 +00:00
|
|
|
"strings"
|
2019-06-04 13:04:18 +00:00
|
|
|
"time"
|
|
|
|
|
2021-10-12 07:25:13 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
2021-09-27 17:51:55 +00:00
|
|
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
2021-09-23 16:25:51 +00:00
|
|
|
"github.com/woodpecker-ci/woodpecker/server/remote"
|
2019-06-04 13:04:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type configFetcher struct {
|
|
|
|
remote_ remote.Remote
|
|
|
|
user *model.User
|
|
|
|
repo *model.Repo
|
|
|
|
build *model.Build
|
|
|
|
}
|
|
|
|
|
2021-08-20 14:32:52 +00:00
|
|
|
func NewConfigFetcher(remote remote.Remote, user *model.User, repo *model.Repo, build *model.Build) *configFetcher {
|
|
|
|
return &configFetcher{
|
|
|
|
remote_: remote,
|
|
|
|
user: user,
|
|
|
|
repo: repo,
|
|
|
|
build: build,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 00:04:21 +00:00
|
|
|
// configFetchTimeout determine seconds the configFetcher wait until cancel fetch process
|
|
|
|
var configFetchTimeout = 3 // seconds
|
|
|
|
|
2021-09-28 10:56:59 +00:00
|
|
|
// Fetch pipeline config from source forge
|
|
|
|
func (cf *configFetcher) Fetch(ctx context.Context) (files []*remote.FileMeta, err error) {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("Start Fetching config for '%s'", cf.repo.FullName)
|
2021-09-27 21:32:08 +00:00
|
|
|
|
2021-09-28 10:56:59 +00:00
|
|
|
// try to fetch 3 times, timeout is one second longer each time
|
|
|
|
for i := 0; i < 3; i++ {
|
2021-10-19 00:04:21 +00:00
|
|
|
files, err = cf.fetch(ctx, time.Second*time.Duration(configFetchTimeout), strings.TrimSpace(cf.repo.Config))
|
2021-10-19 09:44:49 +00:00
|
|
|
log.Trace().Msgf("%d try failed: %v", i, err)
|
2021-09-28 10:56:59 +00:00
|
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetch config by timeout
|
2021-10-19 00:04:21 +00:00
|
|
|
// TODO: deduplicate code
|
2021-09-28 10:56:59 +00:00
|
|
|
func (cf *configFetcher) fetch(c context.Context, timeout time.Duration, config string) ([]*remote.FileMeta, error) {
|
|
|
|
ctx, cancel := context.WithTimeout(c, timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if len(config) > 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: use user config '%s'", cf.repo.FullName, config)
|
2021-09-28 10:56:59 +00:00
|
|
|
// either a file
|
|
|
|
if !strings.HasSuffix(config, "/") {
|
|
|
|
file, err := cf.remote_.File(ctx, cf.user, cf.repo, cf.build, config)
|
|
|
|
if err == nil && len(file) != 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: found file '%s'", cf.repo.FullName, config)
|
2021-09-28 10:56:59 +00:00
|
|
|
return []*remote.FileMeta{{
|
|
|
|
Name: config,
|
|
|
|
Data: file,
|
|
|
|
}}, nil
|
2019-06-25 11:45:43 +00:00
|
|
|
}
|
2021-09-28 10:56:59 +00:00
|
|
|
}
|
2019-06-25 11:45:43 +00:00
|
|
|
|
2021-09-28 10:56:59 +00:00
|
|
|
// or a folder
|
|
|
|
files, err := cf.remote_.Dir(ctx, cf.user, cf.repo, cf.build, strings.TrimSuffix(config, "/"))
|
|
|
|
if err == nil && len(files) != 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: found %d files in '%s'", cf.repo.FullName, len(files), config)
|
2021-09-28 10:56:59 +00:00
|
|
|
return filterPipelineFiles(files), nil
|
2019-06-04 13:04:18 +00:00
|
|
|
}
|
2021-09-28 10:56:59 +00:00
|
|
|
|
|
|
|
return nil, fmt.Errorf("config '%s' not found: %s", config, err)
|
|
|
|
}
|
|
|
|
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: user did not defined own config follow default procedure", cf.repo.FullName)
|
2021-09-28 10:56:59 +00:00
|
|
|
// no user defined config so try .woodpecker/*.yml -> .woodpecker.yml -> .drone.yml
|
|
|
|
|
|
|
|
// test .woodpecker/ folder
|
|
|
|
// if folder is not supported we will get a "Not implemented" error and continue
|
|
|
|
config = ".woodpecker"
|
|
|
|
files, err := cf.remote_.Dir(ctx, cf.user, cf.repo, cf.build, config)
|
|
|
|
files = filterPipelineFiles(files)
|
|
|
|
if err == nil && len(files) != 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: found %d files in '%s'", cf.repo.FullName, len(files), config)
|
2021-09-28 10:56:59 +00:00
|
|
|
return files, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
config = ".woodpecker.yml"
|
|
|
|
file, err := cf.remote_.File(ctx, cf.user, cf.repo, cf.build, config)
|
|
|
|
if err == nil && len(file) != 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: found file '%s'", cf.repo.FullName, config)
|
2021-09-28 10:56:59 +00:00
|
|
|
return []*remote.FileMeta{{
|
|
|
|
Name: config,
|
|
|
|
Data: file,
|
|
|
|
}}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
config = ".drone.yml"
|
|
|
|
file, err = cf.remote_.File(ctx, cf.user, cf.repo, cf.build, config)
|
|
|
|
if err == nil && len(file) != 0 {
|
2021-10-12 07:25:13 +00:00
|
|
|
log.Trace().Msgf("ConfigFetch[%s]: found file '%s'", cf.repo.FullName, config)
|
2021-09-28 10:56:59 +00:00
|
|
|
return []*remote.FileMeta{{
|
|
|
|
Name: config,
|
|
|
|
Data: file,
|
|
|
|
}}, nil
|
2019-06-04 13:04:18 +00:00
|
|
|
}
|
2021-08-30 20:54:21 +00:00
|
|
|
|
2021-09-28 10:56:59 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, ctx.Err()
|
|
|
|
default:
|
|
|
|
return []*remote.FileMeta{}, fmt.Errorf("ConfigFetcher: Fallback did not found config: %s", err)
|
|
|
|
}
|
2019-06-04 13:04:18 +00:00
|
|
|
}
|
2021-08-30 20:54:21 +00:00
|
|
|
|
|
|
|
func filterPipelineFiles(files []*remote.FileMeta) []*remote.FileMeta {
|
|
|
|
var res []*remote.FileMeta
|
|
|
|
|
|
|
|
for _, file := range files {
|
2021-09-28 12:07:25 +00:00
|
|
|
if strings.HasSuffix(file.Name, ".yml") {
|
2021-08-30 20:54:21 +00:00
|
|
|
res = append(res, file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|