Skip to content

Commit d502713

Browse files
authored
feat(materials): Helm Chart material support (#689)
Signed-off-by: Javier Rodriguez <[email protected]>
1 parent 9282152 commit d502713

File tree

11 files changed

+303
-16
lines changed

11 files changed

+303
-16
lines changed

app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go

Lines changed: 19 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ message CraftingSchema {
8383
// Static analysis output format
8484
// https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md
8585
SARIF = 9;
86+
HELM_CHART = 10;
8687
}
8788
}
8889
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ require (
310310
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
311311
gopkg.in/inf.v0 v0.9.1 // indirect
312312
gopkg.in/ini.v1 v1.67.0 // indirect
313-
gopkg.in/yaml.v2 v2.4.0 // indirect
313+
gopkg.in/yaml.v2 v2.4.0
314314
gopkg.in/yaml.v3 v3.0.1 // indirect
315315
k8s.io/api v0.28.3 // indirect
316316
k8s.io/apimachinery v0.28.3
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package materials
17+
18+
import (
19+
"archive/tar"
20+
"compress/gzip"
21+
"context"
22+
"fmt"
23+
"io"
24+
"os"
25+
"strings"
26+
27+
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
28+
api "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1"
29+
"github.com/chainloop-dev/chainloop/internal/casclient"
30+
"github.com/rs/zerolog"
31+
"gopkg.in/yaml.v2"
32+
)
33+
34+
const (
35+
// chartFileName is the name of the Chart.yaml file in the helm chart
36+
chartFileName = "Chart.yaml"
37+
// chartValuesYamlFileName is the name of the values.yaml file in the helm chart
38+
chartValuesYamlFileName = "values.yaml"
39+
)
40+
41+
type HelmChartCrafter struct {
42+
backend *casclient.CASBackend
43+
*crafterCommon
44+
}
45+
46+
func NewHelmChartCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend,
47+
l *zerolog.Logger) (*HelmChartCrafter, error) {
48+
if materialSchema.Type != schemaapi.CraftingSchema_Material_HELM_CHART {
49+
return nil, fmt.Errorf("material type is not HELM_CHART format")
50+
}
51+
52+
return &HelmChartCrafter{
53+
backend: backend,
54+
crafterCommon: &crafterCommon{logger: l, input: materialSchema},
55+
}, nil
56+
}
57+
58+
func (c *HelmChartCrafter) Craft(ctx context.Context, filepath string) (*api.Attestation_Material, error) {
59+
// Open the helm chart tar file
60+
f, err := os.Open(filepath)
61+
if err != nil {
62+
return nil, fmt.Errorf("can't open the file: %w", err)
63+
}
64+
defer f.Close()
65+
66+
// Decompress the file if possible
67+
uncompressedStream, err := gzip.NewReader(f)
68+
if err != nil {
69+
return nil, fmt.Errorf("can't uncompress file, unexpected material type: %w", err)
70+
}
71+
72+
// Create a tar reader
73+
tarReader := tar.NewReader(uncompressedStream)
74+
75+
// Flags to track whether required files are found
76+
var chartFileValid, chartValuesValid bool
77+
78+
// Iterate through the files in the tar archive
79+
for {
80+
header, err := tarReader.Next()
81+
if err == io.EOF {
82+
// Reached the end of tar archive
83+
break
84+
}
85+
if err != nil {
86+
return nil, fmt.Errorf("error reading tar file: %w", err)
87+
}
88+
89+
// Check if the file is a regular file
90+
if header.Typeflag != tar.TypeReg {
91+
continue // Skip if it's not a regular file
92+
}
93+
94+
// Validate Chart.yaml and values.yaml files. The files will have prepended the path of the directory
95+
// it was compressed from. So, we can check if the file name contains the required file names
96+
// Ex: helm-chart/Chart.yaml, helm-chart/values.yaml
97+
if strings.Contains(header.Name, chartFileName) {
98+
if err := c.validateYamlFile(tarReader); err != nil {
99+
return nil, fmt.Errorf("invalid Chart.yaml file: %w", err)
100+
}
101+
chartFileValid = true
102+
} else if strings.Contains(header.Name, chartValuesYamlFileName) {
103+
if err := c.validateYamlFile(tarReader); err != nil {
104+
return nil, fmt.Errorf("invalid values.yaml file: %w", err)
105+
}
106+
chartValuesValid = true
107+
}
108+
109+
// Stop iterating if both files are found
110+
if chartValuesValid && chartFileValid {
111+
break
112+
}
113+
}
114+
115+
// If the chart.yaml and values.yaml files are not found, return an error
116+
if !chartFileValid || !chartValuesValid {
117+
return nil, fmt.Errorf("missing required files in the helm chart: Chart.yaml and values.yaml")
118+
}
119+
120+
// Upload and craft the chart
121+
return uploadAndCraft(ctx, c.input, c.backend, filepath, c.logger)
122+
}
123+
124+
// validateYamlFile validates the YAML file just by trying to unmarshal it
125+
func (c *HelmChartCrafter) validateYamlFile(r io.Reader) error {
126+
v := make(map[string]interface{})
127+
if err := yaml.NewDecoder(r).Decode(v); err != nil {
128+
return fmt.Errorf("failed to unmarshal YAML file: %w", err)
129+
}
130+
131+
return nil
132+
}

0 commit comments

Comments
 (0)