integrate clustertool sourcecode
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/truecharts/private/clustertool/pkg/helper"
|
||||
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
minHelmVersion = "3.11"
|
||||
maxHelmVersion = "3.15"
|
||||
kubeVersion = ">=1.24.0-0"
|
||||
apiVersion = "v2"
|
||||
chartType = "application"
|
||||
maintainerName = "TrueCharts"
|
||||
maintainerEmail = "info@truecharts.org"
|
||||
maintainerURL = "https://truecharts.org"
|
||||
defaultCategory = "unsorted"
|
||||
defaultAppVersion = "unknown"
|
||||
defaultDescription = "No description provided."
|
||||
defaultHome = "https://truecharts.org"
|
||||
defaultIcon = "https://github.com/truecharts/website/blob/main/static/svg/logo.svg"
|
||||
)
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
// Maintainer represents a maintainer of the Helm chart.
|
||||
type Maintainer struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Email string `yaml:"email"`
|
||||
URL string `yaml:"url" validate:"required"`
|
||||
}
|
||||
|
||||
// Dependency represents a dependency of the Helm chart.
|
||||
type Dependency struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Version string `yaml:"version" validate:"required"`
|
||||
Repository string `yaml:"repository" validate:"required"`
|
||||
Condition string `yaml:"condition"`
|
||||
Alias string `yaml:"alias"`
|
||||
Tags []string `yaml:"tags"`
|
||||
ImportValues []string `yaml:"import-values"`
|
||||
}
|
||||
|
||||
// ChartMetadata represents the metadata structure in Chart.yaml.
|
||||
type ChartMetadata struct {
|
||||
Annotations map[string]string `yaml:"annotations"`
|
||||
APIVersion string `yaml:"apiVersion" validate:"required"`
|
||||
AppVersion string `yaml:"appVersion" validate:"required"`
|
||||
Dependencies []Dependency `yaml:"dependencies"`
|
||||
Deprecated bool `yaml:"deprecated"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
Home string `yaml:"home" validate:"required"`
|
||||
Icon string `yaml:"icon" validate:"required"`
|
||||
Keywords []string `yaml:"keywords"`
|
||||
KubeVersion string `yaml:"kubeVersion" validate:"required"`
|
||||
Maintainers []Maintainer `yaml:"maintainers" validate:"required,dive"`
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Sources []string `yaml:"sources"`
|
||||
Type string `yaml:"type"`
|
||||
Version string `yaml:"version" validate:"required"`
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
// HelmChart represents the entire Chart.yaml structure.
|
||||
type HelmChart struct {
|
||||
K *koanf.Koanf
|
||||
Metadata ChartMetadata `yaml:"metadata" validate:"required,dive"`
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
func NewHelmChart() *HelmChart {
|
||||
return &HelmChart{
|
||||
K: koanf.New("."),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromFile loads values from a YAML file into the HelmChart struct.
|
||||
func (h *HelmChart) LoadFromFile(filename string) error {
|
||||
// Load YAML file using koanf
|
||||
if err := h.K.Load(file.Provider(filename), yaml.Parser()); err != nil {
|
||||
return fmt.Errorf("error loading from file %s: %v", filename, err)
|
||||
}
|
||||
|
||||
// Unmarshal the data into the HelmChart struct
|
||||
if err := h.K.Unmarshal("", &h.Metadata); err != nil {
|
||||
return fmt.Errorf("error unmarshalling data: %v", err)
|
||||
}
|
||||
|
||||
// Set default values for fields if they are not set or empty
|
||||
h.setDefaultValues()
|
||||
|
||||
// Initialize validator
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
if err := validate.Struct(h.Metadata); err != nil {
|
||||
return fmt.Errorf("chart.yaml validation error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setDefaultValues sets default values for fields in ChartMetadata if they are not set or empty.
|
||||
func (h *HelmChart) setDefaultValues() {
|
||||
h.setDeprecation()
|
||||
h.setApiVersion(apiVersion)
|
||||
h.setKubeVersion(kubeVersion)
|
||||
h.setType(chartType)
|
||||
h.setAppVersion(defaultAppVersion)
|
||||
h.setDescription(defaultDescription)
|
||||
h.setIcon(defaultIcon)
|
||||
h.setHome(defaultHome)
|
||||
|
||||
h.setMaintainers(Maintainer{
|
||||
Name: maintainerName,
|
||||
Email: maintainerEmail,
|
||||
URL: maintainerURL,
|
||||
})
|
||||
|
||||
// Make sure annotations is not nil
|
||||
if h.Metadata.Annotations == nil {
|
||||
h.Metadata.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
h.setAnnotation("truecharts.org/category", defaultCategory, false)
|
||||
h.setAnnotation("truecharts.org/min_helm_version", minHelmVersion, true)
|
||||
h.setAnnotation("truecharts.org/max_helm_version", maxHelmVersion, true)
|
||||
|
||||
// Set default values for other fields as needed
|
||||
}
|
||||
|
||||
// SaveToFile saves the Helm chart metadata back to the Chart.yaml file.
|
||||
func (h *HelmChart) SaveToFile(filename string) error {
|
||||
|
||||
// Initialize validator
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
if err := validate.Struct(h.Metadata); err != nil {
|
||||
return fmt.Errorf("chart.yaml validation error: %v", err)
|
||||
}
|
||||
|
||||
var configBytes bytes.Buffer
|
||||
err := helper.MarshalYaml(&configBytes, h.Metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding data: %v", err)
|
||||
}
|
||||
|
||||
// Write the configuration to the file using os.WriteFile
|
||||
err = os.WriteFile(filename, configBytes.Bytes(), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to file %s: %v", filename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAnnotation sets the annotation key to value if it is not set or empty or force is true.
|
||||
func (h *HelmChart) setAnnotation(key, value string, force bool) {
|
||||
if a, ok := h.Metadata.Annotations[key]; !ok || a == "" || force {
|
||||
h.Metadata.Annotations[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// setDeprecation sets the deprecation field to false if it is not set.
|
||||
func (h *HelmChart) setDeprecation() {
|
||||
if !h.Metadata.Deprecated {
|
||||
h.Metadata.Deprecated = false
|
||||
}
|
||||
}
|
||||
|
||||
// setIcon sets the icon field to icon if it is not set or empty.
|
||||
func (h *HelmChart) setIcon(icon string) {
|
||||
if h.Metadata.Icon == "" {
|
||||
h.Metadata.Icon = icon
|
||||
}
|
||||
}
|
||||
|
||||
// setHome sets the home field to home if it is not set or empty.
|
||||
func (h *HelmChart) setHome(home string) {
|
||||
if h.Metadata.Home == "" {
|
||||
h.Metadata.Home = home
|
||||
}
|
||||
}
|
||||
|
||||
// setDescription sets the description field to description if it is not set or empty.
|
||||
func (h *HelmChart) setDescription(description string) {
|
||||
if h.Metadata.Description == "" {
|
||||
h.Metadata.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
// setAppVersion sets the appVersion field to appVersion if it is not set or empty.
|
||||
func (h *HelmChart) setAppVersion(appVersion string) {
|
||||
if h.Metadata.AppVersion == "" {
|
||||
h.Metadata.AppVersion = appVersion
|
||||
}
|
||||
}
|
||||
|
||||
// setType sets the type field to cType if it is not set or empty.
|
||||
func (h *HelmChart) setType(cType string) {
|
||||
if h.Metadata.Type == "" {
|
||||
h.Metadata.Type = cType
|
||||
}
|
||||
}
|
||||
|
||||
// setApiVersion sets the apiVersion field to apiVersion
|
||||
func (h *HelmChart) setApiVersion(apiVersion string) {
|
||||
h.Metadata.APIVersion = apiVersion
|
||||
}
|
||||
|
||||
// setKubeVersion sets the kubeVersion field to kubeVersion
|
||||
func (h *HelmChart) setKubeVersion(kubeVersion string) {
|
||||
h.Metadata.KubeVersion = kubeVersion
|
||||
}
|
||||
|
||||
// setMaintainers sets the maintainers field to maintainers
|
||||
func (h *HelmChart) setMaintainers(maintainers Maintainer) {
|
||||
h.Metadata.Maintainers = make([]Maintainer, 1)
|
||||
h.Metadata.Maintainers[0] = maintainers
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetAnnotation(t *testing.T) {
|
||||
type args struct {
|
||||
key string
|
||||
value string
|
||||
force bool
|
||||
}
|
||||
|
||||
type testData struct {
|
||||
name string
|
||||
data args
|
||||
initial map[string]string
|
||||
want map[string]string
|
||||
}
|
||||
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should set annotation when not present",
|
||||
initial: map[string]string{},
|
||||
want: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
data: args{
|
||||
key: "test",
|
||||
value: "test",
|
||||
force: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set annotation when present and force is false",
|
||||
initial: map[string]string{
|
||||
"test": "value",
|
||||
},
|
||||
want: map[string]string{
|
||||
"test": "value",
|
||||
},
|
||||
data: args{
|
||||
key: "test",
|
||||
value: "test",
|
||||
force: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should set annotation when present and force is true",
|
||||
initial: map[string]string{
|
||||
"test": "value",
|
||||
},
|
||||
want: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
data: args{
|
||||
key: "test",
|
||||
value: "test",
|
||||
force: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata.Annotations = tt.initial
|
||||
|
||||
h.setAnnotation(tt.data.key, tt.data.value, tt.data.force)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata.Annotations, tt.want) {
|
||||
t.Errorf("%s - Annotations, got %v, want %v", tt.name, h.Metadata.Annotations, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDeprecation(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
input ChartMetadata
|
||||
want ChartMetadata
|
||||
}
|
||||
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should set deprecation to false when not present",
|
||||
input: ChartMetadata{
|
||||
Deprecated: false,
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Deprecated: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set deprecation when present",
|
||||
input: ChartMetadata{
|
||||
Deprecated: true,
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Deprecated: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.input
|
||||
|
||||
h.setDeprecation()
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type TestData struct {
|
||||
name string
|
||||
value string
|
||||
initial ChartMetadata
|
||||
want ChartMetadata
|
||||
}
|
||||
|
||||
func TestSetIcon(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set icon when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Icon: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Icon: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set icon when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Icon: "some-icon",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Icon: "some-icon",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setIcon(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHome(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set home when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Home: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Home: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set home when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Home: "some-home",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Home: "some-home",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setHome(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDescription(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set description when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Description: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Description: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set description when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Description: "some-description",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Description: "some-description",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setDescription(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAppVersion(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set appVersion when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
AppVersion: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
AppVersion: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set appVersion when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
AppVersion: "some-appVersion",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
AppVersion: "some-appVersion",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setAppVersion(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetType(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set type when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Type: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should not set type when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
Type: "some-type",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Type: "some-type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setType(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetApiVersion(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set apiVersion when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
APIVersion: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
APIVersion: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should always set apiVersion when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
APIVersion: "some-apiVersion",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
APIVersion: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setApiVersion(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetKubeVersion(t *testing.T) {
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Should set kubeVersion when not present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
KubeVersion: "",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
KubeVersion: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should always set kubeVersion when present",
|
||||
value: "test",
|
||||
initial: ChartMetadata{
|
||||
KubeVersion: "some-kubeVersion",
|
||||
},
|
||||
want: ChartMetadata{
|
||||
KubeVersion: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setKubeVersion(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMaintainers(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
value Maintainer
|
||||
initial ChartMetadata
|
||||
want ChartMetadata
|
||||
}
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should set maintainers when not present",
|
||||
value: Maintainer{
|
||||
Name: "test-name",
|
||||
Email: "test-mail",
|
||||
URL: "test-url",
|
||||
},
|
||||
initial: ChartMetadata{
|
||||
Maintainers: []Maintainer{},
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Maintainers: []Maintainer{
|
||||
{
|
||||
Name: "test-name",
|
||||
Email: "test-mail",
|
||||
URL: "test-url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should always set maintainers when present",
|
||||
value: Maintainer{
|
||||
Name: "test-name",
|
||||
Email: "test-mail",
|
||||
URL: "test-url",
|
||||
},
|
||||
initial: ChartMetadata{
|
||||
Maintainers: []Maintainer{
|
||||
{
|
||||
Name: "some-maintainer",
|
||||
Email: "some-mail",
|
||||
URL: "some-url",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: ChartMetadata{
|
||||
Maintainers: []Maintainer{
|
||||
{
|
||||
Name: "test-name",
|
||||
Email: "test-mail",
|
||||
URL: "test-url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setMaintainers(tt.value)
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata.Maintainers, tt.want.Maintainers) {
|
||||
t.Errorf("%s - Maintainers, got %v, want %v", tt.name, h.Metadata.Maintainers, tt.want.Maintainers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaults(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
initial ChartMetadata
|
||||
want ChartMetadata
|
||||
}
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should set defaults",
|
||||
initial: ChartMetadata{},
|
||||
want: ChartMetadata{
|
||||
KubeVersion: kubeVersion,
|
||||
APIVersion: apiVersion,
|
||||
Type: chartType,
|
||||
Deprecated: false,
|
||||
AppVersion: defaultAppVersion,
|
||||
Description: defaultDescription,
|
||||
Home: defaultHome,
|
||||
Icon: defaultIcon,
|
||||
Maintainers: []Maintainer{
|
||||
{
|
||||
Name: maintainerName,
|
||||
Email: maintainerEmail,
|
||||
URL: maintainerURL,
|
||||
},
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"truecharts.org/category": defaultCategory,
|
||||
"truecharts.org/min_helm_version": minHelmVersion,
|
||||
"truecharts.org/max_helm_version": maxHelmVersion,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
h := NewHelmChart()
|
||||
h.Metadata = tt.initial
|
||||
|
||||
h.setDefaultValues()
|
||||
|
||||
if !reflect.DeepEqual(h.Metadata, tt.want) {
|
||||
t.Errorf("%s - Metadata, got %v, want %v", tt.name, h.Metadata, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLoadFromFile(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
file string
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
testDataPath := "../../testdata/chart_yaml"
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should load from file",
|
||||
file: "validChart.yaml",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Should fail to load from malformed file",
|
||||
file: "malformedChart.yaml",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Should fail to load from missing file",
|
||||
file: "missingChart.yaml",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Should fail to load from invalid file",
|
||||
file: "invalidChart.yaml",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Should fail to load from unmashalable file",
|
||||
file: "unmarshalableChart.yaml",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
|
||||
err := h.LoadFromFile(fmt.Sprintf("%s/%s", testDataPath, tt.file))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%s - LoadFromFile() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveToFile(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
inFile string
|
||||
outFile string
|
||||
mutatedData ChartMetadata
|
||||
shouldMutate bool
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
testDataPath := "../../testdata/chart_yaml"
|
||||
tests := []testData{
|
||||
{
|
||||
name: "Should fail to save to file",
|
||||
inFile: "validChart.yaml",
|
||||
outFile: "/tmp/test.yaml",
|
||||
mutatedData: ChartMetadata{},
|
||||
shouldMutate: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Should save to file",
|
||||
inFile: "validChart.yaml",
|
||||
outFile: "/tmp/test.yaml",
|
||||
mutatedData: ChartMetadata{},
|
||||
shouldMutate: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Should fail to write to file",
|
||||
inFile: "validChart.yaml",
|
||||
outFile: "/non-existent-dir/test.yaml",
|
||||
mutatedData: ChartMetadata{},
|
||||
shouldMutate: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := NewHelmChart()
|
||||
if err := h.LoadFromFile(fmt.Sprintf("%s/%s", testDataPath, tt.inFile)); err != nil {
|
||||
t.Errorf("%s - LoadFromFile() error = %v", tt.name, err)
|
||||
}
|
||||
|
||||
if tt.shouldMutate {
|
||||
h.Metadata = tt.mutatedData
|
||||
}
|
||||
|
||||
err := h.SaveToFile(tt.outFile)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%s - SaveToFile() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/private/clustertool/pkg/charts/helmignore"
|
||||
"github.com/truecharts/private/clustertool/pkg/charts/image"
|
||||
"github.com/truecharts/private/clustertool/pkg/charts/readme"
|
||||
"github.com/truecharts/private/clustertool/pkg/charts/version"
|
||||
)
|
||||
|
||||
// UpdateChartFile updates the specified Chart.yaml file with an optional bump parameter.
|
||||
func UpdateChartFile(chartPathOrFolder, bump string) error {
|
||||
fileInfo, err := os.Stat(chartPathOrFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chartPath := chartPathOrFolder
|
||||
if fileInfo.IsDir() {
|
||||
chartPath = filepath.Join(chartPathOrFolder, "Chart.yaml")
|
||||
}
|
||||
|
||||
log.Info().Msgf("🏃 Processing chart [%s]", chartPath)
|
||||
chart := NewHelmChart()
|
||||
if err := chart.LoadFromFile(chartPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chart.Metadata.Annotations == nil {
|
||||
chart.Metadata.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
train := GetTrain(chartPath, chart)
|
||||
setMetadata(chart, train)
|
||||
|
||||
var values image.Images
|
||||
// Fetch image details from values.yaml
|
||||
if err := values.LoadValuesFile(filepath.Join(filepath.Dir(chartPath), "values.yaml")); err != nil {
|
||||
return err
|
||||
}
|
||||
setAppVersionFromImage(chart, &values, "image")
|
||||
|
||||
var imageLinks []string
|
||||
for _, details := range values.ImagesMap {
|
||||
imageLinks = append(imageLinks, details.Link)
|
||||
}
|
||||
|
||||
// Attempt to update sources
|
||||
if err := updateSources(chart, train, imageLinks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update appVersion, icon, and home URLs
|
||||
if bump == version.Major || bump == version.Minor || bump == version.Patch {
|
||||
newVersion, err := version.IncrementVersion(chart.Metadata.Version, bump)
|
||||
log.Info().Msgf("🆚 Bumping [%s], from [%s] to [%s]", chart.Metadata.Name, chart.Metadata.Version, newVersion)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error bumping version")
|
||||
}
|
||||
chart.Metadata.Version = newVersion
|
||||
}
|
||||
|
||||
// Save the modified metadata back to the file
|
||||
if err := chart.SaveToFile(chartPath); err != nil {
|
||||
return fmt.Errorf("error saving Chart.yaml: %s", err)
|
||||
}
|
||||
|
||||
log.Info().Msgf("Chart file updated and saved to [%s]", chartPath)
|
||||
|
||||
templateDir := chartPath
|
||||
for i := 0; i < 4; i++ {
|
||||
templateDir = filepath.Dir(templateDir)
|
||||
}
|
||||
|
||||
// Generate README.md for the specified train and chart
|
||||
readmeErr := readme.GenerateReadme(templateDir, chartPath, chart.Metadata.Name, train)
|
||||
if readmeErr != nil {
|
||||
log.Info().Msgf("Error Generating readme for %v: %v\n", chart.Metadata.Name, readmeErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Generate .helmignore for the specified train and chart
|
||||
helmignoreErr := helmignore.GenerateHelmIgnore(templateDir, chartPath)
|
||||
if helmignoreErr != nil {
|
||||
log.Info().Msgf("Error Generating helmignore for %v: %v\n", chart.Metadata.Name, helmignoreErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAppVersionFromImage(chart *HelmChart, imageMap *image.Images, key string) {
|
||||
imageDetails, exists := imageMap.ImagesMap[key]
|
||||
if !exists {
|
||||
log.Warn().Msgf("Details for image key [%s] not found in values.yaml, skipping setting appVersion", key)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msgf("Detected - Tag [%s], Image Version [%s]", imageDetails.Tag, imageDetails.Version)
|
||||
chart.Metadata.AppVersion = imageDetails.Version
|
||||
}
|
||||
|
||||
// detectTrainFromFile detects the train name based on the path of Chart.yaml.
|
||||
func detectTrainFromFile(chartFilename string) string {
|
||||
parts := strings.Split(
|
||||
// Remove the filename from the path
|
||||
filepath.Dir(chartFilename),
|
||||
string(os.PathSeparator),
|
||||
)
|
||||
|
||||
if len(parts) >= 2 {
|
||||
return parts[len(parts)-2]
|
||||
}
|
||||
|
||||
// One case to reach here is when the tool is run from inside the train directory
|
||||
// But we can't safely assume the current directory is the train directory
|
||||
log.Error().Msgf("Unable to detect train from path [%s]", chartFilename)
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetTrain(chartPath string, chart *HelmChart) string {
|
||||
// Detect the train from the path of Chart.yaml
|
||||
// Do not rely on the annotations in the chart, as they may be outdated
|
||||
train := detectTrainFromFile(chartPath)
|
||||
if train == "" {
|
||||
// If the train cannot be detected from the path, fallback to detect it from the annotations
|
||||
if val, exists := chart.Metadata.Annotations["truecharts.org/train"]; exists {
|
||||
train = val
|
||||
} else {
|
||||
log.Error().Msgf("Unable to detect train for chart [%s]. Setting as [unknown]", chart.Metadata.Name)
|
||||
train = "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
return train
|
||||
}
|
||||
|
||||
func setMetadata(chart *HelmChart, train string) {
|
||||
chart.Metadata.Annotations["truecharts.org/train"] = train
|
||||
chart.Metadata.Icon = fmt.Sprintf("https://truecharts.org/img/hotlink-ok/chart-icons/%s.webp", chart.Metadata.Name)
|
||||
chart.Metadata.Home = fmt.Sprintf("https://truecharts.org/charts/%s/%s", train, chart.Metadata.Name)
|
||||
}
|
||||
|
||||
// UpdateSources updates the sources in Chart.yaml using Go.
|
||||
func updateSources(chart *HelmChart, train string, imageLinks []string) error {
|
||||
var updatedSources []string
|
||||
|
||||
// Those sources are automatically generated by this tool,
|
||||
// So we only need to keep sources that are not in this list
|
||||
for _, source := range chart.Metadata.Sources {
|
||||
if !strings.HasPrefix(source, "https://ghcr") &&
|
||||
!strings.HasPrefix(source, "https://docker.io") &&
|
||||
!strings.HasPrefix(source, "https://hub.docker") &&
|
||||
!strings.HasPrefix(source, "https://fleet.linuxserver") &&
|
||||
!strings.HasPrefix(source, "https://mcr.microsoft") &&
|
||||
!strings.HasPrefix(source, "https://cr.hotio.dev") &&
|
||||
!strings.HasPrefix(source, "https://github.com/truecharts") &&
|
||||
!strings.HasPrefix(source, "https://gallery.ecr.aws") &&
|
||||
!strings.HasPrefix(source, "https://gcr") &&
|
||||
!strings.HasPrefix(source, "https://quay") &&
|
||||
!strings.HasPrefix(source, "http://") &&
|
||||
!strings.Contains(source, ".azurecr.io") &&
|
||||
!strings.Contains(source, ".ocir.io") {
|
||||
if source != "" {
|
||||
log.Info().Msgf("🔗 Keeping source [%s]", source)
|
||||
updatedSources = append(updatedSources, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the GitHub source for the chart
|
||||
ghSource := fmt.Sprintf("https://github.com/truecharts/charts/tree/master/charts/%s/%s", train, chart.Metadata.Name)
|
||||
updatedSources = append(updatedSources, ghSource)
|
||||
|
||||
// Add new sources for each image
|
||||
updatedSources = append(updatedSources, imageLinks...)
|
||||
|
||||
// Deduplicate sources
|
||||
deduplicatedSources := make(map[string]bool)
|
||||
var finalSources []string
|
||||
for _, source := range updatedSources {
|
||||
// Skip empty sources
|
||||
if source == "" {
|
||||
continue
|
||||
}
|
||||
// Skip sources that have already been added
|
||||
if _, exists := deduplicatedSources[source]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the source to the list of sources and mark it as added
|
||||
deduplicatedSources[source] = true
|
||||
finalSources = append(finalSources, source)
|
||||
}
|
||||
|
||||
// Sort the sources, so subsequent commits will only include actual changes
|
||||
slices.Sort(finalSources)
|
||||
|
||||
// Update the chart's sources
|
||||
chart.Metadata.Sources = finalSources
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/truecharts/private/clustertool/pkg/charts/image"
|
||||
)
|
||||
|
||||
func TestSetAppVersionFromImage(t *testing.T) {
|
||||
type TestData struct {
|
||||
chart *HelmChart
|
||||
image *image.Images
|
||||
key string
|
||||
result string
|
||||
}
|
||||
|
||||
tests := []TestData{
|
||||
{
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
AppVersion: "1.0.0",
|
||||
},
|
||||
},
|
||||
image: &image.Images{
|
||||
ImagesMap: map[string]image.ImageDetails{
|
||||
"image": {
|
||||
Repository: "nginx",
|
||||
Tag: "1.15.8",
|
||||
Link: "https://hub.docker.com/_/nginx",
|
||||
Version: "1.15.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
key: "image",
|
||||
result: "1.15.8",
|
||||
},
|
||||
{
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
AppVersion: "1.0.0",
|
||||
},
|
||||
},
|
||||
image: &image.Images{
|
||||
ImagesMap: map[string]image.ImageDetails{
|
||||
"image": {
|
||||
Repository: "nginx",
|
||||
Tag: "1.15.8",
|
||||
Link: "https://hub.docker.com/_/nginx",
|
||||
Version: "1.15.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
key: "nonexistent",
|
||||
result: "1.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
setAppVersionFromImage(tt.chart, tt.image, tt.key)
|
||||
if tt.chart.Metadata.AppVersion != tt.result {
|
||||
t.Errorf("Expected %s, got %s", tt.result, tt.chart.Metadata.AppVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestGetTrain(t *testing.T) {
|
||||
type TestData struct {
|
||||
name string
|
||||
chart *HelmChart
|
||||
chartPath string
|
||||
result string
|
||||
}
|
||||
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Test get train from path",
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Annotations: map[string]string{
|
||||
"truecharts.org/train": "express",
|
||||
},
|
||||
},
|
||||
},
|
||||
chartPath: "../../testdata/updater/stable/my-app/Chart.yaml",
|
||||
result: "stable",
|
||||
},
|
||||
{
|
||||
name: "Test get train from annotations as fallback",
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Annotations: map[string]string{
|
||||
"truecharts.org/train": "dev",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Too short path, cant detect train from path
|
||||
// so we should fallback to annotations
|
||||
chartPath: "my-app/Chart.yaml",
|
||||
result: "dev",
|
||||
},
|
||||
{
|
||||
name: "Test failing to get train from path or annotations",
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
// Too short path, cant detect train from path
|
||||
// so we should fallback to annotations
|
||||
chartPath: "my-app/Chart.yaml",
|
||||
result: "unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
train := GetTrain(tt.chartPath, tt.chart)
|
||||
|
||||
if train != tt.result {
|
||||
t.Errorf("Expected train to be %s, but got %s", tt.result, train)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMetadata(t *testing.T) {
|
||||
type TestData struct {
|
||||
chart *HelmChart
|
||||
train string
|
||||
expected *HelmChart
|
||||
}
|
||||
|
||||
tests := []TestData{
|
||||
{
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
train: "stable",
|
||||
expected: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Annotations: map[string]string{
|
||||
"truecharts.org/train": "stable",
|
||||
},
|
||||
Icon: "https://truecharts.org/img/hotlink-ok/chart-icons/test-chart.webp",
|
||||
Home: "https://truecharts.org/charts/stable/test-chart",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.chart.Metadata.Name, func(t *testing.T) {
|
||||
setMetadata(tt.chart, tt.train)
|
||||
|
||||
if !reflect.DeepEqual(tt.chart, tt.expected) {
|
||||
t.Errorf("Expected chart to be %v, but got %v", tt.expected, tt.chart)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSources(t *testing.T) {
|
||||
type TestData struct {
|
||||
name string
|
||||
chart *HelmChart
|
||||
train string
|
||||
imageLinks []string
|
||||
expected []string
|
||||
}
|
||||
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Test update sources",
|
||||
chart: &HelmChart{
|
||||
Metadata: ChartMetadata{
|
||||
Name: "test-chart",
|
||||
Sources: []string{
|
||||
"",
|
||||
"https://ghcr/truecharts/some-chart",
|
||||
"https://docker.io/truecharts/some-chart",
|
||||
"https://hub.docker/truecharts/some-chart",
|
||||
"https://fleet.linuxserver/truecharts/some-chart",
|
||||
"https://mcr.microsoft/truecharts/some-chart",
|
||||
"https://github.com/truecharts/some-chart",
|
||||
"https://gallery.ecr.aws/truecharts/some-chart",
|
||||
"https://gcr/truecharts/some-chart",
|
||||
"https://quay/truecharts/some-chart",
|
||||
"http://truecharts/some-chart",
|
||||
"https://truecharts.azurecr.io/some-chart",
|
||||
"https://truecharts.ocir.io/some-chart",
|
||||
"https://unrelated.com/some-chart",
|
||||
"https://unrelated.com/some-chart",
|
||||
"https://cr.hotio.dev/truecharts/some-chart",
|
||||
},
|
||||
},
|
||||
},
|
||||
train: "stable",
|
||||
imageLinks: []string{
|
||||
"",
|
||||
"https://hub.docker.com/_/nginx",
|
||||
"https://quay.io/truecharts/test-chart",
|
||||
},
|
||||
expected: []string{
|
||||
"https://github.com/truecharts/charts/tree/master/charts/stable/test-chart",
|
||||
"https://hub.docker.com/_/nginx",
|
||||
"https://quay.io/truecharts/test-chart",
|
||||
"https://unrelated.com/some-chart",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.chart.Metadata.Name, func(t *testing.T) {
|
||||
if err := updateSources(tt.chart, tt.train, tt.imageLinks); err != nil {
|
||||
t.Errorf("Expected no error, but got %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.chart.Metadata.Sources, tt.expected) {
|
||||
t.Errorf("Expected chart to be %v, but got %v", tt.expected, tt.chart.Metadata.Sources)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user