apply.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apply
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "io"
  18. "net/http"
  19. "strings"
  20. "time"
  21. "github.com/jonboulle/clockwork"
  22. "github.com/spf13/cobra"
  23. corev1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/errors"
  25. "k8s.io/apimachinery/pkg/api/meta"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  28. "k8s.io/apimachinery/pkg/runtime"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/apimachinery/pkg/types"
  31. "k8s.io/apimachinery/pkg/util/jsonmergepatch"
  32. "k8s.io/apimachinery/pkg/util/mergepatch"
  33. "k8s.io/apimachinery/pkg/util/sets"
  34. "k8s.io/apimachinery/pkg/util/strategicpatch"
  35. "k8s.io/apimachinery/pkg/util/wait"
  36. "k8s.io/cli-runtime/pkg/genericclioptions"
  37. "k8s.io/cli-runtime/pkg/printers"
  38. "k8s.io/cli-runtime/pkg/resource"
  39. "k8s.io/client-go/discovery"
  40. "k8s.io/client-go/dynamic"
  41. "k8s.io/klog"
  42. oapi "k8s.io/kube-openapi/pkg/util/proto"
  43. "k8s.io/kubernetes/pkg/kubectl"
  44. "k8s.io/kubernetes/pkg/kubectl/cmd/delete"
  45. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  46. "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
  47. "k8s.io/kubernetes/pkg/kubectl/scheme"
  48. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  49. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  50. "k8s.io/kubernetes/pkg/kubectl/validation"
  51. )
  52. // ApplyOptions defines flags and other configuration parameters for the `apply` command
  53. type ApplyOptions struct {
  54. RecordFlags *genericclioptions.RecordFlags
  55. Recorder genericclioptions.Recorder
  56. PrintFlags *genericclioptions.PrintFlags
  57. ToPrinter func(string) (printers.ResourcePrinter, error)
  58. DeleteFlags *delete.DeleteFlags
  59. DeleteOptions *delete.DeleteOptions
  60. ServerSideApply bool
  61. ForceConflicts bool
  62. FieldManager string
  63. Selector string
  64. DryRun bool
  65. ServerDryRun bool
  66. Prune bool
  67. PruneResources []pruneResource
  68. cmdBaseName string
  69. All bool
  70. Overwrite bool
  71. OpenAPIPatch bool
  72. PruneWhitelist []string
  73. Validator validation.Schema
  74. Builder *resource.Builder
  75. Mapper meta.RESTMapper
  76. DynamicClient dynamic.Interface
  77. DiscoveryClient discovery.DiscoveryInterface
  78. OpenAPISchema openapi.Resources
  79. Namespace string
  80. EnforceNamespace bool
  81. genericclioptions.IOStreams
  82. }
  83. const (
  84. // maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
  85. maxPatchRetry = 5
  86. // backOffPeriod is the period to back off when apply patch results in error.
  87. backOffPeriod = 1 * time.Second
  88. // how many times we can retry before back off
  89. triesBeforeBackOff = 1
  90. )
  91. var (
  92. applyLong = templates.LongDesc(i18n.T(`
  93. Apply a configuration to a resource by filename or stdin.
  94. The resource name must be specified. This resource will be created if it doesn't exist yet.
  95. To use 'apply', always create the resource initially with either 'apply' or 'create --save-config'.
  96. JSON and YAML formats are accepted.
  97. Alpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.`))
  98. applyExample = templates.Examples(i18n.T(`
  99. # Apply the configuration in pod.json to a pod.
  100. kubectl apply -f ./pod.json
  101. # Apply resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml.
  102. kubectl apply -k dir/
  103. # Apply the JSON passed into stdin to a pod.
  104. cat pod.json | kubectl apply -f -
  105. # Note: --prune is still in Alpha
  106. # Apply the configuration in manifest.yaml that matches label app=nginx and delete all the other resources that are not in the file and match label app=nginx.
  107. kubectl apply --prune -f manifest.yaml -l app=nginx
  108. # Apply the configuration in manifest.yaml and delete all the other configmaps that are not in the file.
  109. kubectl apply --prune -f manifest.yaml --all --prune-whitelist=core/v1/ConfigMap`))
  110. warningNoLastAppliedConfigAnnotation = "Warning: %[1]s apply should be used on resource created by either %[1]s create --save-config or %[1]s apply\n"
  111. )
  112. // NewApplyOptions creates new ApplyOptions for the `apply` command
  113. func NewApplyOptions(ioStreams genericclioptions.IOStreams) *ApplyOptions {
  114. return &ApplyOptions{
  115. RecordFlags: genericclioptions.NewRecordFlags(),
  116. DeleteFlags: delete.NewDeleteFlags("that contains the configuration to apply"),
  117. PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
  118. Overwrite: true,
  119. OpenAPIPatch: true,
  120. Recorder: genericclioptions.NoopRecorder{},
  121. IOStreams: ioStreams,
  122. }
  123. }
  124. // NewCmdApply creates the `apply` command
  125. func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  126. o := NewApplyOptions(ioStreams)
  127. // Store baseName for use in printing warnings / messages involving the base command name.
  128. // This is useful for downstream command that wrap this one.
  129. o.cmdBaseName = baseName
  130. cmd := &cobra.Command{
  131. Use: "apply (-f FILENAME | -k DIRECTORY)",
  132. DisableFlagsInUseLine: true,
  133. Short: i18n.T("Apply a configuration to a resource by filename or stdin"),
  134. Long: applyLong,
  135. Example: applyExample,
  136. Run: func(cmd *cobra.Command, args []string) {
  137. cmdutil.CheckErr(o.Complete(f, cmd))
  138. cmdutil.CheckErr(validateArgs(cmd, args))
  139. cmdutil.CheckErr(validatePruneAll(o.Prune, o.All, o.Selector))
  140. cmdutil.CheckErr(o.Run())
  141. },
  142. }
  143. // bind flag structs
  144. o.DeleteFlags.AddFlags(cmd)
  145. o.RecordFlags.AddFlags(cmd)
  146. o.PrintFlags.AddFlags(cmd)
  147. cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
  148. cmd.Flags().BoolVar(&o.Prune, "prune", o.Prune, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
  149. cmdutil.AddValidateFlags(cmd)
  150. cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
  151. cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.")
  152. cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune")
  153. cmd.Flags().BoolVar(&o.OpenAPIPatch, "openapi-patch", o.OpenAPIPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
  154. cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
  155. cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it. Warning: --dry-run cannot accurately output the result of merging the local manifest and the server-side data. Use --server-dry-run to get the merged result instead.")
  156. cmdutil.AddIncludeUninitializedFlag(cmd)
  157. cmdutil.AddServerSideApplyFlags(cmd)
  158. // apply subcommands
  159. cmd.AddCommand(NewCmdApplyViewLastApplied(f, ioStreams))
  160. cmd.AddCommand(NewCmdApplySetLastApplied(f, ioStreams))
  161. cmd.AddCommand(NewCmdApplyEditLastApplied(f, ioStreams))
  162. return cmd
  163. }
  164. // Complete verifies if ApplyOptions are valid and without conflicts.
  165. func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
  166. o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
  167. o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
  168. o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
  169. o.DryRun = cmdutil.GetDryRunFlag(cmd)
  170. if o.ForceConflicts && !o.ServerSideApply {
  171. return fmt.Errorf("--experimental-force-conflicts only works with --experimental-server-side")
  172. }
  173. if o.DryRun && o.ServerSideApply {
  174. return fmt.Errorf("--dry-run doesn't work with --experimental-server-side (did you mean --server-dry-run instead?)")
  175. }
  176. if o.DryRun && o.ServerDryRun {
  177. return fmt.Errorf("--dry-run and --server-dry-run can't be used together")
  178. }
  179. // allow for a success message operation to be specified at print time
  180. o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
  181. o.PrintFlags.NamePrintFlags.Operation = operation
  182. if o.DryRun {
  183. o.PrintFlags.Complete("%s (dry run)")
  184. }
  185. if o.ServerDryRun {
  186. o.PrintFlags.Complete("%s (server dry run)")
  187. }
  188. return o.PrintFlags.ToPrinter()
  189. }
  190. var err error
  191. o.RecordFlags.Complete(cmd)
  192. o.Recorder, err = o.RecordFlags.ToRecorder()
  193. if err != nil {
  194. return err
  195. }
  196. o.DiscoveryClient, err = f.ToDiscoveryClient()
  197. if err != nil {
  198. return err
  199. }
  200. dynamicClient, err := f.DynamicClient()
  201. if err != nil {
  202. return err
  203. }
  204. o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
  205. err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize()
  206. if err != nil {
  207. return err
  208. }
  209. o.OpenAPISchema, _ = f.OpenAPISchema()
  210. o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
  211. o.Builder = f.NewBuilder()
  212. o.Mapper, err = f.ToRESTMapper()
  213. if err != nil {
  214. return err
  215. }
  216. o.DynamicClient, err = f.DynamicClient()
  217. if err != nil {
  218. return err
  219. }
  220. o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  221. if err != nil {
  222. return err
  223. }
  224. return nil
  225. }
  226. func validateArgs(cmd *cobra.Command, args []string) error {
  227. if len(args) != 0 {
  228. return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
  229. }
  230. return nil
  231. }
  232. func validatePruneAll(prune, all bool, selector string) error {
  233. if all && len(selector) > 0 {
  234. return fmt.Errorf("cannot set --all and --selector at the same time")
  235. }
  236. if prune && !all && selector == "" {
  237. return fmt.Errorf("all resources selected for prune without explicitly passing --all. To prune all resources, pass the --all flag. If you did not mean to prune all resources, specify a label selector")
  238. }
  239. return nil
  240. }
  241. func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource, error) {
  242. pruneResources := []pruneResource{}
  243. for _, groupVersionKind := range gvks {
  244. gvk := strings.Split(groupVersionKind, "/")
  245. if len(gvk) != 3 {
  246. return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow <group/version/kind>", groupVersionKind)
  247. }
  248. if gvk[0] == "core" {
  249. gvk[0] = ""
  250. }
  251. mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1])
  252. if err != nil {
  253. return pruneResources, err
  254. }
  255. var namespaced bool
  256. namespaceScope := mapping.Scope.Name()
  257. switch namespaceScope {
  258. case meta.RESTScopeNameNamespace:
  259. namespaced = true
  260. case meta.RESTScopeNameRoot:
  261. namespaced = false
  262. default:
  263. return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope)
  264. }
  265. pruneResources = append(pruneResources, pruneResource{gvk[0], gvk[1], gvk[2], namespaced})
  266. }
  267. return pruneResources, nil
  268. }
  269. func isIncompatibleServerError(err error) bool {
  270. // 415: Unsupported media type means we're talking to a server which doesn't
  271. // support server-side apply.
  272. if _, ok := err.(*errors.StatusError); !ok {
  273. // Non-StatusError means the error isn't because the server is incompatible.
  274. return false
  275. }
  276. return err.(*errors.StatusError).Status().Code == http.StatusUnsupportedMediaType
  277. }
  278. // Run executes the `apply` command.
  279. func (o *ApplyOptions) Run() error {
  280. var openapiSchema openapi.Resources
  281. if o.OpenAPIPatch {
  282. openapiSchema = o.OpenAPISchema
  283. }
  284. dryRunVerifier := &DryRunVerifier{
  285. Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
  286. OpenAPIGetter: o.DiscoveryClient,
  287. }
  288. // include the uninitialized objects by default if --prune is true
  289. // unless explicitly set --include-uninitialized=false
  290. r := o.Builder.
  291. Unstructured().
  292. Schema(o.Validator).
  293. ContinueOnError().
  294. NamespaceParam(o.Namespace).DefaultNamespace().
  295. FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
  296. LabelSelectorParam(o.Selector).
  297. Flatten().
  298. Do()
  299. if err := r.Err(); err != nil {
  300. return err
  301. }
  302. var err error
  303. if o.Prune {
  304. o.PruneResources, err = parsePruneResources(o.Mapper, o.PruneWhitelist)
  305. if err != nil {
  306. return err
  307. }
  308. }
  309. output := *o.PrintFlags.OutputFormat
  310. shortOutput := output == "name"
  311. visitedUids := sets.NewString()
  312. visitedNamespaces := sets.NewString()
  313. var objs []runtime.Object
  314. count := 0
  315. err = r.Visit(func(info *resource.Info, err error) error {
  316. if err != nil {
  317. return err
  318. }
  319. // If server-dry-run is requested but the type doesn't support it, fail right away.
  320. if o.ServerDryRun {
  321. if err := dryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
  322. return err
  323. }
  324. }
  325. if info.Namespaced() {
  326. visitedNamespaces.Insert(info.Namespace)
  327. }
  328. if err := o.Recorder.Record(info.Object); err != nil {
  329. klog.V(4).Infof("error recording current command: %v", err)
  330. }
  331. if o.ServerSideApply {
  332. // Send the full object to be applied on the server side.
  333. data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
  334. if err != nil {
  335. return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
  336. }
  337. options := metav1.PatchOptions{
  338. Force: &o.ForceConflicts,
  339. FieldManager: o.FieldManager,
  340. }
  341. if o.ServerDryRun {
  342. options.DryRun = []string{metav1.DryRunAll}
  343. }
  344. obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(
  345. info.Namespace,
  346. info.Name,
  347. types.ApplyPatchType,
  348. data,
  349. &options,
  350. )
  351. if err == nil {
  352. info.Refresh(obj, true)
  353. metadata, err := meta.Accessor(info.Object)
  354. if err != nil {
  355. return err
  356. }
  357. visitedUids.Insert(string(metadata.GetUID()))
  358. count++
  359. if len(output) > 0 && !shortOutput {
  360. objs = append(objs, info.Object)
  361. return nil
  362. }
  363. printer, err := o.ToPrinter("serverside-applied")
  364. if err != nil {
  365. return err
  366. }
  367. return printer.PrintObj(info.Object, o.Out)
  368. } else if !isIncompatibleServerError(err) {
  369. return err
  370. }
  371. // If we're talking to a server which does not implement server-side apply,
  372. // continue with the client side apply after this block.
  373. klog.Warningf("serverside-apply incompatible server: %v", err)
  374. }
  375. // Get the modified configuration of the object. Embed the result
  376. // as an annotation in the modified configuration, so that it will appear
  377. // in the patch sent to the server.
  378. modified, err := kubectl.GetModifiedConfiguration(info.Object, true, unstructured.UnstructuredJSONScheme)
  379. if err != nil {
  380. return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%s\nfor:", info.String()), info.Source, err)
  381. }
  382. // Print object only if output format other than "name" is specified
  383. printObject := len(output) > 0 && !shortOutput
  384. if err := info.Get(); err != nil {
  385. if !errors.IsNotFound(err) {
  386. return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
  387. }
  388. // Create the resource if it doesn't exist
  389. // First, update the annotation used by kubectl apply
  390. if err := kubectl.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
  391. return cmdutil.AddSourceToErr("creating", info.Source, err)
  392. }
  393. if !o.DryRun {
  394. // Then create the resource and skip the three-way merge
  395. options := metav1.CreateOptions{}
  396. if o.ServerDryRun {
  397. options.DryRun = []string{metav1.DryRunAll}
  398. }
  399. obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, &options)
  400. if err != nil {
  401. return cmdutil.AddSourceToErr("creating", info.Source, err)
  402. }
  403. info.Refresh(obj, true)
  404. }
  405. metadata, err := meta.Accessor(info.Object)
  406. if err != nil {
  407. return err
  408. }
  409. visitedUids.Insert(string(metadata.GetUID()))
  410. count++
  411. if printObject {
  412. objs = append(objs, info.Object)
  413. return nil
  414. }
  415. printer, err := o.ToPrinter("created")
  416. if err != nil {
  417. return err
  418. }
  419. return printer.PrintObj(info.Object, o.Out)
  420. }
  421. metadata, err := meta.Accessor(info.Object)
  422. if err != nil {
  423. return err
  424. }
  425. visitedUids.Insert(string(metadata.GetUID()))
  426. if !o.DryRun {
  427. annotationMap := metadata.GetAnnotations()
  428. if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
  429. fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
  430. }
  431. helper := resource.NewHelper(info.Client, info.Mapping)
  432. patcher := &Patcher{
  433. Mapping: info.Mapping,
  434. Helper: helper,
  435. DynamicClient: o.DynamicClient,
  436. Overwrite: o.Overwrite,
  437. BackOff: clockwork.NewRealClock(),
  438. Force: o.DeleteOptions.ForceDeletion,
  439. Cascade: o.DeleteOptions.Cascade,
  440. Timeout: o.DeleteOptions.Timeout,
  441. GracePeriod: o.DeleteOptions.GracePeriod,
  442. ServerDryRun: o.ServerDryRun,
  443. OpenapiSchema: openapiSchema,
  444. Retries: maxPatchRetry,
  445. }
  446. patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
  447. if err != nil {
  448. return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
  449. }
  450. info.Refresh(patchedObject, true)
  451. if string(patchBytes) == "{}" && !printObject {
  452. count++
  453. printer, err := o.ToPrinter("unchanged")
  454. if err != nil {
  455. return err
  456. }
  457. return printer.PrintObj(info.Object, o.Out)
  458. }
  459. }
  460. count++
  461. if printObject {
  462. objs = append(objs, info.Object)
  463. return nil
  464. }
  465. printer, err := o.ToPrinter("configured")
  466. if err != nil {
  467. return err
  468. }
  469. return printer.PrintObj(info.Object, o.Out)
  470. })
  471. if err != nil {
  472. return err
  473. }
  474. if count == 0 {
  475. return fmt.Errorf("no objects passed to apply")
  476. }
  477. // print objects
  478. if len(objs) > 0 {
  479. printer, err := o.ToPrinter("")
  480. if err != nil {
  481. return err
  482. }
  483. objToPrint := objs[0]
  484. if len(objs) > 1 {
  485. list := &corev1.List{
  486. TypeMeta: metav1.TypeMeta{
  487. Kind: "List",
  488. APIVersion: "v1",
  489. },
  490. ListMeta: metav1.ListMeta{},
  491. }
  492. if err := meta.SetList(list, objs); err != nil {
  493. return err
  494. }
  495. objToPrint = list
  496. }
  497. if err := printer.PrintObj(objToPrint, o.Out); err != nil {
  498. return err
  499. }
  500. }
  501. if !o.Prune {
  502. return nil
  503. }
  504. p := pruner{
  505. mapper: o.Mapper,
  506. dynamicClient: o.DynamicClient,
  507. labelSelector: o.Selector,
  508. visitedUids: visitedUids,
  509. cascade: o.DeleteOptions.Cascade,
  510. dryRun: o.DryRun,
  511. serverDryRun: o.ServerDryRun,
  512. gracePeriod: o.DeleteOptions.GracePeriod,
  513. toPrinter: o.ToPrinter,
  514. out: o.Out,
  515. }
  516. namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(o.Mapper, &(o.PruneResources))
  517. if err != nil {
  518. return fmt.Errorf("error retrieving RESTMappings to prune: %v", err)
  519. }
  520. for n := range visitedNamespaces {
  521. for _, m := range namespacedRESTMappings {
  522. if err := p.prune(n, m); err != nil {
  523. return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
  524. }
  525. }
  526. }
  527. for _, m := range nonNamespacedRESTMappings {
  528. if err := p.prune(metav1.NamespaceNone, m); err != nil {
  529. return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
  530. }
  531. }
  532. return nil
  533. }
  534. type pruneResource struct {
  535. group string
  536. version string
  537. kind string
  538. namespaced bool
  539. }
  540. func (pr pruneResource) String() string {
  541. return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced)
  542. }
  543. func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) {
  544. if len(*pruneResources) == 0 {
  545. // default whitelist
  546. // TODO: need to handle the older api versions - e.g. v1beta1 jobs. Github issue: #35991
  547. *pruneResources = []pruneResource{
  548. {"", "v1", "ConfigMap", true},
  549. {"", "v1", "Endpoints", true},
  550. {"", "v1", "Namespace", false},
  551. {"", "v1", "PersistentVolumeClaim", true},
  552. {"", "v1", "PersistentVolume", false},
  553. {"", "v1", "Pod", true},
  554. {"", "v1", "ReplicationController", true},
  555. {"", "v1", "Secret", true},
  556. {"", "v1", "Service", true},
  557. {"batch", "v1", "Job", true},
  558. {"batch", "v1beta1", "CronJob", true},
  559. {"extensions", "v1beta1", "Ingress", true},
  560. {"apps", "v1", "DaemonSet", true},
  561. {"apps", "v1", "Deployment", true},
  562. {"apps", "v1", "ReplicaSet", true},
  563. {"apps", "v1", "StatefulSet", true},
  564. }
  565. }
  566. for _, resource := range *pruneResources {
  567. addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version)
  568. if err != nil {
  569. return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err)
  570. }
  571. if resource.namespaced {
  572. namespaced = append(namespaced, addedMapping)
  573. } else {
  574. nonNamespaced = append(nonNamespaced, addedMapping)
  575. }
  576. }
  577. return namespaced, nonNamespaced, nil
  578. }
  579. type pruner struct {
  580. mapper meta.RESTMapper
  581. dynamicClient dynamic.Interface
  582. visitedUids sets.String
  583. labelSelector string
  584. fieldSelector string
  585. cascade bool
  586. serverDryRun bool
  587. dryRun bool
  588. gracePeriod int
  589. toPrinter func(string) (printers.ResourcePrinter, error)
  590. out io.Writer
  591. }
  592. func (p *pruner) prune(namespace string, mapping *meta.RESTMapping) error {
  593. objList, err := p.dynamicClient.Resource(mapping.Resource).
  594. Namespace(namespace).
  595. List(metav1.ListOptions{
  596. LabelSelector: p.labelSelector,
  597. FieldSelector: p.fieldSelector,
  598. })
  599. if err != nil {
  600. return err
  601. }
  602. objs, err := meta.ExtractList(objList)
  603. if err != nil {
  604. return err
  605. }
  606. for _, obj := range objs {
  607. metadata, err := meta.Accessor(obj)
  608. if err != nil {
  609. return err
  610. }
  611. annots := metadata.GetAnnotations()
  612. if _, ok := annots[corev1.LastAppliedConfigAnnotation]; !ok {
  613. // don't prune resources not created with apply
  614. continue
  615. }
  616. uid := metadata.GetUID()
  617. if p.visitedUids.Has(string(uid)) {
  618. continue
  619. }
  620. name := metadata.GetName()
  621. if !p.dryRun {
  622. if err := p.delete(namespace, name, mapping); err != nil {
  623. return err
  624. }
  625. }
  626. printer, err := p.toPrinter("pruned")
  627. if err != nil {
  628. return err
  629. }
  630. printer.PrintObj(obj, p.out)
  631. }
  632. return nil
  633. }
  634. func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error {
  635. return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.serverDryRun)
  636. }
  637. func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascade bool, gracePeriod int, serverDryRun bool) error {
  638. options := &metav1.DeleteOptions{}
  639. if gracePeriod >= 0 {
  640. options = metav1.NewDeleteOptions(int64(gracePeriod))
  641. }
  642. if serverDryRun {
  643. options.DryRun = []string{metav1.DryRunAll}
  644. }
  645. policy := metav1.DeletePropagationForeground
  646. if !cascade {
  647. policy = metav1.DeletePropagationOrphan
  648. }
  649. options.PropagationPolicy = &policy
  650. return c.Resource(mapping.Resource).Namespace(namespace).Delete(name, options)
  651. }
  652. func (p *Patcher) delete(namespace, name string) error {
  653. return runDelete(namespace, name, p.Mapping, p.DynamicClient, p.Cascade, p.GracePeriod, p.ServerDryRun)
  654. }
  655. // Patcher defines options to patch OpenAPI objects.
  656. type Patcher struct {
  657. Mapping *meta.RESTMapping
  658. Helper *resource.Helper
  659. DynamicClient dynamic.Interface
  660. Overwrite bool
  661. BackOff clockwork.Clock
  662. Force bool
  663. Cascade bool
  664. Timeout time.Duration
  665. GracePeriod int
  666. ServerDryRun bool
  667. // If set, forces the patch against a specific resourceVersion
  668. ResourceVersion *string
  669. // Number of retries to make if the patch fails with conflict
  670. Retries int
  671. OpenapiSchema openapi.Resources
  672. }
  673. // DryRunVerifier verifies if a given group-version-kind supports DryRun
  674. // against the current server. Sending dryRun requests to apiserver that
  675. // don't support it will result in objects being unwillingly persisted.
  676. //
  677. // It reads the OpenAPI to see if the given GVK supports dryRun. If the
  678. // GVK can not be found, we assume that CRDs will have the same level of
  679. // support as "namespaces", and non-CRDs will not be supported. We
  680. // delay the check for CRDs as much as possible though, since it
  681. // requires an extra round-trip to the server.
  682. type DryRunVerifier struct {
  683. Finder cmdutil.CRDFinder
  684. OpenAPIGetter discovery.OpenAPISchemaInterface
  685. }
  686. // HasSupport verifies if the given gvk supports DryRun. An error is
  687. // returned if it doesn't.
  688. func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
  689. oapi, err := v.OpenAPIGetter.OpenAPISchema()
  690. if err != nil {
  691. return fmt.Errorf("failed to download openapi: %v", err)
  692. }
  693. supports, err := openapi.SupportsDryRun(oapi, gvk)
  694. if err != nil {
  695. // We assume that we couldn't find the type, then check for namespace:
  696. supports, _ = openapi.SupportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
  697. // If namespace supports dryRun, then we will support dryRun for CRDs only.
  698. if supports {
  699. supports, err = v.Finder.HasCRD(gvk.GroupKind())
  700. if err != nil {
  701. return fmt.Errorf("failed to check CRD: %v", err)
  702. }
  703. }
  704. }
  705. if !supports {
  706. return fmt.Errorf("%v doesn't support dry-run", gvk)
  707. }
  708. return nil
  709. }
  710. func addResourceVersion(patch []byte, rv string) ([]byte, error) {
  711. var patchMap map[string]interface{}
  712. err := json.Unmarshal(patch, &patchMap)
  713. if err != nil {
  714. return nil, err
  715. }
  716. u := unstructured.Unstructured{Object: patchMap}
  717. a, err := meta.Accessor(&u)
  718. if err != nil {
  719. return nil, err
  720. }
  721. a.SetResourceVersion(rv)
  722. return json.Marshal(patchMap)
  723. }
  724. func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
  725. // Serialize the current configuration of the object from the server.
  726. current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
  727. if err != nil {
  728. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", obj), source, err)
  729. }
  730. // Retrieve the original configuration of the object from the annotation.
  731. original, err := kubectl.GetOriginalConfiguration(obj)
  732. if err != nil {
  733. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", obj), source, err)
  734. }
  735. var patchType types.PatchType
  736. var patch []byte
  737. var lookupPatchMeta strategicpatch.LookupPatchMeta
  738. var schema oapi.Schema
  739. createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
  740. // Create the versioned struct from the type defined in the restmapping
  741. // (which is the API version we'll be submitting the patch to)
  742. versionedObject, err := scheme.Scheme.New(p.Mapping.GroupVersionKind)
  743. switch {
  744. case runtime.IsNotRegisteredError(err):
  745. // fall back to generic JSON merge patch
  746. patchType = types.MergePatchType
  747. preconditions := []mergepatch.PreconditionFunc{mergepatch.RequireKeyUnchanged("apiVersion"),
  748. mergepatch.RequireKeyUnchanged("kind"), mergepatch.RequireMetadataKeyUnchanged("name")}
  749. patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current, preconditions...)
  750. if err != nil {
  751. if mergepatch.IsPreconditionFailed(err) {
  752. return nil, nil, fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed")
  753. }
  754. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
  755. }
  756. case err != nil:
  757. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf("getting instance of versioned object for %v:", p.Mapping.GroupVersionKind), source, err)
  758. case err == nil:
  759. // Compute a three way strategic merge patch to send to server.
  760. patchType = types.StrategicMergePatchType
  761. // Try to use openapi first if the openapi spec is available and can successfully calculate the patch.
  762. // Otherwise, fall back to baked-in types.
  763. if p.OpenapiSchema != nil {
  764. if schema = p.OpenapiSchema.LookupResource(p.Mapping.GroupVersionKind); schema != nil {
  765. lookupPatchMeta = strategicpatch.PatchMetaFromOpenAPI{Schema: schema}
  766. if openapiPatch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
  767. fmt.Fprintf(errOut, "warning: error calculating patch from openapi spec: %v\n", err)
  768. } else {
  769. patchType = types.StrategicMergePatchType
  770. patch = openapiPatch
  771. }
  772. }
  773. }
  774. if patch == nil {
  775. lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject)
  776. if err != nil {
  777. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
  778. }
  779. patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite)
  780. if err != nil {
  781. return nil, nil, cmdutil.AddSourceToErr(fmt.Sprintf(createPatchErrFormat, original, modified, current), source, err)
  782. }
  783. }
  784. }
  785. if string(patch) == "{}" {
  786. return patch, obj, nil
  787. }
  788. if p.ResourceVersion != nil {
  789. patch, err = addResourceVersion(patch, *p.ResourceVersion)
  790. if err != nil {
  791. return nil, nil, cmdutil.AddSourceToErr("Failed to insert resourceVersion in patch", source, err)
  792. }
  793. }
  794. options := metav1.PatchOptions{}
  795. if p.ServerDryRun {
  796. options.DryRun = []string{metav1.DryRunAll}
  797. }
  798. patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, &options)
  799. return patch, patchedObj, err
  800. }
  801. // Patch tries to patch an OpenAPI resource. On success, returns the merge patch as well
  802. // the final patched object. On failure, returns an error.
  803. func (p *Patcher) Patch(current runtime.Object, modified []byte, source, namespace, name string, errOut io.Writer) ([]byte, runtime.Object, error) {
  804. var getErr error
  805. patchBytes, patchObject, err := p.patchSimple(current, modified, source, namespace, name, errOut)
  806. if p.Retries == 0 {
  807. p.Retries = maxPatchRetry
  808. }
  809. for i := 1; i <= p.Retries && errors.IsConflict(err); i++ {
  810. if i > triesBeforeBackOff {
  811. p.BackOff.Sleep(backOffPeriod)
  812. }
  813. current, getErr = p.Helper.Get(namespace, name, false)
  814. if getErr != nil {
  815. return nil, nil, getErr
  816. }
  817. patchBytes, patchObject, err = p.patchSimple(current, modified, source, namespace, name, errOut)
  818. }
  819. if err != nil && (errors.IsConflict(err) || errors.IsInvalid(err)) && p.Force {
  820. patchBytes, patchObject, err = p.deleteAndCreate(current, modified, namespace, name)
  821. }
  822. return patchBytes, patchObject, err
  823. }
  824. func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, namespace, name string) ([]byte, runtime.Object, error) {
  825. if err := p.delete(namespace, name); err != nil {
  826. return modified, nil, err
  827. }
  828. // TODO: use wait
  829. if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
  830. if _, err := p.Helper.Get(namespace, name, false); !errors.IsNotFound(err) {
  831. return false, err
  832. }
  833. return true, nil
  834. }); err != nil {
  835. return modified, nil, err
  836. }
  837. versionedObject, _, err := unstructured.UnstructuredJSONScheme.Decode(modified, nil, nil)
  838. if err != nil {
  839. return modified, nil, err
  840. }
  841. options := metav1.CreateOptions{}
  842. if p.ServerDryRun {
  843. options.DryRun = []string{metav1.DryRunAll}
  844. }
  845. createdObject, err := p.Helper.Create(namespace, true, versionedObject, &options)
  846. if err != nil {
  847. // restore the original object if we fail to create the new one
  848. // but still propagate and advertise error to user
  849. recreated, recreateErr := p.Helper.Create(namespace, true, original, &options)
  850. if recreateErr != nil {
  851. err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
  852. } else {
  853. createdObject = recreated
  854. }
  855. }
  856. return modified, createdObject, err
  857. }