2019-08-01 14:01:40 -04:00
/ *
Copyright 2014 The Kubernetes Authors .
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 replace
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2019-08-01 14:01:40 -04:00
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cli-runtime/pkg/genericclioptions"
2023-04-05 07:07:46 -04:00
"k8s.io/cli-runtime/pkg/genericiooptions"
2019-08-01 14:01:40 -04:00
"k8s.io/cli-runtime/pkg/resource"
2019-08-01 16:14:06 -04:00
"k8s.io/kubectl/pkg/cmd/delete"
2019-08-01 14:01:40 -04:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/rawhttp"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
2021-07-07 11:30:59 -04:00
"k8s.io/kubectl/pkg/util/slice"
2019-08-01 14:01:40 -04:00
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/validation"
)
var (
replaceLong = templates . LongDesc ( i18n . T ( `
2021-07-06 15:05:26 -04:00
Replace a resource by file name or stdin .
2019-08-01 14:01:40 -04:00
JSON and YAML formats are accepted . If replacing an existing resource , the
complete resource spec must be provided . This can be obtained by
$ kubectl get TYPE NAME - o yaml ` ) )
replaceExample = templates . Examples ( i18n . T ( `
2021-07-06 15:05:26 -04:00
# Replace a pod using the data in pod . json
2019-08-01 14:01:40 -04:00
kubectl replace - f . / pod . json
2021-07-06 15:05:26 -04:00
# Replace a pod based on the JSON passed into stdin
2019-08-01 14:01:40 -04:00
cat pod . json | kubectl replace - f -
# Update a single - container pod ' s image version ( tag ) to v4
kubectl get pod mypod - o yaml | sed ' s / \ ( image : myimage \ ) : . * $ / \ 1 : v4 / ' | kubectl replace - f -
# Force replace , delete and then re - create the resource
kubectl replace -- force - f . / pod . json ` ) )
)
2021-07-07 11:30:59 -04:00
var supportedSubresources = [ ] string { "status" , "scale" }
2019-08-01 14:01:40 -04:00
type ReplaceOptions struct {
PrintFlags * genericclioptions . PrintFlags
RecordFlags * genericclioptions . RecordFlags
DeleteFlags * delete . DeleteFlags
DeleteOptions * delete . DeleteOptions
2022-12-11 21:20:28 -05:00
DryRunStrategy cmdutil . DryRunStrategy
validationDirective string
2020-02-18 18:08:25 -05:00
2019-08-01 14:01:40 -04:00
PrintObj func ( obj runtime . Object ) error
createAnnotation bool
Schema validation . Schema
Builder func ( ) * resource . Builder
BuilderArgs [ ] string
Namespace string
EnforceNamespace bool
Raw string
Recorder genericclioptions . Recorder
2021-07-07 11:30:59 -04:00
Subresource string
2023-04-05 07:07:46 -04:00
genericiooptions . IOStreams
2020-03-04 22:04:01 -05:00
fieldManager string
2019-08-01 14:01:40 -04:00
}
2023-04-05 07:07:46 -04:00
func NewReplaceOptions ( streams genericiooptions . IOStreams ) * ReplaceOptions {
2019-08-01 14:01:40 -04:00
return & ReplaceOptions {
PrintFlags : genericclioptions . NewPrintFlags ( "replaced" ) ,
2022-07-27 23:33:34 -04:00
DeleteFlags : delete . NewDeleteFlags ( "The files that contain the configurations to replace." ) ,
2019-08-01 14:01:40 -04:00
IOStreams : streams ,
}
}
2023-04-05 07:07:46 -04:00
func NewCmdReplace ( f cmdutil . Factory , streams genericiooptions . IOStreams ) * cobra . Command {
2019-08-01 14:01:40 -04:00
o := NewReplaceOptions ( streams )
cmd := & cobra . Command {
Use : "replace -f FILENAME" ,
DisableFlagsInUseLine : true ,
2021-07-06 15:05:26 -04:00
Short : i18n . T ( "Replace a resource by file name or stdin" ) ,
2019-08-01 14:01:40 -04:00
Long : replaceLong ,
Example : replaceExample ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
2022-05-17 04:38:20 -04:00
cmdutil . CheckErr ( o . Validate ( ) )
2019-08-01 14:01:40 -04:00
cmdutil . CheckErr ( o . Run ( f ) )
} ,
}
o . PrintFlags . AddFlags ( cmd )
o . DeleteFlags . AddFlags ( cmd )
o . RecordFlags . AddFlags ( cmd )
cmdutil . AddValidateFlags ( cmd )
cmdutil . AddApplyAnnotationFlags ( cmd )
2020-02-18 18:08:25 -05:00
cmdutil . AddDryRunFlag ( cmd )
2019-08-01 14:01:40 -04:00
cmd . Flags ( ) . StringVar ( & o . Raw , "raw" , o . Raw , "Raw URI to PUT to the server. Uses the transport specified by the kubeconfig file." )
2020-03-04 22:04:01 -05:00
cmdutil . AddFieldManagerFlagVar ( cmd , & o . fieldManager , "kubectl-replace" )
2021-07-07 11:30:59 -04:00
cmdutil . AddSubresourceFlags ( cmd , & o . Subresource , "If specified, replace will operate on the subresource of the requested object." , supportedSubresources ... )
2019-08-01 14:01:40 -04:00
return cmd
}
func ( o * ReplaceOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
var err error
o . RecordFlags . Complete ( cmd )
o . Recorder , err = o . RecordFlags . ToRecorder ( )
if err != nil {
return err
}
2022-03-09 09:52:32 -05:00
o . validationDirective , err = cmdutil . GetValidationDirective ( cmd )
if err != nil {
return err
}
2019-08-01 14:01:40 -04:00
o . createAnnotation = cmdutil . GetFlagBool ( cmd , cmdutil . ApplyAnnotationsFlag )
2020-02-18 18:08:25 -05:00
o . DryRunStrategy , err = cmdutil . GetDryRunStrategy ( cmd )
2019-08-01 14:01:40 -04:00
if err != nil {
return err
}
2020-02-18 18:08:25 -05:00
dynamicClient , err := f . DynamicClient ( )
if err != nil {
return err
}
cmdutil . PrintFlagsWithDryRunStrategy ( o . PrintFlags , o . DryRunStrategy )
2019-08-01 14:01:40 -04:00
2020-02-18 18:08:25 -05:00
printer , err := o . PrintFlags . ToPrinter ( )
2019-08-01 14:01:40 -04:00
if err != nil {
return err
}
2020-02-18 18:08:25 -05:00
o . PrintObj = func ( obj runtime . Object ) error {
return printer . PrintObj ( obj , o . Out )
}
2020-07-23 06:45:59 -04:00
deleteOpts , err := o . DeleteFlags . ToOptions ( dynamicClient , o . IOStreams )
if err != nil {
return err
}
2019-08-01 14:01:40 -04:00
//Replace will create a resource if it doesn't exist already, so ignore not found error
deleteOpts . IgnoreNotFound = true
if o . PrintFlags . OutputFormat != nil {
deleteOpts . Output = * o . PrintFlags . OutputFormat
}
if deleteOpts . GracePeriod == 0 {
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1 and wait until the object is successfully deleted.
deleteOpts . GracePeriod = 1
deleteOpts . WaitForDeletion = true
}
o . DeleteOptions = deleteOpts
err = o . DeleteOptions . FilenameOptions . RequireFilenameOrKustomize ( )
if err != nil {
return err
}
2022-12-11 21:20:28 -05:00
schema , err := f . Validator ( o . validationDirective )
2019-08-01 14:01:40 -04:00
if err != nil {
return err
}
o . Schema = schema
o . Builder = f . NewBuilder
o . BuilderArgs = args
o . Namespace , o . EnforceNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
return nil
}
2022-05-17 04:38:20 -04:00
func ( o * ReplaceOptions ) Validate ( ) error {
2019-08-01 14:01:40 -04:00
if o . DeleteOptions . GracePeriod >= 0 && ! o . DeleteOptions . ForceDeletion {
return fmt . Errorf ( "--grace-period must have --force specified" )
}
if o . DeleteOptions . Timeout != 0 && ! o . DeleteOptions . ForceDeletion {
return fmt . Errorf ( "--timeout must have --force specified" )
}
2022-06-01 07:01:40 -04:00
if o . DeleteOptions . ForceDeletion && o . DryRunStrategy != cmdutil . DryRunNone {
2022-07-07 08:05:17 -04:00
return fmt . Errorf ( "--dry-run can not be used when --force is set" )
2022-06-01 07:01:40 -04:00
}
2019-08-01 14:01:40 -04:00
if cmdutil . IsFilenameSliceEmpty ( o . DeleteOptions . FilenameOptions . Filenames , o . DeleteOptions . FilenameOptions . Kustomize ) {
2022-07-07 08:05:17 -04:00
return fmt . Errorf ( "must specify --filename to replace" )
2019-08-01 14:01:40 -04:00
}
if len ( o . Raw ) > 0 {
if len ( o . DeleteOptions . FilenameOptions . Filenames ) != 1 {
2022-05-17 04:38:20 -04:00
return fmt . Errorf ( "--raw can only use a single local file or stdin" )
2019-08-01 14:01:40 -04:00
}
if strings . Index ( o . DeleteOptions . FilenameOptions . Filenames [ 0 ] , "http://" ) == 0 || strings . Index ( o . DeleteOptions . FilenameOptions . Filenames [ 0 ] , "https://" ) == 0 {
2022-05-17 04:38:20 -04:00
return fmt . Errorf ( "--raw cannot read from a url" )
2019-08-01 14:01:40 -04:00
}
if o . DeleteOptions . FilenameOptions . Recursive {
2022-05-17 04:38:20 -04:00
return fmt . Errorf ( "--raw and --recursive are mutually exclusive" )
2019-08-01 14:01:40 -04:00
}
2022-05-17 04:38:20 -04:00
if o . PrintFlags . OutputFormat != nil && len ( * o . PrintFlags . OutputFormat ) > 0 {
return fmt . Errorf ( "--raw and --output are mutually exclusive" )
2019-08-01 14:01:40 -04:00
}
if _ , err := url . ParseRequestURI ( o . Raw ) ; err != nil {
2022-05-17 04:38:20 -04:00
return fmt . Errorf ( "--raw must be a valid URL path: %v" , err )
2019-08-01 14:01:40 -04:00
}
}
2021-07-07 11:30:59 -04:00
if len ( o . Subresource ) > 0 && ! slice . ContainsString ( supportedSubresources , o . Subresource , nil ) {
return fmt . Errorf ( "invalid subresource value: %q. Must be one of %v" , o . Subresource , supportedSubresources )
}
2019-08-01 14:01:40 -04:00
return nil
}
func ( o * ReplaceOptions ) Run ( f cmdutil . Factory ) error {
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
// the validator enforces this, so
if len ( o . Raw ) > 0 {
restClient , err := f . RESTClient ( )
if err != nil {
return err
}
return rawhttp . RawPut ( restClient , o . IOStreams , o . Raw , o . DeleteOptions . Filenames [ 0 ] )
}
if o . DeleteOptions . ForceDeletion {
return o . forceReplace ( )
}
r := o . Builder ( ) .
Unstructured ( ) .
Schema ( o . Schema ) .
ContinueOnError ( ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . EnforceNamespace , & o . DeleteOptions . FilenameOptions ) .
2021-07-07 11:30:59 -04:00
Subresource ( o . Subresource ) .
2019-08-01 14:01:40 -04:00
Flatten ( ) .
Do ( )
if err := r . Err ( ) ; err != nil {
return err
}
return r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
if err := util . CreateOrUpdateAnnotation ( o . createAnnotation , info . Object , scheme . DefaultJSONEncoder ( ) ) ; err != nil {
return cmdutil . AddSourceToErr ( "replacing" , info . Source , err )
}
if err := o . Recorder . Record ( info . Object ) ; err != nil {
klog . V ( 4 ) . Infof ( "error recording current command: %v" , err )
}
2020-02-18 18:08:25 -05:00
if o . DryRunStrategy == cmdutil . DryRunClient {
return o . PrintObj ( info . Object )
}
2019-08-01 14:01:40 -04:00
// Serialize the object with the annotation applied.
2020-02-18 18:08:25 -05:00
obj , err := resource .
NewHelper ( info . Client , info . Mapping ) .
DryRun ( o . DryRunStrategy == cmdutil . DryRunServer ) .
2020-03-04 22:04:01 -05:00
WithFieldManager ( o . fieldManager ) .
2022-03-09 09:52:32 -05:00
WithFieldValidation ( o . validationDirective ) .
2021-07-07 11:30:59 -04:00
WithSubresource ( o . Subresource ) .
2020-02-18 18:08:25 -05:00
Replace ( info . Namespace , info . Name , true , info . Object )
2019-08-01 14:01:40 -04:00
if err != nil {
return cmdutil . AddSourceToErr ( "replacing" , info . Source , err )
}
info . Refresh ( obj , true )
return o . PrintObj ( info . Object )
} )
}
func ( o * ReplaceOptions ) forceReplace ( ) error {
2021-06-14 17:16:54 -04:00
stdinInUse := false
2019-08-01 14:01:40 -04:00
for i , filename := range o . DeleteOptions . FilenameOptions . Filenames {
if filename == "-" {
2022-07-30 09:31:16 -04:00
tempDir , err := os . MkdirTemp ( "" , "kubectl_replace_" )
2019-08-01 14:01:40 -04:00
if err != nil {
return err
}
defer os . RemoveAll ( tempDir )
tempFilename := filepath . Join ( tempDir , "resource.stdin" )
err = cmdutil . DumpReaderToFile ( os . Stdin , tempFilename )
if err != nil {
return err
}
o . DeleteOptions . FilenameOptions . Filenames [ i ] = tempFilename
2021-06-14 17:16:54 -04:00
stdinInUse = true
2019-08-01 14:01:40 -04:00
}
}
2021-06-14 17:16:54 -04:00
b := o . Builder ( ) .
2019-08-01 14:01:40 -04:00
Unstructured ( ) .
ContinueOnError ( ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) .
ResourceTypeOrNameArgs ( false , o . BuilderArgs ... ) . RequireObject ( false ) .
FilenameParam ( o . EnforceNamespace , & o . DeleteOptions . FilenameOptions ) .
2021-07-07 11:30:59 -04:00
Subresource ( o . Subresource ) .
2021-06-14 17:16:54 -04:00
Flatten ( )
if stdinInUse {
b = b . StdinInUse ( )
}
r := b . Do ( )
2019-08-01 14:01:40 -04:00
if err := r . Err ( ) ; err != nil {
return err
}
if err := o . DeleteOptions . DeleteResult ( r ) ; err != nil {
return err
}
timeout := o . DeleteOptions . Timeout
if timeout == 0 {
timeout = 5 * time . Minute
}
err := r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
return wait . PollImmediate ( 1 * time . Second , timeout , func ( ) ( bool , error ) {
if err := info . Get ( ) ; ! errors . IsNotFound ( err ) {
return false , err
}
return true , nil
} )
} )
if err != nil {
return err
}
2021-06-14 17:16:54 -04:00
b = o . Builder ( ) .
2019-08-01 14:01:40 -04:00
Unstructured ( ) .
Schema ( o . Schema ) .
ContinueOnError ( ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . EnforceNamespace , & o . DeleteOptions . FilenameOptions ) .
2021-07-07 11:30:59 -04:00
Subresource ( o . Subresource ) .
2021-06-14 17:16:54 -04:00
Flatten ( )
if stdinInUse {
b = b . StdinInUse ( )
}
r = b . Do ( )
2019-08-01 14:01:40 -04:00
err = r . Err ( )
if err != nil {
return err
}
count := 0
err = r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
if err := util . CreateOrUpdateAnnotation ( o . createAnnotation , info . Object , scheme . DefaultJSONEncoder ( ) ) ; err != nil {
return err
}
if err := o . Recorder . Record ( info . Object ) ; err != nil {
klog . V ( 4 ) . Infof ( "error recording current command: %v" , err )
}
2020-03-04 22:04:01 -05:00
obj , err := resource . NewHelper ( info . Client , info . Mapping ) .
WithFieldManager ( o . fieldManager ) .
2022-03-09 09:52:32 -05:00
WithFieldValidation ( o . validationDirective ) .
2020-03-04 22:04:01 -05:00
Create ( info . Namespace , true , info . Object )
2019-08-01 14:01:40 -04:00
if err != nil {
return err
}
count ++
info . Refresh ( obj , true )
return o . PrintObj ( info . Object )
} )
if err != nil {
return err
}
if count == 0 {
return fmt . Errorf ( "no objects passed to replace" )
}
return nil
}