Working but no recursion
This commit is contained in:
parent
80d7bccbdd
commit
42fe353444
1 changed files with 298 additions and 2 deletions
300
cmd/convert.go
300
cmd/convert.go
|
|
@ -1,15 +1,125 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
|
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/yookoala/realpath"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"internal/fileflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type helmfileStructure struct {
|
||||||
|
Repositories []helmfileRepository `json:"repositories"`
|
||||||
|
Releases []helmfileHelmRelease `json:"releases"`
|
||||||
|
HelmFiles []helmfileHemfileRef `json:"helmfiles"`
|
||||||
|
Environments map[string]interface{} `json:"environments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type helmfileRepository struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type helmfileHelmRelease struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Installed bool `json:"installed"`
|
||||||
|
Labels string `json:"labels"`
|
||||||
|
Chart string `json:"chart"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Values []map[string]interface{} `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type helmfileHemfileRef struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Values []map[string]interface{} `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoProjectMeta struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoProjectDestination struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Server string `json:"server"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoResourceWhitelistItem struct {
|
||||||
|
Group string `json:"group"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoProjectSpec struct {
|
||||||
|
SourceRepos []string `json:"sourceRepos"`
|
||||||
|
Destinations []argoProjectDestination `json:"Destinations"`
|
||||||
|
ClusterResourceWhitelist []argoResourceWhitelistItem `json:"clusterResourceWhitelist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoProject struct {
|
||||||
|
ApiVersion string `json:"apiVersion"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Metadata argoProjectMeta `json:"metadata"`
|
||||||
|
Spec argoProjectSpec `json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoAppMeta struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoAppSpecSourceHelm struct {
|
||||||
|
ReleaseName string `releaseName`
|
||||||
|
ValuesObject map[string]interface{} `valuesObject`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoAppSpecSource struct {
|
||||||
|
Chart string `json:"chart"`
|
||||||
|
RepoURL string `json:"repoURL"`
|
||||||
|
targetRevision string `json:"targetRevision"`
|
||||||
|
Helm argoAppSpecSourceHelm `json:"helm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoAppSpecDestination struct {
|
||||||
|
Server string `json:"server"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoAppSpec struct {
|
||||||
|
Project string `json:"project"`
|
||||||
|
Source argoAppSpecSource `json:"source"`
|
||||||
|
Destination argoAppSpecDestination `json: "destination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type argoApp struct {
|
||||||
|
ApiVersion string `json:"apiVersion"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Metadata argoAppMeta `json:"metadata"`
|
||||||
|
Spec argoAppSpec `json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var flagInputYaml fileflag.InputFileFlag = "helmfile.yaml"
|
||||||
|
var singleFile bool = true
|
||||||
|
var outputDir string = "-" //TODO: actually handle files
|
||||||
|
var appNamespace string = "argocd"
|
||||||
|
var projectName string = "helmfile-imported"
|
||||||
|
var createProject bool = true
|
||||||
|
var serverURL string = "https://kubernetes.default.svc"
|
||||||
|
var recursiveConvert bool = false
|
||||||
|
var loadEnvironmentValues string = ""
|
||||||
|
|
||||||
|
var reposFromHelmChart []helmfileRepository
|
||||||
|
var environmentValues map[string]interface{}
|
||||||
|
|
||||||
// convertCmd represents the convert command
|
// convertCmd represents the convert command
|
||||||
var convertCmd = &cobra.Command{
|
var convertCmd = &cobra.Command{
|
||||||
Use: "convert",
|
Use: "convert",
|
||||||
|
|
@ -18,10 +128,186 @@ var convertCmd = &cobra.Command{
|
||||||
a set of .yaml files describing argocd Applications that are built from the
|
a set of .yaml files describing argocd Applications that are built from the
|
||||||
releases section.`,
|
releases section.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Println("convert called")
|
helmfileContents := parseHelmfile(string(flagInputYaml))
|
||||||
|
fmt.Println("converting file:", flagInputYaml)
|
||||||
|
|
||||||
|
if (loadEnvironmentValues != "") {
|
||||||
|
environmentMap, ok := helmfileContents.Environments[loadEnvironmentValues].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("Error, no enviroments defined in helmfile, but environment passed in command.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
var valueFilesMap = environmentMap["values"].([]interface{})
|
||||||
|
var valueFiles []string
|
||||||
|
for i := range valueFilesMap {
|
||||||
|
valueFiles = append(valueFiles, valueFilesMap[i].(string))
|
||||||
|
}
|
||||||
|
environmentValues = fetchEnvironmentValues(valueFiles)
|
||||||
|
}
|
||||||
|
if createProject && (len(helmfileContents.Repositories) > 0) {
|
||||||
|
outputProject := baseProject(projectName)
|
||||||
|
for i := 0; i <= len(helmfileContents.Repositories)-1; i++ {
|
||||||
|
outputProject.Spec.SourceRepos = append(outputProject.Spec.SourceRepos, helmfileContents.Repositories[i].Url)
|
||||||
|
}
|
||||||
|
outputProjectYaml, _ := yaml.Marshal(outputProject)
|
||||||
|
outputYamlFile(outputProjectYaml)
|
||||||
|
}
|
||||||
|
reposFromHelmChart = helmfileContents.Repositories
|
||||||
|
if len(helmfileContents.Releases) > 0 {
|
||||||
|
for i := 0; i <= len(helmfileContents.Releases)-1; i++ {
|
||||||
|
thisRelease := helmfileContents.Releases[i]
|
||||||
|
outputApp := baseArgoApp(thisRelease.Name)
|
||||||
|
outputApp.Spec.Project = projectName
|
||||||
|
if thisRelease.Chart[0:4] == "http" {
|
||||||
|
outputApp.Spec.Source.Chart = "UNSUPPORTED_BY_ARGO"
|
||||||
|
outputApp.Spec.Source.RepoURL = thisRelease.Chart
|
||||||
|
} else {
|
||||||
|
var chartmatcher = regexp.MustCompile(`(^[^/]*)/(.*)`)
|
||||||
|
chart := chartmatcher.ReplaceAllString(thisRelease.Chart, `$2`)
|
||||||
|
repo := chartmatcher.ReplaceAllString(thisRelease.Chart, `$1`)
|
||||||
|
outputApp.Spec.Source.Chart = chart
|
||||||
|
var err error
|
||||||
|
err, outputApp.Spec.Source.RepoURL = repoURLbyName(repo)
|
||||||
|
if err != nil {
|
||||||
|
outputApp.Spec.Source.RepoURL = "UNDEFINED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputApp.Spec.Source.Helm.ReleaseName = thisRelease.Name
|
||||||
|
if (loadEnvironmentValues != "") {
|
||||||
|
//flattened := flattenHelmfileWeirdValues(thisRelease.Values)
|
||||||
|
if (environmentValues == nil) {
|
||||||
|
outputApp.Spec.Source.Helm.ValuesObject = flattenHelmfileWeirdValues(thisRelease.Values)
|
||||||
|
} else if (thisRelease.Values == nil) {
|
||||||
|
outputApp.Spec.Source.Helm.ValuesObject = environmentValues
|
||||||
|
} else {
|
||||||
|
merged := environmentValues
|
||||||
|
maps.Copy(merged, flattenHelmfileWeirdValues(thisRelease.Values))
|
||||||
|
outputApp.Spec.Source.Helm.ValuesObject = merged
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outputApp.Spec.Source.Helm.ValuesObject = flattenHelmfileWeirdValues(thisRelease.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputApp.Spec.Destination.Server = serverURL
|
||||||
|
outputApp.Spec.Destination.Namespace = thisRelease.Namespace
|
||||||
|
|
||||||
|
outputAppYaml, _ := yaml.Marshal(outputApp)
|
||||||
|
outputYamlFile(outputAppYaml)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flattenHelmfileWeirdValues(hfv []map[string]interface{}) map[string]interface{} {
|
||||||
|
var nv map[string]interface{}
|
||||||
|
for i := range hfv {
|
||||||
|
if i == 0 {
|
||||||
|
nv = hfv[i]
|
||||||
|
} else {
|
||||||
|
maps.Copy(nv,hfv[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nv
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHelmfile(helmfileFilename string) helmfileStructure {
|
||||||
|
|
||||||
|
fmt.Println("parsing file:", flagInputYaml)
|
||||||
|
yamlFile, err := os.ReadFile(helmfileFilename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("yamlFile.Get err #%v ", err)
|
||||||
|
}
|
||||||
|
templatingMatch := regexp.MustCompile(`{{[^{}]*}}`)
|
||||||
|
passOneRemoveTemplating := templatingMatch.ReplaceAllString(string(yamlFile), `UNDEFINED`)
|
||||||
|
passTwoRemoveTemplating := templatingMatch.ReplaceAllString(passOneRemoveTemplating, `UNDEFINED`)
|
||||||
|
multidocMatch := regexp.MustCompile(`\n---\n`)
|
||||||
|
passThreeRemoveMultiDoc := multidocMatch.ReplaceAllString(passTwoRemoveTemplating, "\n")
|
||||||
|
helmfileContents := &helmfileStructure{}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal([]byte(passThreeRemoveMultiDoc), helmfileContents)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
return *helmfileContents
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchEnvironmentValues(files []string) map[string]interface{} {
|
||||||
|
var allValues map[string]interface{}
|
||||||
|
for i := 0; i <= len(files)-1; i++ {
|
||||||
|
absolutePath, err := realpath.Realpath(string(flagInputYaml))
|
||||||
|
basePath := filepath.Dir(absolutePath)
|
||||||
|
finalPath, err := realpath.Realpath(basePath + string(os.PathSeparator) + files[i])
|
||||||
|
fmt.Print("Loading values from ")
|
||||||
|
fmt.Println(finalPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
yamlFile, err := os.ReadFile(finalPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var thisFilesValues map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(yamlFile, &thisFilesValues); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(thisFilesValues)
|
||||||
|
if i == 0 {
|
||||||
|
allValues = thisFilesValues
|
||||||
|
} else {
|
||||||
|
maps.Copy(allValues, thisFilesValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(allValues)
|
||||||
|
return allValues
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchRepos(helmfileFilename string) []helmfileRepository {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoURLbyName(repoName string) (error, string) {
|
||||||
|
for i := 0; i <= len(reposFromHelmChart)-1; i++ {
|
||||||
|
if reposFromHelmChart[i].Name == repoName {
|
||||||
|
return nil, reposFromHelmChart[i].Url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("No repo by that name found!"), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputString(data string, filename string) {
|
||||||
|
if filename == "-" {
|
||||||
|
fmt.Println(data)
|
||||||
|
} else {
|
||||||
|
fmt.Println(filename + " is not -")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputYamlFile(bytes []byte) {
|
||||||
|
if singleFile || (string(outputDir) == "-") {
|
||||||
|
outputString("---", string(outputDir))
|
||||||
|
}
|
||||||
|
outputString(string(bytes), string(outputDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseProject(name string) argoProject {
|
||||||
|
outputProject := argoProject{}
|
||||||
|
outputProject.ApiVersion = "argoproj.io/v1alpha1"
|
||||||
|
outputProject.Kind = "AppProject"
|
||||||
|
outputProject.Spec.Destinations = append(outputProject.Spec.Destinations, argoProjectDestination{Namespace: "*", Server: "*"})
|
||||||
|
outputProject.Spec.ClusterResourceWhitelist = append(outputProject.Spec.ClusterResourceWhitelist, argoResourceWhitelistItem{Group: "*", Kind: "*"})
|
||||||
|
outputProject.Metadata.Name = name
|
||||||
|
return outputProject
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseArgoApp(name string) argoApp {
|
||||||
|
outputApp := argoApp{}
|
||||||
|
outputApp.ApiVersion = "argoproj.io/v1alpha1"
|
||||||
|
outputApp.Kind = "Application"
|
||||||
|
outputApp.Metadata.Namespace = appNamespace
|
||||||
|
outputApp.Metadata.Name = name
|
||||||
|
return outputApp
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(convertCmd)
|
rootCmd.AddCommand(convertCmd)
|
||||||
|
|
||||||
|
|
@ -34,4 +320,14 @@ func init() {
|
||||||
// Cobra supports local flags which will only run when this command
|
// Cobra supports local flags which will only run when this command
|
||||||
// is called directly, e.g.:
|
// is called directly, e.g.:
|
||||||
// convertCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
// convertCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
convertCmd.Flags().VarP(&flagInputYaml, "input", "i", `helmfile to convert`)
|
||||||
|
convertCmd.Flags().BoolVarP(&singleFile, "single-file", "1", true, `single output file (always true if output is "-")`)
|
||||||
|
convertCmd.Flags().StringVarP(&outputDir, "output", "o", "-", `output folder or "-" for stdout`)
|
||||||
|
convertCmd.Flags().StringVarP(&appNamespace, "appnamespace", "a", "argocd", `namespace for application objects`)
|
||||||
|
convertCmd.Flags().StringVarP(&projectName, "projectname", "p", "helmfile-imported", `project name for all apps`)
|
||||||
|
convertCmd.Flags().BoolVarP(&createProject, "create-project", "c", true, `create project with above projectname and all repositories from helmfile`)
|
||||||
|
convertCmd.Flags().StringVarP(&serverURL, "server-name", "n", "https://kubernetes.default.svc", `spec.destination.server value in the application objects`)
|
||||||
|
convertCmd.Flags().BoolVarP(&recursiveConvert, "recursive", "r", false, `recurse into sub helmfiles`)
|
||||||
|
convertCmd.Flags().StringVarP(&loadEnvironmentValues, "load-environment-values", "e", "", `set to an environment name to load environment-based values in the top-level helmfile`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue