Skip to content

Commit

Permalink
add pvc and emptyDir to function_volumes (#1666)
Browse files Browse the repository at this point in the history
* add pvc and emptyDir to function_volumes

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* add pvc and emptydir to deployer

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* add config functions

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* update tests

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* use random string for emptydir

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* include func yaml schema

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* fix make schema-generate

it needs to be regenrated every time.

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* make function volumes dependency-free

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* add prompt for extension flags

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

* add dependency in func_yaml-schema

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>

---------

Signed-off-by: Zuhair AlSader <zuhair@koor.tech>
  • Loading branch information
zalsader committed May 3, 2023
1 parent 1ba7015 commit 6558f96
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ $(BIN_WINDOWS): generate/zz_filesystem_generated.go
##@ Schemas
######################
schema-generate: schema/func_yaml-schema.json ## Generate func.yaml schema
schema/func_yaml-schema.json: pkg/functions/function.go
schema/func_yaml-schema.json: pkg/functions/function.go pkg/functions/function_*.go
go run schema/generator/main.go

schema-check: ## Check that func.yaml schema is up-to-date
Expand Down
65 changes: 48 additions & 17 deletions cmd/config_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,27 +129,33 @@ func runAddVolumesPrompt(ctx context.Context, f fn.Function) (err error) {
if err != nil {
return
}
persistentVolumeClaims, err := k8s.ListPersistentVolumeClaimsNamesIfConnected(ctx, f.Deploy.Namespace)
if err != nil {
return
}

// SECTION - select resource type to be mounted
options := []string{}
selectedOption := ""
const optionConfigMap = "ConfigMap"
const optionSecret = "Secret"
const optionPersistentVolumeClaim = "PersistentVolumeClaim"
const optionEmptyDir = "EmptyDir"

if len(configMaps) > 0 {
options = append(options, optionConfigMap)
}
if len(secrets) > 0 {
options = append(options, optionSecret)
}
if len(persistentVolumeClaims) > 0 {
options = append(options, optionPersistentVolumeClaim)
}
options = append(options, optionEmptyDir)

switch len(options) {
case 0:
fmt.Printf("There aren't any Secrets or ConfiMaps in the namespace \"%s\"\n", f.Deploy.Namespace)
return
case 1:
if len(options) == 1 {
selectedOption = options[0]
case 2:
} else {
err = survey.AskOne(&survey.Select{
Message: "What do you want to mount as a Volume?",
Options: options,
Expand All @@ -159,49 +165,74 @@ func runAddVolumesPrompt(ctx context.Context, f fn.Function) (err error) {
}
}

// SECTION - display a help message to enable advanced features
if selectedOption == optionEmptyDir || selectedOption == optionPersistentVolumeClaim {
fmt.Printf("Please make sure to enable the %s extension flag: https://knative.dev/docs/serving/configuration/feature-flags/\n", selectedOption)
}

// SECTION - select the specific resource to be mounted
optionsResoures := []string{}
resourceType := ""
switch selectedOption {
case optionConfigMap:
resourceType = optionConfigMap
optionsResoures = configMaps
case optionSecret:
resourceType = optionSecret
optionsResoures = secrets
case optionPersistentVolumeClaim:
optionsResoures = persistentVolumeClaims
}

selectedResource := ""
err = survey.AskOne(&survey.Select{
Message: fmt.Sprintf("Which \"%s\" do you want to mount?", resourceType),
Options: optionsResoures,
}, &selectedResource)
if err != nil {
return
if selectedOption != optionEmptyDir {
err = survey.AskOne(&survey.Select{
Message: fmt.Sprintf("Which \"%s\" do you want to mount?", selectedOption),
Options: optionsResoures,
}, &selectedResource)
if err != nil {
return
}
}

// SECTION - specify mount Path of the Volume

path := ""
err = survey.AskOne(&survey.Input{
Message: fmt.Sprintf("Please specify the path where the %s should be mounted:", resourceType),
Message: fmt.Sprintf("Please specify the path where the %s should be mounted:", selectedOption),
}, &path, survey.WithValidator(func(val interface{}) error {
if str, ok := val.(string); !ok || len(str) <= 0 || !strings.HasPrefix(str, "/") {
return fmt.Errorf("The input must be non-empty absolute path.")
return fmt.Errorf("the input must be non-empty absolute path")
}
return nil
}))
if err != nil {
return
}

// SECTION - is this read only for pvc
readOnly := false
if selectedOption == optionPersistentVolumeClaim {
err = survey.AskOne(&survey.Confirm{
Message: "Is this volume read-only?",
Default: false,
}, &readOnly)
if err != nil {
return
}
}

// we have all necessary information -> let's store the new Volume
newVolume := fn.Volume{Path: &path}
switch selectedOption {
case optionConfigMap:
newVolume.ConfigMap = &selectedResource
case optionSecret:
newVolume.Secret = &selectedResource
case optionPersistentVolumeClaim:
newVolume.PresistentVolumeClaim = &fn.PersistentVolumeClaim{
ClaimName: &selectedResource,
ReadOnly: readOnly,
}
case optionEmptyDir:
newVolume.EmptyDir = &fn.EmptyDir{}
}

f.Run.Volumes = append(f.Run.Volumes, newVolume)
Expand Down
2 changes: 1 addition & 1 deletion pkg/functions/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ func (f Function) ImageName() (image string, err error) {
// registry/parent-namespace/namespace ('quay.io/project/alice') provided
image = f.Registry + "/" + f.Name
} else if len(registryTokens) > 3 { // the name of the image is also provided `quay.io/alice/my.function.name`
return "", fmt.Errorf("registry should be either 'namespace', 'registry/namespace' or 'registry/parent/namespace', the name of the image will be derived from the function name.")
return "", fmt.Errorf("registry should be either 'namespace', 'registry/namespace' or 'registry/parent/namespace', the name of the image will be derived from the function name")
}

// Explicitly append :latest tag. We expect source control to drive
Expand Down
112 changes: 92 additions & 20 deletions pkg/functions/function_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,117 @@ package functions
import "fmt"

type Volume struct {
Secret *string `yaml:"secret,omitempty" jsonschema:"oneof_required=secret"`
ConfigMap *string `yaml:"configMap,omitempty" jsonschema:"oneof_required=configmap"`
Path *string `yaml:"path,omitempty"`
Secret *string `yaml:"secret,omitempty" jsonschema:"oneof_required=secret"`
ConfigMap *string `yaml:"configMap,omitempty" jsonschema:"oneof_required=configmap"`
PresistentVolumeClaim *PersistentVolumeClaim `yaml:"presistentVolumeClaim,omitempty" jsonschema:"oneof_required=presistentVolumeClaim"`
EmptyDir *EmptyDir `yaml:"emptyDir,omitempty" jsonschema:"oneof_required=emptyDir"`
Path *string `yaml:"path,omitempty"`
}

type PersistentVolumeClaim struct {
// claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume.
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
ClaimName *string `yaml:"claimName,omitempty"`
// readOnly Will force the ReadOnly setting in VolumeMounts.
// Default false.
ReadOnly bool `yaml:"readOnly,omitempty"`
}

const (
StorageMediumDefault = "" // use whatever the default is for the node, assume anything we don't explicitly handle is this
StorageMediumMemory = "Memory" // use memory (e.g. tmpfs on linux)
)

type EmptyDir struct {
// medium represents what type of storage medium should back this directory.
// The default is "" which means to use the node's default medium.
// Must be an empty string (default) or Memory.
// More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
Medium string `yaml:"medium,omitempty"`
// sizeLimit is the total amount of local storage required for this EmptyDir volume.
// The size limit is also applicable for memory medium.
// The maximum usage on memory medium EmptyDir would be the minimum value between
// the SizeLimit specified here and the sum of memory limits of all containers in a pod.
// The default is nil which means that the limit is undefined.
// More info: http://kubernetes.io/docs/user-guide/volumes#emptydir
SizeLimit *string `yaml:"sizeLimit,omitempty"`
}

func (v Volume) String() string {
var result string
if v.ConfigMap != nil {
return fmt.Sprintf("ConfigMap \"%s\" mounted at path: \"%s\"", *v.ConfigMap, *v.Path)
result = fmt.Sprintf("ConfigMap \"%s\"", *v.ConfigMap)
} else if v.Secret != nil {
return fmt.Sprintf("Secret \"%s\" mounted at path: \"%s\"", *v.Secret, *v.Path)
result = fmt.Sprintf("Secret \"%s\"", *v.Secret)
} else if v.PresistentVolumeClaim != nil {
result = "PersistentVolumeClaim"
if v.PresistentVolumeClaim.ClaimName != nil {
result += fmt.Sprintf(" \"%s\"", *v.PresistentVolumeClaim.ClaimName)
}
} else if v.EmptyDir != nil {
result = "EmptyDir"
if v.EmptyDir.Medium == StorageMediumMemory {
result += " in memory"
}
if v.EmptyDir.SizeLimit != nil {
result += fmt.Sprintf(" with size limit \"%s\"", *v.EmptyDir.SizeLimit)
}
} else {
result = "No volume type"
}

return ""
if v.Path != nil {
result += fmt.Sprintf(" at path: \"%s\"", *v.Path)
}
return result
}

// validateVolumes checks that input Volumes are correct and contain all necessary fields.
// Returns array of error messages, empty if no errors are found
//
// Allowed settings:
// - secret: example-secret # mount Secret as Volume
// - secret: example-secret # mount Secret as Volume
// path: /etc/secret-volume
// - configMap: example-configMap # mount ConfigMap as Volume
// path: /etc/configMap-volume
// - persistentVolumeClaim: { claimName: example-pvc } # mount PersistentVolumeClaim as Volume
// path: /etc/secret-volume
// - configMap: example-configMap # mount ConfigMap as Volume
// - emptyDir: {} # mount EmptyDir as Volume
// path: /etc/configMap-volume
func validateVolumes(volumes []Volume) (errors []string) {

for i, vol := range volumes {
if vol.Secret != nil && vol.ConfigMap != nil {
errors = append(errors, fmt.Sprintf("volume entry #%d is not properly set, both secret '%s' and configMap '%s' can not be set at the same time",
i, *vol.Secret, *vol.ConfigMap))
} else if vol.Path == nil && vol.Secret == nil && vol.ConfigMap == nil {
errors = append(errors, fmt.Sprintf("volume entry #%d is not properly set", i))
} else if vol.Path == nil {
if vol.Secret != nil {
errors = append(errors, fmt.Sprintf("volume entry #%d is missing path field, only secret '%s' is set", i, *vol.Secret))
} else if vol.ConfigMap != nil {
errors = append(errors, fmt.Sprintf("volume entry #%d is missing path field, only configMap '%s' is set", i, *vol.ConfigMap))
numVolumes := 0
if vol.Secret != nil {
numVolumes++
}

if vol.ConfigMap != nil {
numVolumes++
}

if vol.PresistentVolumeClaim != nil {
numVolumes++
if vol.PresistentVolumeClaim.ClaimName == nil {
errors = append(errors, fmt.Sprintf("volume entry #%d (%s) is missing claim name", i, vol))
}
} else if vol.Path != nil && vol.Secret == nil && vol.ConfigMap == nil {
errors = append(errors, fmt.Sprintf("volume entry #%d is missing secret or configMap field, only path '%s' is set", i, *vol.Path))
}

if vol.EmptyDir != nil {
numVolumes++
if vol.EmptyDir.Medium != StorageMediumDefault && vol.EmptyDir.Medium != StorageMediumMemory {
errors = append(errors, fmt.Sprintf("volume entry #%d (%s) has invalid storage medium (%s)", i, vol, vol.EmptyDir.Medium))
}
}

if numVolumes == 0 {
errors = append(errors, fmt.Sprintf("volume entry #%d (%s) is missing a volume type", i, vol))
} else if numVolumes > 1 {
errors = append(errors, fmt.Sprintf("volume entry #%d (%s) may not specify more than one volume type", i, vol))
}

if vol.Path == nil {
errors = append(errors, fmt.Sprintf("volume entry #%d (%s) is missing path field", i, vol))
}
}

Expand Down
Loading

0 comments on commit 6558f96

Please sign in to comment.