Commit 3cfb0909 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #7893 from pweil-/security-policy

Auto commit by PR queue bot
parents e8f341cb c22f348f
...@@ -330,6 +330,7 @@ _kubectl_get() ...@@ -330,6 +330,7 @@ _kubectl_get()
must_have_one_noun+=("persistentvolume") must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim") must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("pod") must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate") must_have_one_noun+=("podtemplate")
must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("replicationcontroller")
must_have_one_noun+=("resourcequota") must_have_one_noun+=("resourcequota")
...@@ -827,6 +828,7 @@ _kubectl_delete() ...@@ -827,6 +828,7 @@ _kubectl_delete()
must_have_one_noun+=("persistentvolume") must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim") must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("pod") must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate") must_have_one_noun+=("podtemplate")
must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("replicationcontroller")
must_have_one_noun+=("resourcequota") must_have_one_noun+=("resourcequota")
...@@ -1964,6 +1966,7 @@ _kubectl_label() ...@@ -1964,6 +1966,7 @@ _kubectl_label()
must_have_one_noun+=("persistentvolume") must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim") must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("pod") must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate") must_have_one_noun+=("podtemplate")
must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("replicationcontroller")
must_have_one_noun+=("resourcequota") must_have_one_noun+=("resourcequota")
......
...@@ -404,6 +404,13 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) ...@@ -404,6 +404,13 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source)
MaxUnavailable: intstr.FromInt(10), MaxUnavailable: intstr.FromInt(10),
} }
}, },
func(psp *extensions.PodSecurityPolicySpec, c fuzz.Continue) {
c.FuzzNoCustom(psp) // fuzz self without calling this function again
userTypes := []extensions.RunAsUserStrategy{extensions.RunAsUserStrategyMustRunAsNonRoot, extensions.RunAsUserStrategyMustRunAs, extensions.RunAsUserStrategyRunAsAny}
psp.RunAsUser.Type = userTypes[c.Rand.Intn(len(userTypes))]
seLinuxTypes := []extensions.SELinuxContextStrategy{extensions.SELinuxStrategyRunAsAny, extensions.SELinuxStrategyMustRunAs}
psp.SELinuxContext.Type = seLinuxTypes[c.Rand.Intn(len(seLinuxTypes))]
},
) )
return f return f
} }
...@@ -90,7 +90,9 @@ func enableVersions(externalVersions []unversioned.GroupVersion) error { ...@@ -90,7 +90,9 @@ func enableVersions(externalVersions []unversioned.GroupVersion) error {
func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper { func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper {
// the list of kinds that are scoped at the root of the api hierarchy // the list of kinds that are scoped at the root of the api hierarchy
// if a kind is not enumerated here, it is assumed to have a namespace scope // if a kind is not enumerated here, it is assumed to have a namespace scope
rootScoped := sets.NewString() rootScoped := sets.NewString(
"PodSecurityPolicy",
)
ignoredKinds := sets.NewString() ignoredKinds := sets.NewString()
......
...@@ -70,6 +70,8 @@ func addKnownTypes(scheme *runtime.Scheme) { ...@@ -70,6 +70,8 @@ func addKnownTypes(scheme *runtime.Scheme) {
&ReplicaSet{}, &ReplicaSet{},
&ReplicaSetList{}, &ReplicaSetList{},
&api.ExportOptions{}, &api.ExportOptions{},
&PodSecurityPolicy{},
&PodSecurityPolicyList{},
) )
} }
...@@ -94,3 +96,5 @@ func (obj *Ingress) GetObjectKind() unversioned.ObjectKind { ...@@ -94,3 +96,5 @@ func (obj *Ingress) GetObjectKind() unversioned.ObjectKind {
func (obj *IngressList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *IngressList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ReplicaSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ReplicaSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ReplicaSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ReplicaSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PodSecurityPolicy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PodSecurityPolicyList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -842,3 +842,123 @@ type ReplicaSetStatus struct { ...@@ -842,3 +842,123 @@ type ReplicaSetStatus struct {
// ObservedGeneration is the most recent generation observed by the controller. // ObservedGeneration is the most recent generation observed by the controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty"` ObservedGeneration int64 `json:"observedGeneration,omitempty"`
} }
// PodSecurityPolicy governs the ability to make requests that affect the SecurityContext
// that will be applied to a pod and container.
type PodSecurityPolicy struct {
unversioned.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the policy enforced.
Spec PodSecurityPolicySpec `json:"spec,omitempty"`
}
// PodSecurityPolicySpec defines the policy enforced.
type PodSecurityPolicySpec struct {
// Privileged determines if a pod can request to be run as privileged.
Privileged bool `json:"privileged,omitempty"`
// Capabilities is a list of capabilities that can be added.
Capabilities []api.Capability `json:"capabilities,omitempty"`
// Volumes is a white list of allowed volume plugins. Empty indicates that all plugins
// may be used.
Volumes []FSType `json:"volumes,omitempty"`
// HostNetwork determines if the policy allows the use of HostNetwork in the pod spec.
HostNetwork bool `json:"hostNetwork,omitempty"`
// HostPorts determines which host port ranges are allowed to be exposed.
HostPorts []HostPortRange `json:"hostPorts,omitempty"`
// HostPID determines if the policy allows the use of HostPID in the pod spec.
HostPID bool `json:"hostPID,omitempty"`
// HostIPC determines if the policy allows the use of HostIPC in the pod spec.
HostIPC bool `json:"hostIPC,omitempty"`
// SELinuxContext is the strategy that will dictate the allowable labels that may be set.
SELinuxContext SELinuxContextStrategyOptions `json:"seLinuxContext,omitempty"`
// RunAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.
RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty"`
}
// HostPortRange defines a range of host ports that will be enabled by a policy
// for pods to use. It requires both the start and end to be defined.
type HostPortRange struct {
// Min is the start of the range, inclusive.
Min int `json:"min"`
// Max is the end of the range, inclusive.
Max int `json:"max"`
}
// FSType gives strong typing to different file systems that are used by volumes.
type FSType string
var (
HostPath FSType = "hostPath"
EmptyDir FSType = "emptyDir"
GCEPersistentDisk FSType = "gcePersistentDisk"
AWSElasticBlockStore FSType = "awsElasticBlockStore"
GitRepo FSType = "gitRepo"
Secret FSType = "secret"
NFS FSType = "nfs"
ISCSI FSType = "iscsi"
Glusterfs FSType = "glusterfs"
PersistentVolumeClaim FSType = "persistentVolumeClaim"
RBD FSType = "rbd"
Cinder FSType = "cinder"
CephFS FSType = "cephFS"
DownwardAPI FSType = "downwardAPI"
FC FSType = "fc"
)
// SELinuxContextStrategyOptions defines the strategy type and any options used to create the strategy.
type SELinuxContextStrategyOptions struct {
// Type is the strategy that will dictate the allowable labels that may be set.
Type SELinuxContextStrategy `json:"type"`
// seLinuxOptions required to run as; required for MustRunAs
// More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context
SELinuxOptions *api.SELinuxOptions `json:"seLinuxOptions,omitempty"`
}
// SELinuxContextStrategyType denotes strategy types for generating SELinux options for a
// SecurityContext.
type SELinuxContextStrategy string
const (
// container must have SELinux labels of X applied.
SELinuxStrategyMustRunAs SELinuxContextStrategy = "MustRunAs"
// container may make requests for any SELinux context labels.
SELinuxStrategyRunAsAny SELinuxContextStrategy = "RunAsAny"
)
// RunAsUserStrategyOptions defines the strategy type and any options used to create the strategy.
type RunAsUserStrategyOptions struct {
// Type is the strategy that will dictate the allowable RunAsUser values that may be set.
Type RunAsUserStrategy `json:"type"`
// Ranges are the allowed ranges of uids that may be used.
Ranges []IDRange `json:"ranges,omitempty"`
}
// IDRange provides a min/max of an allowed range of IDs.
type IDRange struct {
// Min is the start of the range, inclusive.
Min int64 `json:"min"`
// Max is the end of the range, inclusive.
Max int64 `json:"max"`
}
// RunAsUserStrategyType denotes strategy types for generating RunAsUser values for a
// SecurityContext.
type RunAsUserStrategy string
const (
// container must run as a particular uid.
RunAsUserStrategyMustRunAs RunAsUserStrategy = "MustRunAs"
// container must run as a non-root uid
RunAsUserStrategyMustRunAsNonRoot RunAsUserStrategy = "MustRunAsNonRoot"
// container may make requests for any uid.
RunAsUserStrategyRunAsAny RunAsUserStrategy = "RunAsAny"
)
// PodSecurityPolicyList is a list of PodSecurityPolicy objects.
type PodSecurityPolicyList struct {
unversioned.TypeMeta `json:",inline"`
unversioned.ListMeta `json:"metadata,omitempty"`
Items []PodSecurityPolicy `json:"items"`
}
...@@ -1319,6 +1319,18 @@ func deepCopy_v1beta1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerSt ...@@ -1319,6 +1319,18 @@ func deepCopy_v1beta1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerSt
return nil return nil
} }
func deepCopy_v1beta1_HostPortRange(in HostPortRange, out *HostPortRange, c *conversion.Cloner) error {
out.Min = in.Min
out.Max = in.Max
return nil
}
func deepCopy_v1beta1_IDRange(in IDRange, out *IDRange, c *conversion.Cloner) error {
out.Min = in.Min
out.Max = in.Max
return nil
}
func deepCopy_v1beta1_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) error { func deepCopy_v1beta1_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
...@@ -1587,6 +1599,79 @@ func deepCopy_v1beta1_NodeUtilization(in NodeUtilization, out *NodeUtilization, ...@@ -1587,6 +1599,79 @@ func deepCopy_v1beta1_NodeUtilization(in NodeUtilization, out *NodeUtilization,
return nil return nil
} }
func deepCopy_v1beta1_PodSecurityPolicy(in PodSecurityPolicy, out *PodSecurityPolicy, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
if err := deepCopy_v1beta1_PodSecurityPolicySpec(in.Spec, &out.Spec, c); err != nil {
return err
}
return nil
}
func deepCopy_v1beta1_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSecurityPolicyList, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
return err
}
if in.Items != nil {
out.Items = make([]PodSecurityPolicy, len(in.Items))
for i := range in.Items {
if err := deepCopy_v1beta1_PodSecurityPolicy(in.Items[i], &out.Items[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func deepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error {
out.Privileged = in.Privileged
if in.Capabilities != nil {
out.Capabilities = make([]v1.Capability, len(in.Capabilities))
for i := range in.Capabilities {
out.Capabilities[i] = in.Capabilities[i]
}
} else {
out.Capabilities = nil
}
if in.Volumes != nil {
out.Volumes = make([]FSType, len(in.Volumes))
for i := range in.Volumes {
out.Volumes[i] = in.Volumes[i]
}
} else {
out.Volumes = nil
}
out.HostNetwork = in.HostNetwork
if in.HostPorts != nil {
out.HostPorts = make([]HostPortRange, len(in.HostPorts))
for i := range in.HostPorts {
if err := deepCopy_v1beta1_HostPortRange(in.HostPorts[i], &out.HostPorts[i], c); err != nil {
return err
}
}
} else {
out.HostPorts = nil
}
out.HostPID = in.HostPID
out.HostIPC = in.HostIPC
if err := deepCopy_v1beta1_SELinuxContextStrategyOptions(in.SELinuxContext, &out.SELinuxContext, c); err != nil {
return err
}
if err := deepCopy_v1beta1_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil {
return err
}
return nil
}
func deepCopy_v1beta1_ReplicaSet(in ReplicaSet, out *ReplicaSet, c *conversion.Cloner) error { func deepCopy_v1beta1_ReplicaSet(in ReplicaSet, out *ReplicaSet, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
...@@ -1700,6 +1785,34 @@ func deepCopy_v1beta1_RollingUpdateDeployment(in RollingUpdateDeployment, out *R ...@@ -1700,6 +1785,34 @@ func deepCopy_v1beta1_RollingUpdateDeployment(in RollingUpdateDeployment, out *R
return nil return nil
} }
func deepCopy_v1beta1_RunAsUserStrategyOptions(in RunAsUserStrategyOptions, out *RunAsUserStrategyOptions, c *conversion.Cloner) error {
out.Type = in.Type
if in.Ranges != nil {
out.Ranges = make([]IDRange, len(in.Ranges))
for i := range in.Ranges {
if err := deepCopy_v1beta1_IDRange(in.Ranges[i], &out.Ranges[i], c); err != nil {
return err
}
}
} else {
out.Ranges = nil
}
return nil
}
func deepCopy_v1beta1_SELinuxContextStrategyOptions(in SELinuxContextStrategyOptions, out *SELinuxContextStrategyOptions, c *conversion.Cloner) error {
out.Type = in.Type
if in.SELinuxOptions != nil {
out.SELinuxOptions = new(v1.SELinuxOptions)
if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil {
return err
}
} else {
out.SELinuxOptions = nil
}
return nil
}
func deepCopy_v1beta1_Scale(in Scale, out *Scale, c *conversion.Cloner) error { func deepCopy_v1beta1_Scale(in Scale, out *Scale, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
...@@ -1901,6 +2014,8 @@ func init() { ...@@ -1901,6 +2014,8 @@ func init() {
deepCopy_v1beta1_HorizontalPodAutoscalerList, deepCopy_v1beta1_HorizontalPodAutoscalerList,
deepCopy_v1beta1_HorizontalPodAutoscalerSpec, deepCopy_v1beta1_HorizontalPodAutoscalerSpec,
deepCopy_v1beta1_HorizontalPodAutoscalerStatus, deepCopy_v1beta1_HorizontalPodAutoscalerStatus,
deepCopy_v1beta1_HostPortRange,
deepCopy_v1beta1_IDRange,
deepCopy_v1beta1_Ingress, deepCopy_v1beta1_Ingress,
deepCopy_v1beta1_IngressBackend, deepCopy_v1beta1_IngressBackend,
deepCopy_v1beta1_IngressList, deepCopy_v1beta1_IngressList,
...@@ -1917,6 +2032,9 @@ func init() { ...@@ -1917,6 +2032,9 @@ func init() {
deepCopy_v1beta1_LabelSelectorRequirement, deepCopy_v1beta1_LabelSelectorRequirement,
deepCopy_v1beta1_ListOptions, deepCopy_v1beta1_ListOptions,
deepCopy_v1beta1_NodeUtilization, deepCopy_v1beta1_NodeUtilization,
deepCopy_v1beta1_PodSecurityPolicy,
deepCopy_v1beta1_PodSecurityPolicyList,
deepCopy_v1beta1_PodSecurityPolicySpec,
deepCopy_v1beta1_ReplicaSet, deepCopy_v1beta1_ReplicaSet,
deepCopy_v1beta1_ReplicaSetList, deepCopy_v1beta1_ReplicaSetList,
deepCopy_v1beta1_ReplicaSetSpec, deepCopy_v1beta1_ReplicaSetSpec,
...@@ -1925,6 +2043,8 @@ func init() { ...@@ -1925,6 +2043,8 @@ func init() {
deepCopy_v1beta1_RollbackConfig, deepCopy_v1beta1_RollbackConfig,
deepCopy_v1beta1_RollingUpdateDaemonSet, deepCopy_v1beta1_RollingUpdateDaemonSet,
deepCopy_v1beta1_RollingUpdateDeployment, deepCopy_v1beta1_RollingUpdateDeployment,
deepCopy_v1beta1_RunAsUserStrategyOptions,
deepCopy_v1beta1_SELinuxContextStrategyOptions,
deepCopy_v1beta1_Scale, deepCopy_v1beta1_Scale,
deepCopy_v1beta1_ScaleSpec, deepCopy_v1beta1_ScaleSpec,
deepCopy_v1beta1_ScaleStatus, deepCopy_v1beta1_ScaleStatus,
......
...@@ -60,6 +60,8 @@ func addKnownTypes(scheme *runtime.Scheme) { ...@@ -60,6 +60,8 @@ func addKnownTypes(scheme *runtime.Scheme) {
&v1.DeleteOptions{}, &v1.DeleteOptions{},
&ReplicaSet{}, &ReplicaSet{},
&ReplicaSetList{}, &ReplicaSetList{},
&PodSecurityPolicy{},
&PodSecurityPolicyList{},
) )
} }
...@@ -85,3 +87,5 @@ func (obj *IngressList) GetObjectKind() unversioned.ObjectKind { ...@@ -85,3 +87,5 @@ func (obj *IngressList) GetObjectKind() unversioned.ObjectKind {
func (obj *ListOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ListOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ReplicaSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ReplicaSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *ReplicaSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ReplicaSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PodSecurityPolicy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PodSecurityPolicyList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -917,3 +917,128 @@ type ReplicaSetStatus struct { ...@@ -917,3 +917,128 @@ type ReplicaSetStatus struct {
// ObservedGeneration reflects the generation of the most recently observed ReplicaSet. // ObservedGeneration reflects the generation of the most recently observed ReplicaSet.
ObservedGeneration int64 `json:"observedGeneration,omitempty"` ObservedGeneration int64 `json:"observedGeneration,omitempty"`
} }
// Pod Security Policy governs the ability to make requests that affect the Security Context
// that will be applied to a pod and container.
type PodSecurityPolicy struct {
unversioned.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
v1.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the policy enforced.
Spec PodSecurityPolicySpec `json:"spec,omitempty"`
}
// Pod Security Policy Spec defines the policy enforced.
type PodSecurityPolicySpec struct {
// privileged determines if a pod can request to be run as privileged.
Privileged bool `json:"privileged,omitempty"`
// capabilities is a list of capabilities that can be added.
Capabilities []v1.Capability `json:"capabilities,omitempty"`
// volumes is a white list of allowed volume plugins. Empty indicates that all plugins
// may be used.
Volumes []FSType `json:"volumes,omitempty"`
// hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.
HostNetwork bool `json:"hostNetwork,omitempty"`
// hostPorts determines which host port ranges are allowed to be exposed.
HostPorts []HostPortRange `json:"hostPorts,omitempty"`
// hostPID determines if the policy allows the use of HostPID in the pod spec.
HostPID bool `json:"hostPID,omitempty"`
// hostIPC determines if the policy allows the use of HostIPC in the pod spec.
HostIPC bool `json:"hostIPC,omitempty"`
// seLinuxContext is the strategy that will dictate the allowable labels that may be set.
SELinuxContext SELinuxContextStrategyOptions `json:"seLinuxContext,omitempty"`
// runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.
RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty"`
}
// FS Type gives strong typing to different file systems that are used by volumes.
type FSType string
var (
HostPath FSType = "hostPath"
EmptyDir FSType = "emptyDir"
GCEPersistentDisk FSType = "gcePersistentDisk"
AWSElasticBlockStore FSType = "awsElasticBlockStore"
GitRepo FSType = "gitRepo"
Secret FSType = "secret"
NFS FSType = "nfs"
ISCSI FSType = "iscsi"
Glusterfs FSType = "glusterfs"
PersistentVolumeClaim FSType = "persistentVolumeClaim"
RBD FSType = "rbd"
Cinder FSType = "cinder"
CephFS FSType = "cephFS"
DownwardAPI FSType = "downwardAPI"
FC FSType = "fc"
)
// Host Port Range defines a range of host ports that will be enabled by a policy
// for pods to use. It requires both the start and end to be defined.
type HostPortRange struct {
// min is the start of the range, inclusive.
Min int32 `json:"min"`
// max is the end of the range, inclusive.
Max int32 `json:"max"`
}
// SELinux Context Strategy Options defines the strategy type and any options used to create the strategy.
type SELinuxContextStrategyOptions struct {
// type is the strategy that will dictate the allowable labels that may be set.
Type SELinuxContextStrategy `json:"type"`
// seLinuxOptions required to run as; required for MustRunAs
// More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context
SELinuxOptions *v1.SELinuxOptions `json:"seLinuxOptions,omitempty"`
}
// SELinux Context Strategy Type denotes strategy types for generating SELinux options for a
// Security Context.
type SELinuxContextStrategy string
const (
// container must have SELinux labels of X applied.
SELinuxStrategyMustRunAs SELinuxContextStrategy = "MustRunAs"
// container may make requests for any SELinux context labels.
SELinuxStrategyRunAsAny SELinuxContextStrategy = "RunAsAny"
)
// Run A sUser Strategy Options defines the strategy type and any options used to create the strategy.
type RunAsUserStrategyOptions struct {
// type is the strategy that will dictate the allowable RunAsUser values that may be set.
Type RunAsUserStrategy `json:"type"`
// Ranges are the allowed ranges of uids that may be used.
Ranges []IDRange `json:"ranges,omitempty"`
}
// ID Range provides a min/max of an allowed range of IDs.
type IDRange struct {
// Min is the start of the range, inclusive.
Min int64 `json:"min"`
// Max is the end of the range, inclusive.
Max int64 `json:"max"`
}
// Run As User Strategy Type denotes strategy types for generating RunAsUser values for a
// Security Context.
type RunAsUserStrategy string
const (
// container must run as a particular uid.
RunAsUserStrategyMustRunAs RunAsUserStrategy = "MustRunAs"
// container must run as a non-root uid
RunAsUserStrategyMustRunAsNonRoot RunAsUserStrategy = "MustRunAsNonRoot"
// container may make requests for any uid.
RunAsUserStrategyRunAsAny RunAsUserStrategy = "RunAsAny"
)
// Pod Security Policy List is a list of PodSecurityPolicy objects.
type PodSecurityPolicyList struct {
unversioned.TypeMeta `json:",inline"`
// Standard list metadata.
// More info: http://docs.k8s.io/api-conventions.md#metadata
unversioned.ListMeta `json:"metadata,omitempty"`
// Items is a list of schema objects.
Items []PodSecurityPolicy `json:"items"`
}
...@@ -292,6 +292,26 @@ func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { ...@@ -292,6 +292,26 @@ func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string {
return map_HorizontalPodAutoscalerStatus return map_HorizontalPodAutoscalerStatus
} }
var map_HostPortRange = map[string]string{
"": "Host Port Range defines a range of host ports that will be enabled by a policy for pods to use. It requires both the start and end to be defined.",
"min": "min is the start of the range, inclusive.",
"max": "max is the end of the range, inclusive.",
}
func (HostPortRange) SwaggerDoc() map[string]string {
return map_HostPortRange
}
var map_IDRange = map[string]string{
"": "ID Range provides a min/max of an allowed range of IDs.",
"min": "Min is the start of the range, inclusive.",
"max": "Max is the end of the range, inclusive.",
}
func (IDRange) SwaggerDoc() map[string]string {
return map_IDRange
}
var map_Ingress = map[string]string{ var map_Ingress = map[string]string{
"": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.", "": "Ingress is a collection of rules that allow inbound connections to reach the endpoints defined by a backend. An Ingress can be configured to give services externally-reachable urls, load balance traffic, terminate SSL, offer name based virtual hosting etc.",
"metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata",
...@@ -464,6 +484,43 @@ func (NodeUtilization) SwaggerDoc() map[string]string { ...@@ -464,6 +484,43 @@ func (NodeUtilization) SwaggerDoc() map[string]string {
return map_NodeUtilization return map_NodeUtilization
} }
var map_PodSecurityPolicy = map[string]string{
"": "Pod Security Policy governs the ability to make requests that affect the Security Context that will be applied to a pod and container.",
"metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata",
"spec": "Spec defines the policy enforced.",
}
func (PodSecurityPolicy) SwaggerDoc() map[string]string {
return map_PodSecurityPolicy
}
var map_PodSecurityPolicyList = map[string]string{
"": "Pod Security Policy List is a list of PodSecurityPolicy objects.",
"metadata": "Standard list metadata. More info: http://docs.k8s.io/api-conventions.md#metadata",
"items": "Items is a list of schema objects.",
}
func (PodSecurityPolicyList) SwaggerDoc() map[string]string {
return map_PodSecurityPolicyList
}
var map_PodSecurityPolicySpec = map[string]string{
"": "Pod Security Policy Spec defines the policy enforced.",
"privileged": "privileged determines if a pod can request to be run as privileged.",
"capabilities": "capabilities is a list of capabilities that can be added.",
"volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.",
"hostNetwork": "hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.",
"hostPorts": "hostPorts determines which host port ranges are allowed to be exposed.",
"hostPID": "hostPID determines if the policy allows the use of HostPID in the pod spec.",
"hostIPC": "hostIPC determines if the policy allows the use of HostIPC in the pod spec.",
"seLinuxContext": "seLinuxContext is the strategy that will dictate the allowable labels that may be set.",
"runAsUser": "runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.",
}
func (PodSecurityPolicySpec) SwaggerDoc() map[string]string {
return map_PodSecurityPolicySpec
}
var map_ReplicaSet = map[string]string{ var map_ReplicaSet = map[string]string{
"": "ReplicaSet represents the configuration of a ReplicaSet.", "": "ReplicaSet represents the configuration of a ReplicaSet.",
"metadata": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "metadata": "If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata",
...@@ -542,6 +599,26 @@ func (RollingUpdateDeployment) SwaggerDoc() map[string]string { ...@@ -542,6 +599,26 @@ func (RollingUpdateDeployment) SwaggerDoc() map[string]string {
return map_RollingUpdateDeployment return map_RollingUpdateDeployment
} }
var map_RunAsUserStrategyOptions = map[string]string{
"": "Run A sUser Strategy Options defines the strategy type and any options used to create the strategy.",
"type": "type is the strategy that will dictate the allowable RunAsUser values that may be set.",
"ranges": "Ranges are the allowed ranges of uids that may be used.",
}
func (RunAsUserStrategyOptions) SwaggerDoc() map[string]string {
return map_RunAsUserStrategyOptions
}
var map_SELinuxContextStrategyOptions = map[string]string{
"": "SELinux Context Strategy Options defines the strategy type and any options used to create the strategy.",
"type": "type is the strategy that will dictate the allowable labels that may be set.",
"seLinuxOptions": "seLinuxOptions required to run as; required for MustRunAs More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context",
}
func (SELinuxContextStrategyOptions) SwaggerDoc() map[string]string {
return map_SELinuxContextStrategyOptions
}
var map_Scale = map[string]string{ var map_Scale = map[string]string{
"": "represents a scaling request for a resource.", "": "represents a scaling request for a resource.",
"metadata": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata.", "metadata": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata.",
......
...@@ -697,3 +697,116 @@ func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selecto ...@@ -697,3 +697,116 @@ func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selecto
} }
return allErrs return allErrs
} }
// ValidatePodSecurityPolicyName can be used to check whether the given
// pod security policy name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
func ValidatePodSecurityPolicyName(name string, prefix bool) (bool, string) {
return apivalidation.NameIsDNSSubdomain(name, prefix)
}
func ValidatePodSecurityPolicy(psp *extensions.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
return allErrs
}
func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...)
allErrs = append(allErrs, validatePSPSELinuxContext(fldPath.Child("seLinuxContext"), &spec.SELinuxContext)...)
allErrs = append(allErrs, validatePodSecurityPolicyVolumes(fldPath, spec.Volumes)...)
return allErrs
}
// validatePSPSELinuxContext validates the SELinuxContext fields of PodSecurityPolicy.
func validatePSPSELinuxContext(fldPath *field.Path, seLinuxContext *extensions.SELinuxContextStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
// ensure the selinux strategy has a valid type
supportedSELinuxContextTypes := sets.NewString(string(extensions.SELinuxStrategyMustRunAs),
string(extensions.SELinuxStrategyRunAsAny))
if !supportedSELinuxContextTypes.Has(string(seLinuxContext.Type)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), seLinuxContext.Type, supportedSELinuxContextTypes.List()))
}
return allErrs
}
// validatePSPRunAsUser validates the RunAsUser fields of PodSecurityPolicy.
func validatePSPRunAsUser(fldPath *field.Path, runAsUser *extensions.RunAsUserStrategyOptions) field.ErrorList {
allErrs := field.ErrorList{}
// ensure the user strategy has a valid type
supportedRunAsUserTypes := sets.NewString(string(extensions.RunAsUserStrategyMustRunAs),
string(extensions.RunAsUserStrategyMustRunAsNonRoot),
string(extensions.RunAsUserStrategyRunAsAny))
if !supportedRunAsUserTypes.Has(string(runAsUser.Type)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), runAsUser.Type, supportedRunAsUserTypes.List()))
}
// validate range settings
for idx, rng := range runAsUser.Ranges {
allErrs = append(allErrs, validateIDRanges(fldPath.Child("ranges").Index(idx), rng)...)
}
return allErrs
}
// validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy.
func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []extensions.FSType) field.ErrorList {
allErrs := field.ErrorList{}
allowed := sets.NewString(string(extensions.HostPath),
string(extensions.EmptyDir),
string(extensions.GCEPersistentDisk),
string(extensions.AWSElasticBlockStore),
string(extensions.GitRepo),
string(extensions.Secret),
string(extensions.NFS),
string(extensions.ISCSI),
string(extensions.Glusterfs),
string(extensions.PersistentVolumeClaim),
string(extensions.RBD),
string(extensions.Cinder),
string(extensions.CephFS),
string(extensions.DownwardAPI),
string(extensions.FC))
for _, v := range volumes {
if !allowed.Has(string(v)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List()))
}
}
return allErrs
}
// validateIDRanges ensures the range is valid.
func validateIDRanges(fldPath *field.Path, rng extensions.IDRange) field.ErrorList {
allErrs := field.ErrorList{}
// if 0 <= Min <= Max then we do not need to validate max. It is always greater than or
// equal to 0 and Min.
if rng.Min < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), rng.Min, "min cannot be negative"))
}
if rng.Max < 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("max"), rng.Max, "max cannot be negative"))
}
if rng.Min > rng.Max {
allErrs = append(allErrs, field.Invalid(fldPath.Child("min"), rng.Min, "min cannot be greater than max"))
}
return allErrs
}
// ValidatePodSecurityPolicyUpdate validates a PSP for updates.
func ValidatePodSecurityPolicyUpdate(old *extensions.PodSecurityPolicy, new *extensions.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&old.ObjectMeta, &new.ObjectMeta, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, field.NewPath("spec"))...)
return allErrs
}
...@@ -1977,3 +1977,126 @@ func newInt(val int) *int { ...@@ -1977,3 +1977,126 @@ func newInt(val int) *int {
*p = val *p = val
return p return p
} }
func TestValidatePodSecurityPolicy(t *testing.T) {
validSCC := func() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: extensions.PodSecurityPolicySpec{
SELinuxContext: extensions.SELinuxContextStrategyOptions{
Type: extensions.SELinuxStrategyRunAsAny,
},
RunAsUser: extensions.RunAsUserStrategyOptions{
Type: extensions.RunAsUserStrategyRunAsAny,
},
},
}
}
noUserOptions := validSCC()
noUserOptions.Spec.RunAsUser.Type = ""
noSELinuxOptions := validSCC()
noSELinuxOptions.Spec.SELinuxContext.Type = ""
invalidUserStratType := validSCC()
invalidUserStratType.Spec.RunAsUser.Type = "invalid"
invalidSELinuxStratType := validSCC()
invalidSELinuxStratType.Spec.SELinuxContext.Type = "invalid"
missingObjectMetaName := validSCC()
missingObjectMetaName.ObjectMeta.Name = ""
invalidRangeMinGreaterThanMax := validSCC()
invalidRangeMinGreaterThanMax.Spec.RunAsUser.Ranges = []extensions.IDRange{
{Min: 2, Max: 1},
}
invalidRangeNegativeMin := validSCC()
invalidRangeNegativeMin.Spec.RunAsUser.Ranges = []extensions.IDRange{
{Min: -1, Max: 10},
}
invalidRangeNegativeMax := validSCC()
invalidRangeNegativeMax.Spec.RunAsUser.Ranges = []extensions.IDRange{
{Min: 1, Max: -10},
}
errorCases := map[string]struct {
scc *extensions.PodSecurityPolicy
errorDetail string
}{
"no user options": {
scc: noUserOptions,
errorDetail: "supported values: MustRunAs, MustRunAsNonRoot, RunAsAny",
},
"no selinux options": {
scc: noSELinuxOptions,
errorDetail: "supported values: MustRunAs, RunAsAny",
},
"invalid user strategy type": {
scc: invalidUserStratType,
errorDetail: "supported values: MustRunAs, MustRunAsNonRoot, RunAsAny",
},
"invalid selinux strategy type": {
scc: invalidSELinuxStratType,
errorDetail: "supported values: MustRunAs, RunAsAny",
},
"missing object meta name": {
scc: missingObjectMetaName,
errorDetail: "name or generateName is required",
},
"invalid range min greater than max": {
scc: invalidRangeMinGreaterThanMax,
errorDetail: "min cannot be greater than max",
},
"invalid range negative min": {
scc: invalidRangeNegativeMin,
errorDetail: "min cannot be negative",
},
"invalid range negative max": {
scc: invalidRangeNegativeMax,
errorDetail: "max cannot be negative",
},
}
for k, v := range errorCases {
if errs := ValidatePodSecurityPolicy(v.scc); len(errs) == 0 || errs[0].Detail != v.errorDetail {
t.Errorf("Expected error with detail %s for %s, got %v", v.errorDetail, k, errs[0].Detail)
}
}
mustRunAs := validSCC()
mustRunAs.Spec.RunAsUser.Type = extensions.RunAsUserStrategyMustRunAs
mustRunAs.Spec.RunAsUser.Ranges = []extensions.IDRange{
{
Min: 1,
Max: 1,
},
}
mustRunAs.Spec.SELinuxContext.Type = extensions.SELinuxStrategyMustRunAs
runAsNonRoot := validSCC()
runAsNonRoot.Spec.RunAsUser.Type = extensions.RunAsUserStrategyMustRunAsNonRoot
successCases := map[string]struct {
scc *extensions.PodSecurityPolicy
}{
"must run as": {
scc: mustRunAs,
},
"run as any": {
scc: validSCC(),
},
"run as non-root (user only)": {
scc: runAsNonRoot,
},
}
for k, v := range successCases {
if errs := ValidatePodSecurityPolicy(v.scc); len(errs) != 0 {
t.Errorf("Expected success for %s, got %v", k, errs)
}
}
}
...@@ -35,6 +35,7 @@ type ExtensionsInterface interface { ...@@ -35,6 +35,7 @@ type ExtensionsInterface interface {
IngressNamespacer IngressNamespacer
ThirdPartyResourceNamespacer ThirdPartyResourceNamespacer
ReplicaSetsNamespacer ReplicaSetsNamespacer
PodSecurityPoliciesInterface
} }
// ExtensionsClient is used to interact with experimental Kubernetes features. // ExtensionsClient is used to interact with experimental Kubernetes features.
...@@ -44,6 +45,10 @@ type ExtensionsClient struct { ...@@ -44,6 +45,10 @@ type ExtensionsClient struct {
*RESTClient *RESTClient
} }
func (c *ExtensionsClient) PodSecurityPolicies() PodSecurityPolicyInterface {
return newPodSecurityPolicy(c)
}
func (c *ExtensionsClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { func (c *ExtensionsClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface {
return newHorizontalPodAutoscalers(c, namespace) return newHorizontalPodAutoscalers(c, namespace)
} }
......
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/watch"
)
type PodSecurityPoliciesInterface interface {
PodSecurityPolicies() PodSecurityPolicyInterface
}
type PodSecurityPolicyInterface interface {
Get(name string) (result *extensions.PodSecurityPolicy, err error)
Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error)
List(opts api.ListOptions) (*extensions.PodSecurityPolicyList, error)
Delete(name string) error
Update(*extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error)
Watch(opts api.ListOptions) (watch.Interface, error)
}
// podSecurityPolicy implements PodSecurityPolicyInterface
type podSecurityPolicy struct {
client *ExtensionsClient
}
// newPodSecurityPolicy returns a podSecurityPolicy object.
func newPodSecurityPolicy(c *ExtensionsClient) *podSecurityPolicy {
return &podSecurityPolicy{c}
}
func (s *podSecurityPolicy) Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) {
result := &extensions.PodSecurityPolicy{}
err := s.client.Post().
Resource("podsecuritypolicies").
Body(scc).
Do().
Into(result)
return result, err
}
// List returns a list of PodSecurityPolicies matching the selectors.
func (s *podSecurityPolicy) List(opts api.ListOptions) (*extensions.PodSecurityPolicyList, error) {
result := &extensions.PodSecurityPolicyList{}
err := s.client.Get().
Resource("podsecuritypolicies").
VersionedParams(&opts, api.ParameterCodec).
Do().
Into(result)
return result, err
}
// Get returns the given PodSecurityPolicy, or an error.
func (s *podSecurityPolicy) Get(name string) (*extensions.PodSecurityPolicy, error) {
result := &extensions.PodSecurityPolicy{}
err := s.client.Get().
Resource("podsecuritypolicies").
Name(name).
Do().
Into(result)
return result, err
}
// Watch starts watching for PodSecurityPolicies matching the given selectors.
func (s *podSecurityPolicy) Watch(opts api.ListOptions) (watch.Interface, error) {
return s.client.Get().
Prefix("watch").
Resource("podsecuritypolicies").
VersionedParams(&opts, api.ParameterCodec).
Watch()
}
func (s *podSecurityPolicy) Delete(name string) error {
return s.client.Delete().
Resource("podsecuritypolicies").
Name(name).
Do().
Error()
}
func (s *podSecurityPolicy) Update(psp *extensions.PodSecurityPolicy) (result *extensions.PodSecurityPolicy, err error) {
result = &extensions.PodSecurityPolicy{}
err = s.client.Put().
Resource("podsecuritypolicies").
Name(psp.Name).
Body(psp).
Do().
Into(result)
return
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned_test
import (
"fmt"
"net/url"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/extensions"
. "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/testclient/simple"
)
func TestPodSecurityPolicyCreate(t *testing.T) {
ns := api.NamespaceNone
scc := &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "abc",
},
}
c := &simple.Client{
Request: simple.Request{
Method: "POST",
Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, ""),
Query: simple.BuildQueryValues(nil),
Body: scc,
},
Response: simple.Response{StatusCode: 200, Body: scc},
}
response, err := c.Setup(t).PodSecurityPolicies().Create(scc)
c.Validate(t, response, err)
}
func TestPodSecurityPolicyGet(t *testing.T) {
ns := api.NamespaceNone
scc := &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "abc",
},
}
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, "abc"),
Query: simple.BuildQueryValues(nil),
Body: nil,
},
Response: simple.Response{StatusCode: 200, Body: scc},
}
response, err := c.Setup(t).PodSecurityPolicies().Get("abc")
c.Validate(t, response, err)
}
func TestPodSecurityPolicyList(t *testing.T) {
ns := api.NamespaceNone
sccList := &extensions.PodSecurityPolicyList{
Items: []extensions.PodSecurityPolicy{
{
ObjectMeta: api.ObjectMeta{
Name: "abc",
},
},
},
}
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, ""),
Query: simple.BuildQueryValues(nil),
Body: nil,
},
Response: simple.Response{StatusCode: 200, Body: sccList},
}
response, err := c.Setup(t).PodSecurityPolicies().List(api.ListOptions{})
c.Validate(t, response, err)
}
func TestPodSecurityPolicyUpdate(t *testing.T) {
ns := api.NamespaceNone
scc := &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "abc",
ResourceVersion: "1",
},
}
c := &simple.Client{
Request: simple.Request{Method: "PUT", Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, "abc"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{StatusCode: 200, Body: scc},
}
response, err := c.Setup(t).PodSecurityPolicies().Update(scc)
c.Validate(t, response, err)
}
func TestPodSecurityPolicyDelete(t *testing.T) {
ns := api.NamespaceNone
c := &simple.Client{
Request: simple.Request{Method: "DELETE", Path: testapi.Extensions.ResourcePath(getPSPResourcename(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{StatusCode: 200},
}
err := c.Setup(t).PodSecurityPolicies().Delete("foo")
c.Validate(t, nil, err)
}
func TestPodSecurityPolicyWatch(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: fmt.Sprintf("%s/watch/%s", testapi.Extensions.ResourcePath("", "", ""), getPSPResourcename()),
Query: url.Values{"resourceVersion": []string{}}},
Response: simple.Response{StatusCode: 200},
}
_, err := c.Setup(t).PodSecurityPolicies().Watch(api.ListOptions{})
c.Validate(t, nil, err)
}
func getPSPResourcename() string {
return "podsecuritypolicies"
}
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testclient
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/watch"
)
// FakePodSecurityPolicy implements PodSecurityPolicyInterface. Meant to be
// embedded into a struct to get a default implementation. This makes faking out just
// the method you want to test easier.
type FakePodSecurityPolicy struct {
Fake *Fake
Namespace string
}
func (c *FakePodSecurityPolicy) List(opts api.ListOptions) (*extensions.PodSecurityPolicyList, error) {
obj, err := c.Fake.Invokes(NewListAction("podsecuritypolicies", c.Namespace, opts), &extensions.PodSecurityPolicyList{})
if obj == nil {
return nil, err
}
return obj.(*extensions.PodSecurityPolicyList), err
}
func (c *FakePodSecurityPolicy) Get(name string) (*extensions.PodSecurityPolicy, error) {
obj, err := c.Fake.Invokes(NewGetAction("podsecuritypolicies", c.Namespace, name), &extensions.PodSecurityPolicy{})
if obj == nil {
return nil, err
}
return obj.(*extensions.PodSecurityPolicy), err
}
func (c *FakePodSecurityPolicy) Create(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) {
obj, err := c.Fake.Invokes(NewCreateAction("podsecuritypolicies", c.Namespace, scc), &extensions.PodSecurityPolicy{})
if obj == nil {
return nil, err
}
return obj.(*extensions.PodSecurityPolicy), err
}
func (c *FakePodSecurityPolicy) Update(scc *extensions.PodSecurityPolicy) (*extensions.PodSecurityPolicy, error) {
obj, err := c.Fake.Invokes(NewUpdateAction("podsecuritypolicies", c.Namespace, scc), &extensions.PodSecurityPolicy{})
if obj == nil {
return nil, err
}
return obj.(*extensions.PodSecurityPolicy), err
}
func (c *FakePodSecurityPolicy) Delete(name string) error {
_, err := c.Fake.Invokes(NewDeleteAction("podsecuritypolicies", c.Namespace, name), &extensions.PodSecurityPolicy{})
return err
}
func (c *FakePodSecurityPolicy) Watch(opts api.ListOptions) (watch.Interface, error) {
return c.Fake.InvokesWatch(NewWatchAction("podsecuritypolicies", c.Namespace, opts))
}
...@@ -230,6 +230,10 @@ func (c *Fake) Nodes() client.NodeInterface { ...@@ -230,6 +230,10 @@ func (c *Fake) Nodes() client.NodeInterface {
return &FakeNodes{Fake: c} return &FakeNodes{Fake: c}
} }
func (c *Fake) PodSecurityPolicies() client.PodSecurityPolicyInterface {
return &FakePodSecurityPolicy{Fake: c}
}
func (c *Fake) Events(namespace string) client.EventInterface { func (c *Fake) Events(namespace string) client.EventInterface {
return &FakeEvents{Fake: c, Namespace: namespace} return &FakeEvents{Fake: c, Namespace: namespace}
} }
......
...@@ -113,6 +113,7 @@ func expandResourceShortcut(resource unversioned.GroupVersionResource) unversion ...@@ -113,6 +113,7 @@ func expandResourceShortcut(resource unversioned.GroupVersionResource) unversion
"no": api.SchemeGroupVersion.WithResource("nodes"), "no": api.SchemeGroupVersion.WithResource("nodes"),
"ns": api.SchemeGroupVersion.WithResource("namespaces"), "ns": api.SchemeGroupVersion.WithResource("namespaces"),
"po": api.SchemeGroupVersion.WithResource("pods"), "po": api.SchemeGroupVersion.WithResource("pods"),
"psp": api.SchemeGroupVersion.WithResource("podSecurityPolicies"),
"pvc": api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), "pvc": api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
"pv": api.SchemeGroupVersion.WithResource("persistentvolumes"), "pv": api.SchemeGroupVersion.WithResource("persistentvolumes"),
"quota": api.SchemeGroupVersion.WithResource("resourcequotas"), "quota": api.SchemeGroupVersion.WithResource("resourcequotas"),
......
...@@ -415,6 +415,7 @@ var horizontalPodAutoscalerColumns = []string{"NAME", "REFERENCE", "TARGET", "CU ...@@ -415,6 +415,7 @@ var horizontalPodAutoscalerColumns = []string{"NAME", "REFERENCE", "TARGET", "CU
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
var deploymentColumns = []string{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"} var deploymentColumns = []string{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"}
var configMapColumns = []string{"NAME", "DATA", "AGE"} var configMapColumns = []string{"NAME", "DATA", "AGE"}
var podSecurityPolicyColumns = []string{"NAME", "PRIV", "CAPS", "VOLUMEPLUGINS", "SELINUX", "RUNASUSER"}
// addDefaultHandlers adds print handlers for default Kubernetes types. // addDefaultHandlers adds print handlers for default Kubernetes types.
func (h *HumanReadablePrinter) addDefaultHandlers() { func (h *HumanReadablePrinter) addDefaultHandlers() {
...@@ -462,6 +463,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { ...@@ -462,6 +463,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(horizontalPodAutoscalerColumns, printHorizontalPodAutoscalerList) h.Handler(horizontalPodAutoscalerColumns, printHorizontalPodAutoscalerList)
h.Handler(configMapColumns, printConfigMap) h.Handler(configMapColumns, printConfigMap)
h.Handler(configMapColumns, printConfigMapList) h.Handler(configMapColumns, printConfigMapList)
h.Handler(podSecurityPolicyColumns, printPodSecurityPolicy)
h.Handler(podSecurityPolicyColumns, printPodSecurityPolicyList)
} }
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
...@@ -1478,6 +1481,23 @@ func printConfigMapList(list *api.ConfigMapList, w io.Writer, options PrintOptio ...@@ -1478,6 +1481,23 @@ func printConfigMapList(list *api.ConfigMapList, w io.Writer, options PrintOptio
return nil return nil
} }
func printPodSecurityPolicy(item *extensions.PodSecurityPolicy, w io.Writer, options PrintOptions) error {
_, err := fmt.Fprintf(w, "%s\t%t\t%v\t%t\t%s\t%s\n", item.Name, item.Spec.Privileged,
item.Spec.Capabilities, item.Spec.Volumes, item.Spec.SELinuxContext.Type,
item.Spec.RunAsUser.Type)
return err
}
func printPodSecurityPolicyList(list *extensions.PodSecurityPolicyList, w io.Writer, options PrintOptions) error {
for _, item := range list.Items {
if err := printPodSecurityPolicy(&item, w, options); err != nil {
return err
}
}
return nil
}
func appendLabels(itemLabels map[string]string, columnLabels []string) string { func appendLabels(itemLabels map[string]string, columnLabels []string) string {
var buffer bytes.Buffer var buffer bytes.Buffer
......
...@@ -57,6 +57,7 @@ import ( ...@@ -57,6 +57,7 @@ import (
pvetcd "k8s.io/kubernetes/pkg/registry/persistentvolume/etcd" pvetcd "k8s.io/kubernetes/pkg/registry/persistentvolume/etcd"
pvcetcd "k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd" pvcetcd "k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd"
podetcd "k8s.io/kubernetes/pkg/registry/pod/etcd" podetcd "k8s.io/kubernetes/pkg/registry/pod/etcd"
pspetcd "k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd"
podtemplateetcd "k8s.io/kubernetes/pkg/registry/podtemplate/etcd" podtemplateetcd "k8s.io/kubernetes/pkg/registry/podtemplate/etcd"
resourcequotaetcd "k8s.io/kubernetes/pkg/registry/resourcequota/etcd" resourcequotaetcd "k8s.io/kubernetes/pkg/registry/resourcequota/etcd"
secretetcd "k8s.io/kubernetes/pkg/registry/secret/etcd" secretetcd "k8s.io/kubernetes/pkg/registry/secret/etcd"
...@@ -587,7 +588,7 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV ...@@ -587,7 +588,7 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
// getExperimentalResources returns the resources for extenstions api // getExperimentalResources returns the resources for extenstions api
func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
// All resources except these are disabled by default. // All resources except these are disabled by default.
enabledResources := sets.NewString("jobs", "horizontalpodautoscalers", "ingresses") enabledResources := sets.NewString("jobs", "horizontalpodautoscalers", "ingresses", "podsecuritypolicy")
resourceOverrides := m.ApiGroupVersionOverrides["extensions/v1beta1"].ResourceOverrides resourceOverrides := m.ApiGroupVersionOverrides["extensions/v1beta1"].ResourceOverrides
isEnabled := func(resource string) bool { isEnabled := func(resource string) bool {
// Check if the resource has been overriden. // Check if the resource has been overriden.
...@@ -650,6 +651,10 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { ...@@ -650,6 +651,10 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
storage["ingresses"] = ingressStorage storage["ingresses"] = ingressStorage
storage["ingresses/status"] = ingressStatusStorage storage["ingresses/status"] = ingressStatusStorage
} }
if isEnabled("podsecuritypolicy") {
podSecurityPolicyStorage := pspetcd.NewREST(dbClient("podsecuritypolicy"), storageDecorator)
storage["podSecurityPolicies"] = podSecurityPolicyStorage
}
return storage return storage
} }
......
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package podsecuritypolicy provides Registry interface and its REST
// implementation for storing PodSecurityPolicy api objects.
package podsecuritypolicy
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/registry/podsecuritypolicy"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
)
// REST implements a RESTStorage for PodSecurityPolicies against etcd.
type REST struct {
*etcdgeneric.Etcd
}
const Prefix = "/podsecuritypolicies"
// NewREST returns a RESTStorage object that will work against PodSecurityPolicy objects.
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) *REST {
newListFunc := func() runtime.Object { return &extensions.PodSecurityPolicyList{} }
storageInterface := storageDecorator(
s, 100, &extensions.PodSecurityPolicy{}, Prefix, podsecuritypolicy.Strategy, newListFunc)
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &extensions.PodSecurityPolicy{} },
NewListFunc: newListFunc,
KeyRootFunc: func(ctx api.Context) string {
return Prefix
},
KeyFunc: func(ctx api.Context, name string) (string, error) {
return etcdgeneric.NoNamespaceKeyFunc(ctx, Prefix, name)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*extensions.PodSecurityPolicy).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return podsecuritypolicy.MatchPodSecurityPolicy(label, field)
},
QualifiedResource: extensions.Resource("podsecuritypolicies"),
CreateStrategy: podsecuritypolicy.Strategy,
UpdateStrategy: podsecuritypolicy.Strategy,
ReturnDeletedObject: true,
Storage: storageInterface,
}
return &REST{store}
}
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
// Ensure that extensions/v1beta1 package is initialized.
_ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
)
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions")
return NewREST(etcdStorage, generic.UndecoratedStorage), server
}
func validNewPodSecurityPolicy() *extensions.PodSecurityPolicy {
return &extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: extensions.PodSecurityPolicySpec{
SELinuxContext: extensions.SELinuxContextStrategyOptions{
Type: extensions.SELinuxStrategyRunAsAny,
},
RunAsUser: extensions.RunAsUserStrategyOptions{
Type: extensions.RunAsUserStrategyRunAsAny,
},
},
}
}
func TestCreate(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope()
scc := validNewPodSecurityPolicy()
scc.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"}
test.TestCreate(
// valid
scc,
// invalid
&extensions.PodSecurityPolicy{
ObjectMeta: api.ObjectMeta{Name: "name with spaces"},
},
)
}
func TestUpdate(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope()
test.TestUpdate(
// valid
validNewPodSecurityPolicy(),
// updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*extensions.PodSecurityPolicy)
object.Labels = map[string]string{"a": "b"}
return object
},
)
}
func TestDelete(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope().ReturnDeletedObject()
test.TestDelete(validNewPodSecurityPolicy())
}
func TestGet(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope()
test.TestGet(validNewPodSecurityPolicy())
}
func TestList(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope()
test.TestList(validNewPodSecurityPolicy())
}
func TestWatch(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd).ClusterScope()
test.TestWatch(
validNewPodSecurityPolicy(),
// matching labels
[]labels.Set{},
// not matching labels
[]labels.Set{
{"foo": "bar"},
},
// matching fields
[]fields.Set{
{"metadata.name": "foo"},
},
// not matching fields
[]fields.Set{
{"metadata.name": "bar"},
{"name": "foo"},
},
)
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package podsecuritypolicy
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// strategy implements behavior for PodSecurityPolicy objects
type strategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating PodSecurityPolicy
// objects via the REST API.
var Strategy = strategy{api.Scheme, api.SimpleNameGenerator}
var _ = rest.RESTCreateStrategy(Strategy)
var _ = rest.RESTUpdateStrategy(Strategy)
func (strategy) NamespaceScoped() bool {
return false
}
func (strategy) AllowCreateOnUpdate() bool {
return false
}
func (strategy) AllowUnconditionalUpdate() bool {
return true
}
func (strategy) PrepareForCreate(obj runtime.Object) {
}
func (strategy) PrepareForUpdate(obj, old runtime.Object) {
}
func (strategy) Canonicalize(obj runtime.Object) {
}
func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
return validation.ValidatePodSecurityPolicy(obj.(*extensions.PodSecurityPolicy))
}
func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidatePodSecurityPolicyUpdate(old.(*extensions.PodSecurityPolicy), obj.(*extensions.PodSecurityPolicy))
}
// Matcher returns a generic matcher for a given label and field selector.
func MatchPodSecurityPolicy(label labels.Selector, field fields.Selector) generic.Matcher {
return &generic.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
psp, ok := obj.(*extensions.PodSecurityPolicy)
if !ok {
return nil, nil, fmt.Errorf("given object is not a pod security policy.")
}
return labels.Set(psp.ObjectMeta.Labels), PodSecurityPolicyToSelectableFields(psp), nil
},
}
}
// PodSecurityPolicyToSelectableFields returns a label set that represents the object
func PodSecurityPolicyToSelectableFields(obj *extensions.PodSecurityPolicy) fields.Set {
return generic.ObjectMetaFieldsSet(obj.ObjectMeta, false)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment