Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pvc and emptyDir to function_volumes #1666

Merged
merged 10 commits into from
May 3, 2023
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
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