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
61 changes: 46 additions & 15 deletions cli/cmd/release_lint.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package cmd

import (
"archive/tar"
"bytes"
"io"
"io/fs"
"os"
"path/filepath"

"github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/replicatedhq/replicated/pkg/types"
Expand Down Expand Up @@ -162,28 +164,57 @@ func shouldFail(lintResult []types.LintMessage, failOn string) bool {
}

func tarYAMLDir(yamlDir string) ([]byte, error) {
archiveDir, err := os.MkdirTemp("", "replicated")
if err != nil {
return nil, errors.Wrap(err, "failed to create temp dir for archive")
}
defer os.RemoveAll(archiveDir)
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
defer tw.Close()

archiveFile := filepath.Join(archiveDir, "kots-release.tar")
topLevelDir := filepath.Base(yamlDir)

tar := archiver.Tar{
ImplicitTopLevelFolder: true,
}
err := filepath.Walk(yamlDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(yamlDir, path)
if err != nil {
return err
}

if err := tar.Archive([]string{yamlDir}, archiveFile); err != nil {
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
header.Name = filepath.Join(topLevelDir, relPath)
if info.IsDir() {
header.Name += "/"
}

if err := tw.WriteHeader(header); err != nil {
return err
}

if info.IsDir() {
return nil
}

f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

_, err = io.Copy(tw, f)
return err
})
if err != nil {
return nil, errors.Wrap(err, "failed to archive")
}

data, err := os.ReadFile(archiveFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read archive file")
if err := tw.Close(); err != nil {
return nil, errors.Wrap(err, "failed to close tar writer")
}

return data, nil
return buf.Bytes(), nil
}

func isHelmChartsOnly(yamlDir string) (bool, error) {
Expand Down
156 changes: 156 additions & 0 deletions cli/cmd/release_lint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package cmd

import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"sort"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// readTarEntries extracts all entries from a tar archive, returning a map of
// path -> content (empty string for directories).
func readTarEntries(t *testing.T, data []byte) map[string]string {
t.Helper()
entries := make(map[string]string)
tr := tar.NewReader(bytes.NewReader(data))
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
require.NoError(t, err)
if hdr.Typeflag == tar.TypeDir {
entries[hdr.Name] = ""
continue
}
buf, err := io.ReadAll(tr)
require.NoError(t, err)
entries[hdr.Name] = string(buf)
}
return entries
}

func TestTarYAMLDir_SingleFile(t *testing.T) {
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "manifests")
require.NoError(t, os.Mkdir(yamlDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(yamlDir, "app.yaml"), []byte("apiVersion: v1"), 0644))

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

entries := readTarEntries(t, data)

assert.Contains(t, entries, "manifests/app.yaml")
assert.Equal(t, "apiVersion: v1", entries["manifests/app.yaml"])
}

func TestTarYAMLDir_NestedDirectories(t *testing.T) {
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "release")
subDir := filepath.Join(yamlDir, "charts", "mychart")
require.NoError(t, os.MkdirAll(subDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(yamlDir, "config.yaml"), []byte("top-level"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "values.yaml"), []byte("nested"), 0644))

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

entries := readTarEntries(t, data)

assert.Equal(t, "top-level", entries["release/config.yaml"])
assert.Equal(t, "nested", entries["release/charts/mychart/values.yaml"])
}

func TestTarYAMLDir_MultipleFiles(t *testing.T) {
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "yamls")
require.NoError(t, os.Mkdir(yamlDir, 0755))

files := map[string]string{
"a.yaml": "a-content",
"b.yaml": "b-content",
"c.yaml": "c-content",
}
for name, content := range files {
require.NoError(t, os.WriteFile(filepath.Join(yamlDir, name), []byte(content), 0644))
}

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

entries := readTarEntries(t, data)

for name, content := range files {
key := filepath.Join("yamls", name)
assert.Equal(t, content, entries[key], "mismatch for %s", name)
}
}

func TestTarYAMLDir_EmptyDirectory(t *testing.T) {
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "empty")
require.NoError(t, os.Mkdir(yamlDir, 0755))

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

// Should contain only the top-level directory entry
entries := readTarEntries(t, data)
assert.Len(t, entries, 1)
assert.Contains(t, entries, "empty/")
}

func TestTarYAMLDir_PreservesTopLevelDirName(t *testing.T) {
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "my-release-v1.2.3")
require.NoError(t, os.Mkdir(yamlDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(yamlDir, "test.yaml"), []byte("data"), 0644))

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

entries := readTarEntries(t, data)
assert.Contains(t, entries, "my-release-v1.2.3/test.yaml")
}

func TestTarYAMLDir_NonExistentDir(t *testing.T) {
_, err := tarYAMLDir("/nonexistent/path")
assert.Error(t, err)
}

func TestTarYAMLDir_ProducesValidTar(t *testing.T) {
// Verify the tar can be fully read without errors and contains the expected
// number of entries (directories + files).
tmpDir := t.TempDir()
yamlDir := filepath.Join(tmpDir, "app")
sub := filepath.Join(yamlDir, "sub")
require.NoError(t, os.MkdirAll(sub, 0755))
require.NoError(t, os.WriteFile(filepath.Join(yamlDir, "root.yaml"), []byte("r"), 0644))
require.NoError(t, os.WriteFile(filepath.Join(sub, "child.yaml"), []byte("c"), 0644))

data, err := tarYAMLDir(yamlDir)
require.NoError(t, err)

var names []string
tr := tar.NewReader(bytes.NewReader(data))
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
require.NoError(t, err)
names = append(names, hdr.Name)
}

sort.Strings(names)
expected := []string{"app/", "app/root.yaml", "app/sub/", "app/sub/child.yaml"}
sort.Strings(expected)
assert.Equal(t, expected, names)
}
5 changes: 3 additions & 2 deletions dagger.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "replicated",
"engineVersion": "v0.18.9",
"engineVersion": "v0.19.9",
"sdk": {
"source": "go"
},
Expand All @@ -16,5 +16,6 @@
"pin": "0b03c8c560c2067f34dab800c92154abc3834841"
}
],
"source": "dagger"
"source": "dagger",
"disableDefaultFunctionCaching": true
}
1 change: 1 addition & 0 deletions dagger/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/internal/dagger
/internal/querybuilder
/internal/telemetry
/.env
70 changes: 36 additions & 34 deletions dagger/go.mod
Original file line number Diff line number Diff line change
@@ -1,52 +1,54 @@
module dagger/replicated

go 1.24
go 1.25.0

require (
github.com/99designs/gqlgen v0.17.73
github.com/99designs/gqlgen v0.17.81
github.com/Khan/genqlient v0.8.1
github.com/pkg/errors v0.9.1
github.com/vektah/gqlparser/v2 v2.5.27
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0
go.opentelemetry.io/otel/log v0.8.0
go.opentelemetry.io/otel/sdk v1.34.0
go.opentelemetry.io/otel/sdk/log v0.8.0
go.opentelemetry.io/otel/trace v1.34.0
go.opentelemetry.io/proto/otlp v1.3.1
golang.org/x/sync v0.14.0
google.golang.org/grpc v1.72.1
github.com/vektah/gqlparser/v2 v2.5.30
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
go.opentelemetry.io/otel/log v0.14.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/sdk/log v0.14.0
go.opentelemetry.io/otel/trace v1.42.0
go.opentelemetry.io/proto/otlp v1.8.0
golang.org/x/sync v0.17.0
google.golang.org/grpc v1.76.0
)

require github.com/cespare/xxhash/v2 v2.3.0 // indirect

require (
github.com/Masterminds/semver v1.5.0
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/sosodev/duration v1.3.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0
go.opentelemetry.io/otel/sdk/metric v1.34.0
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/protobuf v1.36.6 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0
go.opentelemetry.io/otel/sdk/metric v1.42.0
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/protobuf v1.36.9 // indirect
)

replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0
replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0

replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0
replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0

replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0
replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.14.0

replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0
replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.14.0
Loading
Loading