blob: 5d2c699a2637c506f2b51ce5af30ae81f8af9ebd [file] [log] [blame]
package main
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
"text/template"
camel "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
"github.com/iancoleman/strcase"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/yaml"
)
func main() {
if len(os.Args) != 3 {
println("usage: generator kamelets-path doc-root")
os.Exit(1)
}
projectBaseDir := os.Args[1]
docBaseDir := os.Args[2]
funcMap := template.FuncMap{
"ToCamel": strcase.ToCamel,
}
templateFile := path.Join(docBaseDir, "kamelet.adoc.tmpl")
kameletBindingFile := path.Join(docBaseDir, "kamelet-binding-sink-source.tmpl")
propertiesListFile := path.Join(docBaseDir, "properties-list.tmpl")
docTemplate, err := template.New("kamelet.adoc.tmpl").Funcs(funcMap).ParseFiles(templateFile, kameletBindingFile, propertiesListFile)
handleGeneralError(fmt.Sprintf("cannot load template file from %s", templateFile), err)
camelKYamlBindingsBaseDir := filepath.Join(projectBaseDir, "templates", "bindings", "camel-k")
yamlTemplateFile := path.Join(camelKYamlBindingsBaseDir, "kamelet.yaml.tmpl")
yamlTemplate, err := template.New("kamelet.yaml.tmpl").Funcs(funcMap).ParseFiles(yamlTemplateFile, kameletBindingFile, propertiesListFile)
handleGeneralError(fmt.Sprintf("cannot load template file from %s", templateFile), err)
coreYamlBindingsBaseDir := filepath.Join(projectBaseDir, "templates", "bindings", "core")
coreYamlTemplateFile := path.Join(coreYamlBindingsBaseDir, "kamelet-core-binding.yaml.tmpl")
parameterListFile := path.Join(coreYamlBindingsBaseDir, "parameter-list.tmpl")
coreYamlTemplate, err := template.New("kamelet-core-binding.yaml.tmpl").Funcs(funcMap).ParseFiles(coreYamlTemplateFile, kameletBindingFile, parameterListFile)
handleGeneralError(fmt.Sprintf("cannot load template file from %s", templateFile), err)
kamelets := listKamelets(projectBaseDir)
links := make([]string, 0)
for _, k := range kamelets {
img := saveImage(k, docBaseDir)
ctx := NewTemplateContext(k, img)
processDocTemplate(k, docBaseDir, err, docTemplate, &ctx)
links = updateImageLink(k, img, links)
processYamlTemplate(k, projectBaseDir, err, yamlTemplate, &ctx)
processCoreYamlTemplate(k, projectBaseDir, err, coreYamlTemplate, &ctx)
}
saveNav(links, docBaseDir)
}
func processDocTemplate(k camel.Kamelet, baseDir string, err error, docTemplate *template.Template, ctx *TemplateContext) {
buffer := new(bytes.Buffer)
err = docTemplate.Execute(buffer, &ctx)
handleGeneralError("cannot process documentation template", err)
produceDocFile(k, baseDir, buffer.String())
}
func processYamlTemplate(k camel.Kamelet, baseDir string, err error, yamlTemplate *template.Template, ctx *TemplateContext) {
buffer := new(bytes.Buffer)
err = yamlTemplate.Execute(buffer, ctx)
handleGeneralError("cannot process yaml binding template", err)
produceBindingFile(k, baseDir, "camel-k", buffer.String())
}
func processCoreYamlTemplate(k camel.Kamelet, baseDir string, err error, yamlTemplate *template.Template, ctx *TemplateContext) {
buffer := new(bytes.Buffer)
err = yamlTemplate.Execute(buffer, ctx)
handleGeneralError("cannot process yaml binding template", err)
produceBindingFile(k, baseDir, "core", buffer.String())
}
func updateImageLink(k camel.Kamelet, img string, links []string) []string {
return append(links, fmt.Sprintf("* xref:ROOT:%s.adoc[%s %s]", k.Name, img, k.Spec.Definition.Title))
}
type TemplateContext struct {
Kamelet camel.Kamelet
Image string
TemplateProperties map[string]string
}
func NewTemplateContext(kamelet camel.Kamelet, image string) TemplateContext {
return TemplateContext{
Kamelet: kamelet,
Image: image,
TemplateProperties: map[string]string{},
}
}
type Prop struct {
Name string
Title string
Required bool
Default *string
Example *string
}
func (p Prop) GetSampleValue() string {
if p.Default != nil {
return *p.Default
}
if p.Example != nil {
return *p.Example
}
return fmt.Sprintf(`"The %s"`, p.Title)
}
func getSortedProps(k camel.Kamelet) []Prop {
required := make(map[string]bool)
props := make([]Prop, 0, len(k.Spec.Definition.Properties))
for _, r := range k.Spec.Definition.Required {
required[r] = true
}
for key := range k.Spec.Definition.Properties {
prop := k.Spec.Definition.Properties[key]
var def *string
if prop.Default != nil {
b, err := prop.Default.MarshalJSON()
handleGeneralError(fmt.Sprintf("cannot marshal property %q default value in Kamelet %s", key, k.Name), err)
defVal := string(b)
def = &defVal
}
var ex *string
if prop.Example != nil {
b, err := prop.Example.MarshalJSON()
handleGeneralError(fmt.Sprintf("cannot marshal property %q example value in Kamelet %s", key, k.Name), err)
exVal := string(b)
ex = &exVal
}
props = append(props, Prop{Name: key, Title: prop.Title, Required: required[key], Default: def, Example: ex})
}
sort.Slice(props, func(i, j int) bool {
ri := props[i].Required
rj := props[j].Required
if ri && !rj {
return true
} else if !ri && rj {
return false
}
return props[i].Name < props[j].Name
})
return props
}
func (ctx *TemplateContext) HasProperties() bool {
return len(ctx.Kamelet.Spec.Definition.Properties) > 0
}
func (ctx *TemplateContext) HasRequiredProperties() bool {
propDefs := getSortedProps(ctx.Kamelet)
for _, propDef := range propDefs {
if propDef.Required {
return true
}
}
return false
}
func (ctx *TemplateContext) PropertyList() string {
propDefs := getSortedProps(ctx.Kamelet)
sampleConfig := make([]string, 0)
for _, propDef := range propDefs {
if !propDef.Required {
continue
}
key := propDef.Name
if propDef.Default == nil {
ex := propDef.GetSampleValue()
sampleConfig = append(sampleConfig, fmt.Sprintf("%s: %s", key, ex))
}
}
/*
Creates the properties list in the YAML format.
*/
props := ""
if len(sampleConfig) > 0 {
props = fmt.Sprintf("\n %s:\n %s", "properties", strings.Join(sampleConfig, "\n "))
}
return props
}
func (ctx *TemplateContext) ParameterList() string {
tp := ctx.Kamelet.ObjectMeta.Labels["camel.apache.org/kamelet.type"]
propDefs := getSortedProps(ctx.Kamelet)
sampleConfig := make([]string, 0)
for _, propDef := range propDefs {
if !propDef.Required {
continue
}
key := propDef.Name
if propDef.Default == nil {
ex := propDef.GetSampleValue()
sampleConfig = append(sampleConfig, fmt.Sprintf("%s: %s", key, ex))
}
}
props := ""
if len(sampleConfig) > 0 {
paddingSpace := ""
switch tp {
case "sink":
props = fmt.Sprintf("\n%10s%s:\n%12s%s", "", "parameters",
"", strings.Join(sampleConfig, fmt.Sprintf("\n%12s", "")))
case "source":
props = fmt.Sprintf("\n%6s%s:\n%8s%s", "", "parameters",
"", strings.Join(sampleConfig, fmt.Sprintf("\n%8s", "")))
case "action":
props = fmt.Sprintf("\n%10s%s:\n%12s%s", paddingSpace, "parameters",
"", strings.Join(sampleConfig, fmt.Sprintf("\n%8s", "")))
}
}
return props
}
func (ctx *TemplateContext) SetVal(key, val string) string {
ctx.TemplateProperties[key] = val
return ""
}
func (ctx *TemplateContext) GetVal(key string) string {
return ctx.TemplateProperties[key]
}
func (ctx *TemplateContext) Properties() string {
content := ""
if len(ctx.Kamelet.Spec.Definition.Properties) > 0 {
props := getSortedProps(ctx.Kamelet)
content += `[width="100%",cols="2,^2,3,^2,^2,^3",options="header"]` + "\n"
content += "|===\n"
content += tableLine("Property", "Name", "Description", "Type", "Default", "Example")
for _, propDef := range props {
key := propDef.Name
prop := ctx.Kamelet.Spec.Definition.Properties[key]
name := key
if propDef.Required {
name = "*" + name + " {empty}* *"
}
var def string
if propDef.Default != nil {
def = "`" + strings.ReplaceAll(*propDef.Default, "`", "'") + "`"
}
var ex string
if propDef.Example != nil {
ex = "`" + strings.ReplaceAll(*propDef.Example, "`", "'") + "`"
}
content += tableLine(name, prop.Title, prop.Description, prop.Type, def, ex)
}
content += "|===\n"
}
return content
}
func (ctx *TemplateContext) ExampleKamelBindCommand(ref string) string {
tp := ctx.Kamelet.ObjectMeta.Labels["camel.apache.org/kamelet.type"]
var prefix string
switch tp {
case "source":
prefix = "source."
case "sink":
prefix = "sink."
case "action":
prefix = "step-0."
default:
handleGeneralError("unknown kamelet type", errors.New(tp))
}
cmd := "kamel bind "
timer := "timer-source?message=Hello"
kamelet := ctx.Kamelet.Name
propDefs := getSortedProps(ctx.Kamelet)
for _, p := range propDefs {
if p.Required && p.Default == nil {
val := p.GetSampleValue()
if strings.HasPrefix(val, `"`) {
kamelet += fmt.Sprintf(` -p "%s%s=%s`, prefix, p.Name, val[1:])
} else {
kamelet += fmt.Sprintf(" -p %s%s=%s", prefix, p.Name, val)
}
}
}
switch tp {
case "source":
return cmd + kamelet + " " + ref
case "sink":
return cmd + ref + " " + kamelet
case "action":
return cmd + timer + " --step " + kamelet + " " + ref
default:
handleGeneralError("unknown kamelet type", errors.New(tp))
}
return ""
}
func saveNav(links []string, out string) {
content := "// THIS FILE IS AUTOMATICALLY GENERATED: DO NOT EDIT\n"
for _, l := range links {
content += l + "\n"
}
content += "// THIS FILE IS AUTOMATICALLY GENERATED: DO NOT EDIT\n"
dest := filepath.Join(out, "nav.adoc")
if _, err := os.Stat(dest); err == nil {
err = os.Remove(dest)
handleGeneralError(fmt.Sprintf("cannot remove file %q", dest), err)
}
err := ioutil.WriteFile(dest, []byte(content), 0666)
handleGeneralError(fmt.Sprintf("cannot write file %q", dest), err)
fmt.Printf("%q written\n", dest)
}
func saveImage(k camel.Kamelet, out string) string {
if ic, ok := k.ObjectMeta.Annotations["camel.apache.org/kamelet.icon"]; ok {
svgb64Prefix := "data:image/svg+xml;base64,"
if strings.HasPrefix(ic, svgb64Prefix) {
data := ic[len(svgb64Prefix):]
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(data))
iconContent, err := ioutil.ReadAll(decoder)
handleGeneralError(fmt.Sprintf("cannot decode icon from Kamelet %s", k.Name), err)
dest := filepath.Join(out, "assets", "images", "kamelets", fmt.Sprintf("%s.svg", k.Name))
if _, err := os.Stat(dest); err == nil {
err = os.Remove(dest)
handleGeneralError(fmt.Sprintf("cannot remove file %q", dest), err)
}
err = ioutil.WriteFile(dest, iconContent, 0666)
handleGeneralError(fmt.Sprintf("cannot write file %q", dest), err)
fmt.Printf("%q written\n", dest)
return fmt.Sprintf("image:kamelets/%s.svg[]", k.Name)
}
}
return ""
}
func produceDocFile(k camel.Kamelet, baseDir string, content string) {
outputDir := filepath.Join(baseDir, "pages")
produceOutputFile(k, outputDir, content,".adoc")
}
func produceBindingFile(k camel.Kamelet, baseDir string, projectName string, content string) {
camelKOutputDir := filepath.Join(baseDir, "templates", "bindings", projectName)
produceOutputFile(k, camelKOutputDir, content,"-binding.yaml")
}
func produceOutputFile(k camel.Kamelet, outputDir string, content string, extension string) {
outputFile := filepath.Join(outputDir, k.Name + extension)
if _, err := os.Stat(outputFile); err == nil {
err = os.Remove(outputFile)
handleGeneralError(fmt.Sprintf("cannot remove file %q", outputFile), err)
}
err := ioutil.WriteFile(outputFile, []byte(content), 0666)
handleGeneralError(fmt.Sprintf("cannot write to file %q", outputFile), err)
fmt.Printf("%q written\n", outputFile)
}
func tableLine(val ...string) string {
res := ""
for _, s := range val {
clean := strings.ReplaceAll(s, "|", "\\|")
res += "| " + clean
}
return res + "\n"
}
func listKamelets(dir string) []camel.Kamelet {
scheme := runtime.NewScheme()
err := camel.AddToScheme(scheme)
handleGeneralError("cannot to add camel APIs to scheme", err)
codecs := serializer.NewCodecFactory(scheme)
gv := camel.SchemeGroupVersion
gvk := schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: "Kamelet",
}
decoder := codecs.UniversalDecoder(gv)
kamelets := make([]camel.Kamelet, 0)
files, err := ioutil.ReadDir(dir)
filesSorted := make([]string, 0)
handleGeneralError(fmt.Sprintf("cannot list dir %q", dir), err)
for _, fd := range files {
if !fd.IsDir() && strings.HasSuffix(fd.Name(), ".kamelet.yaml") {
fullName := filepath.Join(dir, fd.Name())
filesSorted = append(filesSorted, fullName)
}
}
sort.Strings(filesSorted)
for _, fileName := range filesSorted {
content, err := ioutil.ReadFile(fileName)
handleGeneralError(fmt.Sprintf("cannot read file %q", fileName), err)
json, err := yaml.ToJSON(content)
handleGeneralError(fmt.Sprintf("cannot convert file %q to JSON", fileName), err)
kamelet := camel.Kamelet{}
_, _, err = decoder.Decode(json, &gvk, &kamelet)
handleGeneralError(fmt.Sprintf("cannot unmarshal file %q into Kamelet", fileName), err)
kamelets = append(kamelets, kamelet)
}
return kamelets
}
func handleGeneralError(desc string, err error) {
if err != nil {
fmt.Printf("%s: %+v\n", desc, err)
os.Exit(2)
}
}