1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-16 11:09:33 +02:00
sap-jenkins-library/pkg/generator/helper/helper.go

467 lines
14 KiB
Go
Raw Normal View History

2019-11-08 16:35:11 +02:00
package helper
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/piperutils"
2019-11-08 16:35:11 +02:00
)
type stepInfo struct {
CobraCmdFuncName string
CreateCmdVar string
ExportPrefix string
FlagsFunc string
Long string
Metadata []config.StepParameters
OSImport bool
OutputResources []map[string]string
2019-11-08 16:35:11 +02:00
Short string
StepFunc string
StepName string
}
//StepGoTemplate ...
const stepGoTemplate = `package cmd
import (
{{ if .OSImport }}"os"{{ end }}
{{ if .OutputResources }}"fmt"{{ end }}
{{ if .OutputResources }}"path/filepath"{{ end }}
2019-11-08 16:35:11 +02:00
{{ if .ExportPrefix}}{{ .ExportPrefix }} "github.com/SAP/jenkins-library/cmd"{{ end -}}
2019-11-08 16:35:11 +02:00
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
2020-01-29 14:17:54 +02:00
"github.com/SAP/jenkins-library/pkg/telemetry"
{{ if .OutputResources }}"github.com/SAP/jenkins-library/pkg/piperenv"{{ end }}
2019-11-08 16:35:11 +02:00
"github.com/spf13/cobra"
)
type {{ .StepName }}Options struct {
{{- range $key, $value := .Metadata }}
{{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + `{{end}}
}
{{ range $notused, $oRes := .OutputResources }}
{{ index $oRes "def"}}
{{ end }}
2019-11-08 16:35:11 +02:00
var my{{ .StepName | title}}Options {{.StepName}}Options
// {{.CobraCmdFuncName}} {{.Short}}
func {{.CobraCmdFuncName}}() *cobra.Command {
metadata := {{ .StepName }}Metadata()
{{- range $notused, $oRes := .OutputResources }}
var {{ index $oRes "name" }} {{ index $oRes "objectname" }}{{ end }}
2019-11-08 16:35:11 +02:00
var {{.CreateCmdVar}} = &cobra.Command{
Use: "{{.StepName}}",
Short: "{{.Short}}",
Long: {{ $tick := "` + "`" + `" }}{{ $tick }}{{.Long | longName }}{{ $tick }},
PreRunE: func(cmd *cobra.Command, args []string) error {
log.SetStepName("{{ .StepName }}")
log.SetVerbose({{if .ExportPrefix}}{{ .ExportPrefix }}.{{end}}GeneralConfig.Verbose)
return {{if .ExportPrefix}}{{ .ExportPrefix }}.{{end}}PrepareConfig(cmd, &metadata, "{{ .StepName }}", &my{{ .StepName | title}}Options, config.OpenPiperFile)
2019-11-08 16:35:11 +02:00
},
RunE: func(cmd *cobra.Command, args []string) error {
{{ if .OutputResources -}}
handler := func() {
{{- range $notused, $oRes := .OutputResources }}
{{ index $oRes "name" }}.persist(GeneralConfig.EnvRootPath, "{{ index $oRes "name" }}"){{ end }}
}
log.DeferExitHandler(handler)
defer handler()
{{- end }}
2020-01-29 14:17:54 +02:00
telemetry.Initialize(GeneralConfig.NoTelemetry, "{{ .StepName }}")
telemetry.Send(&telemetry.CustomData{})
return {{.StepName}}(my{{ .StepName | title }}Options{{ range $notused, $oRes := .OutputResources}}, &{{ index $oRes "name" }}{{ end }})
2019-11-08 16:35:11 +02:00
},
}
{{.FlagsFunc}}({{.CreateCmdVar}})
return {{.CreateCmdVar}}
}
func {{.FlagsFunc}}(cmd *cobra.Command) {
{{- range $key, $value := .Metadata }}
cmd.Flags().{{ $value.Type | flagType }}(&my{{ $.StepName | title }}Options.{{ $value.Name | golangName }}, "{{ $value.Name }}", {{ $value.Default }}, "{{ $value.Description }}"){{ end }}
{{- printf "\n" }}
{{- range $key, $value := .Metadata }}{{ if $value.Mandatory }}
cmd.MarkFlagRequired("{{ $value.Name }}"){{ end }}{{ end }}
}
// retrieve step metadata
func {{ .StepName }}Metadata() config.StepData {
var theMetaData = config.StepData{
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{{- range $key, $value := .Metadata }}
{
Name: "{{ $value.Name }}",
ResourceRef: []config.ResourceReference{{ "{" }}{{ range $notused, $ref := $value.ResourceRef }}{{ "{" }}Name: "{{ $ref.Name }}", Param: "{{ $ref.Param }}"{{ "}" }},{{ end }}{{ "}" }},
2019-11-08 16:35:11 +02:00
Scope: []string{{ "{" }}{{ range $notused, $scope := $value.Scope }}"{{ $scope }}",{{ end }}{{ "}" }},
Type: "{{ $value.Type }}",
Mandatory: {{ $value.Mandatory }},
Aliases: []config.Alias{{ "{" }}{{ range $notused, $alias := $value.Aliases }}{{ "{" }}Name: "{{ $alias.Name }}"{{ "}" }},{{ end }}{{ "}" }},
2019-11-08 16:35:11 +02:00
},{{ end }}
},
},
},
}
return theMetaData
}
`
//StepTestGoTemplate ...
const stepTestGoTemplate = `package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test{{.CobraCmdFuncName}}(t *testing.T) {
testCmd := {{.CobraCmdFuncName}}()
// only high level testing performed - details are tested in step generation procudure
assert.Equal(t, "{{.StepName}}", testCmd.Use, "command name incorrect")
}
`
const stepGoImplementationTemplate = `package cmd
import (
"github.com/SAP/jenkins-library/pkg/log"
)
func {{.StepName}}(config {{ .StepName }}Options{{ range $notused, $oRes := .OutputResources}}, {{ index $oRes "name" }} *{{ index $oRes "objectname" }} {{ end }}) error {
log.Entry().WithField("customKey", "customValue").Info("This is how you write a log message with a custom field ...")
return nil
}
`
2019-11-08 16:35:11 +02:00
// ProcessMetaFiles generates step coding based on step configuration provided in yaml files
2019-11-21 12:12:30 +02:00
func ProcessMetaFiles(metadataFiles []string, stepHelperData StepHelperData, docuHelperData DocuHelperData) error {
2019-11-08 16:35:11 +02:00
for key := range metadataFiles {
var stepData config.StepData
configFilePath := metadataFiles[key]
2019-11-21 12:12:30 +02:00
metadataFile, err := stepHelperData.OpenFile(configFilePath)
2019-11-08 16:35:11 +02:00
checkError(err)
defer metadataFile.Close()
fmt.Printf("Reading file %v\n", configFilePath)
err = stepData.ReadPipelineStepData(metadataFile)
checkError(err)
fmt.Printf("Step name: %v\n", stepData.Metadata.Name)
//Switch Docu or Step Files
if !docuHelperData.IsGenerateDocu {
osImport := false
osImport, err = setDefaultParameters(&stepData)
checkError(err)
2019-11-08 16:35:11 +02:00
myStepInfo, err := getStepInfo(&stepData, osImport, stepHelperData.ExportPrefix)
checkError(err)
2019-11-08 16:35:11 +02:00
step := stepTemplate(myStepInfo)
2019-11-21 12:12:30 +02:00
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v_generated.go", stepData.Metadata.Name), step, 0644)
checkError(err)
2019-11-08 16:35:11 +02:00
test := stepTestTemplate(myStepInfo)
2019-11-21 12:12:30 +02:00
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v_generated_test.go", stepData.Metadata.Name), test, 0644)
checkError(err)
exists, _ := piperutils.FileExists(fmt.Sprintf("cmd/%v.go", stepData.Metadata.Name))
if !exists {
impl := stepImplementation(myStepInfo)
err = stepHelperData.WriteFile(fmt.Sprintf("cmd/%v.go", stepData.Metadata.Name), impl, 0644)
checkError(err)
}
} else {
err = generateStepDocumentation(stepData, docuHelperData)
if err != nil {
fmt.Printf("%v\n", err)
}
}
2019-11-08 16:35:11 +02:00
}
return nil
}
func openMetaFile(name string) (io.ReadCloser, error) {
return os.Open(name)
}
func fileWriter(filename string, data []byte, perm os.FileMode) error {
return ioutil.WriteFile(filename, data, perm)
}
func setDefaultParameters(stepData *config.StepData) (bool, error) {
//ToDo: custom function for default handling, support all relevant parameter types
osImportRequired := false
for k, param := range stepData.Spec.Inputs.Parameters {
if param.Default == nil {
switch param.Type {
case "bool":
// ToDo: Check if default should be read from env
param.Default = "false"
case "int":
param.Default = "0"
case "string":
param.Default = fmt.Sprintf("os.Getenv(\"PIPER_%v\")", param.Name)
osImportRequired = true
2019-11-08 16:35:11 +02:00
case "[]string":
// ToDo: Check if default should be read from env
param.Default = "[]string{}"
default:
return false, fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
}
} else {
switch param.Type {
case "bool":
boolVal := "false"
if param.Default.(bool) == true {
boolVal = "true"
}
param.Default = boolVal
case "int":
param.Default = fmt.Sprintf("%v", param.Default)
case "string":
param.Default = fmt.Sprintf("\"%v\"", param.Default)
2019-11-08 16:35:11 +02:00
case "[]string":
param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(getStringSliceFromInterface(param.Default), "\", \""))
2019-11-08 16:35:11 +02:00
default:
return false, fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
}
}
stepData.Spec.Inputs.Parameters[k] = param
}
return osImportRequired, nil
}
func getStepInfo(stepData *config.StepData, osImport bool, exportPrefix string) (stepInfo, error) {
oRes, err := getOutputResourceDetails(stepData)
2019-11-08 16:35:11 +02:00
return stepInfo{
StepName: stepData.Metadata.Name,
CobraCmdFuncName: fmt.Sprintf("%vCommand", strings.Title(stepData.Metadata.Name)),
CreateCmdVar: fmt.Sprintf("create%vCmd", strings.Title(stepData.Metadata.Name)),
Short: stepData.Metadata.Description,
Long: stepData.Metadata.LongDescription,
Metadata: stepData.Spec.Inputs.Parameters,
FlagsFunc: fmt.Sprintf("add%vFlags", strings.Title(stepData.Metadata.Name)),
OSImport: osImport,
OutputResources: oRes,
ExportPrefix: exportPrefix,
},
err
}
func getOutputResourceDetails(stepData *config.StepData) ([]map[string]string, error) {
outputResources := []map[string]string{}
for _, res := range stepData.Spec.Outputs.Resources {
currentResource := map[string]string{}
currentResource["name"] = res.Name
switch res.Type {
case "piperEnvironment":
var envResource PiperEnvironmentResource
envResource.Name = res.Name
envResource.StepName = stepData.Metadata.Name
for _, param := range res.Parameters {
paramSections := strings.Split(fmt.Sprintf("%v", param["name"]), "/")
category := ""
name := paramSections[0]
if len(paramSections) > 1 {
name = strings.Join(paramSections[1:], "_")
category = paramSections[0]
if !contains(envResource.Categories, category) {
envResource.Categories = append(envResource.Categories, category)
}
}
envParam := PiperEnvironmentParameter{Category: category, Name: name}
envResource.Parameters = append(envResource.Parameters, envParam)
}
def, err := envResource.StructString()
if err != nil {
return outputResources, err
}
currentResource["def"] = def
currentResource["objectname"] = envResource.StructName()
outputResources = append(outputResources, currentResource)
case "influx":
var influxResource InfluxResource
influxResource.Name = res.Name
influxResource.StepName = stepData.Metadata.Name
for _, measurement := range res.Parameters {
influxMeasurement := InfluxMeasurement{Name: fmt.Sprintf("%v", measurement["name"])}
if fields, ok := measurement["fields"].([]interface{}); ok {
for _, field := range fields {
if fieldParams, ok := field.(map[string]interface{}); ok {
influxMeasurement.Fields = append(influxMeasurement.Fields, InfluxMetric{Name: fmt.Sprintf("%v", fieldParams["name"])})
}
}
}
if tags, ok := measurement["tags"].([]interface{}); ok {
for _, tag := range tags {
if tagParams, ok := tag.(map[string]interface{}); ok {
influxMeasurement.Tags = append(influxMeasurement.Tags, InfluxMetric{Name: fmt.Sprintf("%v", tagParams["name"])})
}
}
}
influxResource.Measurements = append(influxResource.Measurements, influxMeasurement)
}
def, err := influxResource.StructString()
if err != nil {
return outputResources, err
}
currentResource["def"] = def
currentResource["objectname"] = influxResource.StructName()
outputResources = append(outputResources, currentResource)
}
2019-11-08 16:35:11 +02:00
}
return outputResources, nil
2019-11-08 16:35:11 +02:00
}
// MetadataFiles provides a list of all step metadata files
func MetadataFiles(sourceDirectory string) ([]string, error) {
var metadataFiles []string
err := filepath.Walk(sourceDirectory, func(path string, info os.FileInfo, err error) error {
if filepath.Ext(path) == ".yaml" {
metadataFiles = append(metadataFiles, path)
}
return nil
})
if err != nil {
return metadataFiles, nil
}
return metadataFiles, nil
}
func stepTemplate(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"flagType": flagType,
"golangName": golangNameTitle,
2019-11-08 16:35:11 +02:00
"title": strings.Title,
"longName": longName,
}
tmpl, err := template.New("step").Funcs(funcMap).Parse(stepGoTemplate)
checkError(err)
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, myStepInfo)
checkError(err)
return generatedCode.Bytes()
}
func stepTestTemplate(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"flagType": flagType,
"golangName": golangNameTitle,
2019-11-08 16:35:11 +02:00
"title": strings.Title,
}
tmpl, err := template.New("stepTest").Funcs(funcMap).Parse(stepTestGoTemplate)
checkError(err)
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, myStepInfo)
checkError(err)
return generatedCode.Bytes()
}
func stepImplementation(myStepInfo stepInfo) []byte {
funcMap := template.FuncMap{
"title": strings.Title,
}
tmpl, err := template.New("impl").Funcs(funcMap).Parse(stepGoImplementationTemplate)
checkError(err)
var generatedCode bytes.Buffer
err = tmpl.Execute(&generatedCode, myStepInfo)
checkError(err)
return generatedCode.Bytes()
}
2019-11-08 16:35:11 +02:00
func longName(long string) string {
l := strings.ReplaceAll(long, "`", "` + \"`\" + `")
l = strings.TrimSpace(l)
return l
}
func golangName(name string) string {
properName := strings.Replace(name, "Api", "API", -1)
properName = strings.Replace(properName, "api", "API", -1)
properName = strings.Replace(properName, "Url", "URL", -1)
properName = strings.Replace(properName, "Id", "ID", -1)
properName = strings.Replace(properName, "Json", "JSON", -1)
properName = strings.Replace(properName, "json", "JSON", -1)
return properName
}
func golangNameTitle(name string) string {
return strings.Title(golangName(name))
2019-11-08 16:35:11 +02:00
}
func flagType(paramType string) string {
var theFlagType string
switch paramType {
case "bool":
theFlagType = "BoolVar"
case "int":
theFlagType = "IntVar"
2019-11-08 16:35:11 +02:00
case "string":
theFlagType = "StringVar"
case "[]string":
theFlagType = "StringSliceVar"
default:
fmt.Printf("Meta data type not set or not known: '%v'\n", paramType)
os.Exit(1)
}
return theFlagType
}
func getStringSliceFromInterface(iSlice interface{}) []string {
s := []string{}
t, ok := iSlice.([]interface{})
if ok {
for _, v := range t {
s = append(s, fmt.Sprintf("%v", v))
}
} else {
s = append(s, fmt.Sprintf("%v", iSlice))
}
return s
}