Enhancements, see details

- Added a message to explain to the user that it needs to build and push
  images
- The Release Name string is now a constant ins source code, later we will be able to
  change it to (for example) a template call
- SHA256 injected label (from docker-compose file content) is a bit long, SHA1 is shorter
- We now add compose command line and generation date in the Values file
- Code cleanup, refactorisation and enhancements
- More documentation in the source code
This commit is contained in:
2021-12-05 09:05:48 +01:00
parent 714ccf771d
commit fe2a655796
11 changed files with 73 additions and 34 deletions

View File

@@ -10,6 +10,10 @@ import (
"gopkg.in/yaml.v3"
)
const (
ICON_EXCLAMATION = "❕"
)
// Parser is a docker-compose parser.
type Parser struct {
Data *Compose
@@ -72,6 +76,18 @@ func NewParser(filename string) *Parser {
log.Fatal(strings.Join(missing, "\n"))
}
// check the build element
for name, s := range c.Services {
if s.RawBuild == nil {
continue
}
fmt.Println(ICON_EXCLAMATION +
" \x1b[33myou will need to build and push your image named \"" + s.Image + "\"" +
" for the \"" + name + "\" service \x1b[0m")
}
return p
}

View File

@@ -25,4 +25,5 @@ type Service struct {
Volumes []string `yaml:"volumes"`
Expose []int `yaml:"expose"`
EnvFiles []string `yaml:"env_file"`
RawBuild interface{} `yaml:"build"`
}

View File

@@ -29,6 +29,10 @@ const (
ICON_INGRESS = "🌐"
)
const (
RELEASE_NAME = helm.RELEASE_NAME
)
// Values is kept in memory to create a values.yaml file.
var Values = make(map[string]map[string]interface{})
var VolumeValues = make(map[string]map[string]map[string]interface{})
@@ -38,7 +42,7 @@ OK=0
echo "Checking __service__ port"
while [ $OK != 1 ]; do
echo -n "."
nc -z {{ .Release.Name }}-__service__ __port__ && OK=1
nc -z ` + RELEASE_NAME + `-__service__ __port__ && OK=1
sleep 1
done
echo
@@ -172,7 +176,7 @@ func parseService(name string, s *compose.Service, ret chan interface{}) {
cm := buildCMFromPath(volname)
volname = strings.Replace(volname, "./", "", 1)
volname = strings.ReplaceAll(volname, ".", "-")
cm.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + volname + "-" + name
cm.K8sBase.Metadata.Name = RELEASE_NAME + "-" + volname + "-" + name
// build a configmap from the volume path
volumes = append(volumes, map[string]interface{}{
"name": volname,
@@ -191,7 +195,7 @@ func parseService(name string, s *compose.Service, ret chan interface{}) {
volumes = append(volumes, map[string]interface{}{
"name": volname,
"persistentVolumeClaim": map[string]string{
"claimName": "{{ .Release.Name }}-" + volname,
"claimName": RELEASE_NAME + "-" + volname,
},
})
mountPoints = append(mountPoints, map[string]interface{}{
@@ -324,7 +328,7 @@ func createService(name string, s *compose.Service) []interface{} {
if v, ok := s.Labels[helm.LABEL_INGRESS]; ok {
port, err := strconv.Atoi(v)
if err != nil {
log.Fatalf("The given port \"%v\" as ingress port in %s service is not an integer\n", v, name)
log.Fatalf("The given port \"%v\" as ingress port in \"%s\" service is not an integer\n", v, name)
}
Cyanf(ICON_INGRESS+" Create an ingress for port %d on %s service\n", port, name)
ing := createIngress(name, port, s)
@@ -362,7 +366,7 @@ func createIngress(name string, port int, s *compose.Service) *helm.Ingress {
PathType: "Prefix",
Backend: helm.IngressBackend{
Service: helm.IngressService{
Name: "{{ .Release.Name }}-" + name,
Name: RELEASE_NAME + "-" + name,
Port: map[string]interface{}{
"number": port,
},
@@ -419,7 +423,7 @@ func waitPort(name string) chan int {
func buildSelector(name string, s *compose.Service) map[string]string {
return map[string]string{
"katenary.io/component": name,
"katenary.io/release": "{{ .Release.Name }}",
"katenary.io/release": RELEASE_NAME,
}
}

View File

@@ -22,7 +22,7 @@ func NewConfigMap(name string) *ConfigMap {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "ConfigMap"
base.Metadata.Name = "{{ .Release.Name }}-" + name
base.Metadata.Name = RELEASE_NAME + "-" + name
base.Metadata.Labels[K+"/component"] = name
return &ConfigMap{
K8sBase: base,
@@ -66,7 +66,7 @@ func NewSecret(name string) *Secret {
base := NewBase()
base.ApiVersion = "v1"
base.Kind = "Secret"
base.Metadata.Name = "{{ .Release.Name }}-" + name
base.Metadata.Name = RELEASE_NAME + "-" + name
base.Metadata.Labels[K+"/component"] = name
return &Secret{
K8sBase: base,

View File

@@ -10,7 +10,7 @@ type Deployment struct {
func NewDeployment(name string) *Deployment {
d := &Deployment{K8sBase: NewBase(), Spec: NewDepSpec()}
d.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
d.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
d.K8sBase.ApiVersion = "apps/v1"
d.K8sBase.Kind = "Deployment"
d.K8sBase.Metadata.Labels[K+"/component"] = name
@@ -67,7 +67,7 @@ func NewContainer(name, image string, environment, labels map[string]string) *Co
for n, v := range environment {
for _, name := range toServices {
if name == n {
v = "{{ .Release.Name }}-" + v
v = RELEASE_NAME + "-" + v
}
}
container.Env[idx] = Value{Name: n, Value: v}

View File

@@ -8,7 +8,7 @@ type Ingress struct {
func NewIngress(name string) *Ingress {
i := &Ingress{}
i.K8sBase = NewBase()
i.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
i.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
i.K8sBase.Kind = "Ingress"
i.ApiVersion = "networking.k8s.io/v1"
i.K8sBase.Metadata.Labels[K+"/component"] = name

View File

@@ -10,7 +10,7 @@ Your application is now deployed. This may take a while to be up and responding.
__list__
`
func GenNotes(ingressess map[string]*Ingress) string {
func GenerateNotesFile(ingressess map[string]*Ingress) string {
list := make([]string, 0)

View File

@@ -10,7 +10,7 @@ func NewService(name string) *Service {
K8sBase: NewBase(),
Spec: NewServiceSpec(),
}
s.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name
s.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name
s.K8sBase.Kind = "Service"
s.K8sBase.ApiVersion = "v1"
s.K8sBase.Metadata.Labels[K+"/component"] = name

View File

@@ -11,7 +11,7 @@ func NewPVC(name, storageName string) *Storage {
pvc.K8sBase.Kind = "PersistentVolumeClaim"
pvc.K8sBase.Metadata.Labels[K+"/pvc-name"] = storageName
pvc.K8sBase.ApiVersion = "v1"
pvc.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + storageName
pvc.K8sBase.Metadata.Name = RELEASE_NAME + "-" + storageName
pvc.K8sBase.Metadata.Labels[K+"/component"] = name
pvc.Spec = &PVCSpec{
Resouces: map[string]interface{}{

View File

@@ -1,7 +1,7 @@
package helm
import (
"crypto/sha256"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
@@ -9,6 +9,7 @@ import (
)
const K = "katenary.io"
const RELEASE_NAME = "{{ .Release.Name }}"
const (
LABEL_ENV_SECRET = K + "/secret-envfiles"
LABEL_PORT = K + "/ports"
@@ -17,9 +18,10 @@ const (
LABEL_VOL_CM = K + "/configmap-volumes"
)
var Appname = ""
var Version = "1.0" // should be set from main.Version
var (
Appname = ""
Version = "1.0" // should be set from main.Version
)
type Kinded interface {
Get() string
@@ -58,16 +60,18 @@ func NewBase() *K8sBase {
b := &K8sBase{
Metadata: NewMetadata(),
}
// add some information of the build
b.Metadata.Labels[K+"/project"] = GetProjectName()
b.Metadata.Labels[K+"/release"] = "{{ .Release.Name }}"
b.Metadata.Labels[K+"/release"] = RELEASE_NAME
b.Metadata.Annotations[K+"/version"] = Version
return b
}
func (k *K8sBase) BuildSHA(filename string) {
c, _ := ioutil.ReadFile(filename)
sum := sha256.Sum256(c)
k.Metadata.Annotations[K+"/docker-compose-sha256"] = fmt.Sprintf("%x", string(sum[:]))
//sum := sha256.Sum256(c)
sum := sha1.Sum(c)
k.Metadata.Annotations[K+"/docker-compose-sha1"] = fmt.Sprintf("%x", string(sum[:]))
}
func (k *K8sBase) Get() string {

40
main.go
View File

@@ -11,6 +11,7 @@ import (
"path/filepath"
"regexp"
"strings"
"time"
"gopkg.in/yaml.v3"
)
@@ -32,20 +33,24 @@ func main() {
force := flag.Bool("force", false, "force the removal of the chart-dir")
flag.Parse()
// Only display the version
if *version {
fmt.Println(Version)
os.Exit(0)
}
// make the appname global (yes...)
// make the appname global (yes... ugly but easy)
helm.Appname = AppName
dirname := filepath.Join(ChartsDir, AppName)
helm.Version = Version
dirname := filepath.Join(ChartsDir, AppName)
if _, err := os.Stat(dirname); err == nil && !*force {
response := ""
for response != "y" && response != "n" {
response = "n"
fmt.Printf("The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\nDo you really want to continue ? [y/N]: ", dirname)
fmt.Printf(""+
"The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+
"Do you really want to continue ? [y/N]: ", dirname)
fmt.Scanf("%s", &response)
response = strings.ToLower(response)
}
@@ -55,29 +60,22 @@ func main() {
}
}
// cleanup and create the chart directory (until "templates")
os.RemoveAll(dirname)
templatesDir := filepath.Join(dirname, "templates")
os.MkdirAll(templatesDir, 0755)
helm.Version = Version
// Parse the compose file now
p := compose.NewParser(ComposeFile)
p.Parse(AppName)
files := make(map[string]chan interface{})
//wait := sync.WaitGroup{}
for name, s := range p.Data.Services {
//wait.Add(1)
// it's mandatory to build in goroutines because some dependencies can
// wait for a port number discovery.
// So the entire services are built in parallel.
//go func(name string, s compose.Service) {
// defer wait.Done()
o := generator.CreateReplicaObject(name, s)
files[name] = o
//}(name, s)
}
//wait.Wait()
// to generate notes, we need to keep an Ingresses list
ingresses := make(map[string]*helm.Ingress)
@@ -89,9 +87,16 @@ func main() {
}
kind := c.(helm.Kinded).Get()
kind = strings.ToLower(kind)
// Add a SHA inside the generated file, it's only
// to make it easy to check it the compose file corresponds to the
// generated helm chart
c.(helm.Signable).BuildSHA(ComposeFile)
// Some types need special fixes in yaml generation
switch c := c.(type) {
case *helm.Storage:
// For storage, we need to add a "condition" to activate it
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
fp, _ := os.Create(fname)
volname := c.K8sBase.Metadata.Labels[helm.K+"/pvc-name"]
@@ -101,6 +106,8 @@ func main() {
enc.Encode(c)
fp.WriteString("{{- end -}}")
case *helm.Deployment:
// for the deployment, we need to fix persitence volumes to be activated
// only when the storage is "enabled", either we use an "emptyDir"
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
fp, _ := os.Create(fname)
buffer := bytes.NewBuffer(nil)
@@ -127,6 +134,7 @@ func main() {
fp.Close()
case *helm.Service:
// Change the type for service if it's an "exposed" port
suffix := ""
if c.Spec.Type == "NodePort" {
suffix = "-external"
@@ -139,6 +147,7 @@ func main() {
fp.Close()
case *helm.Ingress:
// we need to make ingresses "activable" from values
buffer := bytes.NewBuffer(nil)
fname := filepath.Join(templatesDir, n+"."+kind+".yaml")
ingresses[n] = c // keep it to generate notes
@@ -188,13 +197,17 @@ func main() {
}
}
// Create the values.yaml file
fp, _ := os.Create(filepath.Join(dirname, "values.yaml"))
enc := yaml.NewEncoder(fp)
enc.SetIndent(2)
enc.Encode(generator.Values)
fp.Close()
// Create tht Chart.yaml file
fp, _ = os.Create(filepath.Join(dirname, "Chart.yaml"))
fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n")
fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n")
enc = yaml.NewEncoder(fp)
enc.SetIndent(2)
enc.Encode(map[string]interface{}{
@@ -207,7 +220,8 @@ func main() {
})
fp.Close()
// And finally, create a NOTE.txt file
fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt"))
fp.WriteString(helm.GenNotes(ingresses))
fp.WriteString(helm.GenerateNotesFile(ingresses))
fp.Close()
}