Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion actions/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ The available configuration options for the NAIS deploy GitHub action.
| RESOURCE | \(required\) | Comma-separated list of files containing Kubernetes resources. Must be JSON or YAML format. |
| RETRY | `true` | Automatically retry deploying if deploy service is unavailable. |
| TEAM | \(auto-detect\) | Team making the deployment. |
| TELEMETRY | | Lets nais/docker-build-push send telemetry that is used to calculate more precise lead time for deploy. |
| TIMEOUT | `10m` | Time to wait for deployment completion, especially when using `WAIT`. |
| VAR | | Comma-separated list of template variables in the form `key=value`. Will overwrite any identical template variable in the `VARS` file. |
| VARS | `/dev/null` | File containing template variables. Will be interpolated with the `$RESOURCE` file. Must be JSON or YAML format. |
Expand Down
17 changes: 3 additions & 14 deletions cmd/deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"errors"
"fmt"
"os"

Expand Down Expand Up @@ -46,14 +45,10 @@ func run() error {

err := cfg.Validate()
if err != nil {
if !errors.Is(err, deployclient.ErrInvalidTelemetryFormat) {
if !cfg.DryRun {
return deployclient.ErrorWrap(deployclient.ExitInvocationFailure, err)
}
log.Warnf("Configuration did not pass validation: %s", err)
} else {
log.Warnf("Telemetry configuration did not pass validation: %s", err)
if !cfg.DryRun {
return deployclient.ErrorWrap(deployclient.ExitInvocationFailure, err)
}
log.Warnf("Configuration did not pass validation: %s", err)
}

// OpenTelemetry
Expand All @@ -71,16 +66,10 @@ func run() error {
}()

// Inherit traceparent from pipeline, if any.
// If TRACEPARENT is set, ignore the TELEMETRY value.
// If not, start a new top-level trace using the TELEMETRY variable.
var span otrace.Span
if len(cfg.Traceparent) > 0 {
log.Infof("Using traceparent header %s", cfg.Traceparent)
ctx = telemetry.WithTraceParent(ctx, cfg.Traceparent)
} else if cfg.Telemetry != nil {
log.Infof("Importing pipeline telemetry data as this request's top-level trace")
ctx, span = cfg.Telemetry.StartTracing(ctx)
defer span.End()
} else {
log.Infof("No top-level trace detected, starting a new one.")
}
Expand Down
10 changes: 0 additions & 10 deletions pkg/deployclient/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package deployclient

import (
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/nais/deploy/pkg/telemetry"
flag "github.com/spf13/pflag"
)

Expand All @@ -34,8 +32,6 @@ type Config struct {
RetryInterval time.Duration
Team string
Traceparent string
TelemetryInput string
Telemetry *telemetry.PipelineTimings
Timeout time.Duration
TracingDashboardURL string
Variables []string
Expand Down Expand Up @@ -65,7 +61,6 @@ func InitConfig(cfg *Config) {
flag.BoolVar(&cfg.Retry, "retry", getEnvBool("RETRY", true), "Retry deploy when encountering transient errors. (env RETRY)")
flag.StringVar(&cfg.Team, "team", os.Getenv("TEAM"), "Team making the deployment. Auto-detected from nais.yaml if possible. (env TEAM)")
flag.StringVar(&cfg.Traceparent, "traceparent", os.Getenv("TRACEPARENT"), "The W3C Trace Context traceparent value for the workflow run. (env TRACEPARENT)")
flag.StringVar(&cfg.TelemetryInput, "telemetry", os.Getenv("TELEMETRY"), "Telemetry data from CI pipeline. (env TELEMETRY)")
flag.DurationVar(&cfg.Timeout, "timeout", getEnvDuration("TIMEOUT", DefaultDeployTimeout), "Time to wait for successful deployment. (env TIMEOUT)")
flag.StringVar(&cfg.TracingDashboardURL, "tracing-dashboard-url", getEnv("TRACING_DASHBOARD_URL", DefaultTracingDashboardURL), "Base URL to Grafana tracing dashboard onto which the trace ID can be appended (env TRACING_DASHBOARD_URL)")
flag.StringSliceVar(&cfg.Variables, "var", getEnvStringSlice("VAR"), "Template variable in the form KEY=VALUE. Can be specified multiple times. (env VAR)")
Expand Down Expand Up @@ -152,10 +147,5 @@ func (cfg *Config) Validate() error {
return ErrMalformedAPIKey
}

cfg.Telemetry, err = telemetry.ParsePipelineTelemetry(cfg.TelemetryInput)
if err != nil {
return fmt.Errorf("%w: %w", ErrInvalidTelemetryFormat, err)
}

return nil
}
1 change: 0 additions & 1 deletion pkg/deployclient/deployclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var (
ErrAuthRequired = errors.New("Github token or API key required")
ErrClusterRequired = errors.New("cluster required; see reference section in the documentation for available environments")
ErrMalformedAPIKey = errors.New("API key must be a hex encoded string")
ErrInvalidTelemetryFormat = errors.New("telemetry input format malformed")
)

type Deployer struct {
Expand Down
97 changes: 0 additions & 97 deletions pkg/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ package telemetry

import (
"context"
"fmt"
"runtime"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -131,101 +129,6 @@ func AddDeploymentRequestSpanAttributes(span otrace.Span, request *pb.Deployment
)
}

// Holds timestamps from pipeline indicating when certain steps were started or finished.
// If `Validate()` returns nil, this object is safe to use and contains chronologically ordered timestamps
// for every field.
type PipelineTimings struct {
LatestCommit time.Time
Start time.Time
BuildStart time.Time
AttestStart time.Time
End time.Time
}

func (pt *PipelineTimings) Validate() error {
if pt.LatestCommit.After(pt.BuildStart) || pt.Start.After(pt.BuildStart) || pt.BuildStart.After(pt.AttestStart) || pt.AttestStart.After(pt.End) {
return fmt.Errorf("pipeline timings are not in expected chronological order, ensure that: latest_commit < pipeline_start < build_start < attest_start < pipeline_end")
}
return nil
}

// Imports tracing data from the build pipeline and starts a new top-level trace.
//
// The tracing data is generated by the GitHub action `docker-build-push`.
//
// Callers MUST call `span.End()` in order to send traces to the collector.
func (pt *PipelineTimings) StartTracing(ctx context.Context) (context.Context, otrace.Span) {
rootCtx, rootSpan := Tracer().Start(ctx, "Continuous integration pipeline", otrace.WithTimestamp(pt.LatestCommit), otrace.WithSpanKind(otrace.SpanKindClient))
rootSpan.AddEvent("Latest commit to repository", otrace.WithTimestamp(pt.LatestCommit))
{
ciCtx, ciSpan := Tracer().Start(rootCtx, "Github Action: docker-build-push", otrace.WithTimestamp(pt.Start), otrace.WithSpanKind(otrace.SpanKindClient))
{
_, buildSpan := Tracer().Start(ciCtx, "Docker: Build and push", otrace.WithTimestamp(pt.BuildStart))
buildSpan.End(otrace.WithTimestamp(pt.AttestStart))
}
{
_, attestSpan := Tracer().Start(ciCtx, "SLSA: SBOM sign and attest", otrace.WithTimestamp(pt.AttestStart))
attestSpan.End(otrace.WithTimestamp(pt.End))
}
ciSpan.End(otrace.WithTimestamp(pt.End))
}

return rootCtx, rootSpan
}

// Parse pipeline build timings.
//
// Uses the following input format:
//
// latest_commit=1726040395,pipeline_start=1726050395,pipeline_end=1726050512,build_start=1726050400,attest_start=1726050492
//
// This output usually comes from `docker-build-push.steps.output.telemetry`.
//
// If there is no timing data, both return values will be nil.
// If all timing data is valid, returns a timings object and nil error.
func ParsePipelineTelemetry(s string) (*PipelineTimings, error) {
if len(s) == 0 {
return nil, nil
}

timings := &PipelineTimings{}
fragments := strings.Split(s, ",")
for _, keyValue := range fragments {
key, value, found := strings.Cut(keyValue, "=")
if !found {
return nil, fmt.Errorf("expected 'key=value', found '%s'", keyValue)
}

epoch, err := strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("expected UNIX epoch, found '%s'", value)
}

ts := time.Unix(int64(epoch), 0)
ts = ts.UTC()

switch key {
case "latest_commit":
timings.LatestCommit = ts
case "pipeline_start":
timings.Start = ts
case "pipeline_end":
timings.End = ts
case "build_start":
timings.BuildStart = ts
case "attest_start":
timings.AttestStart = ts
default:
return nil, fmt.Errorf("expected key to be one of 'latest_commit', 'pipeline_start', 'pipeline_end', 'build_start', 'attest_start'; found '%s'", key)
}
}
err := timings.Validate()
if err != nil {
return nil, err
}
return timings, nil
}

func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
Expand Down
104 changes: 0 additions & 104 deletions pkg/telemetry/telemetry_test.go

This file was deleted.