Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 99 additions & 125 deletions commands/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"fmt"
"os"
"sort"
"strings"

"github.com/openfaas/faas-cli/builder"
v2 "github.com/openfaas/faas-cli/schema/store/v2"

"github.com/openfaas/faas-cli/proxy"
"github.com/openfaas/faas-cli/schema"
knativev1 "github.com/openfaas/faas-cli/schema/knative/v1"
openfaasv1 "github.com/openfaas/faas-cli/schema/openfaas/v1"
"github.com/openfaas/faas-cli/util"
"github.com/openfaas/go-sdk/stack"
Expand All @@ -29,23 +29,27 @@ const (
defaultAPIVersion = "openfaas.com/v1"
)

var apiVersions = []string{"openfaas.com/v1", "openfaas.com/v2alpha2"}

var (
api string
name string
functionNamespace string
crdFunctionNamespace string
fromStore string
desiredArch string
outputFormat string
annotationArgs []string
labelArgs []string
)

func init() {

generateCmd.Flags().StringVar(&fromStore, "from-store", "", "generate using a store image")
generateCmd.Flags().StringVar(&name, "name", "", "for use with --from-store, override the name for the Function CR")
generateCmd.Flags().StringVar(&name, "name", "", "for use with --from-store, override the name for the generated function")
generateCmd.Flags().StringVar(&outputFormat, "output", "", "output format e.g stack.yaml, for use with --from-store to generate an OpenFaaS stack.yaml")

generateCmd.Flags().StringVar(&api, "api", defaultAPIVersion, "CRD API version e.g openfaas.com/v1, serving.knative.dev/v1")
generateCmd.Flags().StringVar(&api, "api", defaultAPIVersion, "CRD API version e.g openfaas.com/v1, openfaas.com/v2alpha2")
generateCmd.Flags().StringVarP(&crdFunctionNamespace, "namespace", "n", "openfaas-fn", "Kubernetes namespace for functions")
generateCmd.Flags().Var(&tagFormat, "tag", "Override latest tag on function Docker image, accepts 'digest', 'latest', 'sha', 'branch', 'describe'")
generateCmd.Flags().BoolVar(&envsubst, "envsubst", true, "Substitute environment variables in stack.yaml file")
Expand All @@ -62,19 +66,41 @@ var generateCmd = &cobra.Command{
Long: `The generate command creates kubernetes CRD YAML file for functions`,
Example: ` faas-cli generate --api=openfaas.com/v1 --yaml stack.yaml | kubectl apply -f -
faas-cli generate --api=openfaas.com/v1 -f stack.yaml
faas-cli generate --api=serving.knative.dev/v1 -f stack.yaml
faas-cli generate --api=openfaas.com/v1 --namespace openfaas-fn -f stack.yaml
faas-cli generate --api=openfaas.com/v1 -f stack.yaml --tag branch -n openfaas-fn`,
faas-cli generate --api=openfaas.com/v1 -f stack.yaml --tag branch -n openfaas-fn
faas-cli generate --from-store nodeinfo --output stack.yaml > stack.yaml`,
PreRunE: preRunGenerate,
RunE: runGenerate,
}

func preRunGenerate(cmd *cobra.Command, args []string) error {
if outputFormat != "" && !isStackOutput(outputFormat) {
return fmt.Errorf("unsupported output format %q, must be one of: stack.yaml, stack", outputFormat)
}

if isStackOutput(outputFormat) {
if len(fromStore) == 0 {
return fmt.Errorf("--output %s can only be used with --from-store", outputFormat)
}

return nil
}

if len(api) == 0 {
return fmt.Errorf("you must supply the API version with the --api flag")
}

return nil
for _, version := range apiVersions {
if api == version {
return nil
}
}

return fmt.Errorf("unsupported API version %q, must be one of: %s", api, strings.Join(apiVersions, ", "))
}

func isStackOutput(format string) bool {
return format == "stack.yaml" || format == "stack"
}

func filterStoreItem(items []v2.StoreFunction, fromStore string) (*v2.StoreFunction, error) {
Expand Down Expand Up @@ -190,6 +216,16 @@ Alternatively, to generate a definition for store functions, use "--from-store"`
os.Exit(1)
}

if isStackOutput(outputFormat) {
stackYAML, err := generateStackYAML(services, gateway)
if err != nil {
return err
}

fmt.Println(stackYAML)
return nil
}

objectsString, err := generateCRDYAML(services, tagFormat, api, crdFunctionNamespace,
builder.NewFunctionMetadataSourceLive())
if err != nil {
Expand All @@ -202,17 +238,70 @@ Alternatively, to generate a definition for store functions, use "--from-store"`
return nil
}

// stackYAML is the output format for --output stack.yaml, mirroring an
// OpenFaaS stack.yaml with only the values known from the store item
type stackYAML struct {
Version string `yaml:"version"`
Provider stack.Provider `yaml:"provider"`
Functions map[string]stackYAMLFn `yaml:"functions"`
}

type stackYAMLFn struct {
Image string `yaml:"image"`
SkipBuild bool `yaml:"skip_build"`
Environment map[string]string `yaml:"environment,omitempty"`
Labels *map[string]string `yaml:"labels,omitempty"`
Annotations *map[string]string `yaml:"annotations,omitempty"`
}

// generateStackYAML generates an OpenFaaS stack.yaml for functions, with
// skip_build set so the pre-built image is deployed without a rebuild
func generateStackYAML(services stack.Services, gateway string) (string, error) {
functions := make(map[string]stackYAMLFn, len(services.Functions))
for name, function := range services.Functions {
if function.Labels != nil && len(*function.Labels) == 0 {
function.Labels = nil
}

if function.Annotations != nil && len(*function.Annotations) == 0 {
function.Annotations = nil
}

functions[name] = stackYAMLFn{
Image: function.Image,
SkipBuild: true,
Environment: function.Environment,
Labels: function.Labels,
Annotations: function.Annotations,
}
}

output := stackYAML{
Version: "1.0",
Provider: stack.Provider{
Name: "openfaas",
GatewayURL: gateway,
},
Functions: functions,
}

var buff bytes.Buffer
yamlEncoder := yaml.NewEncoder(&buff)
yamlEncoder.SetIndent(2)
if err := yamlEncoder.Encode(&output); err != nil {
return "", err
}

return buff.String(), nil
}

// generateCRDYAML generates CRD YAML for functions
func generateCRDYAML(services stack.Services, format schema.BuildFormat, apiVersion, namespace string, metadataSource builder.FunctionMetadataSource) (string, error) {

var objectsString string

if len(services.Functions) > 0 {

if apiVersion == knativev1.APIVersionLatest {
return generateknativev1ServingServiceCRDYAML(services, format, api, crdFunctionNamespace)
}

orderedNames := generateFunctionOrder(services.Functions)

for _, name := range orderedNames {
Expand Down Expand Up @@ -274,103 +363,6 @@ func generateCRDYAML(services stack.Services, format schema.BuildFormat, apiVers
return objectsString, nil
}

func generateknativev1ServingServiceCRDYAML(services stack.Services, format schema.BuildFormat, apiVersion, namespace string) (string, error) {
crds := []knativev1.ServingServiceCRD{}

orderedNames := generateFunctionOrder(services.Functions)

for _, name := range orderedNames {

function := services.Functions[name]

fileEnvironment, err := readFiles(function.EnvironmentFile)
if err != nil {
return "", err
}

//combine all environment variables
allEnvironment, envErr := compileEnvironment([]string{}, function.Environment, fileEnvironment)
if envErr != nil {
return "", envErr
}

env := orderknativeEnv(allEnvironment)

var annotations map[string]string

if function.Annotations != nil {
annotations = *function.Annotations
}

branch, version, err := builder.GetImageTagValues(tagFormat, function.Handler)
if err != nil {
return "", err
}

imageName := schema.BuildImageName(format, function.Image, version, branch)

crd := knativev1.ServingServiceCRD{
Metadata: schema.Metadata{
Name: name,
Namespace: namespace,
Annotations: annotations,
},
APIVersion: apiVersion,
Kind: "Service",

Spec: knativev1.ServingServiceSpec{
ServingServiceSpecTemplate: knativev1.ServingServiceSpecTemplate{
Template: knativev1.ServingServiceSpecTemplateSpec{
Containers: []knativev1.ServingSpecContainersContainerSpec{},
},
},
},
}

crd.Spec.Template.Containers = append(crd.Spec.Template.Containers, knativev1.ServingSpecContainersContainerSpec{
Image: imageName,
Env: env,
})

var mounts []knativev1.VolumeMount
var volumes []knativev1.Volume

for _, secret := range function.Secrets {
mounts = append(mounts, knativev1.VolumeMount{
MountPath: "/var/openfaas/secrets/" + secret,
ReadOnly: true,
Name: secret,
})
volumes = append(volumes, knativev1.Volume{
Name: secret,
Secret: knativev1.Secret{
SecretName: secret,
},
})
}

crd.Spec.Template.Volumes = volumes
crd.Spec.Template.Containers[0].VolumeMounts = mounts

crds = append(crds, crd)
}

var objectsString string
for _, crd := range crds {

var buff bytes.Buffer
yamlEncoder := yaml.NewEncoder(&buff)
yamlEncoder.SetIndent(2) // this is what you're looking for
if err := yamlEncoder.Encode(&crd); err != nil {
return "", err
}

objectsString += "---\n" + string(buff.Bytes())
}

return objectsString, nil
}

func generateFunctionOrder(functions map[string]stack.Function) []string {

var functionNames []string
Expand All @@ -383,21 +375,3 @@ func generateFunctionOrder(functions map[string]stack.Function) []string {

return functionNames
}

func orderknativeEnv(environment map[string]string) []knativev1.EnvPair {

var orderedEnvironment []string
var envVars []knativev1.EnvPair

for k := range environment {
orderedEnvironment = append(orderedEnvironment, k)
}

sort.Strings(orderedEnvironment)

for _, envVar := range orderedEnvironment {
envVars = append(envVars, knativev1.EnvPair{Name: envVar, Value: environment[envVar]})
}

return envVars
}
Loading
Loading