Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore pipelines without config #2949

Merged
merged 12 commits into from
Dec 21, 2023
1 change: 1 addition & 0 deletions docs/docs/91-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Some versions need some changes to the server configuration or the pipeline conf
## `next`

- Removed `WOODPECKER_ROOT_PATH` and `WOODPECKER_ROOT_URL` config variables. Use `WOODPECKER_HOST` with a path instead
- Pipelines without a config file will now be skipped instead of failing

## 2.0.0

Expand Down
2 changes: 2 additions & 0 deletions server/api/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func handlePipelineErr(c *gin.Context, err error) {
} else if errors.Is(err, &pipeline.ErrBadRequest{}) {
c.String(http.StatusBadRequest, "%s", err)
} else if errors.Is(err, pipeline.ErrFiltered) {
// for debugging purpose we add a header
c.Writer.Header().Add("Pipeline-Filtered", "true")
c.Status(http.StatusNoContent)
} else {
_ = c.AbortWithError(http.StatusInternalServerError, err)
Expand Down
43 changes: 25 additions & 18 deletions server/forge/configFetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
// could be adapted to allow the user to supply a list like we do in the defaults
configs := []string{config}

fileMeta, err := cf.getFirstAvailableConfig(ctx, configs, true)
fileMeta, err := cf.getFirstAvailableConfig(ctx, configs)
if err == nil {
return fileMeta, err
}
Expand All @@ -116,7 +116,7 @@

log.Trace().Msgf("ConfigFetch[%s]: user did not define own config, following default procedure", cf.repo.FullName)
// for the order see shared/constants/constants.go
fileMeta, err := cf.getFirstAvailableConfig(ctx, constant.DefaultConfigOrder[:], false)
fileMeta, err := cf.getFirstAvailableConfig(ctx, constant.DefaultConfigOrder[:])
if err == nil {
return fileMeta, err
}
Expand All @@ -141,7 +141,7 @@
return res
}

func (cf *configFetcher) checkPipelineFile(c context.Context, config string) (fileMeta []*types.FileMeta, found bool) {
func (cf *configFetcher) checkPipelineFile(c context.Context, config string) ([]*types.FileMeta, error) {
file, err := cf.forge.File(c, cf.user, cf.repo, cf.pipeline, config)

if err == nil && len(file) != 0 {
Expand All @@ -150,40 +150,47 @@
return []*types.FileMeta{{
Name: config,
Data: file,
}}, true
}}, nil
}

return nil, false
return nil, err
}

func (cf *configFetcher) getFirstAvailableConfig(c context.Context, configs []string, userDefined bool) ([]*types.FileMeta, error) {
userDefinedLog := ""
if userDefined {
userDefinedLog = "user defined"
}

func (cf *configFetcher) getFirstAvailableConfig(c context.Context, configs []string) ([]*types.FileMeta, error) {
var forgeErr []error
for _, fileOrFolder := range configs {
if strings.HasSuffix(fileOrFolder, "/") {
// config is a folder
files, err := cf.forge.Dir(c, cf.user, cf.repo, cf.pipeline, strings.TrimSuffix(fileOrFolder, "/"))
// if folder is not supported we will get a "Not implemented" error and continue
if err != nil && !errors.Is(err, types.ErrNotImplemented) {
log.Error().Err(err).Str("repo", cf.repo.FullName).Str("user", cf.user.Login).Msg("could not get folder from forge")
if err != nil {
if !(errors.Is(err, types.ErrNotImplemented) || errors.Is(err, &types.ErrConfigNotFound{})) {
log.Error().Err(err).Str("repo", cf.repo.FullName).Str("user", cf.user.Login).Msg("could not get folder from forge")
forgeErr = append(forgeErr, err)
}
continue
}
files = filterPipelineFiles(files)
if err == nil && len(files) != 0 {
log.Trace().Msgf("ConfigFetch[%s]: found %d %s files in '%s'", cf.repo.FullName, len(files), userDefinedLog, fileOrFolder)
if len(files) != 0 {
log.Trace().Msgf("ConfigFetch[%s]: found %d files in '%s'", cf.repo.FullName, len(files), fileOrFolder)
return files, nil
}
}

// config is a file
if fileMeta, found := cf.checkPipelineFile(c, fileOrFolder); found {
log.Trace().Msgf("ConfigFetch[%s]: found %s file: '%s'", cf.repo.FullName, userDefinedLog, fileOrFolder)
if fileMeta, err := cf.checkPipelineFile(c, fileOrFolder); err == nil {
log.Trace().Msgf("ConfigFetch[%s]: found file: '%s'", cf.repo.FullName, fileOrFolder)
return fileMeta, nil
} else if !errors.Is(err, &types.ErrConfigNotFound{}) {
forgeErr = append(forgeErr, err)
}
}

// got unexpected errors
if len(forgeErr) != 0 {
return nil, errors.Join(forgeErr...)
}

// nothing found
return nil, fmt.Errorf("%s configs not found searched: %s", userDefinedLog, strings.Join(configs, ", "))
return nil, &types.ErrConfigNotFound{Configs: configs}

Check warning on line 195 in server/forge/configFetcher.go

View check run for this annotation

Codecov / codecov/patch

server/forge/configFetcher.go#L195

Added line #L195 was not covered by tests
}
9 changes: 8 additions & 1 deletion server/forge/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/common"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store"
Expand Down Expand Up @@ -289,7 +290,10 @@
return nil, err
}

cfg, _, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
cfg, resp, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
if err != nil && resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, errors.Join(err, &types.ErrConfigNotFound{Configs: []string{f}})
}

Check warning on line 296 in server/forge/gitea/gitea.go

View check run for this annotation

Codecov / codecov/patch

server/forge/gitea/gitea.go#L295-L296

Added lines #L295 - L296 were not covered by tests
return cfg, err
}

Expand All @@ -314,6 +318,9 @@
if m, _ := filepath.Match(f, e.Path); m && e.Type == "blob" {
data, err := c.File(ctx, u, r, b, e.Path)
if err != nil {
if errors.Is(err, &types.ErrConfigNotFound{}) {
return nil, fmt.Errorf("git tree reported existence of file but we got: %s", err.Error())
}

Check warning on line 323 in server/forge/gitea/gitea.go

View check run for this annotation

Codecov / codecov/patch

server/forge/gitea/gitea.go#L321-L323

Added lines #L321 - L323 were not covered by tests
return nil, fmt.Errorf("multi-pipeline cannot get %s: %w", e.Path, err)
}

Expand Down
15 changes: 13 additions & 2 deletions server/forge/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/url"
Expand All @@ -32,6 +33,7 @@
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/common"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store"
Expand Down Expand Up @@ -226,7 +228,10 @@

opts := new(github.RepositoryContentGetOptions)
opts.Ref = b.Commit
content, _, _, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts)
content, _, resp, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts)
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, errors.Join(err, &types.ErrConfigNotFound{Configs: []string{f}})
}

Check warning on line 234 in server/forge/github/github.go

View check run for this annotation

Codecov / codecov/patch

server/forge/github/github.go#L231-L234

Added lines #L231 - L234 were not covered by tests
if err != nil {
return nil, err
}
Expand All @@ -242,7 +247,10 @@

opts := new(github.RepositoryContentGetOptions)
opts.Ref = b.Commit
_, data, _, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts)
_, data, resp, err := client.Repositories.GetContents(ctx, r.Owner, r.Name, f, opts)
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, errors.Join(err, &types.ErrConfigNotFound{Configs: []string{f}})
}

Check warning on line 253 in server/forge/github/github.go

View check run for this annotation

Codecov / codecov/patch

server/forge/github/github.go#L250-L253

Added lines #L250 - L253 were not covered by tests
if err != nil {
return nil, err
}
Expand All @@ -254,6 +262,9 @@
go func(path string) {
content, err := c.File(ctx, u, r, b, path)
if err != nil {
if errors.Is(err, &types.ErrConfigNotFound{}) {
err = fmt.Errorf("git tree reported existence of file but we got: %s", err.Error())
}

Check warning on line 267 in server/forge/github/github.go

View check run for this annotation

Codecov / codecov/patch

server/forge/github/github.go#L265-L267

Added lines #L265 - L267 were not covered by tests
errc <- err
} else {
fc <- &forge_types.FileMeta{
Expand Down
10 changes: 9 additions & 1 deletion server/forge/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -33,6 +34,7 @@
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/common"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store"
Expand Down Expand Up @@ -351,7 +353,10 @@
if err != nil {
return nil, err
}
file, _, err := client.RepositoryFiles.GetRawFile(_repo.ID, fileName, &gitlab.GetRawFileOptions{Ref: &pipeline.Commit}, gitlab.WithContext(ctx))
file, resp, err := client.RepositoryFiles.GetRawFile(_repo.ID, fileName, &gitlab.GetRawFileOptions{Ref: &pipeline.Commit}, gitlab.WithContext(ctx))
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, errors.Join(err, &types.ErrConfigNotFound{Configs: []string{fileName}})
}

Check warning on line 359 in server/forge/gitlab/gitlab.go

View check run for this annotation

Codecov / codecov/patch

server/forge/gitlab/gitlab.go#L356-L359

Added lines #L356 - L359 were not covered by tests
return file, err
}

Expand Down Expand Up @@ -388,6 +393,9 @@
}
data, err := g.File(ctx, user, repo, pipeline, batch[i].Path)
if err != nil {
if errors.Is(err, &types.ErrConfigNotFound{}) {
return nil, fmt.Errorf("git tree reported existence of file but we got: %s", err.Error())
}

Check warning on line 398 in server/forge/gitlab/gitlab.go

View check run for this annotation

Codecov / codecov/patch

server/forge/gitlab/gitlab.go#L396-L398

Added lines #L396 - L398 were not covered by tests
return nil, err
}
files = append(files, &forge_types.FileMeta{
Expand Down
14 changes: 14 additions & 0 deletions server/forge/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import (
"errors"
"fmt"
"strings"
)

// AuthError represents forge authentication error.
Expand Down Expand Up @@ -56,3 +57,16 @@
_, ok := target.(*ErrIgnoreEvent) //nolint:errorlint
return ok
}

type ErrConfigNotFound struct {
Configs []string
}

func (m *ErrConfigNotFound) Error() string {
return fmt.Sprintf("configs not found: %s", strings.Join(m.Configs, ", "))

Check warning on line 66 in server/forge/types/errors.go

View check run for this annotation

Codecov / codecov/patch

server/forge/types/errors.go#L65-L66

Added lines #L65 - L66 were not covered by tests
}

func (*ErrConfigNotFound) Is(target error) bool {
_, ok := target.(*ErrConfigNotFound) //nolint:errorlint
return ok

Check warning on line 71 in server/forge/types/errors.go

View check run for this annotation

Codecov / codecov/patch

server/forge/types/errors.go#L69-L71

Added lines #L69 - L71 were not covered by tests
}
18 changes: 13 additions & 5 deletions server/pipeline/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

import (
"context"
"errors"
"fmt"
"regexp"

"github.com/rs/zerolog/log"

"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
pipelineErrors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
6543 marked this conversation as resolved.
Show resolved Hide resolved
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
forgeTypes "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
6543 marked this conversation as resolved.
Show resolved Hide resolved
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store"
)
Expand Down Expand Up @@ -64,18 +66,24 @@
// fetch the pipeline file from the forge
configFetcher := forge.NewConfigFetcher(server.Config.Services.Forge, server.Config.Services.Timeout, server.Config.Services.ConfigService, repoUser, repo, pipeline)
forgeYamlConfigs, configFetchErr := configFetcher.Fetch(ctx)

if configFetchErr != nil {
if errors.Is(configFetchErr, &forgeTypes.ErrConfigNotFound{}) {

Check warning on line 69 in server/pipeline/create.go

View check run for this annotation

Codecov / codecov/patch

server/pipeline/create.go#L69

Added line #L69 was not covered by tests
log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("cannot find config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login)
if err := _store.DeletePipeline(pipeline); err != nil {
log.Error().Str("repo", repo.FullName).Err(err).Msg("failed to delete pipeline without config")
}

Check warning on line 73 in server/pipeline/create.go

View check run for this annotation

Codecov / codecov/patch

server/pipeline/create.go#L71-L73

Added lines #L71 - L73 were not covered by tests

return nil, ErrFiltered
} else if configFetchErr != nil {
log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login)

Check warning on line 77 in server/pipeline/create.go

View check run for this annotation

Codecov / codecov/patch

server/pipeline/create.go#L75-L77

Added lines #L75 - L77 were not covered by tests
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName))
}

pipelineItems, parseErr := parsePipeline(_store, pipeline, repoUser, repo, forgeYamlConfigs, nil)
if errors.HasBlockingErrors(parseErr) {
if pipelineErrors.HasBlockingErrors(parseErr) {

Check warning on line 82 in server/pipeline/create.go

View check run for this annotation

Codecov / codecov/patch

server/pipeline/create.go#L82

Added line #L82 was not covered by tests
log.Debug().Str("repo", repo.FullName).Err(parseErr).Msg("failed to parse yaml")
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, parseErr)
} else if parseErr != nil {
pipeline.Errors = errors.GetPipelineErrors(parseErr)
pipeline.Errors = pipelineErrors.GetPipelineErrors(parseErr)

Check warning on line 86 in server/pipeline/create.go

View check run for this annotation

Codecov / codecov/patch

server/pipeline/create.go#L86

Added line #L86 was not covered by tests
}

if len(pipelineItems) == 0 {
Expand Down