2015-09-10 21:32:57 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-09-10 21:32:57 +00:00
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 cmd
import (
"fmt"
"io"
2016-06-09 04:14:17 +00:00
"time"
2015-09-10 21:32:57 +00:00
2016-06-09 04:14:17 +00:00
"github.com/jonboulle/clockwork"
2016-05-20 17:49:56 +00:00
"github.com/renstrom/dedent"
2015-09-10 21:32:57 +00:00
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
2015-11-05 02:29:56 +00:00
"k8s.io/kubernetes/pkg/api/errors"
2016-06-09 04:14:17 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2015-09-10 21:32:57 +00:00
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
2015-12-21 05:37:49 +00:00
"k8s.io/kubernetes/pkg/runtime"
2015-09-10 21:32:57 +00:00
"k8s.io/kubernetes/pkg/util/strategicpatch"
)
2016-09-13 20:10:21 +00:00
type ApplyOptions struct {
FilenameOptions resource . FilenameOptions
Selector string
}
2016-06-09 04:14:17 +00:00
const (
// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
maxPatchRetry = 5
// backOffPeriod is the period to back off when apply patch resutls in error.
backOffPeriod = 1 * time . Second
// how many times we can retry before back off
triesBeforeBackOff = 1
)
2016-05-20 17:49:56 +00:00
var (
apply_long = dedent . Dedent ( `
Apply a configuration to a resource by filename or stdin .
This resource will be created if it doesn ' t exist yet .
To use ' apply ' , always create the resource initially with either ' apply ' or ' create -- save - config ' .
JSON and YAML formats are accepted . ` )
2015-09-10 21:32:57 +00:00
2016-05-20 17:49:56 +00:00
apply_example = dedent . Dedent ( `
# Apply the configuration in pod . json to a pod .
kubectl apply - f . / pod . json
2015-09-10 21:32:57 +00:00
2016-05-20 17:49:56 +00:00
# Apply the JSON passed into stdin to a pod .
cat pod . json | kubectl apply - f - ` )
2015-09-10 21:32:57 +00:00
)
func NewCmdApply ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
2016-09-13 20:10:21 +00:00
var options ApplyOptions
2015-09-10 21:32:57 +00:00
cmd := & cobra . Command {
Use : "apply -f FILENAME" ,
Short : "Apply a configuration to a resource by filename or stdin" ,
Long : apply_long ,
Example : apply_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( validateArgs ( cmd , args ) )
cmdutil . CheckErr ( cmdutil . ValidateOutputArgs ( cmd ) )
2016-09-13 20:10:21 +00:00
cmdutil . CheckErr ( RunApply ( f , cmd , out , & options ) )
2015-09-10 21:32:57 +00:00
} ,
}
2016-08-17 18:28:07 +00:00
usage := "that contains the configuration to apply"
2016-09-13 20:10:21 +00:00
cmdutil . AddFilenameOptionFlags ( cmd , & options . FilenameOptions , usage )
2015-09-10 21:32:57 +00:00
cmd . MarkFlagRequired ( "filename" )
2016-07-18 04:32:13 +00:00
cmd . Flags ( ) . Bool ( "overwrite" , true , "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration" )
2015-09-10 21:32:57 +00:00
cmdutil . AddValidateFlags ( cmd )
2016-09-13 20:10:21 +00:00
cmd . Flags ( ) . StringVarP ( & options . Selector , "selector" , "l" , "" , "Selector (label query) to filter on" )
2015-09-10 21:32:57 +00:00
cmdutil . AddOutputFlagsForMutation ( cmd )
2016-01-22 18:33:23 +00:00
cmdutil . AddRecordFlag ( cmd )
2016-03-10 01:27:19 +00:00
cmdutil . AddInclude3rdPartyFlags ( cmd )
2015-09-10 21:32:57 +00:00
return cmd
}
func validateArgs ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) != 0 {
return cmdutil . UsageError ( cmd , "Unexpected args: %v" , args )
}
return nil
}
2016-09-13 20:10:21 +00:00
func RunApply ( f * cmdutil . Factory , cmd * cobra . Command , out io . Writer , options * ApplyOptions ) error {
2015-09-10 21:32:57 +00:00
shortOutput := cmdutil . GetFlagString ( cmd , "output" ) == "name"
schema , err := f . Validator ( cmdutil . GetFlagBool ( cmd , "validate" ) , cmdutil . GetFlagString ( cmd , "schema-cache-dir" ) )
if err != nil {
return err
}
cmdNamespace , enforceNamespace , err := f . DefaultNamespace ( )
if err != nil {
return err
}
2016-09-16 19:50:34 +00:00
mapper , typer := f . Object ( )
2015-12-21 05:37:49 +00:00
r := resource . NewBuilder ( mapper , typer , resource . ClientMapperFunc ( f . ClientForMapping ) , f . Decoder ( true ) ) .
2015-09-10 21:32:57 +00:00
Schema ( schema ) .
ContinueOnError ( ) .
NamespaceParam ( cmdNamespace ) . DefaultNamespace ( ) .
2016-09-13 20:10:21 +00:00
FilenameParam ( enforceNamespace , & options . FilenameOptions ) .
SelectorParam ( options . Selector ) .
2015-09-10 21:32:57 +00:00
Flatten ( ) .
Do ( )
err = r . Err ( )
if err != nil {
return err
}
2015-12-21 05:37:49 +00:00
encoder := f . JSONEncoder ( )
2016-02-10 00:07:02 +00:00
decoder := f . Decoder ( false )
2015-12-21 05:37:49 +00:00
2015-09-10 21:32:57 +00:00
count := 0
err = r . Visit ( func ( info * resource . Info , err error ) error {
// In this method, info.Object contains the object retrieved from the server
// and info.VersionedObject contains the object decoded from the input source.
if err != nil {
return err
}
// Get the modified configuration of the object. Embed the result
// as an annotation in the modified configuration, so that it will appear
// in the patch sent to the server.
2015-12-21 05:37:49 +00:00
modified , err := kubectl . GetModifiedConfiguration ( info , true , encoder )
2015-09-10 21:32:57 +00:00
if err != nil {
return cmdutil . AddSourceToErr ( fmt . Sprintf ( "retrieving modified configuration from:\n%v\nfor:" , info ) , info . Source , err )
}
if err := info . Get ( ) ; err != nil {
2015-11-05 02:29:56 +00:00
if ! errors . IsNotFound ( err ) {
return cmdutil . AddSourceToErr ( fmt . Sprintf ( "retrieving current configuration of:\n%v\nfrom server for:" , info ) , info . Source , err )
}
// Create the resource if it doesn't exist
// First, update the annotation used by kubectl apply
2015-12-21 05:37:49 +00:00
if err := kubectl . CreateApplyAnnotation ( info , encoder ) ; err != nil {
2015-11-05 02:29:56 +00:00
return cmdutil . AddSourceToErr ( "creating" , info . Source , err )
}
2016-01-22 18:33:23 +00:00
if cmdutil . ShouldRecord ( cmd , info ) {
if err := cmdutil . RecordChangeCause ( info . Object , f . Command ( ) ) ; err != nil {
return cmdutil . AddSourceToErr ( "creating" , info . Source , err )
}
}
2015-11-05 02:29:56 +00:00
// Then create the resource and skip the three-way merge
if err := createAndRefresh ( info ) ; err != nil {
return cmdutil . AddSourceToErr ( "creating" , info . Source , err )
}
count ++
2016-08-23 18:11:39 +00:00
cmdutil . PrintSuccess ( mapper , shortOutput , out , info . Mapping . Resource , info . Name , false , "created" )
2015-11-05 02:29:56 +00:00
return nil
2015-09-10 21:32:57 +00:00
}
2016-07-18 04:32:13 +00:00
overwrite := cmdutil . GetFlagBool ( cmd , "overwrite" )
2015-09-10 21:32:57 +00:00
helper := resource . NewHelper ( info . Client , info . Mapping )
2016-07-18 04:32:13 +00:00
patcher := NewPatcher ( encoder , decoder , info . Mapping , helper , overwrite )
2016-06-09 04:14:17 +00:00
patchBytes , err := patcher . patch ( info . Object , modified , info . Source , info . Namespace , info . Name )
2015-09-10 21:32:57 +00:00
if err != nil {
2016-06-09 04:14:17 +00:00
return cmdutil . AddSourceToErr ( fmt . Sprintf ( "applying patch:\n%s\nto:\n%v\nfor:" , patchBytes , info ) , info . Source , err )
2015-09-10 21:32:57 +00:00
}
2016-01-22 18:33:23 +00:00
if cmdutil . ShouldRecord ( cmd , info ) {
2016-06-09 04:14:17 +00:00
patch , err := cmdutil . ChangeResourcePatch ( info , f . Command ( ) )
2016-01-22 18:33:23 +00:00
if err != nil {
return err
}
_ , err = helper . Patch ( info . Namespace , info . Name , api . StrategicMergePatchType , patch )
if err != nil {
return cmdutil . AddSourceToErr ( fmt . Sprintf ( "applying patch:\n%s\nto:\n%v\nfor:" , patch , info ) , info . Source , err )
}
}
2015-09-10 21:32:57 +00:00
count ++
2016-08-23 18:11:39 +00:00
cmdutil . PrintSuccess ( mapper , shortOutput , out , info . Mapping . Resource , info . Name , false , "configured" )
2015-09-10 21:32:57 +00:00
return nil
} )
if err != nil {
return err
}
if count == 0 {
return fmt . Errorf ( "no objects passed to apply" )
}
return nil
}
2016-06-09 04:14:17 +00:00
type patcher struct {
encoder runtime . Encoder
decoder runtime . Decoder
mapping * meta . RESTMapping
helper * resource . Helper
2016-07-18 04:32:13 +00:00
overwrite bool
backOff clockwork . Clock
2016-06-09 04:14:17 +00:00
}
2016-07-18 04:32:13 +00:00
func NewPatcher ( encoder runtime . Encoder , decoder runtime . Decoder , mapping * meta . RESTMapping , helper * resource . Helper , overwrite bool ) * patcher {
2016-06-09 04:14:17 +00:00
return & patcher {
2016-07-18 04:32:13 +00:00
encoder : encoder ,
decoder : decoder ,
mapping : mapping ,
helper : helper ,
overwrite : overwrite ,
backOff : clockwork . NewRealClock ( ) ,
2016-06-09 04:14:17 +00:00
}
}
func ( p * patcher ) patchSimple ( obj runtime . Object , modified [ ] byte , source , namespace , name string ) ( [ ] byte , error ) {
// Serialize the current configuration of the object from the server.
current , err := runtime . Encode ( p . encoder , obj )
if err != nil {
return nil , cmdutil . AddSourceToErr ( fmt . Sprintf ( "serializing current configuration from:\n%v\nfor:" , obj ) , source , err )
}
// Retrieve the original configuration of the object from the annotation.
original , err := kubectl . GetOriginalConfiguration ( p . mapping , obj )
if err != nil {
return nil , cmdutil . AddSourceToErr ( fmt . Sprintf ( "retrieving original configuration from:\n%v\nfor:" , obj ) , source , err )
}
// Create the versioned struct from the original from the server for
// strategic patch.
// TODO: Move all structs in apply to use raw data. Can be done once
// builder has a RawResult method which delivers raw data instead of
// internal objects.
versionedObject , _ , err := p . decoder . Decode ( current , nil , nil )
if err != nil {
return nil , cmdutil . AddSourceToErr ( fmt . Sprintf ( "converting encoded server-side object back to versioned struct:\n%v\nfor:" , obj ) , source , err )
}
// Compute a three way strategic merge patch to send to server.
2016-07-18 04:32:13 +00:00
patch , err := strategicpatch . CreateThreeWayMergePatch ( original , modified , current , versionedObject , p . overwrite )
2016-06-09 04:14:17 +00:00
if err != nil {
format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
return nil , cmdutil . AddSourceToErr ( fmt . Sprintf ( format , original , modified , current ) , source , err )
}
_ , err = p . helper . Patch ( namespace , name , api . StrategicMergePatchType , patch )
return patch , err
}
func ( p * patcher ) patch ( current runtime . Object , modified [ ] byte , source , namespace , name string ) ( [ ] byte , error ) {
var getErr error
patchBytes , err := p . patchSimple ( current , modified , source , namespace , name )
for i := 1 ; i <= maxPatchRetry && errors . IsConflict ( err ) ; i ++ {
if i > triesBeforeBackOff {
p . backOff . Sleep ( backOffPeriod )
}
current , getErr = p . helper . Get ( namespace , name , false )
if getErr != nil {
return nil , getErr
}
patchBytes , err = p . patchSimple ( current , modified , source , namespace , name )
}
return patchBytes , err
}