mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-22 08:27:06 +00:00
Remove multipart logger (#3200)
This commit is contained in:
parent
685907ddf6
commit
45bf8600ef
7 changed files with 24 additions and 261 deletions
|
@ -23,21 +23,16 @@ import (
|
||||||
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
|
||||||
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/multipart"
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Runner) createLogger(logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.LogFunc {
|
func (r *Runner) createLogger(logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger {
|
||||||
return func(step *backend.Step, rc multipart.Reader) error {
|
return func(step *backend.Step, rc io.Reader) error {
|
||||||
loglogger := logger.With().
|
loglogger := logger.With().
|
||||||
Str("image", step.Image).
|
Str("image", step.Image).
|
||||||
Str("workflowID", workflow.ID).
|
Str("workflowID", workflow.ID).
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
part, rerr := rc.NextPart()
|
|
||||||
if rerr != nil {
|
|
||||||
return rerr
|
|
||||||
}
|
|
||||||
uploads.Add(1)
|
uploads.Add(1)
|
||||||
|
|
||||||
var secrets []string
|
var secrets []string
|
||||||
|
@ -50,7 +45,7 @@ func (r *Runner) createLogger(logger zerolog.Logger, uploads *sync.WaitGroup, wo
|
||||||
loglogger.Debug().Msg("log stream opened")
|
loglogger.Debug().Msg("log stream opened")
|
||||||
|
|
||||||
logStream := rpc.NewLineWriter(r.client, step.UUID, secrets...)
|
logStream := rpc.NewLineWriter(r.client, step.UUID, secrets...)
|
||||||
if _, err := io.Copy(logStream, part); err != nil {
|
if _, err := io.Copy(logStream, rc); err != nil {
|
||||||
log.Error().Err(err).Msg("copy limited logStream part")
|
log.Error().Err(err).Msg("copy limited logStream part")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ import (
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/multipart"
|
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
|
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -253,13 +252,8 @@ func convertPathForWindows(path string) string {
|
||||||
return filepath.ToSlash(path)
|
return filepath.ToSlash(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultLogger = pipeline.LogFunc(func(step *backendTypes.Step, rc multipart.Reader) error {
|
var defaultLogger = pipeline.Logger(func(step *backendTypes.Step, rc io.Reader) error {
|
||||||
part, err := rc.NextPart()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logStream := NewLineWriter(step.Name, step.UUID)
|
logStream := NewLineWriter(step.Name, step.UUID)
|
||||||
_, err = io.Copy(logStream, part)
|
_, err := io.Copy(logStream, rc)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,20 +15,10 @@
|
||||||
package pipeline
|
package pipeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/multipart"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logger handles the process logging.
|
// Logger handles the process logging.
|
||||||
type Logger interface {
|
type Logger func(*backend.Step, io.Reader) error
|
||||||
Log(*backend.Step, multipart.Reader) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogFunc type is an adapter to allow the use of an ordinary
|
|
||||||
// function for process logging.
|
|
||||||
type LogFunc func(*backend.Step, multipart.Reader) error
|
|
||||||
|
|
||||||
// Log calls f(step, r).
|
|
||||||
func (f LogFunc) Log(step *backend.Step, r multipart.Reader) error {
|
|
||||||
return f(step, r)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
// Copyright 2023 Woodpecker Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package multipart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/textproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Reader is an iterator over parts in a multipart log stream.
|
|
||||||
Reader interface {
|
|
||||||
// NextPart returns the next part in the multipart or
|
|
||||||
// an error. When there are no more parts, the error
|
|
||||||
// io.EOF is returned.
|
|
||||||
NextPart() (Part, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Part represents a single part in a multipart body.
|
|
||||||
Part interface {
|
|
||||||
io.Reader
|
|
||||||
|
|
||||||
// Header returns the headers of the body with the
|
|
||||||
// keys canonicalized.
|
|
||||||
Header() textproto.MIMEHeader
|
|
||||||
|
|
||||||
// FileName returns the filename parameter of the
|
|
||||||
// Content-Disposition header.
|
|
||||||
FileName() string
|
|
||||||
|
|
||||||
// FormName returns the name parameter if p has a
|
|
||||||
// Content-Disposition of type form-data.
|
|
||||||
FormName() string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns a new multipart Reader.
|
|
||||||
func New(r io.Reader) Reader {
|
|
||||||
buf := bufio.NewReader(r)
|
|
||||||
out, _ := buf.Peek(8)
|
|
||||||
|
|
||||||
if bytes.Equal(out, []byte("PIPELINE")) {
|
|
||||||
return &multipartReader{
|
|
||||||
reader: multipart.NewReader(buf, "boundary"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &textReader{
|
|
||||||
reader: buf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// wraps the stdlib multi-part reader
|
|
||||||
//
|
|
||||||
|
|
||||||
type multipartReader struct {
|
|
||||||
reader *multipart.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multipartReader) NextPart() (Part, error) {
|
|
||||||
next, err := r.reader.NextPart()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
part := new(part)
|
|
||||||
part.Reader = next
|
|
||||||
part.filename = next.FileName()
|
|
||||||
part.formname = next.FormName()
|
|
||||||
part.header = next.Header
|
|
||||||
return part, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// wraps a simple io.Reader to satisfy the multi-part interface
|
|
||||||
//
|
|
||||||
|
|
||||||
type textReader struct {
|
|
||||||
reader io.Reader
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *textReader) NextPart() (Part, error) {
|
|
||||||
if r.done {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
r.done = true
|
|
||||||
p := new(part)
|
|
||||||
p.Reader = r.reader
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type part struct {
|
|
||||||
io.Reader
|
|
||||||
|
|
||||||
filename string
|
|
||||||
formname string
|
|
||||||
header textproto.MIMEHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *part) Header() textproto.MIMEHeader { return p.header }
|
|
||||||
func (p *part) FileName() string { return p.filename }
|
|
||||||
func (p *part) FormName() string { return p.filename }
|
|
|
@ -1,90 +0,0 @@
|
||||||
// Copyright 2023 Woodpecker Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package multipart
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
|
||||||
b := bytes.NewBufferString(sample)
|
|
||||||
m := New(b)
|
|
||||||
|
|
||||||
part, err := m.NextPart()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
header := part.Header()
|
|
||||||
if got, want := header.Get("Content-Type"), "text/plain"; got != want {
|
|
||||||
t.Errorf("Want Content-Type %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
body, err := io.ReadAll(part)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got, want := string(body), sampleTextPlain; got != want {
|
|
||||||
t.Errorf("Want body %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
part, err = m.NextPart()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header = part.Header()
|
|
||||||
if got, want := header.Get("Content-Type"), "application/json+coverage"; got != want {
|
|
||||||
t.Errorf("Want Content-Type %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
if got, want := header.Get("X-Covered"), "96.00"; got != want {
|
|
||||||
t.Errorf("Want X-Covered %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
if got, want := header.Get("X-Covered-Lines"), "96"; got != want {
|
|
||||||
t.Errorf("Want X-Covered-Lines %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
if got, want := header.Get("X-Total-Lines"), "100"; got != want {
|
|
||||||
t.Errorf("Want X-Total-Lines %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sample = `PIPELINE
|
|
||||||
Content-Type: multipart/mixed; boundary=boundary
|
|
||||||
|
|
||||||
--boundary
|
|
||||||
Content-Type: text/plain
|
|
||||||
|
|
||||||
match: pipeline/frontend/yaml/compiler/coverage.out
|
|
||||||
match: pipeline/frontend/yaml/coverage.out
|
|
||||||
match: pipeline/frontend/yaml/linter/coverage.out
|
|
||||||
|
|
||||||
--boundary
|
|
||||||
Content-Type: application/json+coverage
|
|
||||||
X-Covered: 96.00
|
|
||||||
X-Covered-Lines: 96
|
|
||||||
X-Total-Lines: 100
|
|
||||||
|
|
||||||
{"metrics":{"covered_lines":96,"total_lines":100}}
|
|
||||||
|
|
||||||
--boundary--
|
|
||||||
`
|
|
||||||
|
|
||||||
var sampleTextPlain = `match: pipeline/frontend/yaml/compiler/coverage.out
|
|
||||||
match: pipeline/frontend/yaml/coverage.out
|
|
||||||
match: pipeline/frontend/yaml/linter/coverage.out
|
|
||||||
`
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
|
|
||||||
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/multipart"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: move runtime into "runtime" subpackage
|
// TODO: move runtime into "runtime" subpackage
|
||||||
|
@ -89,7 +88,7 @@ func (r *Runtime) MakeLogger() zerolog.Logger {
|
||||||
return logCtx.Logger()
|
return logCtx.Logger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the execution of an workflow and waits for it to complete
|
// Run starts the execution of a workflow and waits for it to complete
|
||||||
func (r *Runtime) Run(runnerCtx context.Context) error {
|
func (r *Runtime) Run(runnerCtx context.Context) error {
|
||||||
logger := r.MakeLogger()
|
logger := r.MakeLogger()
|
||||||
logger.Debug().Msgf("executing %d stages, in order of:", len(r.spec.Stages))
|
logger.Debug().Msgf("executing %d stages, in order of:", len(r.spec.Stages))
|
||||||
|
@ -166,27 +165,27 @@ func (r *Runtime) execAll(steps []*backend.Step) <-chan error {
|
||||||
logger := r.MakeLogger()
|
logger := r.MakeLogger()
|
||||||
|
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
// required since otherwise the loop variable
|
// Required since otherwise the loop variable
|
||||||
// will be captured by the function. This will
|
// will be captured by the function. This will
|
||||||
// recreate the step "variable"
|
// recreate the step "variable"
|
||||||
step := step
|
step := step
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
// Case the pipeline was already complete.
|
// Case the pipeline was already complete.
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("Step", step.Name).
|
Str("step", step.Name).
|
||||||
Msg("Prepare")
|
Msg("prepare")
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case r.err != nil && !step.OnFailure:
|
case r.err != nil && !step.OnFailure:
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("Step", step.Name).
|
Str("step", step.Name).
|
||||||
Err(r.err).
|
Err(r.err).
|
||||||
Msgf("Skipped due to OnFailure=%t", step.OnFailure)
|
Msgf("skipped due to OnFailure=%t", step.OnFailure)
|
||||||
return nil
|
return nil
|
||||||
case r.err == nil && !step.OnSuccess:
|
case r.err == nil && !step.OnSuccess:
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("Step", step.Name).
|
Str("step", step.Name).
|
||||||
Msgf("Skipped due to OnSuccess=%t", step.OnSuccess)
|
Msgf("skipped due to OnSuccess=%t", step.OnSuccess)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,22 +199,14 @@ func (r *Runtime) execAll(steps []*backend.Step) <-chan error {
|
||||||
metadata.SetDroneEnviron(step.Environment)
|
metadata.SetDroneEnviron(step.Environment)
|
||||||
|
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("Step", step.Name).
|
Str("step", step.Name).
|
||||||
Msg("Executing")
|
Msg("executing")
|
||||||
|
|
||||||
processState, err := r.exec(step)
|
processState, err := r.exec(step)
|
||||||
|
|
||||||
logger.Debug().
|
logger.Debug().
|
||||||
Str("Step", step.Name).
|
Str("step", step.Name).
|
||||||
Msg("Complete")
|
Msg("complete")
|
||||||
|
|
||||||
// if we got a nil process but an error state
|
|
||||||
// then we need to log the internal error to the step.
|
|
||||||
if r.logger != nil && err != nil && !errors.Is(err, ErrCancel) && processState == nil {
|
|
||||||
_ = r.logger.Log(step, multipart.New(strings.NewReader(
|
|
||||||
"Backend engine error while running step: "+err.Error(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the error after tracing it.
|
// Return the error after tracing it.
|
||||||
err = r.traceStep(processState, err, step)
|
err = r.traceStep(processState, err, step)
|
||||||
|
@ -251,7 +242,7 @@ func (r *Runtime) exec(step *backend.Step) (*backend.State, error) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
logger := r.MakeLogger()
|
logger := r.MakeLogger()
|
||||||
|
|
||||||
if err := r.logger.Log(step, multipart.New(rc)); err != nil {
|
if err := r.logger(step, rc); err != nil {
|
||||||
logger.Error().Err(err).Msg("process logging failed")
|
logger.Error().Err(err).Msg("process logging failed")
|
||||||
}
|
}
|
||||||
_ = rc.Close()
|
_ = rc.Close()
|
||||||
|
|
|
@ -153,9 +153,9 @@ func cancelPreviousPipelines(
|
||||||
if err = Cancel(ctx, _store, repo, user, active); err != nil {
|
if err = Cancel(ctx, _store, repo, user, active); err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Str("Ref", active.Ref).
|
Str("ref", active.Ref).
|
||||||
Int64("ID", active.ID).
|
Int64("id", active.ID).
|
||||||
Msg("Failed to cancel pipeline")
|
Msg("failed to cancel pipeline")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue