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>
|
||||
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"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
|
||||
var convertCmd = &cobra.Command{
|
||||
Use: "convert",
|
||||
|
|
@ -18,10 +128,186 @@ var convertCmd = &cobra.Command{
|
|||
a set of .yaml files describing argocd Applications that are built from the
|
||||
releases section.`,
|
||||
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() {
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
|
||||
|
|
@ -34,4 +320,14 @@ func init() {
|
|||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// 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