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 support for hostPath #15648

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions config/core/300-resources/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,10 @@ spec:
description: This is accessible behind a feature flag - kubernetes.podspec-emptydir
type: object
x-kubernetes-preserve-unknown-fields: true
hostPath:
description: This is accessible behind a feature flag - kubernetes.podspec-hostpath
type: object
x-kubernetes-preserve-unknown-fields: true
name:
description: |-
name of the volume.
Expand Down
4 changes: 4 additions & 0 deletions config/core/300-resources/revision.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,10 @@ spec:
description: This is accessible behind a feature flag - kubernetes.podspec-emptydir
type: object
x-kubernetes-preserve-unknown-fields: true
hostPath:
description: This is accessible behind a feature flag - kubernetes.podspec-hostpath
type: object
x-kubernetes-preserve-unknown-fields: true
name:
description: |-
name of the volume.
Expand Down
4 changes: 4 additions & 0 deletions config/core/300-resources/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,10 @@ spec:
description: This is accessible behind a feature flag - kubernetes.podspec-emptydir
type: object
x-kubernetes-preserve-unknown-fields: true
hostPath:
description: This is accessible behind a feature flag - kubernetes.podspec-hostpath
type: object
x-kubernetes-preserve-unknown-fields: true
name:
description: |-
name of the volume.
Expand Down
10 changes: 9 additions & 1 deletion config/core/configmaps/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ metadata:
app.kubernetes.io/component: controller
app.kubernetes.io/version: devel
annotations:
knative.dev/example-checksum: "9ff569ad"
knative.dev/example-checksum: "63a13754"
data:
_example: |-
################################
Expand Down Expand Up @@ -200,6 +200,14 @@ data:
# 2. Disabled: disabling EmptyDir volume support
kubernetes.podspec-volumes-emptydir: "enabled"

# Controls whether volume support for HostPath is enabled or not.
# WARNING: Cannot safely be disabled once enabled.
# WARNING: If you can avoid using a hostPath volume, you should.
# Please read https://kubernetes.io/docs/concepts/storage/volumes/#hostpath before enabling this feature.
# 1. Enabled: enabling HostPath volume support
# 2. Disabled: disabling HostPath volume support
kubernetes.podspec-volumes-hostpath: "disabled"

# Controls whether init containers support is enabled or not.
# 1. Enabled: enabling init containers support
# 2. Disabled: disabling init containers support
Expand Down
7 changes: 7 additions & 0 deletions hack/schemapatch-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ k8s.io/api/core/v1.VolumeSource:
# Following are behind feature flags
- EmptyDir
- PersistentVolumeClaim
- HostPath
k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource:
description: "This is accessible behind a feature flag - kubernetes.podspec-persistent-volume-claim"
additionalMarkers:
Expand All @@ -24,6 +25,12 @@ k8s.io/api/core/v1.EmptyDirVolumeSource:
# Part of a feature flag - so we want to omit the schema and preserve unknown fields
- kubebuilder:validation:DropProperties
- kubebuilder:pruning:PreserveUnknownFields
k8s.io/api/core/v1.HostPathVolumeSource:
description: "This is accessible behind a feature flag - kubernetes.podspec-hostpath"
additionalMarkers:
# Part of a feature flag - so we want to omit the schema and preserve unknown fields
- kubebuilder:validation:DropProperties
- kubebuilder:pruning:PreserveUnknownFields
k8s.io/api/core/v1.VolumeProjection:
fieldMask:
- Secret
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/config/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func defaultFeaturesConfig() *Features {
ContainerSpecAddCapabilities: Disabled,
PodSpecTolerations: Disabled,
PodSpecVolumesEmptyDir: Enabled,
PodSpecVolumesHostPath: Disabled,
PodSpecPersistentVolumeClaim: Disabled,
PodSpecPersistentVolumeWrite: Disabled,
QueueProxyMountPodInfo: Disabled,
Expand Down Expand Up @@ -107,6 +108,7 @@ func NewFeaturesConfigFromMap(data map[string]string) (*Features, error) {
asFlag("kubernetes.containerspec-addcapabilities", &nc.ContainerSpecAddCapabilities),
asFlag("kubernetes.podspec-tolerations", &nc.PodSpecTolerations),
asFlag("kubernetes.podspec-volumes-emptydir", &nc.PodSpecVolumesEmptyDir),
asFlag("kubernetes.podspec-volumes-hostpath", &nc.PodSpecVolumesHostPath),
asFlag("kubernetes.podspec-hostipc", &nc.PodSpecHostIPC),
asFlag("kubernetes.podspec-hostpid", &nc.PodSpecHostPID),
asFlag("kubernetes.podspec-hostnetwork", &nc.PodSpecHostNetwork),
Expand Down Expand Up @@ -151,6 +153,7 @@ type Features struct {
ContainerSpecAddCapabilities Flag
PodSpecTolerations Flag
PodSpecVolumesEmptyDir Flag
PodSpecVolumesHostPath Flag
PodSpecInitContainers Flag
PodSpecPersistentVolumeClaim Flag
PodSpecPersistentVolumeWrite Flag
Expand Down
18 changes: 18 additions & 0 deletions pkg/apis/config/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,24 @@ func TestFeaturesConfiguration(t *testing.T) {
data: map[string]string{
"kubernetes.podspec-volumes-emptydir": "Enabled",
},
}, {
name: "kubernetes.podspec-volumes-hostpath Disabled",
wantErr: false,
wantFeatures: defaultWith(&Features{
PodSpecVolumesHostPath: Disabled,
}),
data: map[string]string{
"kubernetes.podspec-volumes-hostpath": "Disabled",
},
}, {
name: "kubernetes.podspec-volumes-hostpath Enabled",
wantErr: false,
wantFeatures: defaultWith(&Features{
PodSpecVolumesHostPath: Enabled,
}),
data: map[string]string{
"kubernetes.podspec-volumes-hostpath": "Enabled",
},
}, {
name: "kubernetes.podspec-persistent-volume-claim Disabled",
wantErr: false,
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/serving/fieldmask.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func VolumeSourceMask(ctx context.Context, in *corev1.VolumeSource) *corev1.Volu
out.PersistentVolumeClaim = in.PersistentVolumeClaim
}

if cfg.Features.PodSpecVolumesHostPath != config.Disabled {
out.HostPath = in.HostPath
}

// Too many disallowed fields to list

return out
Expand Down
55 changes: 55 additions & 0 deletions pkg/apis/serving/k8s_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"math"
"path"
"path/filepath"
"strings"

"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -161,6 +162,11 @@ func validateVolume(ctx context.Context, volume corev1.Volume) *apis.FieldError
specified = append(specified, "persistentVolumeClaim")
}

if vs.HostPath != nil {
specified = append(specified, "hostPath")
errs = errs.Also(validateHostPathVolumeSource(vs.HostPath).ViaField("hostPath"))
}

if len(specified) == 0 {
fieldPaths := []string{"secret", "configMap", "projected"}
cfg := config.FromContextOrDefaults(ctx)
Expand All @@ -170,6 +176,9 @@ func validateVolume(ctx context.Context, volume corev1.Volume) *apis.FieldError
if cfg.Features.PodSpecPersistentVolumeClaim == config.Enabled {
fieldPaths = append(fieldPaths, "persistentVolumeClaim")
}
if cfg.Features.PodSpecVolumesHostPath == config.Enabled {
fieldPaths = append(fieldPaths, "hostPath")
}
errs = errs.Also(apis.ErrMissingOneOf(fieldPaths...))
} else if len(specified) > 1 {
errs = errs.Also(apis.ErrMultipleOneOf(specified...))
Expand Down Expand Up @@ -286,6 +295,52 @@ func validateEmptyDirFields(dir *corev1.EmptyDirVolumeSource) *apis.FieldError {
return errs
}

func validateHostPathVolumeSource(hostPath *corev1.HostPathVolumeSource) *apis.FieldError {
amarflybot marked this conversation as resolved.
Show resolved Hide resolved
var errs *apis.FieldError
// This is checked at the K8s side for host Path so better validate early
// ref: https://bit.ly/4gcWAVK
if len(hostPath.Path) == 0 {
errs = errs.Also(apis.ErrInvalidValue("''", "path"))
return errs
}
errs = errs.Also(validatePathNoBacksteps(hostPath.Path, "path"))
errs = errs.Also(validateHostPathType(hostPath.Type, "type"))
return errs
}

// validatePathNoBacksteps makes sure the targetPath does not have any `..` path elements when split
//
// This assumes the OS of the apiserver and the nodes are the same. The same check should be done
// on the node to ensure there are no backsteps.
func validatePathNoBacksteps(targetPath string, fldPath string) *apis.FieldError {
var errs *apis.FieldError
parts := strings.Split(filepath.ToSlash(targetPath), "/")
for _, item := range parts {
if item == ".." {
errs = errs.Also(apis.ErrInvalidValue(targetPath, fldPath, "must not contain '..'"))
break // even for `../../..`, one error is sufficient to make the point
}
}
return errs
}

func validateHostPathType(hostPathType *corev1.HostPathType, fldPath string) *apis.FieldError {
var errs *apis.FieldError
supportedHostPathTypes := sets.New(
corev1.HostPathUnset,
corev1.HostPathDirectoryOrCreate,
corev1.HostPathDirectory,
corev1.HostPathFileOrCreate,
corev1.HostPathFile,
corev1.HostPathSocket,
corev1.HostPathCharDev,
corev1.HostPathBlockDev)
if hostPathType != nil && !supportedHostPathTypes.Has(*hostPathType) {
errs = errs.Also(apis.ErrInvalidValue(*hostPathType, fldPath, "unknown type"))
}
return errs
}

func validateEnvValueFrom(ctx context.Context, source *corev1.EnvVarSource) *apis.FieldError {
if source == nil {
return nil
Expand Down
50 changes: 50 additions & 0 deletions pkg/apis/serving/k8s_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ func withMultiContainerProbesEnabled() configOption {
}
}

func withPodSpecVolumesHostPathEnabled() configOption {
return func(cfg *config.Config) *config.Config {
cfg.Features.PodSpecVolumesHostPath = config.Enabled
return cfg
}
}

func withPodSpecDNSPolicyEnabled() configOption {
return func(cfg *config.Config) *config.Config {
cfg.Features.PodSpecDNSPolicy = config.Enabled
Expand Down Expand Up @@ -2911,6 +2918,49 @@ func TestVolumeValidation(t *testing.T) {
Message: `Persistent volume write support is disabled, but found persistent volume claim myclaim that is not read-only`,
}).Also(
&apis.FieldError{Message: "must not set the field(s)", Paths: []string{"persistentVolumeClaim"}}),
}, {
name: "hostPath volume",
v: corev1.Volume{
Name: "foo",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "foo/foo",
},
},
},
cfgOpts: []configOption{withPodSpecVolumesHostPathEnabled()},
}, {
name: "invalid hostPath volume, invalid type",
v: corev1.Volume{
Name: "foo",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "foo/foo",
Type: (*corev1.HostPathType)(ptr.String("wrong")),
},
},
},
cfgOpts: []configOption{withPodSpecVolumesHostPathEnabled()},
want: &apis.FieldError{
Message: `invalid value: wrong`,
Paths: []string{"hostPath.type"},
Details: "unknown type",
},
}, {
name: "invalid hostPath volume, empty path",
v: corev1.Volume{
Name: "foo",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "",
},
},
},
cfgOpts: []configOption{withPodSpecVolumesHostPathEnabled()},
want: &apis.FieldError{
Message: `invalid value: ''`,
Paths: []string{"hostPath.path"},
},
}, {
name: "no volume source",
v: corev1.Volume{
Expand Down
1 change: 1 addition & 0 deletions pkg/reconciler/route/resources/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ func testConfig() *config.Config {
PodSpecNodeSelector: apiConfig.Disabled,
PodSpecTolerations: apiConfig.Disabled,
PodSpecVolumesEmptyDir: apiConfig.Disabled,
PodSpecVolumesHostPath: apiConfig.Disabled,
PodSpecPersistentVolumeClaim: apiConfig.Disabled,
PodSpecPersistentVolumeWrite: apiConfig.Disabled,
PodSpecInitContainers: apiConfig.Disabled,
Expand Down
Loading