fix pre-commit and cleanup
This commit is contained in:
@@ -1,60 +1,60 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ActiveChart struct {
|
||||
Name string
|
||||
Train string
|
||||
Name string
|
||||
Train string
|
||||
}
|
||||
|
||||
type ActiveCharts struct {
|
||||
items map[string]ActiveChart
|
||||
mu *sync.RWMutex
|
||||
items map[string]ActiveChart
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *ActiveCharts) isActiveChart(chartName string) bool {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
_, ok := a.items[chartName]
|
||||
return ok
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
_, ok := a.items[chartName]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (a *ActiveCharts) getActiveChartsWalker(path string, entry os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.Name() != "Chart.yaml" {
|
||||
return nil
|
||||
}
|
||||
// path = charts/<train>/<chart>/Chart.yaml
|
||||
segLen := len(strings.Split(path, "/"))
|
||||
if segLen < 3 {
|
||||
return fmt.Errorf("path (%s) is not valid. expected at least charts/<train>/<chart>/Chart.yaml", path)
|
||||
}
|
||||
// chart = charts/<train>/<chart>/
|
||||
chart, _ := filepath.Split(path)
|
||||
// chart = charts/<train>/<chart>
|
||||
chart = strings.TrimSuffix(chart, "/")
|
||||
// train = charts/<train>
|
||||
train := filepath.Dir(chart)
|
||||
// train = <train>
|
||||
train = filepath.Base(train)
|
||||
// chartName = <chart>
|
||||
chartName := filepath.Base(chart)
|
||||
a.mu.Lock()
|
||||
if _, ok := a.items[chartName]; !ok {
|
||||
a.items[chartName] = ActiveChart{Name: chartName, Train: train}
|
||||
} else {
|
||||
log.Error().Msgf("chart [%s] already exists in activeCharts", chartName)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.Name() != "Chart.yaml" {
|
||||
return nil
|
||||
}
|
||||
// path = charts/<train>/<chart>/Chart.yaml
|
||||
segLen := len(strings.Split(path, "/"))
|
||||
if segLen < 3 {
|
||||
return fmt.Errorf("path (%s) is not valid. expected at least charts/<train>/<chart>/Chart.yaml", path)
|
||||
}
|
||||
// chart = charts/<train>/<chart>/
|
||||
chart, _ := filepath.Split(path)
|
||||
// chart = charts/<train>/<chart>
|
||||
chart = strings.TrimSuffix(chart, "/")
|
||||
// train = charts/<train>
|
||||
train := filepath.Dir(chart)
|
||||
// train = <train>
|
||||
train = filepath.Base(train)
|
||||
// chartName = <chart>
|
||||
chartName := filepath.Base(chart)
|
||||
a.mu.Lock()
|
||||
if _, ok := a.items[chartName]; !ok {
|
||||
a.items[chartName] = ActiveChart{Name: chartName, Train: train}
|
||||
} else {
|
||||
log.Error().Msgf("chart [%s] already exists in activeCharts", chartName)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,187 +1,187 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ChangedData struct {
|
||||
mu *sync.RWMutex `json:"-"`
|
||||
LastCommit string `json:"last_commit"`
|
||||
Charts map[string]*Chart `json:"charts"`
|
||||
mu *sync.RWMutex `json:"-"`
|
||||
LastCommit string `json:"last_commit"`
|
||||
Charts map[string]*Chart `json:"charts"`
|
||||
}
|
||||
|
||||
type Chart struct {
|
||||
Versions map[string]*Version `json:"versions"`
|
||||
SortedVersions []string `json:"-"` // Used only for rendering
|
||||
Name string `json:"-"` // Used only for rendering
|
||||
Train string `json:"-"` // Used only for rendering
|
||||
Versions map[string]*Version `json:"versions"`
|
||||
SortedVersions []string `json:"-"` // Used only for rendering
|
||||
Name string `json:"-"` // Used only for rendering
|
||||
Train string `json:"-"` // Used only for rendering
|
||||
}
|
||||
|
||||
func (c *Chart) SortVersions(reverse bool) ([]*semver.Version, error) {
|
||||
chartVersions := []*semver.Version{}
|
||||
for key := range c.Versions {
|
||||
semVer, err := semver.NewVersion(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chartVersions = append(chartVersions, semVer)
|
||||
}
|
||||
// Sort the versions from oldest to newest
|
||||
sort.Slice(chartVersions, func(i, j int) bool {
|
||||
if reverse {
|
||||
return chartVersions[i].GreaterThan(chartVersions[j])
|
||||
}
|
||||
return chartVersions[i].LessThan(chartVersions[j])
|
||||
})
|
||||
chartVersions := []*semver.Version{}
|
||||
for key := range c.Versions {
|
||||
semVer, err := semver.NewVersion(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chartVersions = append(chartVersions, semVer)
|
||||
}
|
||||
// Sort the versions from oldest to newest
|
||||
sort.Slice(chartVersions, func(i, j int) bool {
|
||||
if reverse {
|
||||
return chartVersions[i].GreaterThan(chartVersions[j])
|
||||
}
|
||||
return chartVersions[i].LessThan(chartVersions[j])
|
||||
})
|
||||
|
||||
for _, version := range chartVersions {
|
||||
c.SortedVersions = append(c.SortedVersions, version.String())
|
||||
}
|
||||
for _, version := range chartVersions {
|
||||
c.SortedVersions = append(c.SortedVersions, version.String())
|
||||
}
|
||||
|
||||
return chartVersions, nil
|
||||
return chartVersions, nil
|
||||
}
|
||||
|
||||
func (c *ChangedData) AddOrUpdateChart(chart string, version string, train string, commit *object.Commit) {
|
||||
if c.Charts == nil {
|
||||
c.Charts = make(map[string]*Chart)
|
||||
}
|
||||
_, exists := c.Charts[chart]
|
||||
if !exists {
|
||||
c.Charts[chart] = &Chart{}
|
||||
}
|
||||
if c.Charts == nil {
|
||||
c.Charts = make(map[string]*Chart)
|
||||
}
|
||||
_, exists := c.Charts[chart]
|
||||
if !exists {
|
||||
c.Charts[chart] = &Chart{}
|
||||
}
|
||||
|
||||
c.Charts[chart].AddVersion(version, train)
|
||||
c.Charts[chart].Versions[version].AddCommit(commit)
|
||||
c.Charts[chart].AddVersion(version, train)
|
||||
c.Charts[chart].Versions[version].AddCommit(commit)
|
||||
}
|
||||
|
||||
func (c *Chart) AddVersion(version string, train string) {
|
||||
if c.Versions == nil {
|
||||
c.Versions = make(map[string]*Version)
|
||||
}
|
||||
_, exists := c.Versions[version]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
c.Versions[version] = &Version{
|
||||
Version: version,
|
||||
Train: train,
|
||||
Commits: make(map[string]*Commit),
|
||||
}
|
||||
if c.Versions == nil {
|
||||
c.Versions = make(map[string]*Version)
|
||||
}
|
||||
_, exists := c.Versions[version]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
c.Versions[version] = &Version{
|
||||
Version: version,
|
||||
Train: train,
|
||||
Commits: make(map[string]*Commit),
|
||||
}
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Version string `json:"version"`
|
||||
Train string `json:"train"`
|
||||
Commits map[string]*Commit `json:"commits"`
|
||||
SortedCommits []*Commit `json:"-"` // Used only for rendering
|
||||
Version string `json:"version"`
|
||||
Train string `json:"train"`
|
||||
Commits map[string]*Commit `json:"commits"`
|
||||
SortedCommits []*Commit `json:"-"` // Used only for rendering
|
||||
}
|
||||
|
||||
func (v *Version) AddCommit(commit *object.Commit) {
|
||||
if v.Commits == nil {
|
||||
v.Commits = make(map[string]*Commit)
|
||||
}
|
||||
if v.Commits == nil {
|
||||
v.Commits = make(map[string]*Commit)
|
||||
}
|
||||
|
||||
_, exists := v.Commits[commit.Hash.String()]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
v.Commits[commit.Hash.String()] = &Commit{
|
||||
CommitHash: commit.Hash.String(),
|
||||
ParentHash: commit.ParentHashes[0].String(),
|
||||
Author: Author{Name: commit.Author.Name, Date: commit.Author.When.Format(dateFormat)},
|
||||
Message: getCommitMessage(commit),
|
||||
Kind: getCommitKind(commit),
|
||||
}
|
||||
_, exists := v.Commits[commit.Hash.String()]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
v.Commits[commit.Hash.String()] = &Commit{
|
||||
CommitHash: commit.Hash.String(),
|
||||
ParentHash: commit.ParentHashes[0].String(),
|
||||
Author: Author{Name: commit.Author.Name, Date: commit.Author.When.Format(dateFormat)},
|
||||
Message: getCommitMessage(commit),
|
||||
Kind: getCommitKind(commit),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Version) SortCommits(reverse bool) ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
for _, commit := range v.Commits {
|
||||
commits = append(commits, commit)
|
||||
}
|
||||
commits := []*Commit{}
|
||||
for _, commit := range v.Commits {
|
||||
commits = append(commits, commit)
|
||||
}
|
||||
|
||||
hasErr := false
|
||||
sort.Slice(commits, func(i, j int) bool {
|
||||
// While we could store the time.Time in the Author struct,
|
||||
// it was giving mixed results as the timezones were different.
|
||||
// The dateFormat we use does not contain timezone, so it sorts better.
|
||||
iDate, err := time.Parse(dateFormat, commits[i].Author.Date)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to parse date [%s]", commits[i].Author.Date)
|
||||
return false
|
||||
}
|
||||
jDate, err := time.Parse(dateFormat, commits[j].Author.Date)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to parse date [%s]", commits[j].Author.Date)
|
||||
return false
|
||||
}
|
||||
if reverse {
|
||||
return iDate.After(jDate)
|
||||
}
|
||||
return iDate.Before(jDate)
|
||||
})
|
||||
hasErr := false
|
||||
sort.Slice(commits, func(i, j int) bool {
|
||||
// While we could store the time.Time in the Author struct,
|
||||
// it was giving mixed results as the timezones were different.
|
||||
// The dateFormat we use does not contain timezone, so it sorts better.
|
||||
iDate, err := time.Parse(dateFormat, commits[i].Author.Date)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to parse date [%s]", commits[i].Author.Date)
|
||||
return false
|
||||
}
|
||||
jDate, err := time.Parse(dateFormat, commits[j].Author.Date)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to parse date [%s]", commits[j].Author.Date)
|
||||
return false
|
||||
}
|
||||
if reverse {
|
||||
return iDate.After(jDate)
|
||||
}
|
||||
return iDate.Before(jDate)
|
||||
})
|
||||
|
||||
if hasErr {
|
||||
return nil, errors.New("failed to sort commits")
|
||||
}
|
||||
if hasErr {
|
||||
return nil, errors.New("failed to sort commits")
|
||||
}
|
||||
|
||||
v.SortedCommits = commits
|
||||
return commits, nil
|
||||
v.SortedCommits = commits
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
CommitHash string `json:"commit_hash"`
|
||||
ParentHash string `json:"parent_hash"`
|
||||
Author Author `json:"author"`
|
||||
Kind string `json:"kind"`
|
||||
Message string `json:"message"`
|
||||
CommitHash string `json:"commit_hash"`
|
||||
ParentHash string `json:"parent_hash"`
|
||||
Author Author `json:"author"`
|
||||
Kind string `json:"kind"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
Date string `json:"date"`
|
||||
Name string `json:"name"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
func (c *ChangedData) LoadFromFile(path string) error {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return fmt.Errorf("path is a directory")
|
||||
}
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return fmt.Errorf("path is a directory")
|
||||
}
|
||||
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytes, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(bytes, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChangedData) WriteToFile(path string) error {
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("Writing changed data to [%s]", path)
|
||||
return os.WriteFile(path, data, 0644)
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("Writing changed data to [%s]", path)
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
)
|
||||
|
||||
type ChangelogOptions struct {
|
||||
RepoPath string // Path to the repository (eg "./charts")
|
||||
TemplatePath string // Path to the template file (eg "./changelog.tmpl")
|
||||
ChangelogFileName string // Name of the changelog file eg "CHANGELOG.md"
|
||||
JSONOutputPath string // Path to the JSON output file
|
||||
PrettyJSON bool // If true, the JSON output will be pretty-printed
|
||||
ChartsDir string // Dir where the charts are located (eg "./charts/")
|
||||
StatusUpdateInterval int // Interval in seconds between status updates
|
||||
SkipCommitsWithBadMessage bool // If true, commits with bad messages will be skipped
|
||||
RepoPath string // Path to the repository (eg "./charts")
|
||||
TemplatePath string // Path to the template file (eg "./changelog.tmpl")
|
||||
ChangelogFileName string // Name of the changelog file eg "CHANGELOG.md"
|
||||
JSONOutputPath string // Path to the JSON output file
|
||||
PrettyJSON bool // If true, the JSON output will be pretty-printed
|
||||
ChartsDir string // Dir where the charts are located (eg "./charts/")
|
||||
StatusUpdateInterval int // Interval in seconds between status updates
|
||||
SkipCommitsWithBadMessage bool // If true, commits with bad messages will be skipped
|
||||
}
|
||||
|
||||
func checkPath(path string, createIfNotExist bool) error {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if createIfNotExist {
|
||||
_, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create path %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("path %s cannot be used: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if createIfNotExist {
|
||||
_, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create path %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("path %s cannot be used: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ChangelogOptions) validate() error {
|
||||
if o.RepoPath == "" {
|
||||
return fmt.Errorf("repo path is empty")
|
||||
}
|
||||
if o.TemplatePath == "" {
|
||||
return fmt.Errorf("template path is empty")
|
||||
}
|
||||
if o.ChangelogFileName == "" {
|
||||
return fmt.Errorf("changelog file name is empty")
|
||||
}
|
||||
if o.ChartsDir == "" {
|
||||
return fmt.Errorf("charts dir is empty")
|
||||
}
|
||||
if o.JSONOutputPath == "" {
|
||||
return fmt.Errorf("json output path is empty")
|
||||
}
|
||||
if o.StatusUpdateInterval <= 0 {
|
||||
return fmt.Errorf("status update interval is zero")
|
||||
}
|
||||
if o.RepoPath == "" {
|
||||
return fmt.Errorf("repo path is empty")
|
||||
}
|
||||
if o.TemplatePath == "" {
|
||||
return fmt.Errorf("template path is empty")
|
||||
}
|
||||
if o.ChangelogFileName == "" {
|
||||
return fmt.Errorf("changelog file name is empty")
|
||||
}
|
||||
if o.ChartsDir == "" {
|
||||
return fmt.Errorf("charts dir is empty")
|
||||
}
|
||||
if o.JSONOutputPath == "" {
|
||||
return fmt.Errorf("json output path is empty")
|
||||
}
|
||||
if o.StatusUpdateInterval <= 0 {
|
||||
return fmt.Errorf("status update interval is zero")
|
||||
}
|
||||
|
||||
paths := map[string]bool{
|
||||
o.TemplatePath: false,
|
||||
o.RepoPath: false,
|
||||
o.ChartsDir: false,
|
||||
o.JSONOutputPath: false,
|
||||
}
|
||||
paths := map[string]bool{
|
||||
o.TemplatePath: false,
|
||||
o.RepoPath: false,
|
||||
o.ChartsDir: false,
|
||||
o.JSONOutputPath: false,
|
||||
}
|
||||
|
||||
for path, create := range paths {
|
||||
if err := checkPath(path, create); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for path, create := range paths {
|
||||
if err := checkPath(path, create); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var changedData ChangedData = ChangedData{mu: &sync.RWMutex{}, Charts: make(map[string]*Chart)}
|
||||
@@ -85,173 +85,173 @@ var skipCommitsWithBadMessage bool
|
||||
var dateFormat = "2006-01-02"
|
||||
|
||||
func (o *ChangelogOptions) Generate() error {
|
||||
start := time.Now()
|
||||
skipCommitsWithBadMessage = o.SkipCommitsWithBadMessage
|
||||
log.Info().Msgf("Starting changelog generation at %s", start)
|
||||
if err := o.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Get active train and charts
|
||||
if err := helper.WalkCharts2([]string{o.RepoPath}, activeCharts.getActiveChartsWalker, helper.AsyncMode); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("Found [%d] active charts in [%s]", len(activeCharts.items), time.Since(start))
|
||||
start := time.Now()
|
||||
skipCommitsWithBadMessage = o.SkipCommitsWithBadMessage
|
||||
log.Info().Msgf("Starting changelog generation at %s", start)
|
||||
if err := o.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Get active train and charts
|
||||
if err := helper.WalkCharts2([]string{o.RepoPath}, activeCharts.getActiveChartsWalker, helper.AsyncMode); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("Found [%d] active charts in [%s]", len(activeCharts.items), time.Since(start))
|
||||
|
||||
// Load existing json file
|
||||
if err := changedData.LoadFromFile(o.JSONOutputPath); err != nil {
|
||||
return fmt.Errorf("failed to load existing json file, maybe it is not matching the current structure: %w", err)
|
||||
}
|
||||
if changedData.LastCommit == "" {
|
||||
log.Info().Msgf("No last commit found in [%s], starting from the beginning", o.JSONOutputPath)
|
||||
} else {
|
||||
log.Info().Msgf("Last commit found in [%s], will start from [%s]", o.JSONOutputPath, changedData.LastCommit)
|
||||
}
|
||||
// Load existing json file
|
||||
if err := changedData.LoadFromFile(o.JSONOutputPath); err != nil {
|
||||
return fmt.Errorf("failed to load existing json file, maybe it is not matching the current structure: %w", err)
|
||||
}
|
||||
if changedData.LastCommit == "" {
|
||||
log.Info().Msgf("No last commit found in [%s], starting from the beginning", o.JSONOutputPath)
|
||||
} else {
|
||||
log.Info().Msgf("Last commit found in [%s], will start from [%s]", o.JSONOutputPath, changedData.LastCommit)
|
||||
}
|
||||
|
||||
// Open repo
|
||||
repo, err := git.PlainOpen(o.RepoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Open repo
|
||||
repo, err := git.PlainOpen(o.RepoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get list of commits. Order by committer time and only keep commits that are in the charts dir
|
||||
// the iterator will yield the newer commits first, so we need to reverse the order
|
||||
cIter, err := repo.Log(&git.LogOptions{Order: git.LogOrderCommitterTime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commits, err := o.reverseCommits(cIter, changedData.LastCommit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(commits) == 0 {
|
||||
log.Info().Msgf("No commits to process in %s", o.RepoPath)
|
||||
return nil
|
||||
}
|
||||
log.Info().Msgf("Found [%d] commits to process in %s", len(commits), o.RepoPath)
|
||||
// Get list of commits. Order by committer time and only keep commits that are in the charts dir
|
||||
// the iterator will yield the newer commits first, so we need to reverse the order
|
||||
cIter, err := repo.Log(&git.LogOptions{Order: git.LogOrderCommitterTime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commits, err := o.reverseCommits(cIter, changedData.LastCommit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(commits) == 0 {
|
||||
log.Info().Msgf("No commits to process in %s", o.RepoPath)
|
||||
return nil
|
||||
}
|
||||
log.Info().Msgf("Found [%d] commits to process in %s", len(commits), o.RepoPath)
|
||||
|
||||
stop := make(chan struct{}) // Stop channel
|
||||
defer close(stop)
|
||||
go o.statusPrinter(stop)
|
||||
stop := make(chan struct{}) // Stop channel
|
||||
defer close(stop)
|
||||
go o.statusPrinter(stop)
|
||||
|
||||
// TODO: Once go-git is thread safe, we can parallelize this
|
||||
// https://github.com/go-git/go-git/issues/773
|
||||
for _, c := range commits {
|
||||
changedData.mu.Lock()
|
||||
changedData.LastCommit = c.Hash.String()
|
||||
changedData.mu.Unlock()
|
||||
commitStart := time.Now()
|
||||
// TODO: Once go-git is thread safe, we can parallelize this
|
||||
// https://github.com/go-git/go-git/issues/773
|
||||
for _, c := range commits {
|
||||
changedData.mu.Lock()
|
||||
changedData.LastCommit = c.Hash.String()
|
||||
changedData.mu.Unlock()
|
||||
commitStart := time.Now()
|
||||
|
||||
if err := processCommit(c); err != nil {
|
||||
log.Error().Err(err).Msgf("Error processing commit: %s", c.Hash.String())
|
||||
return err
|
||||
}
|
||||
if err := processCommit(c); err != nil {
|
||||
log.Error().Err(err).Msgf("Error processing commit: %s", c.Hash.String())
|
||||
return err
|
||||
}
|
||||
|
||||
currentStatus.mu.Lock()
|
||||
currentStatus.processedCount++
|
||||
currentStatus.totalProcessingTime += time.Since(commitStart)
|
||||
currentStatus.avgTime = currentStatus.totalProcessingTime / time.Duration(currentStatus.processedCount+currentStatus.skippedCount)
|
||||
currentStatus.mu.Unlock()
|
||||
}
|
||||
currentStatus.mu.Lock()
|
||||
currentStatus.processedCount++
|
||||
currentStatus.totalProcessingTime += time.Since(commitStart)
|
||||
currentStatus.avgTime = currentStatus.totalProcessingTime / time.Duration(currentStatus.processedCount+currentStatus.skippedCount)
|
||||
currentStatus.mu.Unlock()
|
||||
}
|
||||
|
||||
stop <- struct{}{}
|
||||
stop <- struct{}{}
|
||||
|
||||
if err := mergeStagingToCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := changedData.WriteToFile(o.JSONOutputPath); err != nil {
|
||||
return fmt.Errorf("error writing json new file: %s", err)
|
||||
}
|
||||
log.Info().Msgf("Finished in %s", time.Since(start))
|
||||
o.printStatus(start, false)
|
||||
return nil
|
||||
if err := mergeStagingToCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := changedData.WriteToFile(o.JSONOutputPath); err != nil {
|
||||
return fmt.Errorf("error writing json new file: %s", err)
|
||||
}
|
||||
log.Info().Msgf("Finished in %s", time.Since(start))
|
||||
o.printStatus(start, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have to go over the stagingData, for each chart,
|
||||
// we sort the versions from the changelogData
|
||||
// and we add the commits from stagingData to the nearest next version in changelogData
|
||||
func mergeStagingToCurrent() error {
|
||||
start := time.Now()
|
||||
log.Info().Msgf("Merging staging to current", )
|
||||
changedData.mu.Lock()
|
||||
defer changedData.mu.Unlock()
|
||||
start := time.Now()
|
||||
log.Info().Msgf("Merging staging to current", )
|
||||
changedData.mu.Lock()
|
||||
defer changedData.mu.Unlock()
|
||||
|
||||
stagingData.mu.Lock()
|
||||
defer stagingData.mu.Unlock()
|
||||
for chart, stagingChartItem := range stagingData.Charts {
|
||||
// If the staging chart doesn't exist in the changelogData, we add it and go to the next chart
|
||||
chartItem, ok := changedData.Charts[chart]
|
||||
if !ok {
|
||||
changedData.Charts[chart] = stagingChartItem
|
||||
continue
|
||||
}
|
||||
stagingData.mu.Lock()
|
||||
defer stagingData.mu.Unlock()
|
||||
for chart, stagingChartItem := range stagingData.Charts {
|
||||
// If the staging chart doesn't exist in the changelogData, we add it and go to the next chart
|
||||
chartItem, ok := changedData.Charts[chart]
|
||||
if !ok {
|
||||
changedData.Charts[chart] = stagingChartItem
|
||||
continue
|
||||
}
|
||||
|
||||
// If the chart exists in the changelogData but does not have any versions
|
||||
// we add the versions from stagingData and go to the next chart (probably a new chart)
|
||||
if chartItem.Versions == nil || len(chartItem.Versions) == 0 {
|
||||
chartItem.Versions = stagingChartItem.Versions
|
||||
continue
|
||||
}
|
||||
// If the chart exists in the changelogData but does not have any versions
|
||||
// we add the versions from stagingData and go to the next chart (probably a new chart)
|
||||
if chartItem.Versions == nil || len(chartItem.Versions) == 0 {
|
||||
chartItem.Versions = stagingChartItem.Versions
|
||||
continue
|
||||
}
|
||||
|
||||
// Get all the versions from the changedData chart
|
||||
chartVersions, err := chartItem.SortVersions(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get all the versions from the changedData chart
|
||||
chartVersions, err := chartItem.SortVersions(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Go over the versions in stagingData chart,
|
||||
// for each version, we find the immediately next version in changedData chart
|
||||
for versionKey := range stagingData.Charts[chart].Versions {
|
||||
stagingVer, err := semver.NewVersion(versionKey)
|
||||
if err != nil { // This should never happen
|
||||
return err
|
||||
}
|
||||
// Go over the versions in stagingData chart,
|
||||
// for each version, we find the immediately next version in changedData chart
|
||||
for versionKey := range stagingData.Charts[chart].Versions {
|
||||
stagingVer, err := semver.NewVersion(versionKey)
|
||||
if err != nil { // This should never happen
|
||||
return err
|
||||
}
|
||||
|
||||
foundGreater := false
|
||||
// Go over the versions in the changedData chart versions
|
||||
for _, chartVer := range chartVersions {
|
||||
// If the changedData version is greater than the staging version,
|
||||
// we add the commits to this version and break
|
||||
if !chartVer.GreaterThan(stagingVer) {
|
||||
continue
|
||||
}
|
||||
foundGreater = true
|
||||
chartVerItem, ok := chartItem.Versions[versionKey]
|
||||
if !ok {
|
||||
chartItem.AddVersion(versionKey, stagingChartItem.Versions[versionKey].Train)
|
||||
chartVerItem = chartItem.Versions[versionKey]
|
||||
}
|
||||
foundGreater := false
|
||||
// Go over the versions in the changedData chart versions
|
||||
for _, chartVer := range chartVersions {
|
||||
// If the changedData version is greater than the staging version,
|
||||
// we add the commits to this version and break
|
||||
if !chartVer.GreaterThan(stagingVer) {
|
||||
continue
|
||||
}
|
||||
foundGreater = true
|
||||
chartVerItem, ok := chartItem.Versions[versionKey]
|
||||
if !ok {
|
||||
chartItem.AddVersion(versionKey, stagingChartItem.Versions[versionKey].Train)
|
||||
chartVerItem = chartItem.Versions[versionKey]
|
||||
}
|
||||
|
||||
// Add the commits from stagingData to the given version in the changedData chart
|
||||
for commitKey, commit := range stagingChartItem.Versions[versionKey].Commits {
|
||||
if chartVerItem.Commits == nil {
|
||||
log.Warn().Msgf("Commits were nil for version [%s] in chart [%s]", versionKey, chart)
|
||||
chartVerItem.Commits = make(map[string]*Commit)
|
||||
}
|
||||
// Add the commits from stagingData to the given version in the changedData chart
|
||||
for commitKey, commit := range stagingChartItem.Versions[versionKey].Commits {
|
||||
if chartVerItem.Commits == nil {
|
||||
log.Warn().Msgf("Commits were nil for version [%s] in chart [%s]", versionKey, chart)
|
||||
chartVerItem.Commits = make(map[string]*Commit)
|
||||
}
|
||||
|
||||
if _, ok := chartVerItem.Commits[commitKey]; ok {
|
||||
// This should never happen, but we log it just in case
|
||||
log.Warn().Msgf("Commit [%s] already exists in version [%s]", commitKey, versionKey)
|
||||
continue
|
||||
}
|
||||
chartVerItem.Commits[commitKey] = commit
|
||||
}
|
||||
break
|
||||
}
|
||||
if !foundGreater {
|
||||
// Add the version to the changedData chart
|
||||
for commitKey, commit := range stagingChartItem.Versions[versionKey].Commits {
|
||||
if _, ok := chartItem.Versions[versionKey].Commits[commitKey]; ok {
|
||||
// This should never happen, but we log it just in case
|
||||
log.Warn().Msgf("Commit [%s] already exists in version [%s]", commitKey, versionKey)
|
||||
continue
|
||||
}
|
||||
chartItem.Versions[versionKey].Commits[commitKey] = commit
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := chartVerItem.Commits[commitKey]; ok {
|
||||
// This should never happen, but we log it just in case
|
||||
log.Warn().Msgf("Commit [%s] already exists in version [%s]", commitKey, versionKey)
|
||||
continue
|
||||
}
|
||||
chartVerItem.Commits[commitKey] = commit
|
||||
}
|
||||
break
|
||||
}
|
||||
if !foundGreater {
|
||||
// Add the version to the changedData chart
|
||||
for commitKey, commit := range stagingChartItem.Versions[versionKey].Commits {
|
||||
if _, ok := chartItem.Versions[versionKey].Commits[commitKey]; ok {
|
||||
// This should never happen, but we log it just in case
|
||||
log.Warn().Msgf("Commit [%s] already exists in version [%s]", commitKey, versionKey)
|
||||
continue
|
||||
}
|
||||
chartItem.Versions[versionKey].Commits[commitKey] = commit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Finished merging in %s", time.Since(start))
|
||||
return nil
|
||||
log.Info().Msgf("Finished merging in %s", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// getCommitMessage returns the first line of the commit message
|
||||
func getCommitMessage(c *object.Commit) string {
|
||||
return strings.TrimSpace(strings.Split(c.Message, "\n")[0])
|
||||
return strings.TrimSpace(strings.Split(c.Message, "\n")[0])
|
||||
}
|
||||
|
||||
// TODO: update regex
|
||||
var commitMessageRegex = regexp.MustCompile(`^(chore|feat|fix|docs)\((.+)\)?: (.+)`)
|
||||
|
||||
func getCommitKind(c *object.Commit) string {
|
||||
match := commitMessageRegex.FindStringSubmatch(getCommitMessage(c))
|
||||
if match == nil {
|
||||
return ""
|
||||
}
|
||||
return match[1]
|
||||
match := commitMessageRegex.FindStringSubmatch(getCommitMessage(c))
|
||||
if match == nil {
|
||||
return ""
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
func isValidCommit(c *object.Commit) bool {
|
||||
if c.Message == "" {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit message is empty", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
if c.ParentHashes == nil || len(c.ParentHashes) == 0 {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit is Batman (has no parent)", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
if c.Message == "" {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit message is empty", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
if c.ParentHashes == nil || len(c.ParentHashes) == 0 {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit is Batman (has no parent)", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
|
||||
if skipCommitsWithBadMessage && !commitMessageRegex.MatchString(getCommitMessage(c)) {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit message does not match the pattern", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
if skipCommitsWithBadMessage && !commitMessageRegex.MatchString(getCommitMessage(c)) {
|
||||
currentStatus.incSkippedCount()
|
||||
log.Debug().Msgf("Skipping commit [%s]. Reason: the commit message does not match the pattern", c.Hash.String())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return true
|
||||
}
|
||||
|
||||
type oldNewPaths struct {
|
||||
old diff.File
|
||||
new diff.File
|
||||
old diff.File
|
||||
new diff.File
|
||||
}
|
||||
type chartsWithChangedFiles map[string][]oldNewPaths
|
||||
type chartsWithChangedFile map[string]oldNewPaths
|
||||
|
||||
func processCommit(c *object.Commit) error {
|
||||
var err error
|
||||
if !isValidCommit(c) {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if !isValidCommit(c) {
|
||||
return nil
|
||||
}
|
||||
|
||||
parCommit, err := c.Parent(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get parent commit: %w", err)
|
||||
}
|
||||
patch, err := parCommit.Patch(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get patch: %w", err)
|
||||
}
|
||||
parCommit, err := c.Parent(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get parent commit: %w", err)
|
||||
}
|
||||
patch, err := parCommit.Patch(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get patch: %w", err)
|
||||
}
|
||||
|
||||
// Go over the filePatches (old/new pairs) and get create a
|
||||
// map of charts with an slice of all the old/new fileDiffs
|
||||
chartsWithMultipleFiles, err := getChartsWithMultipleChangedFiles(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get changed files: %w", err)
|
||||
}
|
||||
// Go over the filePatches (old/new pairs) and get create a
|
||||
// map of charts with an slice of all the old/new fileDiffs
|
||||
chartsWithMultipleFiles, err := getChartsWithMultipleChangedFiles(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get changed files: %w", err)
|
||||
}
|
||||
|
||||
// For each chart, keep a single old/new pair, preferably the chart.yaml
|
||||
// otherwise the first file in the list, doesn't matter
|
||||
chartsWithSingleFile := getChartsWithSingleChangedFile(chartsWithMultipleFiles)
|
||||
// For each chart, keep a single old/new pair, preferably the chart.yaml
|
||||
// otherwise the first file in the list, doesn't matter
|
||||
chartsWithSingleFile := getChartsWithSingleChangedFile(chartsWithMultipleFiles)
|
||||
|
||||
// Populate the changedData and stagingData
|
||||
if err := processChartsWithSingleChangedFile(c, parCommit, chartsWithSingleFile); err != nil {
|
||||
return fmt.Errorf("failed to process changed file: %w", err)
|
||||
}
|
||||
// Populate the changedData and stagingData
|
||||
if err := processChartsWithSingleChangedFile(c, parCommit, chartsWithSingleFile); err != nil {
|
||||
return fmt.Errorf("failed to process changed file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var errSkipPatch = errors.New("skip patch")
|
||||
|
||||
func getChangedFilePair(p diff.FilePatch) (string, oldNewPaths, error) {
|
||||
old, new := p.Files()
|
||||
if new == nil { // No new file, nothing to do
|
||||
log.Debug().Msgf("Skipping file patch. Reason: New file is empty")
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
old, new := p.Files()
|
||||
if new == nil { // No new file, nothing to do
|
||||
log.Debug().Msgf("Skipping file patch. Reason: New file is empty")
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
|
||||
// Get chart name and check if its an active chart
|
||||
// if the new.Path() is a path outside of the charts folder,
|
||||
// it will not be an active chart anyway and so we skip the diff
|
||||
chartName := getChartName(new.Path())
|
||||
if chartName == invalidName || !activeCharts.isActiveChart(chartName) {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not an active chart", new.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
if _, err := getChartPath(new.Path()); err != nil {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not a valid chart path", new.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
if old != nil { // If an old file exists in the patch
|
||||
if _, err := getChartPath(old.Path()); err != nil {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not a valid chart path", old.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
}
|
||||
// Get chart name and check if its an active chart
|
||||
// if the new.Path() is a path outside of the charts folder,
|
||||
// it will not be an active chart anyway and so we skip the diff
|
||||
chartName := getChartName(new.Path())
|
||||
if chartName == invalidName || !activeCharts.isActiveChart(chartName) {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not an active chart", new.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
if _, err := getChartPath(new.Path()); err != nil {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not a valid chart path", new.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
if old != nil { // If an old file exists in the patch
|
||||
if _, err := getChartPath(old.Path()); err != nil {
|
||||
log.Debug().Msgf("Skipping file patch. Reason: [%s] is not a valid chart path", old.Path())
|
||||
return "", oldNewPaths{}, errSkipPatch
|
||||
}
|
||||
}
|
||||
|
||||
return chartName, oldNewPaths{new: new, old: old}, nil
|
||||
return chartName, oldNewPaths{new: new, old: old}, nil
|
||||
}
|
||||
|
||||
func getOldAndNewVersion(c *object.Commit, par *object.Commit, paths oldNewPaths) (string, string, error) {
|
||||
newChartPath, err := getChartPath(paths.new.Path())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart path from file path [%s]: %w", paths.new.Path(), err)
|
||||
}
|
||||
newChartVer, err := getChartVersion(c, newChartPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart data from path [%s]: %w", newChartPath, err)
|
||||
}
|
||||
newChartPath, err := getChartPath(paths.new.Path())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart path from file path [%s]: %w", paths.new.Path(), err)
|
||||
}
|
||||
newChartVer, err := getChartVersion(c, newChartPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart data from path [%s]: %w", newChartPath, err)
|
||||
}
|
||||
|
||||
oldChartVer := ""
|
||||
if paths.old != nil { // If an old file exists in the patch
|
||||
oldChartPath, err := getChartPath(paths.old.Path())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart path from file path [%s]: %w", paths.old.Path(), err)
|
||||
}
|
||||
// Note here we pass the parent commit, not the current commit
|
||||
oldChartVer, err = getChartVersion(par, oldChartPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart data from path [%s]: %w", oldChartPath, err)
|
||||
}
|
||||
}
|
||||
oldChartVer := ""
|
||||
if paths.old != nil { // If an old file exists in the patch
|
||||
oldChartPath, err := getChartPath(paths.old.Path())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart path from file path [%s]: %w", paths.old.Path(), err)
|
||||
}
|
||||
// Note here we pass the parent commit, not the current commit
|
||||
oldChartVer, err = getChartVersion(par, oldChartPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get chart data from path [%s]: %w", oldChartPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return oldChartVer, newChartVer, nil
|
||||
return oldChartVer, newChartVer, nil
|
||||
}
|
||||
|
||||
func getChartsWithMultipleChangedFiles(p *object.Patch) (chartsWithChangedFiles, error) {
|
||||
chartsWithMultipleFiles := make(chartsWithChangedFiles)
|
||||
for _, p := range p.FilePatches() {
|
||||
// Get chart name and the "new" file path
|
||||
chartName, paths, err := getChangedFilePair(p)
|
||||
if err != nil {
|
||||
if errors.Is(err, errSkipPatch) {
|
||||
continue
|
||||
}
|
||||
return chartsWithChangedFiles{}, fmt.Errorf("failed to get changed files: %w", err)
|
||||
}
|
||||
// if there is no new file, skip the filePatch
|
||||
if paths.new.Path() == "" {
|
||||
continue
|
||||
}
|
||||
chartsWithMultipleFiles := make(chartsWithChangedFiles)
|
||||
for _, p := range p.FilePatches() {
|
||||
// Get chart name and the "new" file path
|
||||
chartName, paths, err := getChangedFilePair(p)
|
||||
if err != nil {
|
||||
if errors.Is(err, errSkipPatch) {
|
||||
continue
|
||||
}
|
||||
return chartsWithChangedFiles{}, fmt.Errorf("failed to get changed files: %w", err)
|
||||
}
|
||||
// if there is no new file, skip the filePatch
|
||||
if paths.new.Path() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the file to the charts changed files
|
||||
chartsWithMultipleFiles[chartName] = append(chartsWithMultipleFiles[chartName], paths)
|
||||
}
|
||||
// Add the file to the charts changed files
|
||||
chartsWithMultipleFiles[chartName] = append(chartsWithMultipleFiles[chartName], paths)
|
||||
}
|
||||
|
||||
return chartsWithMultipleFiles, nil
|
||||
return chartsWithMultipleFiles, nil
|
||||
}
|
||||
|
||||
func getChartsWithSingleChangedFile(c chartsWithChangedFiles) chartsWithChangedFile {
|
||||
chartsWithSingleFile := make(chartsWithChangedFile)
|
||||
for chartName, filePaths := range c {
|
||||
for _, paths := range filePaths {
|
||||
_, ok := chartsWithSingleFile[chartName]
|
||||
// If the chart hasn't been seen before,
|
||||
// or the filePath is a Chart.yaml file
|
||||
// we add the pair to the map
|
||||
if !ok || strings.HasSuffix(paths.new.Path(), "Chart.yaml") {
|
||||
chartsWithSingleFile[chartName] = paths
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return chartsWithSingleFile
|
||||
chartsWithSingleFile := make(chartsWithChangedFile)
|
||||
for chartName, filePaths := range c {
|
||||
for _, paths := range filePaths {
|
||||
_, ok := chartsWithSingleFile[chartName]
|
||||
// If the chart hasn't been seen before,
|
||||
// or the filePath is a Chart.yaml file
|
||||
// we add the pair to the map
|
||||
if !ok || strings.HasSuffix(paths.new.Path(), "Chart.yaml") {
|
||||
chartsWithSingleFile[chartName] = paths
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return chartsWithSingleFile
|
||||
}
|
||||
|
||||
func processChartsWithSingleChangedFile(c *object.Commit, par *object.Commit, chartsWithSingleFile chartsWithChangedFile) error {
|
||||
// For each chart, get the old and new versions
|
||||
for chartName, paths := range chartsWithSingleFile {
|
||||
oldVer, newVer, err := getOldAndNewVersion(c, par, paths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get old and new versions: %w", err)
|
||||
}
|
||||
// If the old version is empty, (chart addition)
|
||||
// we add the new version to the changedData
|
||||
if oldVer == "" {
|
||||
changedData.mu.Lock()
|
||||
changedData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
changedData.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
// For each chart, get the old and new versions
|
||||
for chartName, paths := range chartsWithSingleFile {
|
||||
oldVer, newVer, err := getOldAndNewVersion(c, par, paths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get old and new versions: %w", err)
|
||||
}
|
||||
// If the old version is empty, (chart addition)
|
||||
// we add the new version to the changedData
|
||||
if oldVer == "" {
|
||||
changedData.mu.Lock()
|
||||
changedData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
changedData.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
oldSemVer, err := semver.NewVersion(oldVer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse old version ([%s]) for file [%s] in commit [%s]: %w", oldVer, paths.new.Path(), c.Hash.String(), err)
|
||||
}
|
||||
newSemVer, err := semver.NewVersion(newVer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse new version ([%s]) for file [%s] in commit [%s]: %w", newVer, paths.new.Path(), c.Hash.String(), err)
|
||||
}
|
||||
oldSemVer, err := semver.NewVersion(oldVer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse old version ([%s]) for file [%s] in commit [%s]: %w", oldVer, paths.new.Path(), c.Hash.String(), err)
|
||||
}
|
||||
newSemVer, err := semver.NewVersion(newVer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse new version ([%s]) for file [%s] in commit [%s]: %w", newVer, paths.new.Path(), c.Hash.String(), err)
|
||||
}
|
||||
|
||||
// if new version is greater than the old version, we add the new version to the changedData
|
||||
if newSemVer.GreaterThan(oldSemVer) {
|
||||
changedData.mu.Lock()
|
||||
changedData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
changedData.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
// if new version is greater than the old version, we add the new version to the changedData
|
||||
if newSemVer.GreaterThan(oldSemVer) {
|
||||
changedData.mu.Lock()
|
||||
changedData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
changedData.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we add the new version to the stagingData
|
||||
// It is probably less or equal to the old version,
|
||||
// in either case the chart changes is unreleased.
|
||||
// so it should go to the "next" version, we do that at the end
|
||||
// although if its less, it will be hard to actually get which is the "next" version
|
||||
// but we can't really do anything about it, so just put it on the immediate next version
|
||||
stagingData.mu.Lock()
|
||||
stagingData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
stagingData.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
// Otherwise, we add the new version to the stagingData
|
||||
// It is probably less or equal to the old version,
|
||||
// in either case the chart changes is unreleased.
|
||||
// so it should go to the "next" version, we do that at the end
|
||||
// although if its less, it will be hard to actually get which is the "next" version
|
||||
// but we can't really do anything about it, so just put it on the immediate next version
|
||||
stagingData.mu.Lock()
|
||||
stagingData.AddOrUpdateChart(chartName, newVer, getChartTrain(paths.new.Path()), c)
|
||||
stagingData.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
"bytes"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
)
|
||||
|
||||
func (o *ChangelogOptions) Render() error {
|
||||
start := time.Now()
|
||||
log.Info().Msgf("Starting changelog render at %s", start)
|
||||
start := time.Now()
|
||||
log.Info().Msgf("Starting changelog render at %s", start)
|
||||
|
||||
changelogData := ChangedData{mu: &sync.RWMutex{}, Charts: make(map[string]*Chart)}
|
||||
activeCharts := ActiveCharts{items: make(map[string]ActiveChart), mu: &sync.RWMutex{}}
|
||||
if err := changelogData.LoadFromFile(o.JSONOutputPath); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to load %s", o.JSONOutputPath)
|
||||
}
|
||||
if err := helper.WalkCharts2([]string{o.RepoPath}, activeCharts.getActiveChartsWalker, helper.AsyncMode); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to walk charts")
|
||||
}
|
||||
changelogData := ChangedData{mu: &sync.RWMutex{}, Charts: make(map[string]*Chart)}
|
||||
activeCharts := ActiveCharts{items: make(map[string]ActiveChart), mu: &sync.RWMutex{}}
|
||||
if err := changelogData.LoadFromFile(o.JSONOutputPath); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to load %s", o.JSONOutputPath)
|
||||
}
|
||||
if err := helper.WalkCharts2([]string{o.RepoPath}, activeCharts.getActiveChartsWalker, helper.AsyncMode); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed to walk charts")
|
||||
}
|
||||
|
||||
for _, chart := range activeCharts.items {
|
||||
if changelogData.Charts[chart.Name] == nil {
|
||||
log.Error().Msgf("chart [%s] not found in %s", chart.Name, o.JSONOutputPath)
|
||||
continue
|
||||
for _, chart := range activeCharts.items {
|
||||
if changelogData.Charts[chart.Name] == nil {
|
||||
log.Error().Msgf("chart [%s] not found in %s", chart.Name, o.JSONOutputPath)
|
||||
continue
|
||||
|
||||
}
|
||||
if changelogData.Charts[chart.Name].Versions == nil {
|
||||
log.Error().Msgf("chart [%s] has no versions in %s", chart.Name, o.JSONOutputPath)
|
||||
continue
|
||||
}
|
||||
// load template
|
||||
tmpl, err := template.ParseFiles(o.TemplatePath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to parse %s", o.TemplatePath)
|
||||
}
|
||||
}
|
||||
if changelogData.Charts[chart.Name].Versions == nil {
|
||||
log.Error().Msgf("chart [%s] has no versions in %s", chart.Name, o.JSONOutputPath)
|
||||
continue
|
||||
}
|
||||
// load template
|
||||
tmpl, err := template.ParseFiles(o.TemplatePath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to parse %s", o.TemplatePath)
|
||||
}
|
||||
|
||||
if _, err := changelogData.Charts[chart.Name].SortVersions(true); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to sort versions for %s", chart.Name)
|
||||
}
|
||||
for _, version := range changelogData.Charts[chart.Name].Versions {
|
||||
version.SortedCommits, err = version.SortCommits(true)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to sort commits for version [%s] in chart [%s]", version.Version, chart.Name)
|
||||
}
|
||||
}
|
||||
if _, err := changelogData.Charts[chart.Name].SortVersions(true); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to sort versions for %s", chart.Name)
|
||||
}
|
||||
for _, version := range changelogData.Charts[chart.Name].Versions {
|
||||
version.SortedCommits, err = version.SortCommits(true)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to sort commits for version [%s] in chart [%s]", version.Version, chart.Name)
|
||||
}
|
||||
}
|
||||
|
||||
changelogData.Charts[chart.Name].Name = chart.Name
|
||||
changelogData.Charts[chart.Name].Train = chart.Train
|
||||
// render template
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, changelogData.Charts[chart.Name])
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to render %s", o.TemplatePath)
|
||||
}
|
||||
changelogData.Charts[chart.Name].Name = chart.Name
|
||||
changelogData.Charts[chart.Name].Train = chart.Train
|
||||
// render template
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, changelogData.Charts[chart.Name])
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to render %s", o.TemplatePath)
|
||||
}
|
||||
|
||||
output := filepath.Join(o.ChartsDir, chart.Train, chart.Name)
|
||||
if err := os.MkdirAll(output, os.ModePerm); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to create %s directory", output)
|
||||
}
|
||||
// write rendered template to file
|
||||
if err := os.WriteFile(filepath.Join(output, o.ChangelogFileName), buf.Bytes(), 0644); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to write %s", o.ChangelogFileName)
|
||||
}
|
||||
}
|
||||
output := filepath.Join(o.ChartsDir, chart.Train, chart.Name)
|
||||
if err := os.MkdirAll(output, os.ModePerm); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to create %s directory", output)
|
||||
}
|
||||
// write rendered template to file
|
||||
if err := os.WriteFile(filepath.Join(output, o.ChangelogFileName), buf.Bytes(), 0644); err != nil {
|
||||
log.Fatal().Err(err).Msgf("failed to write %s", o.ChangelogFileName)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Finished in %s", time.Since(start))
|
||||
return nil
|
||||
log.Info().Msgf("Finished in %s", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type status struct {
|
||||
processedCount int
|
||||
totalCount int
|
||||
skippedCount int
|
||||
avgTime time.Duration
|
||||
totalProcessingTime time.Duration
|
||||
mu *sync.RWMutex
|
||||
processedCount int
|
||||
totalCount int
|
||||
skippedCount int
|
||||
avgTime time.Duration
|
||||
totalProcessingTime time.Duration
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *status) incSkippedCount() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.skippedCount++
|
||||
s.processedCount--
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.skippedCount++
|
||||
s.processedCount--
|
||||
}
|
||||
|
||||
func (o *ChangelogOptions) printStatus(start time.Time, eta bool) {
|
||||
currentStatus.mu.RLock()
|
||||
defer currentStatus.mu.RUnlock()
|
||||
if eta {
|
||||
log.Info().Msgf("Processed [%d + (%d skipped) / %d] commits in %s, ETA: %s (avg commit processing time: %s)", currentStatus.processedCount, currentStatus.skippedCount, currentStatus.totalCount, time.Since(start), time.Duration(currentStatus.totalCount-currentStatus.processedCount-currentStatus.skippedCount)*currentStatus.avgTime, currentStatus.avgTime)
|
||||
} else {
|
||||
log.Info().Msgf("Processed [%d + (%d skipped) / %d] commits in %s", currentStatus.processedCount, currentStatus.skippedCount, currentStatus.totalCount, time.Since(start))
|
||||
}
|
||||
currentStatus.mu.RLock()
|
||||
defer currentStatus.mu.RUnlock()
|
||||
if eta {
|
||||
log.Info().Msgf("Processed [%d + (%d skipped) / %d] commits in %s, ETA: %s (avg commit processing time: %s)", currentStatus.processedCount, currentStatus.skippedCount, currentStatus.totalCount, time.Since(start), time.Duration(currentStatus.totalCount-currentStatus.processedCount-currentStatus.skippedCount)*currentStatus.avgTime, currentStatus.avgTime)
|
||||
} else {
|
||||
log.Info().Msgf("Processed [%d + (%d skipped) / %d] commits in %s", currentStatus.processedCount, currentStatus.skippedCount, currentStatus.totalCount, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ChangelogOptions) statusPrinter(stop <-chan struct{}) {
|
||||
log.Info().Msgf("Printing status every [%d] seconds", o.StatusUpdateInterval)
|
||||
start := time.Now()
|
||||
ticker := time.NewTicker(time.Second * time.Duration(o.StatusUpdateInterval))
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
o.printStatus(start, true)
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Info().Msgf("Printing status every [%d] seconds", o.StatusUpdateInterval)
|
||||
start := time.Now()
|
||||
ticker := time.NewTicker(time.Second * time.Duration(o.StatusUpdateInterval))
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
o.printStatus(start, true)
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ChangelogOptions) reverseCommits(cIter object.CommitIter, lastCommit string) ([]*object.Commit, error) {
|
||||
start := time.Now()
|
||||
var commits []*object.Commit
|
||||
var errDoneReversing = errors.New("done reversing commits")
|
||||
log.Info().Msgf("Reversing commits order", )
|
||||
defer cIter.Close()
|
||||
if err := cIter.ForEach(func(c *object.Commit) error {
|
||||
// We go from newer to oldest, if we hit the last commit, we stop
|
||||
if c.Hash.String() == lastCommit {
|
||||
return errDoneReversing
|
||||
}
|
||||
start := time.Now()
|
||||
var commits []*object.Commit
|
||||
var errDoneReversing = errors.New("done reversing commits")
|
||||
log.Info().Msgf("Reversing commits order", )
|
||||
defer cIter.Close()
|
||||
if err := cIter.ForEach(func(c *object.Commit) error {
|
||||
// We go from newer to oldest, if we hit the last commit, we stop
|
||||
if c.Hash.String() == lastCommit {
|
||||
return errDoneReversing
|
||||
}
|
||||
|
||||
currentStatus.totalCount++
|
||||
// Reverse the order of the commits to get the oldest first
|
||||
commits = append([]*object.Commit{c}, commits...)
|
||||
return nil
|
||||
}); err != nil {
|
||||
if !errors.Is(err, errDoneReversing) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
currentStatus.totalCount++
|
||||
// Reverse the order of the commits to get the oldest first
|
||||
commits = append([]*object.Commit{c}, commits...)
|
||||
return nil
|
||||
}); err != nil {
|
||||
if !errors.Is(err, errDoneReversing) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Finished reversing commits in %s", time.Since(start))
|
||||
return commits, nil
|
||||
log.Info().Msgf("Finished reversing commits in %s", time.Since(start))
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// Just some random text to avoid any chart name conflicts
|
||||
var invalidName = "5fdad45c8f5b954e5643c314"
|
||||
|
||||
func getChartName(path string) string {
|
||||
// path = charts/<train>/<chart>/...
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 3 {
|
||||
log.Debug().Msgf("failed to get chart name from path [%s]", path)
|
||||
return invalidName
|
||||
}
|
||||
return parts[2]
|
||||
// path = charts/<train>/<chart>/...
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 3 {
|
||||
log.Debug().Msgf("failed to get chart name from path [%s]", path)
|
||||
return invalidName
|
||||
}
|
||||
return parts[2]
|
||||
}
|
||||
|
||||
var chartFilePathRegex = regexp.MustCompile(`^charts/([\w-_]+)/([\w-_]+)/Chart.yaml$`)
|
||||
|
||||
func getChartPath(path string) (string, error) {
|
||||
original := path
|
||||
for {
|
||||
if path == "." {
|
||||
return "", fmt.Errorf("path too short [%s], or could not construct chart path", original)
|
||||
}
|
||||
if chartFilePathRegex.MatchString(filepath.Join(path, "Chart.yaml")) {
|
||||
return filepath.Join(path, "Chart.yaml"), nil
|
||||
}
|
||||
// Remove the last part of the path and try again
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
original := path
|
||||
for {
|
||||
if path == "." {
|
||||
return "", fmt.Errorf("path too short [%s], or could not construct chart path", original)
|
||||
}
|
||||
if chartFilePathRegex.MatchString(filepath.Join(path, "Chart.yaml")) {
|
||||
return filepath.Join(path, "Chart.yaml"), nil
|
||||
}
|
||||
// Remove the last part of the path and try again
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
}
|
||||
|
||||
func getChartVersion(c *object.Commit, path string) (string, error) {
|
||||
tree, err := c.Tree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get tree: %w", err)
|
||||
}
|
||||
file, err := tree.File(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get file: %w", err)
|
||||
}
|
||||
strData, err := file.Contents()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get file contents: %w", err)
|
||||
}
|
||||
return getVersion(strData)
|
||||
tree, err := c.Tree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get tree: %w", err)
|
||||
}
|
||||
file, err := tree.File(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get file: %w", err)
|
||||
}
|
||||
strData, err := file.Contents()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get file contents: %w", err)
|
||||
}
|
||||
return getVersion(strData)
|
||||
}
|
||||
|
||||
var charsToRemove = []string{"-"}
|
||||
@@ -130,25 +130,25 @@ var charsToRemove = []string{"-"}
|
||||
// We use this instead of NewHelmChart.Load(), because
|
||||
// this will work even if the Chart.yaml is malformed
|
||||
func getVersion(strData string) (string, error) {
|
||||
lines := strings.Split(strData, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "version:") {
|
||||
ver := strings.TrimSpace(strings.Split(line, ":")[1])
|
||||
// In some cases there was a type in the version (eg "1.0.-2")
|
||||
for _, c := range charsToRemove {
|
||||
ver = strings.ReplaceAll(ver, c, "")
|
||||
}
|
||||
return ver, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("could not find version in file")
|
||||
lines := strings.Split(strData, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "version:") {
|
||||
ver := strings.TrimSpace(strings.Split(line, ":")[1])
|
||||
// In some cases there was a type in the version (eg "1.0.-2")
|
||||
for _, c := range charsToRemove {
|
||||
ver = strings.ReplaceAll(ver, c, "")
|
||||
}
|
||||
return ver, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("could not find version in file")
|
||||
}
|
||||
|
||||
func getChartTrain(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 2 {
|
||||
log.Error().Msgf("Could not get chart train from path [%s]", path)
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 2 {
|
||||
log.Error().Msgf("Could not get chart train from path [%s]", path)
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
@@ -1,227 +1,227 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"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"
|
||||
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"`
|
||||
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"`
|
||||
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
|
||||
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
|
||||
K *koanf.Koanf
|
||||
Metadata ChartMetadata `yaml:"metadata" validate:"required,dive"`
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
func NewHelmChart() *HelmChart {
|
||||
return &HelmChart{
|
||||
K: koanf.New("."),
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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()
|
||||
// Set default values for fields if they are not set or empty
|
||||
h.setDefaultValues()
|
||||
|
||||
// Initialize validator
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
// Initialize validator
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
if err := validate.Struct(h.Metadata); err != nil {
|
||||
return fmt.Errorf("chart.yaml validation error: %v", err)
|
||||
}
|
||||
if err := validate.Struct(h.Metadata); err != nil {
|
||||
return fmt.Errorf("chart.yaml validation error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
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.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,
|
||||
})
|
||||
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)
|
||||
}
|
||||
// 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)
|
||||
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
|
||||
// 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())
|
||||
// Initialize validator
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
if err := validate.Struct(h.Metadata); err != nil {
|
||||
return fmt.Errorf("chart.yaml validation error: %v", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 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
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
h.Metadata.APIVersion = apiVersion
|
||||
}
|
||||
|
||||
// setKubeVersion sets the kubeVersion field to kubeVersion
|
||||
func (h *HelmChart) setKubeVersion(kubeVersion string) {
|
||||
h.Metadata.KubeVersion = kubeVersion
|
||||
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
|
||||
h.Metadata.Maintainers = make([]Maintainer, 1)
|
||||
h.Metadata.Maintainers[0] = maintainers
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,208 +1,208 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/helmignore"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/image"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/readme"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/version"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/helmignore"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/image"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/readme"
|
||||
"github.com/truecharts/public/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
|
||||
}
|
||||
fileInfo, err := os.Stat(chartPathOrFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chartPath := chartPathOrFolder
|
||||
if fileInfo.IsDir() {
|
||||
chartPath = filepath.Join(chartPathOrFolder, "Chart.yaml")
|
||||
}
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
if chart.Metadata.Annotations == nil {
|
||||
chart.Metadata.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
train := GetTrain(chartPath, chart)
|
||||
setMetadata(chart, train)
|
||||
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 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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)
|
||||
log.Info().Msgf("Chart file updated and saved to [%s]", chartPath)
|
||||
|
||||
templateDir := chartPath
|
||||
for i := 0; i < 4; i++ {
|
||||
templateDir = filepath.Dir(templateDir)
|
||||
}
|
||||
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 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
|
||||
// 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
|
||||
}
|
||||
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
|
||||
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),
|
||||
)
|
||||
parts := strings.Split(
|
||||
// Remove the filename from the path
|
||||
filepath.Dir(chartFilename),
|
||||
string(os.PathSeparator),
|
||||
)
|
||||
|
||||
if len(parts) >= 2 {
|
||||
return parts[len(parts)-2]
|
||||
}
|
||||
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 ""
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
// 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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 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...)
|
||||
// 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
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// 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)
|
||||
// Sort the sources, so subsequent commits will only include actual changes
|
||||
slices.Sort(finalSources)
|
||||
|
||||
// Update the chart's sources
|
||||
chart.Metadata.Sources = finalSources
|
||||
// Update the chart's sources
|
||||
chart.Metadata.Sources = finalSources
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,230 +1,230 @@
|
||||
package chartFile
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/image"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/image"
|
||||
)
|
||||
|
||||
func TestSetAppVersionFromImage(t *testing.T) {
|
||||
type TestData struct {
|
||||
chart *HelmChart
|
||||
image *image.Images
|
||||
key string
|
||||
result string
|
||||
}
|
||||
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",
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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",
|
||||
},
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
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",
|
||||
},
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+139
-139
@@ -1,197 +1,197 @@
|
||||
package deps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/chartFile"
|
||||
"github.com/truecharts/public/clustertool/pkg/fluxhandler"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/chartFile"
|
||||
"github.com/truecharts/public/clustertool/pkg/fluxhandler"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
)
|
||||
|
||||
func LoadGPGKey() error {
|
||||
log.Info().Msg("🔑 Fetching and Loading TrueCharts PGP Public Key 🔑")
|
||||
if err := os.MkdirAll(helper.GpgDir, os.ModePerm); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to create GPG directory")
|
||||
}
|
||||
log.Info().Msg("🔑 Fetching and Loading TrueCharts PGP Public Key 🔑")
|
||||
if err := os.MkdirAll(helper.GpgDir, os.ModePerm); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to create GPG directory")
|
||||
}
|
||||
|
||||
keybaseURL := "https://truecharts.org/pub_key.gpg"
|
||||
pubringPath := path.Join(helper.GpgDir, "pubring.gpg")
|
||||
if err := downloadFile(keybaseURL, pubringPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to download keybase public key")
|
||||
}
|
||||
keybaseURL := "https://truecharts.org/pub_key.gpg"
|
||||
pubringPath := path.Join(helper.GpgDir, "pubring.gpg")
|
||||
if err := downloadFile(keybaseURL, pubringPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to download keybase public key")
|
||||
}
|
||||
|
||||
certmanURL := "https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg"
|
||||
certmanPath := path.Join(helper.GpgDir, "certman.gpg")
|
||||
if err := downloadFile(certmanURL, certmanPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to download certman public key")
|
||||
}
|
||||
certmanURL := "https://cert-manager.io/public-keys/cert-manager-keyring-2021-09-20-1020CF3C033D4F35BAE1C19E1226061C665DF13E.gpg"
|
||||
certmanPath := path.Join(helper.GpgDir, "certman.gpg")
|
||||
if err := downloadFile(certmanURL, certmanPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to download certman public key")
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Public Key loaded successfully")
|
||||
return nil
|
||||
log.Info().Msg("✅ Public Key loaded successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadFile(url, destination string) error {
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("❌ Failed to download [%s]", url)
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("❌ Failed to download [%s]", url)
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("❌ Failed to read response body")
|
||||
return err
|
||||
}
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("❌ Failed to read response body")
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(destination, body, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("❌ Failed to write file at [%s]", destination)
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(destination, body, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("❌ Failed to write file at [%s]", destination)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchIndexFile downloads an index file from a repo if not already cached
|
||||
func fetchIndexFile(repo string, repoDir string, repoURL string) error {
|
||||
destPath := path.Join(helper.IndexCache, repoDir, "index.yaml")
|
||||
if strings.HasPrefix(repoURL, "oci") {
|
||||
log.Info().Msgf("⏩ URL [%s] is OCI, skipping index download", repoURL)
|
||||
return nil
|
||||
}
|
||||
destPath := path.Join(helper.IndexCache, repoDir, "index.yaml")
|
||||
if strings.HasPrefix(repoURL, "oci") {
|
||||
log.Info().Msgf("⏩ URL [%s] is OCI, skipping index download", repoURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(destPath); err == nil {
|
||||
log.Info().Msgf("✅ Index file for [%s] already cached", repo)
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(destPath); err == nil {
|
||||
log.Info().Msgf("✅ Index file for [%s] already cached", repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info().Msgf("🙅 Index file for [%s] not cached", repo)
|
||||
log.Info().Msgf("🙅 Index file for [%s] not cached", repo)
|
||||
|
||||
// Create index directory
|
||||
err := os.MkdirAll(path.Join(helper.IndexCache, repoDir), os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to create index directory")
|
||||
}
|
||||
// Create index directory
|
||||
err := os.MkdirAll(path.Join(helper.IndexCache, repoDir), os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to create index directory")
|
||||
}
|
||||
|
||||
// Download index file
|
||||
log.Info().Msgf("⏬ Downloading index [%s]...", repoURL)
|
||||
err = downloadFile(repoURL, destPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("❌ Failed to download index for [%s] from [%s]", repo, repoURL)
|
||||
}
|
||||
// Download index file
|
||||
log.Info().Msgf("⏬ Downloading index [%s]...", repoURL)
|
||||
err = downloadFile(repoURL, destPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("❌ Failed to download index for [%s] from [%s]", repo, repoURL)
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Index File downloaded")
|
||||
log.Info().Msg("✅ Index File downloaded")
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchDependency downloads a dependency from a repo if not already cached
|
||||
func fetchDependency(repo string, repoDir string, name string, version string, repoURL string) error {
|
||||
destPath := path.Join(helper.HelmCache, repoDir, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
if _, err := os.Stat(destPath); err == nil {
|
||||
log.Info().Msgf("✅ Dependency [%s-%s] already cached", name, version)
|
||||
return nil
|
||||
}
|
||||
destPath := path.Join(helper.HelmCache, repoDir, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
if _, err := os.Stat(destPath); err == nil {
|
||||
log.Info().Msgf("✅ Dependency [%s-%s] already cached", name, version)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info().Msgf("🙅 Dependency [%s-%s] not cached", name, version)
|
||||
log.Info().Msgf("🙅 Dependency [%s-%s] not cached", name, version)
|
||||
|
||||
repoCacheDir := path.Join(helper.HelmCache, repoDir)
|
||||
// Create cache directory
|
||||
if err := os.MkdirAll(repoCacheDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create cache directory: %s", err)
|
||||
}
|
||||
repoCacheDir := path.Join(helper.HelmCache, repoDir)
|
||||
// Create cache directory
|
||||
if err := os.MkdirAll(repoCacheDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create cache directory: %s", err)
|
||||
}
|
||||
|
||||
// Download dependency
|
||||
log.Info().Msgf("⏬ Downloading dependency [%s-%s] from [%s]", name, version, repo)
|
||||
if err := fluxhandler.HelmPull(repo, name, version, repoCacheDir, false); err != nil {
|
||||
return fmt.Errorf("❌ Failed to download or verify dependency: %s", err)
|
||||
}
|
||||
// Download dependency
|
||||
log.Info().Msgf("⏬ Downloading dependency [%s-%s] from [%s]", name, version, repo)
|
||||
if err := fluxhandler.HelmPull(repo, name, version, repoCacheDir, false); err != nil {
|
||||
return fmt.Errorf("❌ Failed to download or verify dependency: %s", err)
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Dependency downloaded")
|
||||
log.Info().Msg("✅ Dependency downloaded")
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDependency copies a dependency from the cache to the chart folder
|
||||
func copyDependency(chartFolder string, repo string, repoDir string, name string, version string) error {
|
||||
log.Info().Msg("📝 Copying dependency")
|
||||
log.Info().Msg("📝 Copying dependency")
|
||||
|
||||
targetChartsFolder := path.Join(chartFolder, "charts")
|
||||
if err := os.MkdirAll(targetChartsFolder, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create charts directory: %s", err)
|
||||
}
|
||||
targetChartsFolder := path.Join(chartFolder, "charts")
|
||||
if err := os.MkdirAll(targetChartsFolder, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create charts directory: %s", err)
|
||||
}
|
||||
|
||||
srcPath := path.Join(helper.HelmCache, repoDir, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
destPath := path.Join(targetChartsFolder, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
if err := helper.CopyFile(srcPath, destPath, false); err != nil {
|
||||
return fmt.Errorf("❌ Failed to copy dependency: %s", err)
|
||||
}
|
||||
srcPath := path.Join(helper.HelmCache, repoDir, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
destPath := path.Join(targetChartsFolder, fmt.Sprintf("%s-%s.tgz", name, version))
|
||||
if err := helper.CopyFile(srcPath, destPath, false); err != nil {
|
||||
return fmt.Errorf("❌ Failed to copy dependency: %s", err)
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Dependency copied!")
|
||||
return nil
|
||||
log.Info().Msg("✅ Dependency copied!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func DownloadDeps(chartPath string, placeholder string) error {
|
||||
chartFolder := filepath.Dir(chartPath)
|
||||
chartFolder := filepath.Dir(chartPath)
|
||||
|
||||
helmChart := chartFile.NewHelmChart()
|
||||
err := helmChart.LoadFromFile(chartPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("❌ Failed to load Helm chart from file in [%s]", chartFolder)
|
||||
}
|
||||
helmChart := chartFile.NewHelmChart()
|
||||
err := helmChart.LoadFromFile(chartPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("❌ Failed to load Helm chart from file in [%s]", chartFolder)
|
||||
}
|
||||
|
||||
fmt.Print("\n\n")
|
||||
log.Info().Msgf("🏃 Processing Chart [%s] with [%d] dependencies", chartFolder, len(helmChart.Metadata.Dependencies))
|
||||
fmt.Print("\n\n")
|
||||
log.Info().Msgf("🏃 Processing Chart [%s] with [%d] dependencies", chartFolder, len(helmChart.Metadata.Dependencies))
|
||||
|
||||
// Make sure the directory "charts" exists in the chart folder
|
||||
targetChartsFolder := path.Join(chartFolder, "charts")
|
||||
if err := os.MkdirAll(targetChartsFolder, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create charts directory: %s", err)
|
||||
}
|
||||
// Make sure the directory "charts" exists in the chart folder
|
||||
targetChartsFolder := path.Join(chartFolder, "charts")
|
||||
if err := os.MkdirAll(targetChartsFolder, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("❌ Failed to create charts directory: %s", err)
|
||||
}
|
||||
|
||||
// Process dependencies as needed
|
||||
for _, dep := range helmChart.Metadata.Dependencies {
|
||||
name := dep.Name
|
||||
version := dep.Version
|
||||
repo := dep.Repository
|
||||
repoURL := fmt.Sprintf("%s/index.yaml", strings.TrimRight(repo, "/"))
|
||||
// Process dependencies as needed
|
||||
for _, dep := range helmChart.Metadata.Dependencies {
|
||||
name := dep.Name
|
||||
version := dep.Version
|
||||
repo := dep.Repository
|
||||
repoURL := fmt.Sprintf("%s/index.yaml", strings.TrimRight(repo, "/"))
|
||||
|
||||
fmt.Print("\n")
|
||||
log.Info().Msgf("📦 Dependency [%s]", name)
|
||||
log.Info().Msgf("🆚 Version [%s]", version)
|
||||
log.Info().Msgf("📥 Repo [%s]", repo)
|
||||
log.Info().Msgf("🔗 URL [%s]", repoURL)
|
||||
fmt.Print("\n")
|
||||
log.Info().Msgf("📦 Dependency [%s]", name)
|
||||
log.Info().Msgf("🆚 Version [%s]", version)
|
||||
log.Info().Msgf("📥 Repo [%s]", repo)
|
||||
log.Info().Msgf("🔗 URL [%s]", repoURL)
|
||||
|
||||
repoDir := repo
|
||||
// Remove protocol(s) from repoDir
|
||||
for _, prefix := range []string{"http://", "https://", "oci://"} {
|
||||
repoDir = strings.TrimPrefix(repoDir, prefix)
|
||||
}
|
||||
repoDir := repo
|
||||
// Remove protocol(s) from repoDir
|
||||
for _, prefix := range []string{"http://", "https://", "oci://"} {
|
||||
repoDir = strings.TrimPrefix(repoDir, prefix)
|
||||
}
|
||||
|
||||
if err := fetchIndexFile(repo, repoDir, repoURL); err != nil {
|
||||
return fmt.Errorf("❌ Failed to fetch index file: %s", err)
|
||||
}
|
||||
if err := fetchIndexFile(repo, repoDir, repoURL); err != nil {
|
||||
return fmt.Errorf("❌ Failed to fetch index file: %s", err)
|
||||
}
|
||||
|
||||
if err := fetchDependency(repo, repoDir, name, version, repoURL); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to fetch dependency")
|
||||
}
|
||||
if err := fetchDependency(repo, repoDir, name, version, repoURL); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to fetch dependency")
|
||||
}
|
||||
|
||||
if err := copyDependency(chartFolder, repo, repoDir, name, version); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to copy dependency")
|
||||
}
|
||||
if err := copyDependency(chartFolder, repo, repoDir, name, version); err != nil {
|
||||
log.Fatal().Err(err).Msg("❌ Failed to copy dependency")
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Dependency processed!")
|
||||
}
|
||||
log.Info().Msg("✅ Dependency processed!")
|
||||
}
|
||||
|
||||
log.Info().Msg("✅ Processing complete!")
|
||||
return nil
|
||||
log.Info().Msg("✅ Processing complete!")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
package helmignore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func GenerateHelmIgnore(templatePath string, chartPath string) error {
|
||||
// Define file paths
|
||||
template := filepath.Join(templatePath, "templates/helmignore.tpl")
|
||||
target := filepath.Join(filepath.Dir(chartPath), ".helmignore")
|
||||
// Define file paths
|
||||
template := filepath.Join(templatePath, "templates/helmignore.tpl")
|
||||
target := filepath.Join(filepath.Dir(chartPath), ".helmignore")
|
||||
|
||||
// Read template file
|
||||
templateContent, err := os.ReadFile(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read template file: %v", err)
|
||||
}
|
||||
// Read template file
|
||||
templateContent, err := os.ReadFile(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read template file: %v", err)
|
||||
}
|
||||
|
||||
// Write the modified content to the .helmignore file in the chart directory
|
||||
err = os.WriteFile(target, []byte(templateContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write .helmignore file: %v", err)
|
||||
}
|
||||
// Write the modified content to the .helmignore file in the chart directory
|
||||
err = os.WriteFile(target, []byte(templateContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write .helmignore file: %v", err)
|
||||
}
|
||||
|
||||
log.Info().Msgf("Generated .helmignore for [%s]", chartPath)
|
||||
return nil
|
||||
log.Info().Msgf("Generated .helmignore for [%s]", chartPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,187 +1,187 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
var (
|
||||
// Valid SemVer format (Major.Minor.Patch)
|
||||
semVerPattern = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
|
||||
// Matches tags like "RELEASE.2023-11-20T22-40-07Z"
|
||||
releasePattern = regexp.MustCompile(`^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T`)
|
||||
// Matches tags like "x64-1.2.3" and "arm64-1.2.3" followed by a numeric version
|
||||
archPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]+\.[0-9]+`)
|
||||
// Matches tags like "latest-2023-12-18"
|
||||
prefixYearMonthDayPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]{4}-[0-9]{2}-[0-9]{2}$`)
|
||||
// Matches dates like "2023-11-15" and "2022-04"
|
||||
yearMonthDayPattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}(-[0-9]{2})?$`)
|
||||
// Matches tags like "1.2.3.4" "1.2" and "1"
|
||||
incompleteSemVerPattern = regexp.MustCompile(`^[0-9]+(\.[0-9]+)*$`)
|
||||
// Matches tags like "something-abcdefg" (only chars before dash and exactly 7 characters after the dash)
|
||||
shortCommitHashSuffixPattern = regexp.MustCompile(`^[a-zA-Z]+-[a-zA-Z0-9]{7}$`)
|
||||
// Matches tags like "v1.2.3", "V1.2.3, #1.2.3, $1.2.3, etc"
|
||||
leadingSymbolPattern = regexp.MustCompile(`^version|Version|[vV]|^[^a-zA-Z0-9]+`)
|
||||
// Valid SemVer format (Major.Minor.Patch)
|
||||
semVerPattern = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
|
||||
// Matches tags like "RELEASE.2023-11-20T22-40-07Z"
|
||||
releasePattern = regexp.MustCompile(`^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T`)
|
||||
// Matches tags like "x64-1.2.3" and "arm64-1.2.3" followed by a numeric version
|
||||
archPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]+\.[0-9]+`)
|
||||
// Matches tags like "latest-2023-12-18"
|
||||
prefixYearMonthDayPattern = regexp.MustCompile(`^[a-zA-Z0-9]+-[0-9]{4}-[0-9]{2}-[0-9]{2}$`)
|
||||
// Matches dates like "2023-11-15" and "2022-04"
|
||||
yearMonthDayPattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}(-[0-9]{2})?$`)
|
||||
// Matches tags like "1.2.3.4" "1.2" and "1"
|
||||
incompleteSemVerPattern = regexp.MustCompile(`^[0-9]+(\.[0-9]+)*$`)
|
||||
// Matches tags like "something-abcdefg" (only chars before dash and exactly 7 characters after the dash)
|
||||
shortCommitHashSuffixPattern = regexp.MustCompile(`^[a-zA-Z]+-[a-zA-Z0-9]{7}$`)
|
||||
// Matches tags like "v1.2.3", "V1.2.3, #1.2.3, $1.2.3, etc"
|
||||
leadingSymbolPattern = regexp.MustCompile(`^version|Version|[vV]|^[^a-zA-Z0-9]+`)
|
||||
)
|
||||
|
||||
func CleanTag(tag string) (string, error) {
|
||||
tag = strings.TrimSpace(tag)
|
||||
tag = strings.TrimSpace(tag)
|
||||
|
||||
if tag == "" {
|
||||
return "", fmt.Errorf("tag is empty")
|
||||
}
|
||||
if tag == "" {
|
||||
return "", fmt.Errorf("tag is empty")
|
||||
}
|
||||
|
||||
// Do basic cleaning
|
||||
tag = cleanSha(tag)
|
||||
tag = cleanLeadingSymbol(tag)
|
||||
// Do basic cleaning
|
||||
tag = cleanSha(tag)
|
||||
tag = cleanLeadingSymbol(tag)
|
||||
|
||||
// Return early if the tag is already in SemVer format
|
||||
if semVerPattern.MatchString(tag) {
|
||||
return tag, nil
|
||||
}
|
||||
// Return early if the tag is already in SemVer format
|
||||
if semVerPattern.MatchString(tag) {
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case releasePattern.MatchString(tag):
|
||||
tag = cleanRelease(tag)
|
||||
case archPattern.MatchString(tag):
|
||||
tag = cleanArch(tag)
|
||||
case prefixYearMonthDayPattern.MatchString(tag):
|
||||
tag = cleanPrefixYearMonthDay(tag)
|
||||
case yearMonthDayPattern.MatchString(tag):
|
||||
tag = cleanYearMonthDay(tag)
|
||||
case incompleteSemVerPattern.MatchString(tag):
|
||||
tag = cleanIncompleteSemVer(tag)
|
||||
case shortCommitHashSuffixPattern.MatchString(tag):
|
||||
tag = keepShortCommitHashSuffix(tag)
|
||||
case leadingSymbolPattern.MatchString(tag):
|
||||
tag = cleanLeadingSymbol(tag)
|
||||
}
|
||||
switch {
|
||||
case releasePattern.MatchString(tag):
|
||||
tag = cleanRelease(tag)
|
||||
case archPattern.MatchString(tag):
|
||||
tag = cleanArch(tag)
|
||||
case prefixYearMonthDayPattern.MatchString(tag):
|
||||
tag = cleanPrefixYearMonthDay(tag)
|
||||
case yearMonthDayPattern.MatchString(tag):
|
||||
tag = cleanYearMonthDay(tag)
|
||||
case incompleteSemVerPattern.MatchString(tag):
|
||||
tag = cleanIncompleteSemVer(tag)
|
||||
case shortCommitHashSuffixPattern.MatchString(tag):
|
||||
tag = keepShortCommitHashSuffix(tag)
|
||||
case leadingSymbolPattern.MatchString(tag):
|
||||
tag = cleanLeadingSymbol(tag)
|
||||
}
|
||||
|
||||
// If string contains `-` the second part is usually
|
||||
// either a commit hash or things like "debian" or "alpine"
|
||||
// Make sure the first part is some kind of versioning and strip the rest
|
||||
if strings.Contains(tag, "-") {
|
||||
split := strings.Split(tag, "-")
|
||||
switch {
|
||||
case semVerPattern.MatchString(split[0]):
|
||||
tag = split[0]
|
||||
case incompleteSemVerPattern.MatchString(split[0]):
|
||||
tag = split[0]
|
||||
}
|
||||
}
|
||||
// If string contains `-` the second part is usually
|
||||
// either a commit hash or things like "debian" or "alpine"
|
||||
// Make sure the first part is some kind of versioning and strip the rest
|
||||
if strings.Contains(tag, "-") {
|
||||
split := strings.Split(tag, "-")
|
||||
switch {
|
||||
case semVerPattern.MatchString(split[0]):
|
||||
tag = split[0]
|
||||
case incompleteSemVerPattern.MatchString(split[0]):
|
||||
tag = split[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Re-check for incomplete SemVer after cleaning
|
||||
if incompleteSemVerPattern.MatchString(tag) {
|
||||
tag = cleanIncompleteSemVer(tag)
|
||||
}
|
||||
// Re-check for incomplete SemVer after cleaning
|
||||
if incompleteSemVerPattern.MatchString(tag) {
|
||||
tag = cleanIncompleteSemVer(tag)
|
||||
}
|
||||
|
||||
if err := checkValidLabelValue(tag); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := checkValidLabelValue(tag); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !semVerPattern.MatchString(tag) {
|
||||
log.Warn().Msgf("Could not produce a valid SemVer tag for tag [%s]", tag)
|
||||
}
|
||||
if !semVerPattern.MatchString(tag) {
|
||||
log.Warn().Msgf("Could not produce a valid SemVer tag for tag [%s]", tag)
|
||||
}
|
||||
|
||||
// Build and return the updated SemVer string
|
||||
return tag, nil
|
||||
// Build and return the updated SemVer string
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func Clean(tag string) error {
|
||||
|
||||
newTag, err := CleanTag(tag)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to clean tag [%s]", tag)
|
||||
}
|
||||
newTag, err := CleanTag(tag)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to clean tag [%s]", tag)
|
||||
}
|
||||
|
||||
log.Info().Msgf("Tag [%s] cleaned to [%s]", tag, newTag)
|
||||
return nil
|
||||
log.Info().Msgf("Tag [%s] cleaned to [%s]", tag, newTag)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkValidLabelValue(tag string) error {
|
||||
if errs := validation.IsValidLabelValue(tag); len(errs) > 0 {
|
||||
return fmt.Errorf("tag [%s] is not valid for label use. error: %s", tag, (strings.Join(errs, ", ")))
|
||||
}
|
||||
return nil
|
||||
if errs := validation.IsValidLabelValue(tag); len(errs) > 0 {
|
||||
return fmt.Errorf("tag [%s] is not valid for label use. error: %s", tag, (strings.Join(errs, ", ")))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keepShortCommitHashSuffix keeps the last 7 characters of a tag
|
||||
// eg "something-abcdefg" -> "abcdefg"
|
||||
func keepShortCommitHashSuffix(tag string) string {
|
||||
return strings.Split(tag, "-")[1]
|
||||
return strings.Split(tag, "-")[1]
|
||||
}
|
||||
|
||||
// cleanRelease Transforms release pattern format
|
||||
// eg "RELEASE.2023-11-20T22-40-07Z" -> "2023.11.20"
|
||||
func cleanRelease(tag string) string {
|
||||
tag = strings.Split(tag, ".")[1]
|
||||
tag = strings.Split(tag, "T")[0]
|
||||
tag = strings.ReplaceAll(tag, "-", ".")
|
||||
tag = strings.Split(tag, ".")[1]
|
||||
tag = strings.Split(tag, "T")[0]
|
||||
tag = strings.ReplaceAll(tag, "-", ".")
|
||||
|
||||
return tag
|
||||
return tag
|
||||
}
|
||||
|
||||
// cleanArch removes arch prefixes
|
||||
// eg "x64-1.2.3" -> "1.2.3"
|
||||
func cleanArch(tag string) string {
|
||||
tag = strings.Split(tag, "-")[1]
|
||||
tag = strings.Split(tag, "-")[1]
|
||||
|
||||
return tag
|
||||
return tag
|
||||
}
|
||||
|
||||
// cleanYearMonthDay Transforms date versions
|
||||
// eg "2023-11-15" -> "2023.11.15" and "2022-04" -> "2022.4"
|
||||
func cleanYearMonthDay(tag string) string {
|
||||
tag = strings.ReplaceAll(tag, "-", ".")
|
||||
parts := strings.Split(tag, ".")
|
||||
for idx := range parts {
|
||||
parts[idx] = strings.TrimPrefix(parts[idx], "0")
|
||||
}
|
||||
for len(parts) < 3 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
tag = strings.Join(parts, ".")
|
||||
tag = strings.ReplaceAll(tag, "-", ".")
|
||||
parts := strings.Split(tag, ".")
|
||||
for idx := range parts {
|
||||
parts[idx] = strings.TrimPrefix(parts[idx], "0")
|
||||
}
|
||||
for len(parts) < 3 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
tag = strings.Join(parts, ".")
|
||||
|
||||
return tag
|
||||
return tag
|
||||
}
|
||||
|
||||
// cleanIncompleteSemVer Transforms incomplete SemVer strings
|
||||
// eg "1.2" -> "1.2.0" and "1" -> "1.0.0"
|
||||
// versions with more parts are left as-is
|
||||
func cleanIncompleteSemVer(tag string) string {
|
||||
parts := strings.Split(tag, ".")
|
||||
switch {
|
||||
case len(parts) == 2:
|
||||
tag = tag + ".0"
|
||||
case len(parts) == 1:
|
||||
tag = tag + ".0.0"
|
||||
}
|
||||
parts := strings.Split(tag, ".")
|
||||
switch {
|
||||
case len(parts) == 2:
|
||||
tag = tag + ".0"
|
||||
case len(parts) == 1:
|
||||
tag = tag + ".0.0"
|
||||
}
|
||||
|
||||
return tag
|
||||
return tag
|
||||
}
|
||||
|
||||
// cleanLeadingSymbol Trims leading 'v' or non-alphanumeric characters
|
||||
// e.g "v1.2.3" -> "1.2.3"
|
||||
func cleanLeadingSymbol(tag string) string {
|
||||
return leadingSymbolPattern.ReplaceAllString(tag, "")
|
||||
return leadingSymbolPattern.ReplaceAllString(tag, "")
|
||||
}
|
||||
|
||||
// cleanSha Strips everything after '@'
|
||||
// e.g "v1.2.3@sha256:abc123" -> "v1.2.3"
|
||||
func cleanSha(tag string) string {
|
||||
return strings.Split(tag, "@")[0]
|
||||
return strings.Split(tag, "@")[0]
|
||||
}
|
||||
|
||||
// cleanPrefixYearMonthDay Transforms date versions with prefix
|
||||
// eg "latest-2023-12-18" -> "2023.12.18"
|
||||
func cleanPrefixYearMonthDay(tag string) string {
|
||||
calVer := strings.Split(tag, "-")[1:]
|
||||
tag = strings.Join(calVer, ".")
|
||||
calVer := strings.Split(tag, "-")[1:]
|
||||
tag = strings.Join(calVer, ".")
|
||||
|
||||
return tag
|
||||
return tag
|
||||
}
|
||||
|
||||
@@ -1,277 +1,277 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
tag string
|
||||
tag string
|
||||
}
|
||||
type testdata struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
func TestCleanTag(t *testing.T) {
|
||||
tests := []testdata{
|
||||
// No match with any pattern tests
|
||||
{
|
||||
name: "Test valid SemVer format",
|
||||
args: args{
|
||||
tag: "1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test pattern that cannot be converted to SemVer",
|
||||
args: args{
|
||||
tag: "latest",
|
||||
},
|
||||
want: "latest",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test empty tag",
|
||||
args: args{
|
||||
tag: "",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test tag with only whitespace",
|
||||
args: args{
|
||||
tag: " ",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test full tag with digest",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test tag with longer version and `-suffix`",
|
||||
args: args{
|
||||
tag: "1.2.3.4-suffix",
|
||||
},
|
||||
want: "1.2.3.4",
|
||||
},
|
||||
{
|
||||
name: "Test tag with semver and `-suffix`",
|
||||
args: args{
|
||||
tag: "1.2.3-abc12367",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test tag with calver and `-suffix`",
|
||||
args: args{
|
||||
tag: "2023.11.2-abc12367",
|
||||
},
|
||||
want: "2023.11.2",
|
||||
},
|
||||
{
|
||||
name: "Test with invalid label format",
|
||||
args: args{
|
||||
tag: strings.Repeat("a", 300),
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
// cleanSha tests
|
||||
{
|
||||
name: "Test cleanSha",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanLeadingSymbol tests
|
||||
{
|
||||
name: "Test cleanLeadingSymbol ($)",
|
||||
args: args{
|
||||
tag: "$1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanLeadingSymbol (v)",
|
||||
args: args{
|
||||
tag: "v1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanLeadingSymbol (version)",
|
||||
args: args{
|
||||
tag: "version-a78f38c1",
|
||||
},
|
||||
want: "a78f38c1",
|
||||
},
|
||||
// keepShortCommitHashSuffix tests
|
||||
{
|
||||
name: "Test keepShortCommitHashSuffix (something-hash)",
|
||||
args: args{
|
||||
tag: "something-abcd123",
|
||||
},
|
||||
want: "abcd123",
|
||||
},
|
||||
{
|
||||
name: "Test keepShortCommitHashSuffix (version-hash)",
|
||||
args: args{
|
||||
tag: "version-abcd123",
|
||||
},
|
||||
want: "abcd123",
|
||||
},
|
||||
// cleanIncompleteSemVer tests
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (2 parts)",
|
||||
args: args{
|
||||
tag: "1.2",
|
||||
},
|
||||
want: "1.2.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (1 part)",
|
||||
args: args{
|
||||
tag: "1",
|
||||
},
|
||||
want: "1.0.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (more than 3 parts)",
|
||||
args: args{
|
||||
tag: "1.2.3.4.5",
|
||||
},
|
||||
want: "1.2.3.4.5",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (with suffix)",
|
||||
args: args{
|
||||
tag: "2.440-jdk17",
|
||||
},
|
||||
want: "2.440.0",
|
||||
},
|
||||
// cleanYearMonthDay tests
|
||||
{
|
||||
name: "Test cleanYearMonthDay (year-month-day)",
|
||||
args: args{
|
||||
tag: "2023-11-15",
|
||||
},
|
||||
want: "2023.11.15",
|
||||
},
|
||||
{
|
||||
name: "Test cleanYearMonthDay (year-month)",
|
||||
args: args{
|
||||
tag: "2022-04",
|
||||
},
|
||||
want: "2022.4.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanYearMonthDay (with prefix)",
|
||||
args: args{
|
||||
tag: "latest-2023-12-18",
|
||||
},
|
||||
want: "2023.12.18",
|
||||
},
|
||||
// cleanPrefix tests
|
||||
{
|
||||
name: "Test cleanPrefix (random prefix)",
|
||||
args: args{
|
||||
tag: "abc123-v1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanPrefix (version prefix)",
|
||||
args: args{
|
||||
tag: "version-1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanArch tests
|
||||
{
|
||||
name: "Test cleanArch",
|
||||
args: args{
|
||||
tag: "x64-1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanRelease tests
|
||||
{
|
||||
name: "Test cleanRelease",
|
||||
args: args{
|
||||
tag: "RELEASE.2023-11-20T22-40-07Z",
|
||||
},
|
||||
want: "2023.11.20",
|
||||
},
|
||||
// cleanStupidSemVerLike tests
|
||||
{
|
||||
name: "Test cleanStupidSemVerLike",
|
||||
args: args{
|
||||
tag: "v.1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanStupidSemVerLike (2)",
|
||||
args: args{
|
||||
tag: "V.1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
}
|
||||
tests := []testdata{
|
||||
// No match with any pattern tests
|
||||
{
|
||||
name: "Test valid SemVer format",
|
||||
args: args{
|
||||
tag: "1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test pattern that cannot be converted to SemVer",
|
||||
args: args{
|
||||
tag: "latest",
|
||||
},
|
||||
want: "latest",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test empty tag",
|
||||
args: args{
|
||||
tag: "",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test tag with only whitespace",
|
||||
args: args{
|
||||
tag: " ",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test full tag with digest",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test tag with longer version and `-suffix`",
|
||||
args: args{
|
||||
tag: "1.2.3.4-suffix",
|
||||
},
|
||||
want: "1.2.3.4",
|
||||
},
|
||||
{
|
||||
name: "Test tag with semver and `-suffix`",
|
||||
args: args{
|
||||
tag: "1.2.3-abc12367",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test tag with calver and `-suffix`",
|
||||
args: args{
|
||||
tag: "2023.11.2-abc12367",
|
||||
},
|
||||
want: "2023.11.2",
|
||||
},
|
||||
{
|
||||
name: "Test with invalid label format",
|
||||
args: args{
|
||||
tag: strings.Repeat("a", 300),
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
// cleanSha tests
|
||||
{
|
||||
name: "Test cleanSha",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanLeadingSymbol tests
|
||||
{
|
||||
name: "Test cleanLeadingSymbol ($)",
|
||||
args: args{
|
||||
tag: "$1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanLeadingSymbol (v)",
|
||||
args: args{
|
||||
tag: "v1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanLeadingSymbol (version)",
|
||||
args: args{
|
||||
tag: "version-a78f38c1",
|
||||
},
|
||||
want: "a78f38c1",
|
||||
},
|
||||
// keepShortCommitHashSuffix tests
|
||||
{
|
||||
name: "Test keepShortCommitHashSuffix (something-hash)",
|
||||
args: args{
|
||||
tag: "something-abcd123",
|
||||
},
|
||||
want: "abcd123",
|
||||
},
|
||||
{
|
||||
name: "Test keepShortCommitHashSuffix (version-hash)",
|
||||
args: args{
|
||||
tag: "version-abcd123",
|
||||
},
|
||||
want: "abcd123",
|
||||
},
|
||||
// cleanIncompleteSemVer tests
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (2 parts)",
|
||||
args: args{
|
||||
tag: "1.2",
|
||||
},
|
||||
want: "1.2.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (1 part)",
|
||||
args: args{
|
||||
tag: "1",
|
||||
},
|
||||
want: "1.0.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (more than 3 parts)",
|
||||
args: args{
|
||||
tag: "1.2.3.4.5",
|
||||
},
|
||||
want: "1.2.3.4.5",
|
||||
},
|
||||
{
|
||||
name: "Test cleanIncompleteSemVer (with suffix)",
|
||||
args: args{
|
||||
tag: "2.440-jdk17",
|
||||
},
|
||||
want: "2.440.0",
|
||||
},
|
||||
// cleanYearMonthDay tests
|
||||
{
|
||||
name: "Test cleanYearMonthDay (year-month-day)",
|
||||
args: args{
|
||||
tag: "2023-11-15",
|
||||
},
|
||||
want: "2023.11.15",
|
||||
},
|
||||
{
|
||||
name: "Test cleanYearMonthDay (year-month)",
|
||||
args: args{
|
||||
tag: "2022-04",
|
||||
},
|
||||
want: "2022.4.0",
|
||||
},
|
||||
{
|
||||
name: "Test cleanYearMonthDay (with prefix)",
|
||||
args: args{
|
||||
tag: "latest-2023-12-18",
|
||||
},
|
||||
want: "2023.12.18",
|
||||
},
|
||||
// cleanPrefix tests
|
||||
{
|
||||
name: "Test cleanPrefix (random prefix)",
|
||||
args: args{
|
||||
tag: "abc123-v1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanPrefix (version prefix)",
|
||||
args: args{
|
||||
tag: "version-1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanArch tests
|
||||
{
|
||||
name: "Test cleanArch",
|
||||
args: args{
|
||||
tag: "x64-1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
// cleanRelease tests
|
||||
{
|
||||
name: "Test cleanRelease",
|
||||
args: args{
|
||||
tag: "RELEASE.2023-11-20T22-40-07Z",
|
||||
},
|
||||
want: "2023.11.20",
|
||||
},
|
||||
// cleanStupidSemVerLike tests
|
||||
{
|
||||
name: "Test cleanStupidSemVerLike",
|
||||
args: args{
|
||||
tag: "v.1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
{
|
||||
name: "Test cleanStupidSemVerLike (2)",
|
||||
args: args{
|
||||
tag: "V.1.2.3",
|
||||
},
|
||||
want: "1.2.3",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
got, err := CleanTag(tt.args.tag)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CleanTag() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CleanTag() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
got, err := CleanTag(tt.args.tag)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CleanTag() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CleanTag() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestCheckValidLabelValue(t *testing.T) {
|
||||
tests := []testdata{
|
||||
{
|
||||
name: "Test invalid label format",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test valid label format",
|
||||
args: args{
|
||||
tag: "1.2.3",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
tests := []testdata{
|
||||
{
|
||||
name: "Test invalid label format",
|
||||
args: args{
|
||||
tag: "1.2.3@sha256:abc123",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test valid label format",
|
||||
args: args{
|
||||
tag: "1.2.3",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
err := checkValidLabelValue(tt.args.tag)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkValidLabelValue() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
err := checkValidLabelValue(tt.args.tag)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkValidLabelValue() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Images represents the structure of values.yaml.
|
||||
type Images struct {
|
||||
ImagesMap map[string]ImageDetails
|
||||
K *koanf.Koanf
|
||||
ImagesMap map[string]ImageDetails
|
||||
K *koanf.Koanf
|
||||
}
|
||||
|
||||
// ImageDetails represents details for each image.
|
||||
type ImageDetails struct {
|
||||
Repository string `yaml:"repository"`
|
||||
Tag string `yaml:"tag"`
|
||||
Version string
|
||||
Link string
|
||||
// Add other fields as needed
|
||||
Repository string `yaml:"repository"`
|
||||
Tag string `yaml:"tag"`
|
||||
Version string
|
||||
Link string
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
var imageRegex = regexp.MustCompile(`^image|[a-zA-Z0-9]+Image$`)
|
||||
|
||||
func (i *Images) LoadValuesFile(filename string) error {
|
||||
// Initialize koanf instance
|
||||
i.K = koanf.New(".")
|
||||
// Initialize koanf instance
|
||||
i.K = koanf.New(".")
|
||||
|
||||
// Load YAML file using koanf
|
||||
if err := i.K.Load(file.Provider(filename), yaml.Parser()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Load YAML file using koanf
|
||||
if err := i.K.Load(file.Provider(filename), yaml.Parser()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// List only root-level keys that match the criteria
|
||||
keys := getFilteredRootLevelKeys(i.K)
|
||||
i.ImagesMap = make(map[string]ImageDetails)
|
||||
for _, key := range keys {
|
||||
// Extract relevant fields from the loaded configuration
|
||||
var img ImageDetails
|
||||
if err := i.K.Unmarshal(key, &img); err != nil {
|
||||
return err
|
||||
}
|
||||
// List only root-level keys that match the criteria
|
||||
keys := getFilteredRootLevelKeys(i.K)
|
||||
i.ImagesMap = make(map[string]ImageDetails)
|
||||
for _, key := range keys {
|
||||
// Extract relevant fields from the loaded configuration
|
||||
var img ImageDetails
|
||||
if err := i.K.Unmarshal(key, &img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the Link field based on the repository
|
||||
img.Link = constructLink(img.Repository)
|
||||
// Set the Link field based on the repository
|
||||
img.Link = constructLink(img.Repository)
|
||||
|
||||
// Set the Version field based on the tag
|
||||
version, err := CleanTag(img.Tag)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("❌ Failed to clean tag")
|
||||
}
|
||||
// Set the Version field based on the tag
|
||||
version, err := CleanTag(img.Tag)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("❌ Failed to clean tag")
|
||||
}
|
||||
|
||||
img.Version = version
|
||||
img.Version = version
|
||||
|
||||
// Save the extracted values to the struct
|
||||
i.ImagesMap[key] = img
|
||||
}
|
||||
// Save the extracted values to the struct
|
||||
i.ImagesMap[key] = img
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFilteredRootLevelKeys(k *koanf.Koanf) []string {
|
||||
filteredKeys := []string{}
|
||||
filteredKeys := []string{}
|
||||
|
||||
// k.Raw() returns a map[string]interface{} with all the keys and their values
|
||||
// This means the keys will only be the root-level keys, we can drill into the
|
||||
// values later if we want the nested keys.
|
||||
for key := range k.Raw() {
|
||||
if key == "imageSelector" {
|
||||
log.Error().Msg("❌ Found [imageSelector] in top level keys, this is not supported.")
|
||||
continue
|
||||
}
|
||||
// Filter keys that match the regex
|
||||
if imageRegex.MatchString(key) {
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
}
|
||||
// k.Raw() returns a map[string]interface{} with all the keys and their values
|
||||
// This means the keys will only be the root-level keys, we can drill into the
|
||||
// values later if we want the nested keys.
|
||||
for key := range k.Raw() {
|
||||
if key == "imageSelector" {
|
||||
log.Error().Msg("❌ Found [imageSelector] in top level keys, this is not supported.")
|
||||
continue
|
||||
}
|
||||
// Filter keys that match the regex
|
||||
if imageRegex.MatchString(key) {
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredKeys
|
||||
return filteredKeys
|
||||
}
|
||||
|
||||
// constructLink constructs a link based on the repository using the logic from the main function.
|
||||
func constructLink(repository string) string {
|
||||
prefix := ""
|
||||
prefix := ""
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(repository, "lscr.io/linuxserver/"):
|
||||
prefix = "https://fleet.linuxserver.io/image?name="
|
||||
repository = strings.TrimPrefix(repository, "lscr.io/")
|
||||
case strings.HasPrefix(repository, "tccr.io/tccr/"):
|
||||
prefix = "https://github.com/truecharts/containers/tree/master/apps/"
|
||||
repository = strings.TrimPrefix(repository, "tccr.io/tccr/")
|
||||
case strings.HasPrefix(repository, "mcr.microsoft.com/"):
|
||||
prefix = "https://mcr.microsoft.com/en-us/product/"
|
||||
repository = strings.TrimPrefix(repository, "mcr.microsoft.com/")
|
||||
case strings.HasPrefix(repository, "public.ecr.aws/"):
|
||||
prefix = "https://gallery.ecr.aws/"
|
||||
repository = strings.TrimPrefix(repository, "public.ecr.aws/")
|
||||
case strings.HasPrefix(repository, "ghcr.io/"):
|
||||
prefix = "https://"
|
||||
case strings.HasPrefix(repository, "quay.io/"):
|
||||
prefix = "https://"
|
||||
case strings.HasPrefix(repository, "gcr.io/"):
|
||||
prefix = "https://"
|
||||
case strings.Contains(repository, ".azurecr.io/"):
|
||||
reg := fmt.Sprintf(`%s.azurecr.io/`, strings.Split(repository, ".")[0])
|
||||
prefix = fmt.Sprintf("https://%s", reg)
|
||||
repository = strings.TrimPrefix(repository, reg)
|
||||
case strings.Contains(repository, ".ocir.io/"):
|
||||
prefix = ""
|
||||
default:
|
||||
// Docker Hub or unknown registry
|
||||
prefix = "https://hub.docker.com/r/"
|
||||
repository = strings.TrimPrefix(repository, "docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "index.docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "registry-1.docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "registry.hub.docker.com/")
|
||||
switch {
|
||||
case strings.HasPrefix(repository, "lscr.io/linuxserver/"):
|
||||
prefix = "https://fleet.linuxserver.io/image?name="
|
||||
repository = strings.TrimPrefix(repository, "lscr.io/")
|
||||
case strings.HasPrefix(repository, "tccr.io/tccr/"):
|
||||
prefix = "https://github.com/truecharts/containers/tree/master/apps/"
|
||||
repository = strings.TrimPrefix(repository, "tccr.io/tccr/")
|
||||
case strings.HasPrefix(repository, "mcr.microsoft.com/"):
|
||||
prefix = "https://mcr.microsoft.com/en-us/product/"
|
||||
repository = strings.TrimPrefix(repository, "mcr.microsoft.com/")
|
||||
case strings.HasPrefix(repository, "public.ecr.aws/"):
|
||||
prefix = "https://gallery.ecr.aws/"
|
||||
repository = strings.TrimPrefix(repository, "public.ecr.aws/")
|
||||
case strings.HasPrefix(repository, "ghcr.io/"):
|
||||
prefix = "https://"
|
||||
case strings.HasPrefix(repository, "quay.io/"):
|
||||
prefix = "https://"
|
||||
case strings.HasPrefix(repository, "gcr.io/"):
|
||||
prefix = "https://"
|
||||
case strings.Contains(repository, ".azurecr.io/"):
|
||||
reg := fmt.Sprintf(`%s.azurecr.io/`, strings.Split(repository, ".")[0])
|
||||
prefix = fmt.Sprintf("https://%s", reg)
|
||||
repository = strings.TrimPrefix(repository, reg)
|
||||
case strings.Contains(repository, ".ocir.io/"):
|
||||
prefix = ""
|
||||
default:
|
||||
// Docker Hub or unknown registry
|
||||
prefix = "https://hub.docker.com/r/"
|
||||
repository = strings.TrimPrefix(repository, "docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "index.docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "registry-1.docker.io/")
|
||||
repository = strings.TrimPrefix(repository, "registry.hub.docker.com/")
|
||||
|
||||
// Check for Docker Official Image
|
||||
if strings.Count(repository, "/") == 0 || strings.HasPrefix(repository, "library/") {
|
||||
prefix = "https://hub.docker.com/_/"
|
||||
repository = strings.TrimPrefix(repository, "library/")
|
||||
}
|
||||
// Check for Docker Official Image
|
||||
if strings.Count(repository, "/") == 0 || strings.HasPrefix(repository, "library/") {
|
||||
prefix = "https://hub.docker.com/_/"
|
||||
repository = strings.TrimPrefix(repository, "library/")
|
||||
}
|
||||
|
||||
// Avoid creating a bad link if the image name has more than 1 slash
|
||||
slashes := strings.Count(repository, "/")
|
||||
if slashes > 1 {
|
||||
prefix = ""
|
||||
log.Warn().Msgf("WARNING: Could not determine source repository url for [%s]", repository)
|
||||
}
|
||||
}
|
||||
// Avoid creating a bad link if the image name has more than 1 slash
|
||||
slashes := strings.Count(repository, "/")
|
||||
if slashes > 1 {
|
||||
prefix = ""
|
||||
log.Warn().Msgf("WARNING: Could not determine source repository url for [%s]", repository)
|
||||
}
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
log.Warn().Msgf("WARNING: Could not determine source repository url for [%s]", repository)
|
||||
return ""
|
||||
}
|
||||
if prefix == "" {
|
||||
log.Warn().Msgf("WARNING: Could not determine source repository url for [%s]", repository)
|
||||
return ""
|
||||
}
|
||||
|
||||
containerURL := fmt.Sprintf("%s%s", prefix, repository)
|
||||
return containerURL
|
||||
containerURL := fmt.Sprintf("%s%s", prefix, repository)
|
||||
return containerURL
|
||||
}
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadValuesFile(t *testing.T) {
|
||||
type TestData struct {
|
||||
name string
|
||||
valuesFile string
|
||||
expected map[string]ImageDetails
|
||||
wantErr bool
|
||||
}
|
||||
testDataPath := "../../testdata/values_yaml"
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Test malformed file",
|
||||
valuesFile: "malformedValues.yaml",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test empty file",
|
||||
valuesFile: "emptyValues.yaml",
|
||||
expected: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test single image file",
|
||||
valuesFile: "singleImageValues.yaml",
|
||||
expected: map[string]ImageDetails{
|
||||
"image": {
|
||||
Repository: "nginx",
|
||||
Tag: "1.15.8",
|
||||
Link: "https://hub.docker.com/_/nginx",
|
||||
Version: "1.15.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test multiple image file",
|
||||
valuesFile: "multiImageValues.yaml",
|
||||
expected: map[string]ImageDetails{
|
||||
"image": {
|
||||
Repository: "author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub1Image": {
|
||||
Repository: "docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub2Image": {
|
||||
Repository: "index.docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub3Image": {
|
||||
Repository: "registry-1.docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub4Image": {
|
||||
Repository: "registry.hub.docker.com/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub5Image": {
|
||||
Repository: "image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/_/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub6Image": {
|
||||
Repository: "library/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/_/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"lscrImage": {
|
||||
Repository: "lscr.io/linuxserver/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://fleet.linuxserver.io/image?name=linuxserver/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"tccrImage": {
|
||||
Repository: "tccr.io/tccr/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://github.com/truecharts/containers/tree/master/apps/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"mcrImage": {
|
||||
Repository: "mcr.microsoft.com/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://mcr.microsoft.com/en-us/product/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ecrImage": {
|
||||
Repository: "public.ecr.aws/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://gallery.ecr.aws/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ghcrImage": {
|
||||
Repository: "ghcr.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://ghcr.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"quayImage": {
|
||||
Repository: "quay.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://quay.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"gcrImage": {
|
||||
Repository: "gcr.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://gcr.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"azurecrImage": {
|
||||
Repository: "author.azurecr.io/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://author.azurecr.io/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ocirImage": {
|
||||
Repository: "author.ocir.io/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"unknownImage": {
|
||||
Repository: "unknown.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
type TestData struct {
|
||||
name string
|
||||
valuesFile string
|
||||
expected map[string]ImageDetails
|
||||
wantErr bool
|
||||
}
|
||||
testDataPath := "../../testdata/values_yaml"
|
||||
tests := []TestData{
|
||||
{
|
||||
name: "Test malformed file",
|
||||
valuesFile: "malformedValues.yaml",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test empty file",
|
||||
valuesFile: "emptyValues.yaml",
|
||||
expected: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test single image file",
|
||||
valuesFile: "singleImageValues.yaml",
|
||||
expected: map[string]ImageDetails{
|
||||
"image": {
|
||||
Repository: "nginx",
|
||||
Tag: "1.15.8",
|
||||
Link: "https://hub.docker.com/_/nginx",
|
||||
Version: "1.15.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test multiple image file",
|
||||
valuesFile: "multiImageValues.yaml",
|
||||
expected: map[string]ImageDetails{
|
||||
"image": {
|
||||
Repository: "author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub1Image": {
|
||||
Repository: "docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub2Image": {
|
||||
Repository: "index.docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub3Image": {
|
||||
Repository: "registry-1.docker.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub4Image": {
|
||||
Repository: "registry.hub.docker.com/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/r/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub5Image": {
|
||||
Repository: "image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/_/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"dockerHub6Image": {
|
||||
Repository: "library/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://hub.docker.com/_/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"lscrImage": {
|
||||
Repository: "lscr.io/linuxserver/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://fleet.linuxserver.io/image?name=linuxserver/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"tccrImage": {
|
||||
Repository: "tccr.io/tccr/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://github.com/truecharts/containers/tree/master/apps/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"mcrImage": {
|
||||
Repository: "mcr.microsoft.com/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://mcr.microsoft.com/en-us/product/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ecrImage": {
|
||||
Repository: "public.ecr.aws/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://gallery.ecr.aws/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ghcrImage": {
|
||||
Repository: "ghcr.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://ghcr.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"quayImage": {
|
||||
Repository: "quay.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://quay.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"gcrImage": {
|
||||
Repository: "gcr.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://gcr.io/author/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"azurecrImage": {
|
||||
Repository: "author.azurecr.io/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "https://author.azurecr.io/image",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"ocirImage": {
|
||||
Repository: "author.ocir.io/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"unknownImage": {
|
||||
Repository: "unknown.io/author/image",
|
||||
Tag: "1.0.0",
|
||||
Link: "",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var images Images
|
||||
err := images.LoadValuesFile(fmt.Sprintf("%s/%s", testDataPath, tt.valuesFile))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LoadValuesFile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var images Images
|
||||
err := images.LoadValuesFile(fmt.Sprintf("%s/%s", testDataPath, tt.valuesFile))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LoadValuesFile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
if tt.expected == nil && len(images.ImagesMap) > 0 {
|
||||
t.Errorf("LoadValuesFile() expected = %+v, got %+v", tt.expected, images.ImagesMap)
|
||||
}
|
||||
if tt.expected == nil && len(images.ImagesMap) > 0 {
|
||||
t.Errorf("LoadValuesFile() expected = %+v, got %+v", tt.expected, images.ImagesMap)
|
||||
}
|
||||
|
||||
if tt.expected != nil {
|
||||
if !reflect.DeepEqual(images.ImagesMap, tt.expected) {
|
||||
t.Errorf("LoadValuesFile() expected = %+v, got %+v", tt.expected, images.ImagesMap)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if tt.expected != nil {
|
||||
if !reflect.DeepEqual(images.ImagesMap, tt.expected) {
|
||||
t.Errorf("LoadValuesFile() expected = %+v, got %+v", tt.expected, images.ImagesMap)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
GoVersion string
|
||||
GoArch string
|
||||
GoOS string
|
||||
GoC bool
|
||||
GitCommit string
|
||||
GitDate time.Time
|
||||
GitDirty bool
|
||||
GoVersion string
|
||||
GoArch string
|
||||
GoOS string
|
||||
GoC bool
|
||||
GitCommit string
|
||||
GitDate time.Time
|
||||
GitDirty bool
|
||||
}
|
||||
|
||||
func NewInfo() *Data {
|
||||
info, _ := debug.ReadBuildInfo()
|
||||
data := &Data{
|
||||
GoVersion: info.GoVersion,
|
||||
}
|
||||
info, _ := debug.ReadBuildInfo()
|
||||
data := &Data{
|
||||
GoVersion: info.GoVersion,
|
||||
}
|
||||
|
||||
// Available info: https://github.com/golang/go/blob/master/src/runtime/debug/mod.go#L73
|
||||
for _, kv := range info.Settings {
|
||||
switch kv.Key {
|
||||
case "GOARCH":
|
||||
data.GoArch = kv.Value
|
||||
case "GOOS":
|
||||
data.GoOS = kv.Value
|
||||
case "CGO_ENABLED":
|
||||
data.GoC = kv.Value == "1"
|
||||
case "vcs.revision":
|
||||
data.GitCommit = kv.Value
|
||||
case "vcs.time":
|
||||
data.GitDate, _ = time.Parse(time.RFC3339, kv.Value)
|
||||
case "vcs.modified":
|
||||
data.GitDirty = kv.Value == "true"
|
||||
}
|
||||
}
|
||||
// Available info: https://github.com/golang/go/blob/master/src/runtime/debug/mod.go#L73
|
||||
for _, kv := range info.Settings {
|
||||
switch kv.Key {
|
||||
case "GOARCH":
|
||||
data.GoArch = kv.Value
|
||||
case "GOOS":
|
||||
data.GoOS = kv.Value
|
||||
case "CGO_ENABLED":
|
||||
data.GoC = kv.Value == "1"
|
||||
case "vcs.revision":
|
||||
data.GitCommit = kv.Value
|
||||
case "vcs.time":
|
||||
data.GitDate, _ = time.Parse(time.RFC3339, kv.Value)
|
||||
case "vcs.modified":
|
||||
data.GitDirty = kv.Value == "true"
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
return data
|
||||
}
|
||||
|
||||
func (d *Data) Print() {
|
||||
log.Info().Msgf(`
|
||||
log.Info().Msgf(`
|
||||
Charttool is a tool for managing TrueCharts charts.
|
||||
|
||||
Go
|
||||
Version: %s
|
||||
OS: %s
|
||||
Arch: %s
|
||||
CGO: %t
|
||||
Version: %s
|
||||
OS: %s
|
||||
Arch: %s
|
||||
CGO: %t
|
||||
Git
|
||||
Commit: %s
|
||||
Date: %s
|
||||
Dirty: %t
|
||||
Commit: %s
|
||||
Date: %s
|
||||
Dirty: %t
|
||||
`, d.GoVersion, d.GoOS, d.GoArch, d.GoC, d.GitCommit, d.GitDate, d.GitDirty)
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package readme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func GenerateReadme(templatePath string, chartPath string, chartName string, train string) error {
|
||||
// Define file paths
|
||||
template := filepath.Join(templatePath, "templates/README.md.tpl")
|
||||
target := filepath.Join(filepath.Dir(chartPath), "README.md")
|
||||
// Define file paths
|
||||
template := filepath.Join(templatePath, "templates/README.md.tpl")
|
||||
target := filepath.Join(filepath.Dir(chartPath), "README.md")
|
||||
|
||||
// Read template file
|
||||
templateContent, err := os.ReadFile(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read template file: %v", err)
|
||||
}
|
||||
// Read template file
|
||||
templateContent, err := os.ReadFile(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read template file: %v", err)
|
||||
}
|
||||
|
||||
// Replace placeholders in the template
|
||||
readmeContent := strings.ReplaceAll(string(templateContent), "TRAINPLACEHOLDER", train)
|
||||
readmeContent = strings.ReplaceAll(readmeContent, "CHARTPLACEHOLDER", chartName)
|
||||
// Replace placeholders in the template
|
||||
readmeContent := strings.ReplaceAll(string(templateContent), "TRAINPLACEHOLDER", train)
|
||||
readmeContent = strings.ReplaceAll(readmeContent, "CHARTPLACEHOLDER", chartName)
|
||||
|
||||
// Write the modified content to the README.md file in the chart directory
|
||||
err = os.WriteFile(target, []byte(readmeContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write README.md file: %v", err)
|
||||
}
|
||||
// Write the modified content to the README.md file in the chart directory
|
||||
err = os.WriteFile(target, []byte(readmeContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write README.md file: %v", err)
|
||||
}
|
||||
|
||||
log.Info().Msgf("Generated README.md for [%s] in [%s] train", chartName, train)
|
||||
return nil
|
||||
log.Info().Msgf("Generated README.md for [%s] in [%s] train", chartName, train)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,59 +2,59 @@ package valuesYaml
|
||||
|
||||
// Addons represents the schema for the 'addons' section.
|
||||
type Addons struct {
|
||||
Codeserver Codeserver `yaml:"codeserver,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Netshoot Netshoot `yaml:"netshoot,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
VPN VPN `yaml:"vpn,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Codeserver Codeserver `yaml:"codeserver,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Netshoot Netshoot `yaml:"netshoot,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
VPN VPN `yaml:"vpn,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// Codeserver represents the schema for the 'codeserver' addon.
|
||||
type Codeserver struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Service ServiceConfiguration `yaml:"service,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Ingress IngressConfiguration `yaml:"ingress,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Service ServiceConfiguration `yaml:"service,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Ingress IngressConfiguration `yaml:"ingress,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
}
|
||||
|
||||
// Netshoot represents the schema for the 'netshoot' addon.
|
||||
type Netshoot struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[['type','!=','disabled']]"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[['type','!=','disabled']]"`
|
||||
}
|
||||
|
||||
// VPN represents the schema for the 'vpn' addon.
|
||||
type VPN struct {
|
||||
Type string `yaml:"type,omitempty" schema:"type:string,default:disabled,enum:,disabled,gluetun,tailscale,openvpn,wireguard"`
|
||||
OpenVPN OpenVPNSettings `yaml:"openvpn,omitempty" schema:"additional_attrs:true,type:dict,show_if:[[type,=,openvpn]]"`
|
||||
Tailscale TailscaleSettings `yaml:"tailscale,omitempty" schema:"additional_attrs:true,type:dict,show_if:[[type,=,tailscale]]"`
|
||||
KillSwitch bool `yaml:"killSwitch" schema:"type:boolean,show_if:[[type,!=,disabled]],default:true"`
|
||||
ExcludedNetworksIPv4 []ExcludedNetwork `yaml:"excludedNetworks_IPv4,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
ExcludedNetworksIPv6 []ExcludedNetwork `yaml:"excludedNetworks_IPv6,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
ConfigFile string `yaml:"configFile,omitempty" schema:"type:string,show_if:[[type,!=,disabled]]"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[['type','!=','disabled']]"`
|
||||
Type string `yaml:"type,omitempty" schema:"type:string,default:disabled,enum:,disabled,gluetun,tailscale,openvpn,wireguard"`
|
||||
OpenVPN OpenVPNSettings `yaml:"openvpn,omitempty" schema:"additional_attrs:true,type:dict,show_if:[[type,=,openvpn]]"`
|
||||
Tailscale TailscaleSettings `yaml:"tailscale,omitempty" schema:"additional_attrs:true,type:dict,show_if:[[type,=,tailscale]]"`
|
||||
KillSwitch bool `yaml:"killSwitch" schema:"type:boolean,show_if:[[type,!=,disabled]],default:true"`
|
||||
ExcludedNetworksIPv4 []ExcludedNetwork `yaml:"excludedNetworks_IPv4,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
ExcludedNetworksIPv6 []ExcludedNetwork `yaml:"excludedNetworks_IPv6,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[[type,!=,disabled]]"`
|
||||
ConfigFile string `yaml:"configFile,omitempty" schema:"type:string,show_if:[[type,!=,disabled]]"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict,show_if:[['type','!=','disabled']]"`
|
||||
}
|
||||
|
||||
// OpenVPNSettings represents the schema for OpenVPN settings.
|
||||
type OpenVPNSettings struct {
|
||||
Username string `yaml:"username,omitempty" schema:"type:string,default:''"`
|
||||
Password string `yaml:"password,omitempty" schema:"type:string,show_if:[[username,!=,]],default:''"`
|
||||
Username string `yaml:"username,omitempty" schema:"type:string,default:''"`
|
||||
Password string `yaml:"password,omitempty" schema:"type:string,show_if:[[username,!=,]],default:''"`
|
||||
}
|
||||
|
||||
// TailscaleSettings represents the schema for Tailscale settings.
|
||||
type TailscaleSettings struct {
|
||||
AuthKey string `yaml:"authkey,omitempty" schema:"type:string,private:true,default:''"`
|
||||
AuthOnce bool `yaml:"auth_once,omitempty" schema:"type:boolean,default:true"`
|
||||
AcceptDNS bool `yaml:"accept_dns,omitempty" schema:"type:boolean,default:false"`
|
||||
Userspace bool `yaml:"userspace,omitempty" schema:"type:boolean,default:false"`
|
||||
Routes string `yaml:"routes,omitempty" schema:"type:string,default:''"`
|
||||
DestIP string `yaml:"dest_ip,omitempty" schema:"type:string,default:''"`
|
||||
Sock5Server string `yaml:"sock5_server,omitempty" schema:"type:string,default:''"`
|
||||
OutboundHTTPProxyListen string `yaml:"outbound_http_proxy_listen,omitempty" schema:"type:string,default:''"`
|
||||
ExtraArgs string `yaml:"extra_args,omitempty" schema:"type:string,default:''"`
|
||||
DaemonExtraArgs string `yaml:"daemon_extra_args,omitempty" schema:"type:string,default:''"`
|
||||
AuthKey string `yaml:"authkey,omitempty" schema:"type:string,private:true,default:''"`
|
||||
AuthOnce bool `yaml:"auth_once,omitempty" schema:"type:boolean,default:true"`
|
||||
AcceptDNS bool `yaml:"accept_dns,omitempty" schema:"type:boolean,default:false"`
|
||||
Userspace bool `yaml:"userspace,omitempty" schema:"type:boolean,default:false"`
|
||||
Routes string `yaml:"routes,omitempty" schema:"type:string,default:''"`
|
||||
DestIP string `yaml:"dest_ip,omitempty" schema:"type:string,default:''"`
|
||||
Sock5Server string `yaml:"sock5_server,omitempty" schema:"type:string,default:''"`
|
||||
OutboundHTTPProxyListen string `yaml:"outbound_http_proxy_listen,omitempty" schema:"type:string,default:''"`
|
||||
ExtraArgs string `yaml:"extra_args,omitempty" schema:"type:string,default:''"`
|
||||
DaemonExtraArgs string `yaml:"daemon_extra_args,omitempty" schema:"type:string,default:''"`
|
||||
}
|
||||
|
||||
// ExcludedNetwork represents the schema for an excluded network in the killswitch.
|
||||
type ExcludedNetwork struct {
|
||||
NetworkV4 string `yaml:"networkv4,omitempty" schema:"type:string,required:true"`
|
||||
NetworkV6 string `yaml:"networkv6,omitempty" schema:"type:string,required:true"`
|
||||
NetworkV4 string `yaml:"networkv4,omitempty" schema:"type:string,required:true"`
|
||||
NetworkV6 string `yaml:"networkv6,omitempty" schema:"type:string,required:true"`
|
||||
}
|
||||
|
||||
@@ -2,35 +2,35 @@ package valuesYaml
|
||||
|
||||
// InterfaceConfiguration represents the configuration for an interface.
|
||||
type InterfaceConfiguration struct {
|
||||
HostInterface string `yaml:"hostInterface,omitempty" schema:"type:string" required:"true" description:"Host Interface"`
|
||||
HostInterface string `yaml:"hostInterface,omitempty" schema:"type:string" required:"true" description:"Host Interface"`
|
||||
}
|
||||
|
||||
// IPAMConfiguration represents the configuration for IP Address Management.
|
||||
type IPAMConfiguration struct {
|
||||
Type string `yaml:"type,omitempty" schema:"type:string" required:"true" enum:"[dhcp, static]" description:"IPAM Type"`
|
||||
StaticIPConfigurations []string `yaml:"staticIPConfigurations,omitempty" schema:"type:list" show_if:"[['type', '=', 'static']]" items:"type:ipaddr,cidr:true" description:"Static IP Addresses"`
|
||||
StaticRoutes []StaticRouteConfiguration `yaml:"staticRoutes,omitempty" schema:"type:list" show_if:"[['type', '=', 'static']]" description:"Static Routes"`
|
||||
Type string `yaml:"type,omitempty" schema:"type:string" required:"true" enum:"[dhcp, static]" description:"IPAM Type"`
|
||||
StaticIPConfigurations []string `yaml:"staticIPConfigurations,omitempty" schema:"type:list" show_if:"[['type', '=', 'static']]" items:"type:ipaddr,cidr:true" description:"Static IP Addresses"`
|
||||
StaticRoutes []StaticRouteConfiguration `yaml:"staticRoutes,omitempty" schema:"type:list" show_if:"[['type', '=', 'static']]" description:"Static Routes"`
|
||||
}
|
||||
|
||||
// StaticRouteConfiguration represents the configuration for a static route.
|
||||
type StaticRouteConfiguration struct {
|
||||
Destination string `yaml:"destination,omitempty" schema:"type:ipaddr,cidr:true" required:"true" description:"Destination"`
|
||||
Gateway string `yaml:"gateway,omitempty" schema:"type:ipaddr,cidr:false" required:"true" description:"Gateway"`
|
||||
Destination string `yaml:"destination,omitempty" schema:"type:ipaddr,cidr:true" required:"true" description:"Destination"`
|
||||
Gateway string `yaml:"gateway,omitempty" schema:"type:ipaddr,cidr:false" required:"true" description:"Gateway"`
|
||||
}
|
||||
|
||||
// NetworkingExpertConfiguration represents the expert configuration for networking.
|
||||
type NetworkingExpertConfiguration struct {
|
||||
ScaleExternalInterface bool `yaml:"scaleExternalInterface,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Add External Interfaces"`
|
||||
InterfaceConfiguration InterfaceConfiguration `yaml:"interfaceConfiguration,omitempty" schema:"type:dict" $ref:"normalize/interfaceConfiguration" description:"Interface Configuration"`
|
||||
IPAM IPAMConfiguration `yaml:"ipam,omitempty" schema:"type:dict" required:"true" description:"IP Address Management"`
|
||||
ScaleExternalInterface bool `yaml:"scaleExternalInterface,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Add External Interfaces"`
|
||||
InterfaceConfiguration InterfaceConfiguration `yaml:"interfaceConfiguration,omitempty" schema:"type:dict" $ref:"normalize/interfaceConfiguration" description:"Interface Configuration"`
|
||||
IPAM IPAMConfiguration `yaml:"ipam,omitempty" schema:"type:dict" required:"true" description:"IP Address Management"`
|
||||
}
|
||||
|
||||
// ServiceExpertConfiguration represents the expert configuration for services.
|
||||
type ServiceExpertConfiguration struct {
|
||||
ScaleExternalInterface bool `yaml:"scaleExternalInterface,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Add External Interfaces"`
|
||||
ScaleExternalInterface bool `yaml:"scaleExternalInterface,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Add External Interfaces"`
|
||||
}
|
||||
|
||||
// NetworkingConfiguration represents the configuration for networking.
|
||||
type NetworkingConfiguration struct {
|
||||
ExpertConfiguration NetworkingExpertConfiguration `yaml:"expertConfiguration,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Expert Config"`
|
||||
ExpertConfiguration NetworkingExpertConfiguration `yaml:"expertConfiguration,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Expert Config"`
|
||||
}
|
||||
|
||||
@@ -2,58 +2,58 @@ package valuesYaml
|
||||
|
||||
// MiddlewareEntry represents the schema for a middleware entry.
|
||||
type MiddlewareEntry struct {
|
||||
Name string `yaml:"name" schema:"type:string,default:'',required:true"`
|
||||
Name string `yaml:"name" schema:"type:string,default:'',required:true"`
|
||||
}
|
||||
|
||||
// Integration represents the schema for integrations.
|
||||
type Integration struct {
|
||||
Homepage IntegrationHomepage `yaml:"homepage" schema:"additional_attrs:true,type:dict"`
|
||||
Homepage IntegrationHomepage `yaml:"homepage" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// IntegrationHomepage represents the schema for the Homepage integration.
|
||||
type IntegrationHomepage struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false"`
|
||||
Name string `yaml:"name" schema:"type:string,default:'',show_if:[[enabled,=,true]]"`
|
||||
Description string `yaml:"description" schema:"type:string,default:'',show_if:[[enabled,=,true]]"`
|
||||
Group string `yaml:"group" schema:"type:string,default:default,show_if:[[enabled,=,true]]"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false"`
|
||||
Name string `yaml:"name" schema:"type:string,default:'',show_if:[[enabled,=,true]]"`
|
||||
Description string `yaml:"description" schema:"type:string,default:'',show_if:[[enabled,=,true]]"`
|
||||
Group string `yaml:"group" schema:"type:string,default:default,show_if:[[enabled,=,true]]"`
|
||||
}
|
||||
|
||||
// TLSEntry represents the schema for a TLS entry.
|
||||
type TLSEntry struct {
|
||||
Host []string `yaml:"hosts" schema:"type:list,default:[],items:type:string,required:true"`
|
||||
CertificateIssuer string `yaml:"certificateIssuer" schema:"type:string,default:''"`
|
||||
ClusterCertificate string `yaml:"clusterCertificate" schema:"type:string,show_if:[[certificateIssuer,=,]]"`
|
||||
SecretName string `yaml:"secretName" schema:"type:string,show_if:[[certificateIssuer,=,]]"`
|
||||
ScaleCert int `yaml:"scaleCert" schema:"type:int,show_if:[[certificateIssuer,=,]]"`
|
||||
Host []string `yaml:"hosts" schema:"type:list,default:[],items:type:string,required:true"`
|
||||
CertificateIssuer string `yaml:"certificateIssuer" schema:"type:string,default:''"`
|
||||
ClusterCertificate string `yaml:"clusterCertificate" schema:"type:string,show_if:[[certificateIssuer,=,]]"`
|
||||
SecretName string `yaml:"secretName" schema:"type:string,show_if:[[certificateIssuer,=,]]"`
|
||||
ScaleCert int `yaml:"scaleCert" schema:"type:int,show_if:[[certificateIssuer,=,]]"`
|
||||
}
|
||||
|
||||
// IngressConfiguration represents the schema for Ingress settings with variable name.
|
||||
type IngressConfiguration struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true,hidden:true"`
|
||||
Name string `yaml:"name" schema:"type:string,default:''"`
|
||||
IngressClassName string `yaml:"ingressClassName" schema:"type:string,default:''"`
|
||||
AllowCors bool `yaml:"allowCors" schema:"type:boolean,show_if:[[advanced,=,true]],default:false"`
|
||||
Hosts []HostEntry `yaml:"hosts" schema:"type:list,default:[],items:type:dict"`
|
||||
CertificateIssuer string `yaml:"certificateIssuer" schema:"type:string,default:''"`
|
||||
TLS []TLSEntry `yaml:"tls" schema:"type:list,default:[],items:type:dict,show_if:[[certificateIssuer,=,]]"`
|
||||
Integration Integration `yaml:"integration" schema:"additional_attrs:true,type:dict"`
|
||||
Entrypoint string `yaml:"entrypoint" schema:"type:string,default:websecure,required:true"`
|
||||
Middlewares []MiddlewareEntry `yaml:"middlewares" schema:"type:list,default:[],items:type:dict"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true,hidden:true"`
|
||||
Name string `yaml:"name" schema:"type:string,default:''"`
|
||||
IngressClassName string `yaml:"ingressClassName" schema:"type:string,default:''"`
|
||||
AllowCors bool `yaml:"allowCors" schema:"type:boolean,show_if:[[advanced,=,true]],default:false"`
|
||||
Hosts []HostEntry `yaml:"hosts" schema:"type:list,default:[],items:type:dict"`
|
||||
CertificateIssuer string `yaml:"certificateIssuer" schema:"type:string,default:''"`
|
||||
TLS []TLSEntry `yaml:"tls" schema:"type:list,default:[],items:type:dict,show_if:[[certificateIssuer,=,]]"`
|
||||
Integration Integration `yaml:"integration" schema:"additional_attrs:true,type:dict"`
|
||||
Entrypoint string `yaml:"entrypoint" schema:"type:string,default:websecure,required:true"`
|
||||
Middlewares []MiddlewareEntry `yaml:"middlewares" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// HostEntry represents the schema for a host entry.
|
||||
type HostEntry struct {
|
||||
Host string `yaml:"host" schema:"type:string,default:'',required:true"`
|
||||
Paths []PathEntry `yaml:"paths" schema:"type:list,default:[{path:/,pathType:Prefix}],items:type:dict"`
|
||||
Host string `yaml:"host" schema:"type:string,default:'',required:true"`
|
||||
Paths []PathEntry `yaml:"paths" schema:"type:list,default:[{path:/,pathType:Prefix}],items:type:dict"`
|
||||
}
|
||||
|
||||
// PathEntry represents the schema for a path entry.
|
||||
type PathEntry struct {
|
||||
Path string `yaml:"path" schema:"type:string,required:true,default:/"`
|
||||
PathType string `yaml:"pathType" schema:"type:string,required:true,default:Prefix"`
|
||||
Path string `yaml:"path" schema:"type:string,required:true,default:/"`
|
||||
PathType string `yaml:"pathType" schema:"type:string,required:true,default:Prefix"`
|
||||
}
|
||||
|
||||
// RootReference represents the root-level reference for Ingress settings.
|
||||
type RootReference struct {
|
||||
Ingress map[string]IngressConfiguration `yaml:"ingress" schema:"additional_attrs:true,type:dict" description:"Ingress Settings"`
|
||||
Ingress map[string]IngressConfiguration `yaml:"ingress" schema:"additional_attrs:true,type:dict" description:"Ingress Settings"`
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ package valuesYaml
|
||||
|
||||
// PrometheusRule represents the schema for Prometheus rule settings.
|
||||
type PrometheusRule struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false"`
|
||||
}
|
||||
|
||||
// MetricsConfiguration represents the metrics configuration.
|
||||
type MetricsConfiguration struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
PrometheusRule struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// ... other prometheusRule configuration
|
||||
} `yaml:"prometheusRule"`
|
||||
// ... other metrics configuration
|
||||
Enabled bool `yaml:"enabled"`
|
||||
PrometheusRule struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// ... other prometheusRule configuration
|
||||
} `yaml:"prometheusRule"`
|
||||
// ... other metrics configuration
|
||||
}
|
||||
|
||||
@@ -2,79 +2,79 @@ package valuesYaml
|
||||
|
||||
// NetworkPolicyEntry represents the schema for a network policy entry.
|
||||
type NetworkPolicyEntry struct {
|
||||
Name string `yaml:"name" schema:"type:string,required:true,default:''"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Policy string `yaml:"policyType" schema:"type:string,default:'',enum:,ingress,egress,ingress-egress"`
|
||||
Ingress []IngressEntry `yaml:"ingress" schema:"type:list,default:[],items:type:dict"`
|
||||
Egress []EgressEntry `yaml:"egress" schema:"type:list,default:[],items:type:dict"`
|
||||
Name string `yaml:"name" schema:"type:string,required:true,default:''"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Policy string `yaml:"policyType" schema:"type:string,default:'',enum:,ingress,egress,ingress-egress"`
|
||||
Ingress []IngressEntry `yaml:"ingress" schema:"type:list,default:[],items:type:dict"`
|
||||
Egress []EgressEntry `yaml:"egress" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// IngressEntry represents the schema for an ingress entry.
|
||||
type IngressEntry struct {
|
||||
From []FromEntry `yaml:"from" schema:"type:list,default:[],items:type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
Ports []PortsEntry `yaml:"ports" schema:"type:list,default:[],items:type:dict"`
|
||||
From []FromEntry `yaml:"from" schema:"type:list,default:[],items:type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
Ports []PortsEntry `yaml:"ports" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// EgressEntry represents the schema for an egress entry.
|
||||
type EgressEntry struct {
|
||||
To []ToEntry `yaml:"to" schema:"type:list,default:[],items:type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
Ports []PortsEntry `yaml:"ports" schema:"type:list,default:[],items:type:dict"`
|
||||
To []ToEntry `yaml:"to" schema:"type:list,default:[],items:type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
Ports []PortsEntry `yaml:"ports" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// FromEntry represents the schema for a 'from' entry.
|
||||
type FromEntry struct {
|
||||
IPBlock IPBlock `yaml:"ipBlock" schema:"additional_attrs:true,type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
IPBlock IPBlock `yaml:"ipBlock" schema:"additional_attrs:true,type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// ToEntry represents the schema for a 'to' entry.
|
||||
type ToEntry struct {
|
||||
IPBlock IPBlock `yaml:"ipBlock" schema:"additional_attrs:true,type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
IPBlock IPBlock `yaml:"ipBlock" schema:"additional_attrs:true,type:dict"`
|
||||
NamespaceSel NamespaceSel `yaml:"namespaceSelector" schema:"additional_attrs:true,type:dict"`
|
||||
PodSel PodSel `yaml:"podSelector" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// IPBlock represents the schema for an IP block.
|
||||
type IPBlock struct {
|
||||
CIDR string `yaml:"cidr" schema:"type:string,default:''"`
|
||||
Except []Except `yaml:"except" schema:"type:list,default:[],items:type:dict"`
|
||||
CIDR string `yaml:"cidr" schema:"type:string,default:''"`
|
||||
Except []Except `yaml:"except" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// Except represents the schema for the 'except' field.
|
||||
type Except struct {
|
||||
ExceptInt string `yaml:"exceptint" schema:"type:string"`
|
||||
ExceptInt string `yaml:"exceptint" schema:"type:string"`
|
||||
}
|
||||
|
||||
// NamespaceSel represents the schema for namespace selector.
|
||||
type NamespaceSel struct {
|
||||
MatchExpressions []ExpressionEntry `yaml:"matchExpressions" schema:"type:list,default:[],items:type:dict"`
|
||||
MatchExpressions []ExpressionEntry `yaml:"matchExpressions" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// PodSel represents the schema for pod selector.
|
||||
type PodSel struct {
|
||||
MatchExpressions []ExpressionEntry `yaml:"matchExpressions" schema:"type:list,default:[],items:type:dict"`
|
||||
MatchExpressions []ExpressionEntry `yaml:"matchExpressions" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// ExpressionEntry represents the schema for an expression entry.
|
||||
type ExpressionEntry struct {
|
||||
Key string `yaml:"key" schema:"type:string"`
|
||||
Operator string `yaml:"operator" schema:"type:string,default:TCP,enum:TCP,UDP,SCTP,In,NotIn,Exists,DoesNotExist"`
|
||||
Values []Value `yaml:"values" schema:"type:list,default:[],items:type:dict"`
|
||||
Key string `yaml:"key" schema:"type:string"`
|
||||
Operator string `yaml:"operator" schema:"type:string,default:TCP,enum:TCP,UDP,SCTP,In,NotIn,Exists,DoesNotExist"`
|
||||
Values []Value `yaml:"values" schema:"type:list,default:[],items:type:dict"`
|
||||
}
|
||||
|
||||
// Value represents the schema for a value entry.
|
||||
type Value struct {
|
||||
Value string `yaml:"value" schema:"type:string"`
|
||||
Value string `yaml:"value" schema:"type:string"`
|
||||
}
|
||||
|
||||
// PortsEntry represents the schema for a ports entry.
|
||||
type PortsEntry struct {
|
||||
Port int `yaml:"port" schema:"type:int"`
|
||||
EndPort int `yaml:"endPort" schema:"type:int"`
|
||||
Protocol string `yaml:"protocol" schema:"type:string,default:TCP,enum:TCP,UDP,SCTP"`
|
||||
Port int `yaml:"port" schema:"type:int"`
|
||||
EndPort int `yaml:"endPort" schema:"type:int"`
|
||||
Protocol string `yaml:"protocol" schema:"type:string,default:TCP,enum:TCP,UDP,SCTP"`
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package valuesYaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
)
|
||||
|
||||
// TODO: enable validation
|
||||
@@ -14,100 +14,100 @@ import (
|
||||
|
||||
// GlobalSettings represents the "Global Settings" section of the schema.
|
||||
type GlobalSettings struct {
|
||||
StopAll bool `yaml:"stopAll" description:"Stops All Running pods and hibernates cnpg" default:"false"`
|
||||
StopAll bool `yaml:"stopAll" description:"Stops All Running pods and hibernates cnpg" default:"false"`
|
||||
}
|
||||
|
||||
// ImagePullSecretEntry represents an entry in the Image Pull Secrets configuration.
|
||||
type ImagePullSecretEntry struct {
|
||||
Registry string `yaml:"registry" validate:"required" description:"Registry"`
|
||||
Username string `yaml:"username" validate:"required" description:"Username"`
|
||||
Password string `yaml:"password" validate:"required" description:"Password"`
|
||||
Email string `yaml:"email" validate:"required" description:"Email"`
|
||||
Registry string `yaml:"registry" validate:"required" description:"Registry"`
|
||||
Username string `yaml:"username" validate:"required" description:"Username"`
|
||||
Password string `yaml:"password" validate:"required" description:"Password"`
|
||||
Email string `yaml:"email" validate:"required" description:"Email"`
|
||||
}
|
||||
|
||||
// Values represents the entire configuration.
|
||||
type Values struct {
|
||||
Global GlobalSettings `yaml:"global"`
|
||||
Workload map[string]WorkloadRootReference `yaml:"workload,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
ImagePullSecretList []ImagePullSecretEntry `yaml:"imagePullSecretList,omitempty" schema:"type:list" items:"type:dict" additional_attrs:"true" description:"Image Pull Secrets"`
|
||||
PodOptions PodOptions `yaml:"podOptions,omitempty" description:"Global Pod Options (Advanced)"`
|
||||
Service map[string]ServiceConfiguration `yaml:"service,omitempty" schema:"additional_attrs:true,type:dict" description:"Service Settings"`
|
||||
ServiceExpert ServiceExpertConfiguration `yaml:"serviceexpert,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Expert Config"`
|
||||
ServiceList []ServiceConfiguration `yaml:"serviceList,omitempty" schema:"type:list,default:[]" items:"type:dict" description:"Add Manual Custom Services"`
|
||||
Persistence map[string]Persistence `yaml:"persistence,omitempty" schema:"type:dict" description:"Integrated Persistent Storage"`
|
||||
PersistenceList []Persistence `yaml:"persistenceList,omitempty" schema:"type:list,default:[]" description:"Additional App Storage"`
|
||||
Ingress map[string]IngressConfiguration `yaml:"ingress,omitempty" schema:"additional_attrs:true,type:dict" description:"Ingress Settings"`
|
||||
IngressList []IngressConfiguration `yaml:"ingressList,omitempty" schema:"type:list,default:[]"`
|
||||
SecurityContext SecurityContext `yaml:"securityContext,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Resources Resources `yaml:"resources,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
DeviceList DeviceList `yaml:"deviceList,omitempty" schema:"type:list,default:[]"`
|
||||
ScaleGPU ScaleGPU `yaml:"scaleGPU,omitempty" schema:"type:list,default:[]"`
|
||||
Metrics map[string]MetricsConfiguration `yaml:"metrics,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
NetworkPolicy []NetworkPolicyEntry `yaml:"networkPolicy,omitempty" schema:"type:list,default:[]"`
|
||||
Addons Addons `yaml:"addons,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Global GlobalSettings `yaml:"global"`
|
||||
Workload map[string]WorkloadRootReference `yaml:"workload,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
ImagePullSecretList []ImagePullSecretEntry `yaml:"imagePullSecretList,omitempty" schema:"type:list" items:"type:dict" additional_attrs:"true" description:"Image Pull Secrets"`
|
||||
PodOptions PodOptions `yaml:"podOptions,omitempty" description:"Global Pod Options (Advanced)"`
|
||||
Service map[string]ServiceConfiguration `yaml:"service,omitempty" schema:"additional_attrs:true,type:dict" description:"Service Settings"`
|
||||
ServiceExpert ServiceExpertConfiguration `yaml:"serviceexpert,omitempty" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Expert Config"`
|
||||
ServiceList []ServiceConfiguration `yaml:"serviceList,omitempty" schema:"type:list,default:[]" items:"type:dict" description:"Add Manual Custom Services"`
|
||||
Persistence map[string]Persistence `yaml:"persistence,omitempty" schema:"type:dict" description:"Integrated Persistent Storage"`
|
||||
PersistenceList []Persistence `yaml:"persistenceList,omitempty" schema:"type:list,default:[]" description:"Additional App Storage"`
|
||||
Ingress map[string]IngressConfiguration `yaml:"ingress,omitempty" schema:"additional_attrs:true,type:dict" description:"Ingress Settings"`
|
||||
IngressList []IngressConfiguration `yaml:"ingressList,omitempty" schema:"type:list,default:[]"`
|
||||
SecurityContext SecurityContext `yaml:"securityContext,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Resources Resources `yaml:"resources,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
DeviceList DeviceList `yaml:"deviceList,omitempty" schema:"type:list,default:[]"`
|
||||
ScaleGPU ScaleGPU `yaml:"scaleGPU,omitempty" schema:"type:list,default:[]"`
|
||||
Metrics map[string]MetricsConfiguration `yaml:"metrics,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
NetworkPolicy []NetworkPolicyEntry `yaml:"networkPolicy,omitempty" schema:"type:list,default:[]"`
|
||||
Addons Addons `yaml:"addons,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// ValuesFile represents the entire values.yaml structure.
|
||||
type ValuesFile struct {
|
||||
K *koanf.Koanf
|
||||
Values Values `yaml:"metadata" validate:"required,dive"`
|
||||
K *koanf.Koanf
|
||||
Values Values `yaml:"metadata" validate:"required,dive"`
|
||||
}
|
||||
|
||||
func NewValuesFile() *ValuesFile {
|
||||
return &ValuesFile{
|
||||
K: koanf.New("."),
|
||||
}
|
||||
return &ValuesFile{
|
||||
K: koanf.New("."),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromFile loads values from a YAML file into the Helmvalues struct.
|
||||
func (v *ValuesFile) LoadFromFile(filename string) error {
|
||||
if v.K == nil {
|
||||
v.K = koanf.New(".")
|
||||
}
|
||||
if v.K == nil {
|
||||
v.K = koanf.New(".")
|
||||
}
|
||||
|
||||
if err := v.K.Load(file.Provider(filename), yaml.Parser()); err != nil {
|
||||
return fmt.Errorf("error loading from file %s: %v", filename, err)
|
||||
}
|
||||
if err := v.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 values struct
|
||||
if err := v.K.Unmarshal("", &v.Values); err != nil {
|
||||
return fmt.Errorf("error unmarshalling data: %v", err)
|
||||
}
|
||||
// Unmarshal the data into the values struct
|
||||
if err := v.K.Unmarshal("", &v.Values); err != nil {
|
||||
return fmt.Errorf("error unmarshalling data: %v", err)
|
||||
}
|
||||
|
||||
// NOTE: Can be uncommented for debugging
|
||||
// loadedData, _ := v.K.Marshal(yaml.Parser())
|
||||
// log.Info().Msgf("Loaded struct data:\n%s\n", loadedData)
|
||||
// NOTE: Can be uncommented for debugging
|
||||
// loadedData, _ := v.K.Marshal(yaml.Parser())
|
||||
// log.Info().Msgf("Loaded struct data:\n%s\n", loadedData)
|
||||
|
||||
// Set default values for fields if they are not set or empty
|
||||
v.setDefaultValues()
|
||||
// Set default values for fields if they are not set or empty
|
||||
v.setDefaultValues()
|
||||
|
||||
// TODO: enable validation
|
||||
// Validate the loaded data
|
||||
// if err := validate.Struct(v.Values); err != nil {
|
||||
// return fmt.Errorf("values.yaml validation error: %v", err)
|
||||
// }
|
||||
// TODO: enable validation
|
||||
// Validate the loaded data
|
||||
// if err := validate.Struct(v.Values); err != nil {
|
||||
// return fmt.Errorf("values.yaml validation error: %v", err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// setDefaultValues sets default values for fields in valuesMetadata if they are not set or empty.
|
||||
func (v *ValuesFile) setDefaultValues() {
|
||||
// Set default values for other fields as needed
|
||||
// Set default values for other fields as needed
|
||||
}
|
||||
|
||||
// SaveToFile saves the Helm values metadata back to the values.yaml file.
|
||||
func (v *ValuesFile) SaveToFile(filename string) error {
|
||||
// Marshal the existing metadata to YAML
|
||||
loadedData, err := v.K.Marshal(yaml.Parser())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshalling data: %v", err)
|
||||
}
|
||||
// Marshal the existing metadata to YAML
|
||||
loadedData, err := v.K.Marshal(yaml.Parser())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshalling data: %v", err)
|
||||
}
|
||||
|
||||
// Write the configuration to the file using os.WriteFile
|
||||
err = os.WriteFile(filename, loadedData, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to file %s: %v", filename, err)
|
||||
}
|
||||
// Write the configuration to the file using os.WriteFile
|
||||
err = os.WriteFile(filename, loadedData, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to file %s: %v", filename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,73 +2,73 @@ package valuesYaml
|
||||
|
||||
// ISCSIOptions represents the schema for iSCSI Options.
|
||||
type ISCSIOptions struct {
|
||||
TargetPortal string `yaml:"targetPortal" schema:"type:string,required:true" description:"targetPortal"`
|
||||
IQN string `yaml:"iqn" schema:"type:string,required:true" description:"iqn"`
|
||||
LUN int `yaml:"lun" schema:"type:int,default:0" description:"lun"`
|
||||
AuthSession AuthSession `yaml:"authSession" schema:"type:dict,additional_attrs:true" description:"authSession"`
|
||||
AuthDiscovery AuthDiscovery `yaml:"authDiscovery" schema:"type:dict,additional_attrs:true" description:"authDiscovery"`
|
||||
TargetPortal string `yaml:"targetPortal" schema:"type:string,required:true" description:"targetPortal"`
|
||||
IQN string `yaml:"iqn" schema:"type:string,required:true" description:"iqn"`
|
||||
LUN int `yaml:"lun" schema:"type:int,default:0" description:"lun"`
|
||||
AuthSession AuthSession `yaml:"authSession" schema:"type:dict,additional_attrs:true" description:"authSession"`
|
||||
AuthDiscovery AuthDiscovery `yaml:"authDiscovery" schema:"type:dict,additional_attrs:true" description:"authDiscovery"`
|
||||
}
|
||||
|
||||
// AuthSession represents the schema for authentication session in iSCSI Options.
|
||||
type AuthSession struct {
|
||||
Username string `yaml:"username" schema:"type:string" description:"username"`
|
||||
Password string `yaml:"password" schema:"type:string" description:"password"`
|
||||
UsernameInitiator string `yaml:"usernameInitiator" schema:"type:string" description:"usernameInitiator"`
|
||||
PasswordInitiator string `yaml:"passwordInitiator" schema:"type:string" description:"passwordInitiator"`
|
||||
Username string `yaml:"username" schema:"type:string" description:"username"`
|
||||
Password string `yaml:"password" schema:"type:string" description:"password"`
|
||||
UsernameInitiator string `yaml:"usernameInitiator" schema:"type:string" description:"usernameInitiator"`
|
||||
PasswordInitiator string `yaml:"passwordInitiator" schema:"type:string" description:"passwordInitiator"`
|
||||
}
|
||||
|
||||
// AuthDiscovery represents the schema for authentication discovery in iSCSI Options.
|
||||
type AuthDiscovery struct {
|
||||
Username string `yaml:"username" schema:"type:string" description:"username"`
|
||||
Password string `yaml:"password" schema:"type:string" description:"password"`
|
||||
UsernameInitiator string `yaml:"usernameInitiator" schema:"type:string" description:"usernameInitiator"`
|
||||
PasswordInitiator string `yaml:"passwordInitiator" schema:"type:string" description:"passwordInitiator"`
|
||||
Username string `yaml:"username" schema:"type:string" description:"username"`
|
||||
Password string `yaml:"password" schema:"type:string" description:"password"`
|
||||
UsernameInitiator string `yaml:"usernameInitiator" schema:"type:string" description:"usernameInitiator"`
|
||||
PasswordInitiator string `yaml:"passwordInitiator" schema:"type:string" description:"passwordInitiator"`
|
||||
}
|
||||
|
||||
// AutoPermissions represents the schema for Automatic Permissions Configuration.
|
||||
type AutoPermissions struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true" description:"enabled"`
|
||||
Chown bool `yaml:"chown" schema:"show_if:[[enabled,=,true]],type:boolean,default:false" description:"Run CHOWN"`
|
||||
Chmod string `yaml:"chmod" schema:"show_if:[[enabled,=,true]],type:string,valid_chars:'[0-9]{3}'" description:"Run CHMOD"`
|
||||
Recursive bool `yaml:"recursive" schema:"show_if:[[enabled,=,true]],type:boolean,default:false" description:"Recursive"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:false,show_subquestions_if:true" description:"enabled"`
|
||||
Chown bool `yaml:"chown" schema:"show_if:[[enabled,=,true]],type:boolean,default:false" description:"Run CHOWN"`
|
||||
Chmod string `yaml:"chmod" schema:"show_if:[[enabled,=,true]],type:string,valid_chars:'[0-9]{3}'" description:"Run CHMOD"`
|
||||
Recursive bool `yaml:"recursive" schema:"show_if:[[enabled,=,true]],type:boolean,default:false" description:"Recursive"`
|
||||
}
|
||||
|
||||
// HostPathOptions represents the schema for Host Path Options.
|
||||
type HostPathOptions struct {
|
||||
Path string `yaml:"path" schema:"type:string,required:true" description:"Path inside the container the storage is mounted"`
|
||||
Path string `yaml:"path" schema:"type:string,required:true" description:"Path inside the container the storage is mounted"`
|
||||
}
|
||||
|
||||
// StaticBinding represents the schema for Static Fixed PVC Bindings.
|
||||
type StaticBinding struct {
|
||||
Mode string `yaml:"mode" schema:"type:string,default:disabled,enum:disabled,smb,nfs" description:"mode"`
|
||||
Server string `yaml:"server" schema:"show_if:[[mode,!=,disabled]],type:string,default:'myserver'" description:"Server"`
|
||||
Share string `yaml:"share" schema:"show_if:[[mode,!=,disabled]],type:string,default:'/myshare'" description:"Share"`
|
||||
User string `yaml:"user" schema:"show_if:[[mode,=,smb]],type:string,default:'myuser'" description:"User"`
|
||||
Domain string `yaml:"domain" schema:"show_if:[[mode,=,smb]],type:string" description:"Domain"`
|
||||
Password string `yaml:"password" schema:"show_if:[[mode,=,smb]],type:string" description:"Password"`
|
||||
Mode string `yaml:"mode" schema:"type:string,default:disabled,enum:disabled,smb,nfs" description:"mode"`
|
||||
Server string `yaml:"server" schema:"show_if:[[mode,!=,disabled]],type:string,default:'myserver'" description:"Server"`
|
||||
Share string `yaml:"share" schema:"show_if:[[mode,!=,disabled]],type:string,default:'/myshare'" description:"Share"`
|
||||
User string `yaml:"user" schema:"show_if:[[mode,=,smb]],type:string,default:'myuser'" description:"User"`
|
||||
Domain string `yaml:"domain" schema:"show_if:[[mode,=,smb]],type:string" description:"Domain"`
|
||||
Password string `yaml:"password" schema:"show_if:[[mode,=,smb]],type:string" description:"Password"`
|
||||
}
|
||||
|
||||
// VolumeSnapshot represents the schema for Volume Snapshots.
|
||||
type VolumeSnapshot struct {
|
||||
Name string `yaml:"name" schema:"type:string,default:mysnapshot" description:"Name"`
|
||||
VolumeSnapshotClassName string `yaml:"volumeSnapshotClassName" schema:"type:string" description:"volumeSnapshot Class Name (Advanced)"`
|
||||
Name string `yaml:"name" schema:"type:string,default:mysnapshot" description:"Name"`
|
||||
VolumeSnapshotClassName string `yaml:"volumeSnapshotClassName" schema:"type:string" description:"volumeSnapshot Class Name (Advanced)"`
|
||||
}
|
||||
|
||||
// Persistence represents the schema for Integrated Persistent Storage.
|
||||
type Persistence struct {
|
||||
Name string `yaml:"name,omitempty" schema:"type:string" description:"Custom storage name"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true,hidden:true" description:"Enable Integrated Persistent Storage"`
|
||||
Type string `yaml:"type" schema:"type:string,default:pvc,enum:pvc,hostPath,emptyDir,nfs,iscsi" description:"Sets the persistence type, Anything other than PVC could break rollback!"`
|
||||
Server string `yaml:"server" schema:"show_if:[[type,=,nfs]]type:string default:''" description:"NFS Server"`
|
||||
Path string `yaml:"path" schema:"show_if:[[type,=,nfs]],type:string" description:"Path on NFS Server"`
|
||||
ISCSI ISCSIOptions `yaml:"iscsi" schema:"show_if:[[type,=,iscsi]],type:dict,additional_attrs:true" description:"iSCSI Options"`
|
||||
AutoPermissions AutoPermissions `yaml:"autoPermissions" schema:"show_if:[[type,!=,pvc]],type:dict,additional_attrs:true" description:"Automatic Permissions Configuration"`
|
||||
ReadOnly bool `yaml:"readOnly" schema:"type:boolean,default:false" description:"Read Only"`
|
||||
HostPath HostPathOptions `yaml:"hostPath" schema:"show_if:[[type,=,hostPath]],type:hostpath" description:"Host Path"`
|
||||
MountPath string `yaml:"mountPath" schema:"type:string,required:true,valid_chars:^\\/([a-zA-Z0-9._-]+(\\s?[a-zA-Z0.9._-]+|\\/?)$" description:"Path inside the container the storage is mounted"`
|
||||
Medium string `yaml:"medium" schema:"show_if:[[type,=,emptyDir]],type:string,enum:'Memory'" description:"EmptyDir Medium"`
|
||||
Size string `yaml:"size" schema:"show_if:[[type,=,pvc]],type:string,default:256Gi" description:"Size Quotum of Storage"`
|
||||
StorageClass string `yaml:"storageClass" schema:"show_if:[[type,=,pvc]],type:string" description:"storageClass (Advanced)"`
|
||||
Static StaticBinding `yaml:"static" schema:"show_if:[[type,=,pvc]],type:dict,additional_attrs:true" description:"Static Fixed PVC Bindings (Experimental)"`
|
||||
VolumeSnapshots []VolumeSnapshot `yaml:"volumeSnapshots" schema:"show_if:[[type,=,pvc]],type:list,default:[]" description:"Volume Snapshots (Experimental)"`
|
||||
Name string `yaml:"name,omitempty" schema:"type:string" description:"Custom storage name"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true,hidden:true" description:"Enable Integrated Persistent Storage"`
|
||||
Type string `yaml:"type" schema:"type:string,default:pvc,enum:pvc,hostPath,emptyDir,nfs,iscsi" description:"Sets the persistence type, Anything other than PVC could break rollback!"`
|
||||
Server string `yaml:"server" schema:"show_if:[[type,=,nfs]]type:string default:''" description:"NFS Server"`
|
||||
Path string `yaml:"path" schema:"show_if:[[type,=,nfs]],type:string" description:"Path on NFS Server"`
|
||||
ISCSI ISCSIOptions `yaml:"iscsi" schema:"show_if:[[type,=,iscsi]],type:dict,additional_attrs:true" description:"iSCSI Options"`
|
||||
AutoPermissions AutoPermissions `yaml:"autoPermissions" schema:"show_if:[[type,!=,pvc]],type:dict,additional_attrs:true" description:"Automatic Permissions Configuration"`
|
||||
ReadOnly bool `yaml:"readOnly" schema:"type:boolean,default:false" description:"Read Only"`
|
||||
HostPath HostPathOptions `yaml:"hostPath" schema:"show_if:[[type,=,hostPath]],type:hostpath" description:"Host Path"`
|
||||
MountPath string `yaml:"mountPath" schema:"type:string,required:true,valid_chars:^\\/([a-zA-Z0-9._-]+(\\s?[a-zA-Z0.9._-]+|\\/?)$" description:"Path inside the container the storage is mounted"`
|
||||
Medium string `yaml:"medium" schema:"show_if:[[type,=,emptyDir]],type:string,enum:'Memory'" description:"EmptyDir Medium"`
|
||||
Size string `yaml:"size" schema:"show_if:[[type,=,pvc]],type:string,default:256Gi" description:"Size Quotum of Storage"`
|
||||
StorageClass string `yaml:"storageClass" schema:"show_if:[[type,=,pvc]],type:string" description:"storageClass (Advanced)"`
|
||||
Static StaticBinding `yaml:"static" schema:"show_if:[[type,=,pvc]],type:dict,additional_attrs:true" description:"Static Fixed PVC Bindings (Experimental)"`
|
||||
VolumeSnapshots []VolumeSnapshot `yaml:"volumeSnapshots" schema:"show_if:[[type,=,pvc]],type:list,default:[]" description:"Volume Snapshots (Experimental)"`
|
||||
}
|
||||
|
||||
@@ -2,22 +2,22 @@ package valuesYaml
|
||||
|
||||
// DNSConfigEntry represents an entry in the DNS configuration.
|
||||
type DNSConfigEntry struct {
|
||||
Name string `yaml:"name,omitempty" validate:"required" description:"Name"`
|
||||
Value string `yaml:"value,omitempty" validate:"required" description:"Value"`
|
||||
Name string `yaml:"name,omitempty" validate:"required" description:"Name"`
|
||||
Value string `yaml:"value,omitempty" validate:"required" description:"Value"`
|
||||
}
|
||||
|
||||
// PodOptions represents the "Global Pod Options (Advanced)" section of the schema.
|
||||
type PodOptions struct {
|
||||
ExpertPodOpts struct {
|
||||
Type bool `yaml:"expertPodOpts" description:"Expert - Pod Options" default:"false" show_subquestions_if:"true"`
|
||||
HostNetwork bool `yaml:"hostNetwork,omitempty" description:"Host Networking" default:"false"`
|
||||
DNSConfig DNSConfig `yaml:"dnsConfig,omitempty" description:"DNS Configuration"`
|
||||
} `yaml:"podOptions,omitempty" description:"Global Pod Options (Advanced)"`
|
||||
ExpertPodOpts struct {
|
||||
Type bool `yaml:"expertPodOpts" description:"Expert - Pod Options" default:"false" show_subquestions_if:"true"`
|
||||
HostNetwork bool `yaml:"hostNetwork,omitempty" description:"Host Networking" default:"false"`
|
||||
DNSConfig DNSConfig `yaml:"dnsConfig,omitempty" description:"DNS Configuration"`
|
||||
} `yaml:"podOptions,omitempty" description:"Global Pod Options (Advanced)"`
|
||||
}
|
||||
|
||||
// DNSConfig represents the DNS configuration.
|
||||
type DNSConfig struct {
|
||||
Options []DNSConfigEntry `yaml:"options,omitempty" validate:"dive" description:"Options"`
|
||||
Nameservers []string `yaml:"nameservers,omitempty" validate:"dive,required" description:"Nameservers"`
|
||||
Searches []string `yaml:"searches,omitempty" validate:"dive,required" description:"Searches"`
|
||||
Options []DNSConfigEntry `yaml:"options,omitempty" validate:"dive" description:"Options"`
|
||||
Nameservers []string `yaml:"nameservers,omitempty" validate:"dive,required" description:"Nameservers"`
|
||||
Searches []string `yaml:"searches,omitempty" validate:"dive,required" description:"Searches"`
|
||||
}
|
||||
|
||||
@@ -2,42 +2,42 @@ package valuesYaml
|
||||
|
||||
// Resources represents the schema for resource settings.
|
||||
type Resources struct {
|
||||
Limits ResourceLimits `yaml:"limits" schema:"additional_attrs:true,type:dict"`
|
||||
Requests ResourceLimits `yaml:"requests" schema:"additional_attrs:true,type:dict,hidden:true"`
|
||||
Limits ResourceLimits `yaml:"limits" schema:"additional_attrs:true,type:dict"`
|
||||
Requests ResourceLimits `yaml:"requests" schema:"additional_attrs:true,type:dict,hidden:true"`
|
||||
}
|
||||
|
||||
// ResourceLimits represents the schema for resource limit settings.
|
||||
type ResourceLimits struct {
|
||||
CPU string `yaml:"cpu" schema:"type:string,default:4000m,valid_chars:^(?!^0(\\.0|m|)$)([0-9]+)(\\.[0-9]|m?)$"`
|
||||
Memory string `yaml:"memory" schema:"type:string,default:8Gi,valid_chars:^(?!^0(e[0-9]|[EPTGMK]i?|)$)([0-9]+)(|[EPTGMK]i?|e[0-9]+)$"`
|
||||
CPU string `yaml:"cpu" schema:"type:string,default:4000m,valid_chars:^(?!^0(\\.0|m|)$)([0-9]+)(\\.[0-9]|m?)$"`
|
||||
Memory string `yaml:"memory" schema:"type:string,default:8Gi,valid_chars:^(?!^0(e[0-9]|[EPTGMK]i?|)$)([0-9]+)(|[EPTGMK]i?|e[0-9]+)$"`
|
||||
}
|
||||
|
||||
// DeviceList represents the schema for the list of devices.
|
||||
type DeviceList struct {
|
||||
DeviceListEntry []DeviceEntry `yaml:"deviceList" schema:"type:list,default:[]"`
|
||||
DeviceListEntry []DeviceEntry `yaml:"deviceList" schema:"type:list,default:[]"`
|
||||
}
|
||||
|
||||
// DeviceEntry represents the schema for a device entry.
|
||||
type DeviceEntry struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true"`
|
||||
Type string `yaml:"type" schema:"type:string,default:device,hidden:true"`
|
||||
ReadOnly bool `yaml:"readOnly" schema:"type:boolean,default:false"`
|
||||
HostPath string `yaml:"hostPath" schema:"type:path"`
|
||||
MountPath string `yaml:"mountPath" schema:"type:string,default:/dev/ttyACM0"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean,default:true"`
|
||||
Type string `yaml:"type" schema:"type:string,default:device,hidden:true"`
|
||||
ReadOnly bool `yaml:"readOnly" schema:"type:boolean,default:false"`
|
||||
HostPath string `yaml:"hostPath" schema:"type:path"`
|
||||
MountPath string `yaml:"mountPath" schema:"type:string,default:/dev/ttyACM0"`
|
||||
}
|
||||
|
||||
// ScaleGPU represents the schema for GPU configuration.
|
||||
type ScaleGPU struct {
|
||||
ScaleGPUEntry []GPUEntry `yaml:"scaleGPU" schema:"type:list,default:[]"`
|
||||
ScaleGPUEntry []GPUEntry `yaml:"scaleGPU" schema:"type:list,default:[]"`
|
||||
}
|
||||
|
||||
// GPUEntry represents the schema for a GPU entry.
|
||||
type GPUEntry struct {
|
||||
GPU GPUConfiguration `yaml:"gpu" schema:"additional_attrs:true,type:dict"`
|
||||
Workaround string `yaml:"workaround" schema:"type:string,default:workaround,hidden:true"`
|
||||
GPU GPUConfiguration `yaml:"gpu" schema:"additional_attrs:true,type:dict"`
|
||||
Workaround string `yaml:"workaround" schema:"type:string,default:workaround,hidden:true"`
|
||||
}
|
||||
|
||||
// GPUConfiguration represents the schema for GPU configuration.
|
||||
type GPUConfiguration struct {
|
||||
// Specify GPU configuration here
|
||||
// Specify GPU configuration here
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@ package valuesYaml
|
||||
|
||||
// Container represents the schema for container settings.
|
||||
type Container struct {
|
||||
RunAsUser int `yaml:"runAsUser" schema:"type:int,default:568"`
|
||||
RunAsGroup int `yaml:"runAsGroup" schema:"type:int,default:568"`
|
||||
PUID int `yaml:"PUID" schema:"type:int,default:568,show_if:[[runAsUser,=,0]]"`
|
||||
UMASK string `yaml:"UMASK" schema:"type:string,default:0022"`
|
||||
Advanced bool `yaml:"advanced" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Privileged bool `yaml:"privileged" schema:"type:boolean,default:false,show_if:[[advanced,=,true]]"`
|
||||
ReadOnlyRootFilesystem bool `yaml:"readOnlyRootFilesystem" schema:"type:boolean,default:true,show_if:[[advanced,=,true]]"`
|
||||
RunAsUser int `yaml:"runAsUser" schema:"type:int,default:568"`
|
||||
RunAsGroup int `yaml:"runAsGroup" schema:"type:int,default:568"`
|
||||
PUID int `yaml:"PUID" schema:"type:int,default:568,show_if:[[runAsUser,=,0]]"`
|
||||
UMASK string `yaml:"UMASK" schema:"type:string,default:0022"`
|
||||
Advanced bool `yaml:"advanced" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Privileged bool `yaml:"privileged" schema:"type:boolean,default:false,show_if:[[advanced,=,true]]"`
|
||||
ReadOnlyRootFilesystem bool `yaml:"readOnlyRootFilesystem" schema:"type:boolean,default:true,show_if:[[advanced,=,true]]"`
|
||||
}
|
||||
|
||||
// Pod represents the schema for pod settings.
|
||||
type Pod struct {
|
||||
FsGroupChangePolicy string `yaml:"fsGroupChangePolicy" schema:"type:string,default:OnRootMismatch,enum:OnRootMismatch,Always"`
|
||||
SupplementalGroups []int `yaml:"supplementalGroups" schema:"type:list,default:[],items:type:int"`
|
||||
FsGroup int `yaml:"fsGroup" schema:"type:int,default:568"`
|
||||
FsGroupChangePolicy string `yaml:"fsGroupChangePolicy" schema:"type:string,default:OnRootMismatch,enum:OnRootMismatch,Always"`
|
||||
SupplementalGroups []int `yaml:"supplementalGroups" schema:"type:list,default:[],items:type:int"`
|
||||
FsGroup int `yaml:"fsGroup" schema:"type:int,default:568"`
|
||||
}
|
||||
|
||||
// SecurityContext represents the schema for security context settings.
|
||||
type SecurityContext struct {
|
||||
Container Container `yaml:"container" schema:"additional_attrs:true,type:dict"`
|
||||
Pod Pod `yaml:"pod" schema:"additional_attrs:true,type:dict"`
|
||||
Container Container `yaml:"container" schema:"additional_attrs:true,type:dict"`
|
||||
Pod Pod `yaml:"pod" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
@@ -2,27 +2,27 @@ package valuesYaml
|
||||
|
||||
// PortConfiguration represents the configuration for a service port.
|
||||
type PortConfiguration struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean" default:"true" hidden:"true" description:"Enable the Port"`
|
||||
Name string `yaml:"name" schema:"type:string" default:"" description:"Port Name"`
|
||||
Protocol string `yaml:"protocol" schema:"type:string" default:"tcp" enum:"[http, https, tcp, udp]" description:"Port Type"`
|
||||
TargetPort int `yaml:"targetPort" schema:"type:int" required:"true" description:"Target Port"`
|
||||
Port int `yaml:"port" schema:"type:int" required:"true" description:"Container Port"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean" default:"true" hidden:"true" description:"Enable the Port"`
|
||||
Name string `yaml:"name" schema:"type:string" default:"" description:"Port Name"`
|
||||
Protocol string `yaml:"protocol" schema:"type:string" default:"tcp" enum:"[http, https, tcp, udp]" description:"Port Type"`
|
||||
TargetPort int `yaml:"targetPort" schema:"type:int" required:"true" description:"Target Port"`
|
||||
Port int `yaml:"port" schema:"type:int" required:"true" description:"Container Port"`
|
||||
}
|
||||
|
||||
// AdvancedServiceSettings represents the advanced settings for a service.
|
||||
type AdvancedServiceSettings struct {
|
||||
ExternalIPs []string `yaml:"externalIPs" schema:"type:list" default:"[]" items:"type:string" description:"External IP's"`
|
||||
IPFamilyPolicy string `yaml:"ipFamilyPolicy" schema:"type:string" default:"SingleStack" enum:"[SingleStack, PreferDualStack, RequireDualStack]" description:"IP Family Policy"`
|
||||
IPFamilies []string `yaml:"ipFamilies" schema:"type:list" default:"[]" items:"type:string" description:"(Advanced) The IP Families that should be used"`
|
||||
ExternalIPs []string `yaml:"externalIPs" schema:"type:list" default:"[]" items:"type:string" description:"External IP's"`
|
||||
IPFamilyPolicy string `yaml:"ipFamilyPolicy" schema:"type:string" default:"SingleStack" enum:"[SingleStack, PreferDualStack, RequireDualStack]" description:"IP Family Policy"`
|
||||
IPFamilies []string `yaml:"ipFamilies" schema:"type:list" default:"[]" items:"type:string" description:"(Advanced) The IP Families that should be used"`
|
||||
}
|
||||
|
||||
// ServiceConfiguration represents the configuration for a service.
|
||||
type ServiceConfiguration struct {
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean" default:"true" hidden:"true" description:"Enable the service"`
|
||||
Name string `yaml:"name" schema:"type:string" default:"" description:"Name"`
|
||||
Type string `yaml:"type" schema:"type:string" default:"LoadBalancer" enum:"[LoadBalancer, ClusterIP, Simple]" description:"Service Type"`
|
||||
LoadBalancerIP string `yaml:"loadBalancerIP" schema:"type:string" show_if:"[['type', '=', 'LoadBalancer']]" default:"" description:"LoadBalancer IP"`
|
||||
AdvancedSvcSet AdvancedServiceSettings `yaml:"advancedsvcset" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Advanced Service Settings"`
|
||||
Ports PortConfiguration `yaml:"ports" schema:"type:dict" description:"Service's Port(s) Configuration"`
|
||||
PortsList []PortConfiguration `yaml:"portsList" schema:"type:list" default:"[]" items:"type:dict" description:"Additional Service Ports"`
|
||||
Enabled bool `yaml:"enabled" schema:"type:boolean" default:"true" hidden:"true" description:"Enable the service"`
|
||||
Name string `yaml:"name" schema:"type:string" default:"" description:"Name"`
|
||||
Type string `yaml:"type" schema:"type:string" default:"LoadBalancer" enum:"[LoadBalancer, ClusterIP, Simple]" description:"Service Type"`
|
||||
LoadBalancerIP string `yaml:"loadBalancerIP" schema:"type:string" show_if:"[['type', '=', 'LoadBalancer']]" default:"" description:"LoadBalancer IP"`
|
||||
AdvancedSvcSet AdvancedServiceSettings `yaml:"advancedsvcset" schema:"type:boolean" default:"false" show_subquestions_if:"true" description:"Show Advanced Service Settings"`
|
||||
Ports PortConfiguration `yaml:"ports" schema:"type:dict" description:"Service's Port(s) Configuration"`
|
||||
PortsList []PortConfiguration `yaml:"portsList" schema:"type:list" default:"[]" items:"type:dict" description:"Additional Service Ports"`
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
package valuesYaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// UpdatevaluesFile updates the specified values.yaml file with an optional bump parameter.
|
||||
func UpdatevaluesFile(valuesPathOrFolder, bump string) error {
|
||||
var chartFolder string
|
||||
var valuesPath string
|
||||
var chartFolder string
|
||||
var valuesPath string
|
||||
|
||||
fileInfo, err := os.Stat(valuesPathOrFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo, err := os.Stat(valuesPathOrFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
chartFolder = valuesPathOrFolder
|
||||
} else {
|
||||
chartFolder = filepath.Dir(valuesPathOrFolder)
|
||||
if fileInfo.IsDir() {
|
||||
chartFolder = valuesPathOrFolder
|
||||
} else {
|
||||
chartFolder = filepath.Dir(valuesPathOrFolder)
|
||||
|
||||
}
|
||||
valuesPath = filepath.Join(chartFolder, "values.yaml")
|
||||
}
|
||||
valuesPath = filepath.Join(chartFolder, "values.yaml")
|
||||
|
||||
log.Printf("Processing: %s\n", valuesPath)
|
||||
values := NewValuesFile()
|
||||
if err := values.LoadFromFile(valuesPath); err != nil {
|
||||
log.Info().Msgf("Error loading values: %v\n", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Processing: %s\n", valuesPath)
|
||||
values := NewValuesFile()
|
||||
if err := values.LoadFromFile(valuesPath); err != nil {
|
||||
log.Info().Msgf("Error loading values: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the modified metadata back to the file
|
||||
if err := values.SaveToFile(valuesPath); err != nil {
|
||||
return fmt.Errorf("error saving values.yaml: %s", err)
|
||||
}
|
||||
// Save the modified metadata back to the file
|
||||
if err := values.SaveToFile(valuesPath); err != nil {
|
||||
return fmt.Errorf("error saving values.yaml: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("values file updated and saved to %s\n", valuesPath)
|
||||
log.Printf("values file updated and saved to %s\n", valuesPath)
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,52 +2,52 @@ package valuesYaml
|
||||
|
||||
// WorkloadRootReference represents the schema for the root reference in workload settings.
|
||||
type WorkloadRootReference struct {
|
||||
Type string `yaml:"type,omitempty" schema:"type:string,default:Deployment,enum:,Deployment,DaemonSet"`
|
||||
Replicas int `yaml:"replicas,omitempty" schema:"type:int,show_if:[[type,!=,DaemonSet]],default:1"`
|
||||
PodSpec WorkloadPodSpec `yaml:"podSpec,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
|
||||
Type string `yaml:"type,omitempty" schema:"type:string,default:Deployment,enum:,Deployment,DaemonSet"`
|
||||
Replicas int `yaml:"replicas,omitempty" schema:"type:int,show_if:[[type,!=,DaemonSet]],default:1"`
|
||||
PodSpec WorkloadPodSpec `yaml:"podSpec,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
|
||||
}
|
||||
|
||||
// WorkloadPodSpec represents the schema for the pod spec in workload settings.
|
||||
type WorkloadPodSpec struct {
|
||||
Containers WorkloadContainers `yaml:"containers,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
Containers WorkloadContainers `yaml:"containers,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// WorkloadContainers represents the schema for containers in workload settings.
|
||||
type WorkloadContainers struct {
|
||||
ContainerItem WorkloadContainerItem `yaml:"containerItem,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
ContainerItem WorkloadContainerItem `yaml:"containerItem,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
}
|
||||
|
||||
// WorkloadContainerItem represents the schema for a container item in workload settings.
|
||||
type WorkloadContainerItem struct {
|
||||
Env map[string]string `yaml:"env,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict"`
|
||||
ExtraArgs []string `yaml:"extraArgs,omitempty" schema:"type:list,default:[]"`
|
||||
Advanced WorkloadContainerAdvanced `yaml:"advanced,omitempty" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Probes WorkloadProbes `yaml:"probes,omitempty"`
|
||||
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
|
||||
Env map[string]string `yaml:"env,omitempty" schema:"additional_attrs:true,type:dict"`
|
||||
EnvList []EnvList `yaml:"envList,omitempty" schema:"type:list,default:[],items:type:dict"`
|
||||
ExtraArgs []string `yaml:"extraArgs,omitempty" schema:"type:list,default:[]"`
|
||||
Advanced WorkloadContainerAdvanced `yaml:"advanced,omitempty" schema:"type:boolean,default:false,show_subquestions_if:true"`
|
||||
Probes WorkloadProbes `yaml:"probes,omitempty"`
|
||||
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
|
||||
}
|
||||
|
||||
// EnvList represents the schema for an environment variable list item in workload settings.
|
||||
type EnvList struct {
|
||||
Name string `yaml:"name,omitempty" schema:"type:string"`
|
||||
Value string `yaml:"value,omitempty" schema:"type:string"`
|
||||
Name string `yaml:"name,omitempty" schema:"type:string"`
|
||||
Value string `yaml:"value,omitempty" schema:"type:string"`
|
||||
}
|
||||
|
||||
// WorkloadContainerAdvanced represents the schema for advanced settings in the workload container settings.
|
||||
type WorkloadContainerAdvanced struct {
|
||||
Command []string `yaml:"command,omitempty" schema:"type:list,default:[],items:type:string"`
|
||||
ExtraSettings map[string]string `yaml:"extraSettings,omitempty" schema:"type:dict"`
|
||||
Command []string `yaml:"command,omitempty" schema:"type:list,default:[],items:type:string"`
|
||||
ExtraSettings map[string]string `yaml:"extraSettings,omitempty" schema:"type:dict"`
|
||||
}
|
||||
|
||||
// WorkloadProbes represents the schema for probes in workload container settings.
|
||||
type WorkloadProbes struct {
|
||||
Liveness Probe `yaml:"liveness,omitempty"`
|
||||
Readiness Probe `yaml:"readiness,omitempty"`
|
||||
Startup Probe `yaml:"startup,omitempty"`
|
||||
Liveness Probe `yaml:"liveness,omitempty"`
|
||||
Readiness Probe `yaml:"readiness,omitempty"`
|
||||
Startup Probe `yaml:"startup,omitempty"`
|
||||
}
|
||||
|
||||
// Probe represents the schema for a probe in workload container settings.
|
||||
type Probe struct {
|
||||
Path string `yaml:"path,omitempty"`
|
||||
Path string `yaml:"path,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
Major = "major"
|
||||
Minor = "minor"
|
||||
Patch = "patch"
|
||||
Major = "major"
|
||||
Minor = "minor"
|
||||
Patch = "patch"
|
||||
)
|
||||
|
||||
func IncrementVersion(version, kind string) (string, error) {
|
||||
// Validate SemVer format
|
||||
semVerPattern := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
|
||||
if !semVerPattern.MatchString(version) {
|
||||
return "", fmt.Errorf("invalid SemVer format (Major.Minor.Patch): %s", version)
|
||||
}
|
||||
// Validate SemVer format
|
||||
semVerPattern := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
|
||||
if !semVerPattern.MatchString(version) {
|
||||
return "", fmt.Errorf("invalid SemVer format (Major.Minor.Patch): %s", version)
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Increment the specified version component
|
||||
switch kind {
|
||||
case Major:
|
||||
return v.IncMajor().String(), nil
|
||||
case Minor:
|
||||
return v.IncMinor().String(), nil
|
||||
case Patch:
|
||||
return v.IncPatch().String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid bump kind: %s", kind)
|
||||
}
|
||||
// Increment the specified version component
|
||||
switch kind {
|
||||
case Major:
|
||||
return v.IncMajor().String(), nil
|
||||
case Minor:
|
||||
return v.IncMinor().String(), nil
|
||||
case Patch:
|
||||
return v.IncPatch().String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid bump kind: %s", kind)
|
||||
}
|
||||
}
|
||||
|
||||
func Bump(semVer, kind string) error {
|
||||
|
||||
newVersion, err := IncrementVersion(semVer, kind)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to increment version")
|
||||
}
|
||||
newVersion, err := IncrementVersion(semVer, kind)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to increment version")
|
||||
}
|
||||
|
||||
log.Info().Msgf("🆚 Updated SemVer from [%s] to [%s]", semVer, newVersion)
|
||||
return nil
|
||||
log.Info().Msgf("🆚 Updated SemVer from [%s] to [%s]", semVer, newVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,76 +3,76 @@ package version
|
||||
import "testing"
|
||||
|
||||
func TestIncrementVersion(t *testing.T) {
|
||||
type args struct {
|
||||
version string
|
||||
kind string
|
||||
}
|
||||
type args struct {
|
||||
version string
|
||||
kind string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test increment major",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Major,
|
||||
},
|
||||
want: "2.0.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment minor",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Minor,
|
||||
},
|
||||
want: "1.3.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment patch",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Patch,
|
||||
},
|
||||
want: "1.2.4",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment invalid",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: "invalid",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test increment incomplete",
|
||||
args: args{
|
||||
version: "1.2",
|
||||
kind: "invalid",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test increment major",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Major,
|
||||
},
|
||||
want: "2.0.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment minor",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Minor,
|
||||
},
|
||||
want: "1.3.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment patch",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: Patch,
|
||||
},
|
||||
want: "1.2.4",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test increment invalid",
|
||||
args: args{
|
||||
version: "1.2.3",
|
||||
kind: "invalid",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test increment incomplete",
|
||||
args: args{
|
||||
version: "1.2",
|
||||
kind: "invalid",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
got, err := IncrementVersion(tt.args.version, tt.args.kind)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("IncrementVersion() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("IncrementVersion() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
got, err := IncrementVersion(tt.args.version, tt.args.kind)
|
||||
// If we expected an error, but didn't get one, fail the test
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("IncrementVersion() error = %v, wantErr %t", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("IncrementVersion() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
package website
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/chartFile"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
"github.com/truecharts/public/clustertool/pkg/charts/chartFile"
|
||||
"github.com/truecharts/public/clustertool/pkg/helper"
|
||||
)
|
||||
|
||||
type ChartList struct {
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
Trains []Train `json:"trains"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
Trains []Train `json:"trains"`
|
||||
}
|
||||
|
||||
type Train struct {
|
||||
Name string `json:"name"`
|
||||
Count int64 `json:"count"`
|
||||
Charts []Chart `json:"charts"`
|
||||
Name string `json:"name"`
|
||||
Count int64 `json:"count"`
|
||||
Charts []Chart `json:"charts"`
|
||||
}
|
||||
|
||||
type Chart struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Train string `json:"train"`
|
||||
Link string `json:"link"`
|
||||
Icon string `json:"icon"`
|
||||
Version string `json:"version"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Train string `json:"train"`
|
||||
Link string `json:"link"`
|
||||
Icon string `json:"icon"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type ChartListOptions struct {
|
||||
OutputPath string // Path to put the chart list json file
|
||||
TrainFilter []string // Empty means all trains
|
||||
sync.Mutex
|
||||
list *ChartList
|
||||
OutputPath string // Path to put the chart list json file
|
||||
TrainFilter []string // Empty means all trains
|
||||
sync.Mutex
|
||||
list *ChartList
|
||||
}
|
||||
|
||||
func (o *ChartListOptions) WriteChartList() error {
|
||||
if o.list == nil {
|
||||
return fmt.Errorf("chart list is nil")
|
||||
}
|
||||
if o.list == nil {
|
||||
return fmt.Errorf("chart list is nil")
|
||||
}
|
||||
|
||||
data, err := json.Marshal(o.list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.Marshal(o.list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(o.OutputPath, data, 0644)
|
||||
return os.WriteFile(o.OutputPath, data, 0644)
|
||||
}
|
||||
func (o *ChartListOptions) GetChartData(path string, entry os.DirEntry, err error) error {
|
||||
if o.list == nil {
|
||||
o.list = &ChartList{}
|
||||
}
|
||||
if o.list == nil {
|
||||
o.list = &ChartList{}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip directories that are excluded
|
||||
if entry.IsDir() && slices.Contains(helper.ExcludedDirs, entry.Name()) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// Skip directories that are excluded
|
||||
if entry.IsDir() && slices.Contains(helper.ExcludedDirs, entry.Name()) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if entry.Name() != "Chart.yaml" {
|
||||
return nil
|
||||
}
|
||||
if entry.Name() != "Chart.yaml" {
|
||||
return nil
|
||||
}
|
||||
|
||||
chart := chartFile.NewHelmChart()
|
||||
if err := chart.LoadFromFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
chart := chartFile.NewHelmChart()
|
||||
if err := chart.LoadFromFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
train := chartFile.GetTrain(path, chart)
|
||||
if len(o.TrainFilter) > 0 {
|
||||
if !slices.Contains(o.TrainFilter, train) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
train := chartFile.GetTrain(path, chart)
|
||||
if len(o.TrainFilter) > 0 {
|
||||
if !slices.Contains(o.TrainFilter, train) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
o.Lock()
|
||||
defer o.Unlock()
|
||||
// Increment the total count
|
||||
o.list.TotalCount++
|
||||
webChart := Chart{
|
||||
Name: chart.Metadata.Name,
|
||||
Description: chart.Metadata.Description,
|
||||
Icon: chart.Metadata.Icon,
|
||||
Link: chart.Metadata.Home,
|
||||
Version: chart.Metadata.Version,
|
||||
Train: chartFile.GetTrain(path, chart),
|
||||
}
|
||||
o.Lock()
|
||||
defer o.Unlock()
|
||||
// Increment the total count
|
||||
o.list.TotalCount++
|
||||
webChart := Chart{
|
||||
Name: chart.Metadata.Name,
|
||||
Description: chart.Metadata.Description,
|
||||
Icon: chart.Metadata.Icon,
|
||||
Link: chart.Metadata.Home,
|
||||
Version: chart.Metadata.Version,
|
||||
Train: chartFile.GetTrain(path, chart),
|
||||
}
|
||||
|
||||
trainExists := false
|
||||
for idx, train := range o.list.Trains {
|
||||
if train.Name == webChart.Train {
|
||||
trainExists = true
|
||||
// Increase chart count for the existing train
|
||||
o.list.Trains[idx].Count++
|
||||
// Add the chart to the existing train
|
||||
o.list.Trains[idx].Charts = append(o.list.Trains[idx].Charts, webChart)
|
||||
}
|
||||
}
|
||||
if trainExists {
|
||||
return nil
|
||||
}
|
||||
trainExists := false
|
||||
for idx, train := range o.list.Trains {
|
||||
if train.Name == webChart.Train {
|
||||
trainExists = true
|
||||
// Increase chart count for the existing train
|
||||
o.list.Trains[idx].Count++
|
||||
// Add the chart to the existing train
|
||||
o.list.Trains[idx].Charts = append(o.list.Trains[idx].Charts, webChart)
|
||||
}
|
||||
}
|
||||
if trainExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a new train with the chart
|
||||
o.list.Trains = append(o.list.Trains, Train{
|
||||
Name: webChart.Train,
|
||||
Count: 1,
|
||||
Charts: []Chart{webChart},
|
||||
})
|
||||
// Add a new train with the chart
|
||||
o.list.Trains = append(o.list.Trains, Train{
|
||||
Name: webChart.Train,
|
||||
Count: 1,
|
||||
Charts: []Chart{webChart},
|
||||
})
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user