Skip to content

Commit

Permalink
Extract troubleshoot specs from secrets/config maps (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
divolgin authored Mar 25, 2024
1 parent 622bcae commit 41f6376
Show file tree
Hide file tree
Showing 18 changed files with 481 additions and 19 deletions.
3 changes: 3 additions & 0 deletions kustomize/base/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ spec:
- name: kots-lint
image: kots-lint
imagePullPolicy: IfNotPresent
env:
- name: LOG_LEVEL
value: debug
ports:
- name: kots-lint
containerPort: 8082
Expand Down
14 changes: 11 additions & 3 deletions pkg/handlers/builders_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type LintBuildersReleaseResponse struct {
func LintBuildersRelease(c *gin.Context) {
log.Infof("Received builders lint request with content-length=%s, content-type=%s, client-ip=%s", c.GetHeader("content-length"), c.ContentType(), c.ClientIP())

ctx := c.Request.Context()

specFiles := kots.SpecFiles{}
numChartsRendered := 0

Expand All @@ -51,7 +53,7 @@ func LintBuildersRelease(c *gin.Context) {
}

log.Debugf("adding files for chart %s", header.Name)
files, err := kots.GetFilesFromChartReader(tarReader)
files, err := kots.GetFilesFromChartReader(ctx, tarReader)
if err != nil {
log.Infof("failed to get files from chart %s: %v", header.Name, err)
lintExpressions = append(lintExpressions, kots.LintExpression{
Expand All @@ -63,11 +65,14 @@ func LintBuildersRelease(c *gin.Context) {
continue
}

troubleshootSpecs := kots.GetEmbeddedTroubleshootSpecs(ctx, files)

numChartsRendered += 1
specFiles = append(specFiles, files...)
specFiles = append(specFiles, troubleshootSpecs...)
}
} else if c.ContentType() == "application/gzip" {
files, err := kots.GetFilesFromChartReader(c.Request.Body)
files, err := kots.GetFilesFromChartReader(ctx, c.Request.Body)
if err != nil {
log.Infof("failed to get files from request: %v", err)
lintExpressions = append(lintExpressions, kots.LintExpression{
Expand All @@ -76,8 +81,11 @@ func LintBuildersRelease(c *gin.Context) {
Message: err.Error(),
})
} else {
troubleshootSpecs := kots.GetEmbeddedTroubleshootSpecs(ctx, files)

numChartsRendered += 1
specFiles = append(specFiles, files...)
specFiles = append(specFiles, troubleshootSpecs...)
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "content type must be application/gzip or application/tar"})
Expand All @@ -86,7 +94,7 @@ func LintBuildersRelease(c *gin.Context) {

// Only lint if at least one chart was rendered, otherwise we get missing spec warnings/errors
if numChartsRendered > 0 {
lint, err := kots.LintBuilders(c.Request.Context(), specFiles)
lint, err := kots.LintBuilders(ctx, specFiles)
if err != nil {
log.Errorf("failed to lint builders charts: %v", err)
c.AbortWithError(http.StatusInternalServerError, err)
Expand Down
20 changes: 11 additions & 9 deletions pkg/handlers/builders_lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package handlers

import (
"archive/tar"
"embed"
"encoding/json"
"fmt"
"io"
Expand All @@ -15,13 +14,6 @@ import (
"github.com/stretchr/testify/require"
)

func init() {
kots.InitOPALinting()
}

//go:embed test-data/*
var testdata embed.FS

func Test_LintBuildersRelease(t *testing.T) {

type resultType struct {
Expand Down Expand Up @@ -81,6 +73,16 @@ func Test_LintBuildersRelease(t *testing.T) {
},
},
},
{
name: "one valid chart with preflights",
chartReader: func() io.ReadCloser {
return io.NopCloser(getTarReader([]string{"testchart-with-labels-with-preflightspec-in-secret-16.2.2.tgz"}))
},
contentType: "application/tar",
want: resultType{
LintExpressions: nil,
},
},
{
name: "one valid chart without preflights and one invalid chart",
chartReader: func() io.ReadCloser {
Expand Down Expand Up @@ -169,7 +171,7 @@ func Test_LintBuildersRelease(t *testing.T) {
var got resultType
err = json.Unmarshal(body, &got)
req.NoError(err)
req.Equal(tt.want, got)
req.ElementsMatch(tt.want.LintExpressions, got.LintExpressions)
})
}
}
16 changes: 16 additions & 0 deletions pkg/handlers/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package handlers

import (
"embed"

kjs "github.com/replicatedhq/kots-lint/kubernetes_json_schema"
"github.com/replicatedhq/kots-lint/pkg/kots"
)

func init() {
kjs.KubernetesJsonSchemaDir = "../../kubernetes_json_schema/schema"
kots.InitOPALinting()
}

//go:embed test-data/*
var testdata embed.FS
10 changes: 6 additions & 4 deletions pkg/handlers/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -38,8 +38,10 @@ type LintReleaseResponse struct {
func LintRelease(c *gin.Context) {
log.Infof("Received lint request with content-length=%s, content-type=%s, client-ip=%s", c.GetHeader("content-length"), c.ContentType(), c.ClientIP())

ctx := c.Request.Context()

// read before binding to check if body is a tar stream
data, err := ioutil.ReadAll(c.Request.Body)
data, err := io.ReadAll(c.Request.Body)
c.Request.Body.Close()
if err != nil {
log.Errorf("failed to read request body: %v", err)
Expand All @@ -58,7 +60,7 @@ func LintRelease(c *gin.Context) {
specFiles = f
} else {
// restore request body to its original state to be able to bind it
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))

var request LintReleaseParameters
if err := c.Bind(&request.Body); err != nil {
Expand All @@ -74,7 +76,7 @@ func LintRelease(c *gin.Context) {
}
}

lintExpressions, isComplete, err := kots.LintSpecFiles(specFiles)
lintExpressions, isComplete, err := kots.LintSpecFiles(ctx, specFiles)
if err != nil {
fmt.Printf("failed to lint spec files: %v", err)
c.AbortWithError(http.StatusInternalServerError, err)
Expand Down
220 changes: 220 additions & 0 deletions pkg/handlers/lint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package handlers

import (
"archive/tar"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"

"github.com/gin-gonic/gin"
"github.com/replicatedhq/kots-lint/pkg/kots"
"github.com/stretchr/testify/require"
"gopkg.in/stretchr/testify.v1/assert"
)

func Test_LintRelease(t *testing.T) {

type resultType struct {
LintExpressions []kots.LintExpression `json:"lintExpressions"`
}

getTarReader := func(filesNames []string) io.Reader {
pipeReader, pipeWriter := io.Pipe()

go func() {
defer pipeWriter.Close()

tarWriter := tar.NewWriter(pipeWriter)
defer tarWriter.Close()

for _, fileName := range filesNames {
data, err := testdata.ReadFile(fileName)
if err != nil {
t.Fatalf("failed to open file: %v", err)
}

if strings.HasSuffix(fileName, ".tgz") {
data = []byte(base64.StdEncoding.EncodeToString(data))
}

header := &tar.Header{
Name: filepath.Base(fileName),
Mode: 0644,
Size: int64(len(data)),
}

tarWriter.WriteHeader(header)
tarWriter.Write(data)
}
}()

return pipeReader
}

tests := []struct {
name string
chartReader func(t *testing.T) io.ReadCloser
contentType string
want resultType
}{
{
name: "one valid chart without kotskinds",
chartReader: func(t *testing.T) io.ReadCloser {
yamlFiles, err := testdata.ReadDir("test-data/kots/without-preflight")
assert.NoError(t, err)

files := []string{
"test-data/builders/testchart-with-labels-16.2.2.tgz",
}
for _, f := range yamlFiles {
files = append(files, filepath.Join("test-data/kots/without-preflight", f.Name()))
}

return io.NopCloser(getTarReader(files))
},
contentType: "application/tar",
want: resultType{
LintExpressions: []kots.LintExpression{
{
Rule: "preflight-spec",
Type: "warn",
Message: "Missing preflight spec",
Path: "",
Positions: nil,
},
{
Rule: "application-spec",
Type: "warn",
Message: "Missing application spec",
Path: "",
Positions: nil,
},
{
Rule: "config-spec",
Type: "warn",
Message: "Missing config spec",
Path: "",
Positions: nil,
},
{
Rule: "troubleshoot-spec",
Type: "warn",
Message: "Missing troubleshoot spec",
Path: "",
Positions: nil,
},
},
},
},
{
name: "one valid chart without kotskinds but with preflights",
chartReader: func(t *testing.T) io.ReadCloser {
yamlFiles, err := testdata.ReadDir("test-data/kots/with-preflight")
assert.NoError(t, err)

files := []string{
"test-data/builders/testchart-with-labels-with-preflightspec-in-secret-16.2.2.tgz",
}
for _, f := range yamlFiles {
files = append(files, filepath.Join("test-data/kots/with-preflight", f.Name()))
}

return io.NopCloser(getTarReader(files))
},
contentType: "application/tar",
want: resultType{
LintExpressions: []kots.LintExpression{
{
Rule: "application-spec",
Type: "warn",
Message: "Missing application spec",
Path: "",
Positions: nil,
},
{
Rule: "config-spec",
Type: "warn",
Message: "Missing config spec",
Path: "",
Positions: nil,
},
{
Rule: "troubleshoot-spec",
Type: "warn",
Message: "Missing troubleshoot spec",
Path: "",
Positions: nil,
},
},
},
},
{
name: "one valid chart with kotskinds but without preflights",
chartReader: func(t *testing.T) io.ReadCloser {
yamlFiles, err := testdata.ReadDir("test-data/kots/with-kots-kinds")
assert.NoError(t, err)

files := []string{
"test-data/builders/testchart-with-labels-16.2.2.tgz",
}
for _, f := range yamlFiles {
files = append(files, filepath.Join("test-data/kots/with-kots-kinds", f.Name()))
}

return io.NopCloser(getTarReader(files))
},
contentType: "application/tar",
want: resultType{
LintExpressions: []kots.LintExpression{
{
Rule: "application-statusInformers",
Type: "warn",
Message: "Missing application statusInformers",
Path: "kots-app.yaml",
Positions: []kots.LintExpressionItemPosition{
{
Start: kots.LintExpressionItemLinePosition{
Line: 5,
},
},
},
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := require.New(t)

clientRequest := &http.Request{
Body: tt.chartReader(t),
Header: http.Header{
"Content-Type": []string{tt.contentType},
},
}
respWriter := httptest.NewRecorder()

c, _ := gin.CreateTestContext(respWriter)
c.Request = clientRequest

LintRelease(c)

req.Equal(http.StatusOK, respWriter.Result().StatusCode)

body, err := io.ReadAll(respWriter.Body)
req.NoError(err)

var got resultType
err = json.Unmarshal(body, &got)
req.NoError(err)
req.ElementsMatch(tt.want.LintExpressions, got.LintExpressions)
})
}
}
Binary file not shown.
8 changes: 8 additions & 0 deletions pkg/handlers/test-data/kots/with-kots-kinds/chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: kots.io/v1beta2
kind: HelmChart
metadata:
name: testchart-with-labels
spec:
chart:
name: testchart-with-labels
chartVersion: 16.2.2
Loading

0 comments on commit 41f6376

Please sign in to comment.