mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Merge pull request #35220 from MrHohn/kubectl-apply-type
Automatic merge from submit-queue Implement --prune-whitelist(-w) flag to overwrite default whitelist for --prune From #34274. Updates: As suggested, the new commits implement a default whitelist for `kubectl apply --prune`, which could be overwritten by using `--prune-whitelist`or `-w` flag. The default whitelist and example as below. Also supports `--dry-run` for `--prune` to fix #35222. whitelist: ``` type pruneResource struct { group string version string kind string namespaced bool } *pruneResources = []pruneResource{ {"", "v1", "ConfigMap", true}, {"", "v1", "Endpoints", true}, {"", "v1", "Namespace", false}, {"", "v1", "PersistentVolumeClaim", true}, {"", "v1", "PersistentVolume", false}, {"", "v1", "Pod", true}, {"", "v1", "ReplicationController", true}, {"", "v1", "Secret", true}, {"", "v1", "Service", true}, {"batch", "v1", "Job", true}, {"extensions", "v1beta1", "DaemonSet", true}, {"extensions", "v1beta1", "Deployment", true}, {"extensions", "v1beta1", "HorizontalPodAutoscaler", true}, {"extensions", "v1beta1", "Ingress", true}, {"extensions", "v1beta1", "ReplicaSet", true}, {"apps", "v1beta1", "StatefulSet", true}, } ``` example: ``` $ kubectl apply -f /path/to/file --prune -l test=true \ -w core/v1/Pod \ -w core/v1/Service \ -w extensions/v1beta1/Deployment ``` @mikedanese
This commit is contained in:
commit
e4edd817af
@ -1139,6 +1139,24 @@ __EOF__
|
|||||||
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
|
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
kubectl delete pvc b-pvc 2>&1 "${kube_flags[@]}"
|
kubectl delete pvc b-pvc 2>&1 "${kube_flags[@]}"
|
||||||
|
|
||||||
|
## kubectl apply --prune --prune-whitelist(-w)
|
||||||
|
# Pre-Condition: no POD exists
|
||||||
|
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
# apply pod a
|
||||||
|
kubectl apply --prune -l prune-group=true -f hack/testdata/prune/a.yaml "${kube_flags[@]}"
|
||||||
|
# check right pod exists
|
||||||
|
kube::test::get_object_assert 'pods a' "{{${id_field}}}" 'a'
|
||||||
|
# apply svc and don't prune pod a by overwriting whitelist
|
||||||
|
kubectl apply --prune -l prune-group=true -f hack/testdata/prune/svc.yaml -w core/v1/Service 2>&1 "${kube_flags[@]}"
|
||||||
|
kube::test::get_object_assert 'service prune-svc' "{{${id_field}}}" 'prune-svc'
|
||||||
|
kube::test::get_object_assert 'pods a' "{{${id_field}}}" 'a'
|
||||||
|
# apply svc and prune pod a with default whitelist
|
||||||
|
kubectl apply --prune -l prune-group=true -f hack/testdata/prune/svc.yaml 2>&1 "${kube_flags[@]}"
|
||||||
|
kube::test::get_object_assert 'service prune-svc' "{{${id_field}}}" 'prune-svc'
|
||||||
|
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
# cleanup
|
||||||
|
kubectl delete svc prune-svc 2>&1 "${kube_flags[@]}"
|
||||||
|
|
||||||
## kubectl run should create deployments or jobs
|
## kubectl run should create deployments or jobs
|
||||||
# Pre-Condition: no Job exists
|
# Pre-Condition: no Job exists
|
||||||
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" ''
|
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
12
hack/testdata/prune/svc.yaml
vendored
Normal file
12
hack/testdata/prune/svc.yaml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: prune-svc
|
||||||
|
labels:
|
||||||
|
prune-group: "true"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
prune-group-nomatch: "true"
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jonboulle/clockwork"
|
"github.com/jonboulle/clockwork"
|
||||||
@ -29,6 +30,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||||
@ -46,6 +48,7 @@ type ApplyOptions struct {
|
|||||||
Prune bool
|
Prune bool
|
||||||
Cascade bool
|
Cascade bool
|
||||||
GracePeriod int
|
GracePeriod int
|
||||||
|
PruneResources []pruneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -101,6 +104,7 @@ func NewCmdApply(f cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||||||
cmdutil.AddValidateFlags(cmd)
|
cmdutil.AddValidateFlags(cmd)
|
||||||
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on")
|
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on")
|
||||||
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.")
|
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.")
|
||||||
|
cmd.Flags().StringArrayP("prune-whitelist", "w", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune")
|
||||||
cmdutil.AddDryRunFlag(cmd)
|
cmdutil.AddDryRunFlag(cmd)
|
||||||
cmdutil.AddPrinterFlags(cmd)
|
cmdutil.AddPrinterFlags(cmd)
|
||||||
cmdutil.AddRecordFlag(cmd)
|
cmdutil.AddRecordFlag(cmd)
|
||||||
@ -123,6 +127,28 @@ func validatePruneAll(prune, all bool, selector string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePruneResources(gvks []string) ([]pruneResource, error) {
|
||||||
|
pruneResources := []pruneResource{}
|
||||||
|
for _, groupVersionKind := range gvks {
|
||||||
|
gvk := strings.Split(groupVersionKind, "/")
|
||||||
|
if len(gvk) != 3 {
|
||||||
|
return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow <group/version/kind>", groupVersionKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaced := true
|
||||||
|
if gvk[2] == "Namespace" ||
|
||||||
|
gvk[2] == "Node" ||
|
||||||
|
gvk[2] == "PersistentVolume" {
|
||||||
|
namespaced = false
|
||||||
|
}
|
||||||
|
if gvk[0] == "core" {
|
||||||
|
gvk[0] = ""
|
||||||
|
}
|
||||||
|
pruneResources = append(pruneResources, pruneResource{gvk[0], gvk[1], gvk[2], namespaced})
|
||||||
|
}
|
||||||
|
return pruneResources, nil
|
||||||
|
}
|
||||||
|
|
||||||
func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error {
|
func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error {
|
||||||
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
|
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
|
||||||
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
|
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
|
||||||
@ -135,6 +161,13 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.Prune {
|
||||||
|
options.PruneResources, err = parsePruneResources(cmdutil.GetFlagStringArray(cmd, "prune-whitelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapper, typer := f.Object()
|
mapper, typer := f.Object()
|
||||||
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
||||||
Schema(schema).
|
Schema(schema).
|
||||||
@ -156,8 +189,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
|
|||||||
|
|
||||||
visitedUids := sets.NewString()
|
visitedUids := sets.NewString()
|
||||||
visitedNamespaces := sets.NewString()
|
visitedNamespaces := sets.NewString()
|
||||||
visitedNamespacedRESTMappings := map[unversioned.GroupVersionKind]*meta.RESTMapping{}
|
|
||||||
visitedNonNamespacedRESTMappings := map[unversioned.GroupVersionKind]*meta.RESTMapping{}
|
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
err = r.Visit(func(info *resource.Info, err error) error {
|
err = r.Visit(func(info *resource.Info, err error) error {
|
||||||
@ -169,9 +200,6 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
|
|||||||
|
|
||||||
if info.Namespaced() {
|
if info.Namespaced() {
|
||||||
visitedNamespaces.Insert(info.Namespace)
|
visitedNamespaces.Insert(info.Namespace)
|
||||||
visitedNamespacedRESTMappings[info.Mapping.GroupVersionKind] = info.Mapping
|
|
||||||
} else {
|
|
||||||
visitedNonNamespacedRESTMappings[info.Mapping.GroupVersionKind] = info.Mapping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the modified configuration of the object. Embed the result
|
// Get the modified configuration of the object. Embed the result
|
||||||
@ -271,26 +299,83 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
|
|||||||
visitedUids: visitedUids,
|
visitedUids: visitedUids,
|
||||||
|
|
||||||
cascade: options.Cascade,
|
cascade: options.Cascade,
|
||||||
|
dryRun: dryRun,
|
||||||
gracePeriod: options.GracePeriod,
|
gracePeriod: options.GracePeriod,
|
||||||
|
|
||||||
out: out,
|
out: out,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(&(options.PruneResources))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving RESTMappings to prune: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
for n := range visitedNamespaces {
|
for n := range visitedNamespaces {
|
||||||
for _, m := range visitedNamespacedRESTMappings {
|
for _, m := range namespacedRESTMappings {
|
||||||
if err := p.prune(n, m, shortOutput); err != nil {
|
if err := p.prune(n, m, shortOutput); err != nil {
|
||||||
return fmt.Errorf("error pruning objects: %v", err)
|
return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range visitedNonNamespacedRESTMappings {
|
for _, m := range nonNamespacedRESTMappings {
|
||||||
if err := p.prune(api.NamespaceNone, m, shortOutput); err != nil {
|
if err := p.prune(api.NamespaceNone, m, shortOutput); err != nil {
|
||||||
return fmt.Errorf("error pruning objects: %v", err)
|
return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pruneResource struct {
|
||||||
|
group string
|
||||||
|
version string
|
||||||
|
kind string
|
||||||
|
namespaced bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr pruneResource) String() string {
|
||||||
|
return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRESTMappings(pruneResources *[]pruneResource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) {
|
||||||
|
if len(*pruneResources) == 0 {
|
||||||
|
// default whitelist
|
||||||
|
// TODO: need to handle the older api versions - e.g. v1beta1 jobs. Github issue: #35991
|
||||||
|
*pruneResources = []pruneResource{
|
||||||
|
{"", "v1", "ConfigMap", true},
|
||||||
|
{"", "v1", "Endpoints", true},
|
||||||
|
{"", "v1", "Namespace", false},
|
||||||
|
{"", "v1", "PersistentVolumeClaim", true},
|
||||||
|
{"", "v1", "PersistentVolume", false},
|
||||||
|
{"", "v1", "Pod", true},
|
||||||
|
{"", "v1", "ReplicationController", true},
|
||||||
|
{"", "v1", "Secret", true},
|
||||||
|
{"", "v1", "Service", true},
|
||||||
|
{"batch", "v1", "Job", true},
|
||||||
|
{"extensions", "v1beta1", "DaemonSet", true},
|
||||||
|
{"extensions", "v1beta1", "Deployment", true},
|
||||||
|
{"extensions", "v1beta1", "HorizontalPodAutoscaler", true},
|
||||||
|
{"extensions", "v1beta1", "Ingress", true},
|
||||||
|
{"extensions", "v1beta1", "ReplicaSet", true},
|
||||||
|
{"apps", "v1beta1", "StatefulSet", true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registeredMapper := registered.RESTMapper()
|
||||||
|
for _, resource := range *pruneResources {
|
||||||
|
addedMapping, err := registeredMapper.RESTMapping(unversioned.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err)
|
||||||
|
}
|
||||||
|
if resource.namespaced {
|
||||||
|
namespaced = append(namespaced, addedMapping)
|
||||||
|
} else {
|
||||||
|
nonNamespaced = append(nonNamespaced, addedMapping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return namespaced, nonNamespaced, nil
|
||||||
|
}
|
||||||
|
|
||||||
type pruner struct {
|
type pruner struct {
|
||||||
mapper meta.RESTMapper
|
mapper meta.RESTMapper
|
||||||
clientFunc resource.ClientMapperFunc
|
clientFunc resource.ClientMapperFunc
|
||||||
@ -300,6 +385,7 @@ type pruner struct {
|
|||||||
selector labels.Selector
|
selector labels.Selector
|
||||||
|
|
||||||
cascade bool
|
cascade bool
|
||||||
|
dryRun bool
|
||||||
gracePeriod int
|
gracePeriod int
|
||||||
|
|
||||||
out io.Writer
|
out io.Writer
|
||||||
@ -341,10 +427,12 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.delete(namespace, name, mapping, c); err != nil {
|
if !p.dryRun {
|
||||||
return err
|
if err := p.delete(namespace, name, mapping, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmdutil.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, false, "pruned")
|
cmdutil.PrintSuccess(p.mapper, shortOutput, p.out, mapping.Resource, name, p.dryRun, "pruned")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user