246 lines
6.4 KiB
Go
246 lines
6.4 KiB
Go
package helm
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"text/template"
|
|
|
|
"freeleaps.com/gitops/initializer/api/v1alpha1"
|
|
)
|
|
|
|
//go:embed templates/*
|
|
var embeddedTemplates embed.FS
|
|
|
|
type HelmGenerator struct {
|
|
io.Closer
|
|
Instance *v1alpha1.ProjectInitialize
|
|
workingDir string
|
|
generated bool
|
|
}
|
|
|
|
const leftDelim = "[["
|
|
const rightDelim = "]]"
|
|
|
|
func (hg *HelmGenerator) prepareWorkingDir() error {
|
|
path, err := os.MkdirTemp("", fmt.Sprintf("helm-gen-%s-*", hg.Instance.Name))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create helm generator temp dir: %w", err)
|
|
}
|
|
hg.workingDir = path
|
|
return nil
|
|
}
|
|
|
|
func (hg *HelmGenerator) readTemplate(name string) (*template.Template, error) {
|
|
tpl := template.New(name)
|
|
templateBytes, err := embeddedTemplates.ReadFile(name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read template %s: %w", name, err)
|
|
}
|
|
tpl, err = tpl.
|
|
Delims(leftDelim, rightDelim).
|
|
Funcs(funcMap).
|
|
Parse(string(templateBytes))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse template %s: %w", name, err)
|
|
}
|
|
return tpl, nil
|
|
}
|
|
|
|
func (hg *HelmGenerator) createMetaFiles() error {
|
|
// create Chart.yaml
|
|
tpl, err := hg.readTemplate("templates/metadata/Chart.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chartYamlFd, err := os.Create(hg.workingDir + "/Chart.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create Chart.yaml file: %w", err)
|
|
}
|
|
defer chartYamlFd.Close()
|
|
if err := tpl.Execute(chartYamlFd, hg.Instance.Spec.Helm); err != nil {
|
|
return fmt.Errorf("failed to rendering Chart.yaml with template: %w", err)
|
|
}
|
|
|
|
// create values.yaml
|
|
tpl, err = hg.readTemplate("templates/metadata/values.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
valuesYamlFd, err := os.Create(hg.workingDir + "/values.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create values.yaml file: %w", err)
|
|
}
|
|
defer valuesYamlFd.Close()
|
|
if err := tpl.Execute(valuesYamlFd, hg.Instance.Spec.Helm); err != nil {
|
|
return fmt.Errorf("failed to rendering values.yaml with template: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (hg *HelmGenerator) createComponentManifests(component v1alpha1.HelmComponentSpec) error {
|
|
workRoot := hg.workingDir + "/templates/" + component.Name
|
|
if err := os.MkdirAll(workRoot, 0755); err != nil {
|
|
return fmt.Errorf("failed to create component directory: %w", err)
|
|
}
|
|
|
|
// create deployment.yaml
|
|
tpl, err := hg.readTemplate("templates/deployment/deployment.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deploymentYamlFd, err := os.Create(workRoot + "/deployment.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create deployment.yaml file: %w", err)
|
|
}
|
|
defer deploymentYamlFd.Close()
|
|
if err := tpl.Execute(deploymentYamlFd, component); err != nil {
|
|
return fmt.Errorf("failed to rendering deployment.yaml with template: %w", err)
|
|
}
|
|
|
|
// create service.yaml if there has services
|
|
if len(component.Services) > 0 {
|
|
tpl, err = hg.readTemplate("templates/service/service.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serviceYamlFd, err := os.Create(workRoot + "/service.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create service.yaml file: %w", err)
|
|
}
|
|
defer serviceYamlFd.Close()
|
|
if err := tpl.Execute(serviceYamlFd, component); err != nil {
|
|
return fmt.Errorf("failed to rendering service.yaml with template: %w", err)
|
|
}
|
|
}
|
|
|
|
// create ingress.yaml if there has ingress
|
|
if len(component.Ingresses) > 0 {
|
|
tpl, err = hg.readTemplate("templates/ingress/ingress.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ingressYamlFd, err := os.Create(workRoot + "/ingress.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create ingress.yaml file: %w", err)
|
|
}
|
|
defer ingressYamlFd.Close()
|
|
if err := tpl.Execute(ingressYamlFd, component); err != nil {
|
|
return fmt.Errorf("failed to rendering ingress.yaml with template: %w", err)
|
|
}
|
|
}
|
|
|
|
// create secret.yaml if there has configs
|
|
if len(component.Configs) > 0 {
|
|
for _, config := range component.Configs {
|
|
tpl, err = hg.readTemplate("templates/secret/secret.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
secretYamlFd, err := os.Create(workRoot + "/" + config.Name + ".yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create secret.yaml file: %w", err)
|
|
}
|
|
defer secretYamlFd.Close()
|
|
if err := tpl.Execute(secretYamlFd, struct {
|
|
ComponentName string
|
|
Config v1alpha1.HelmComponentConfigSpec
|
|
}{
|
|
ComponentName: component.Name,
|
|
Config: config,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to rendering secret.yaml with template: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// create certificate if ingress need to sign with tls
|
|
if len(component.Ingresses) > 0 {
|
|
needSign := false
|
|
for _, ingress := range component.Ingresses {
|
|
if !ingress.TLS.Exists {
|
|
needSign = true
|
|
break
|
|
}
|
|
}
|
|
if needSign {
|
|
tpl, err = hg.readTemplate("templates/certificate/certificate.yaml.tpl")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
certificateYamlFd, err := os.Create(workRoot + "/certificate.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create certificate.yaml file: %w", err)
|
|
}
|
|
defer certificateYamlFd.Close()
|
|
if err := tpl.Execute(certificateYamlFd, component); err != nil {
|
|
return fmt.Errorf("failed to rendering certificate.yaml with template: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (hg *HelmGenerator) Generate() error {
|
|
if hg.Instance == nil {
|
|
return errors.New("instance is nil")
|
|
}
|
|
|
|
// create temp working dir
|
|
if err := hg.prepareWorkingDir(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create meta files (Chart.yaml, values.yaml, .helmignore)
|
|
if err := hg.createMetaFiles(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create manifests for each component
|
|
for _, componentSpec := range hg.Instance.Spec.Helm.Components {
|
|
if err := hg.createComponentManifests(componentSpec); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
hg.generated = true
|
|
return nil
|
|
}
|
|
|
|
type HelmGeneratedFile struct {
|
|
QualifiedPath string
|
|
}
|
|
|
|
func (hgf *HelmGeneratedFile) ReadAll() ([]byte, error) {
|
|
return os.ReadFile(hgf.QualifiedPath)
|
|
}
|
|
|
|
type HelmGeneratedFileCallback func(f *HelmGeneratedFile) error
|
|
|
|
func (hg *HelmGenerator) Walk(cb HelmGeneratedFileCallback) error {
|
|
if !hg.generated {
|
|
return errors.New("helm package not generated")
|
|
}
|
|
|
|
return filepath.Walk(hg.workingDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
return cb(&HelmGeneratedFile{QualifiedPath: path})
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (hg *HelmGenerator) Close() error {
|
|
if hg.workingDir != "" {
|
|
return os.RemoveAll(hg.workingDir)
|
|
}
|
|
return nil
|
|
}
|