2014-11-10 17:09:32 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-11-10 17:09:32 +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 .
* /
2015-02-05 00:14:48 +00:00
package util
2014-11-10 17:09:32 +00:00
import (
2015-04-02 03:20:09 +00:00
"bytes"
2015-01-15 03:29:13 +00:00
"encoding/json"
2014-11-10 17:09:32 +00:00
"fmt"
2015-03-07 10:00:45 +00:00
"io"
2014-11-10 17:09:32 +00:00
"io/ioutil"
"net/http"
2015-04-02 03:20:09 +00:00
"net/url"
2014-11-10 17:09:32 +00:00
"os"
"strings"
2014-12-10 21:48:48 +00:00
"time"
2014-11-10 17:09:32 +00:00
2015-08-05 22:05:17 +00:00
"github.com/evanphx/json-patch"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/latest"
2015-09-09 21:59:11 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-08-13 19:01:50 +00:00
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
2015-09-10 21:32:57 +00:00
"k8s.io/kubernetes/pkg/kubectl"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
2015-03-09 22:08:16 +00:00
2014-11-10 17:09:32 +00:00
"github.com/golang/glog"
"github.com/spf13/cobra"
2015-03-09 22:08:16 +00:00
"github.com/spf13/pflag"
2014-11-10 17:09:32 +00:00
)
2015-04-02 03:20:09 +00:00
type debugError interface {
DebugError ( ) ( msg string , args [ ] interface { } )
}
2015-06-23 12:26:27 +00:00
// AddSourceToErr adds handleResourcePrefix and source string to error message.
// verb is the string like "creating", "deleting" etc.
// souce is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
func AddSourceToErr ( verb string , source string , err error ) error {
if source != "" {
if statusError , ok := err . ( * errors . StatusError ) ; ok {
status := statusError . Status ( )
status . Message = fmt . Sprintf ( "error when %s %q: %v" , verb , source , status . Message )
2015-08-08 01:52:23 +00:00
return & errors . StatusError { ErrStatus : status }
2015-06-23 12:26:27 +00:00
}
return fmt . Errorf ( "error when %s %q: %v" , verb , source , err )
}
return err
}
2015-09-09 12:18:31 +00:00
var fatalErrHandler = fatal
// BehaviorOnFatal allows you to override the default behavior when a fatal
// error occurs, which is call os.Exit(1). You can pass 'panic' as a function
// here if you prefer the panic() over os.Exit(1).
func BehaviorOnFatal ( f func ( string ) ) {
fatalErrHandler = f
}
// fatal prints the message and then exits. If V(2) or greater, glog.Fatal
// is invoked for extended information.
func fatal ( msg string ) {
// add newline if needed
if ! strings . HasSuffix ( msg , "\n" ) {
msg += "\n"
}
if glog . V ( 2 ) {
glog . FatalDepth ( 2 , msg )
}
fmt . Fprint ( os . Stderr , msg )
os . Exit ( 1 )
}
2015-05-14 04:35:11 +00:00
// CheckErr prints a user friendly error to STDERR and exits with a non-zero
// exit code. Unrecognized errors will be printed with an "error: " prefix.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
2015-03-04 00:24:29 +00:00
func CheckErr ( err error ) {
2015-09-09 12:18:31 +00:00
checkErr ( err , fatalErrHandler )
2015-06-05 16:58:18 +00:00
}
func checkErr ( err error , handleErr func ( string ) ) {
2015-05-14 04:35:11 +00:00
if err == nil {
return
}
2015-06-08 12:06:20 +00:00
if errors . IsInvalid ( err ) {
details := err . ( * errors . StatusError ) . Status ( ) . Details
2015-07-31 23:43:39 +00:00
prefix := fmt . Sprintf ( "The %s %q is invalid.\n" , details . Kind , details . Name )
2015-06-13 02:13:52 +00:00
errs := statusCausesToAggrError ( details . Causes )
handleErr ( MultilineError ( prefix , errs ) )
}
// handle multiline errors
if clientcmd . IsConfigurationInvalid ( err ) {
handleErr ( MultilineError ( "Error in configuration: " , err ) )
}
if agg , ok := err . ( utilerrors . Aggregate ) ; ok && len ( agg . Errors ( ) ) > 0 {
handleErr ( MultipleErrors ( "" , agg . Errors ( ) ) )
}
2015-05-14 04:35:11 +00:00
msg , ok := StandardErrorMessage ( err )
if ! ok {
2015-08-09 15:41:52 +00:00
msg = fmt . Sprintf ( "error: %s" , err . Error ( ) )
2015-05-14 04:35:11 +00:00
}
2015-06-05 16:58:18 +00:00
handleErr ( msg )
2015-05-14 04:35:11 +00:00
}
2015-09-09 21:59:11 +00:00
func statusCausesToAggrError ( scs [ ] unversioned . StatusCause ) utilerrors . Aggregate {
2015-06-08 12:06:20 +00:00
errs := make ( [ ] error , len ( scs ) )
for i , sc := range scs {
errs [ i ] = fmt . Errorf ( "%s: %s" , sc . Field , sc . Message )
}
return utilerrors . NewAggregate ( errs )
}
2015-05-14 04:35:11 +00:00
// StandardErrorMessage translates common errors into a human readable message, or returns
// false if the error is not one of the recognized types. It may also log extended
// information to glog.
//
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func StandardErrorMessage ( err error ) ( string , bool ) {
if debugErr , ok := err . ( debugError ) ; ok {
glog . V ( 4 ) . Infof ( debugErr . DebugError ( ) )
}
_ , isStatus := err . ( client . APIStatus )
switch {
case isStatus :
return fmt . Sprintf ( "Error from server: %s" , err . Error ( ) ) , true
case errors . IsUnexpectedObjectError ( err ) :
return fmt . Sprintf ( "Server returned an unexpected response: %s" , err . Error ( ) ) , true
}
switch t := err . ( type ) {
case * url . Error :
glog . V ( 4 ) . Infof ( "Connection error: %s %s: %v" , t . Op , t . URL , t . Err )
2015-04-02 03:20:09 +00:00
switch {
2015-05-14 04:35:11 +00:00
case strings . Contains ( t . Err . Error ( ) , "connection refused" ) :
host := t . URL
if server , err := url . Parse ( t . URL ) ; err == nil {
host = server . Host
2015-04-02 03:20:09 +00:00
}
2015-05-14 04:35:11 +00:00
return fmt . Sprintf ( "The connection to the server %s was refused - did you specify the right host or port?" , host ) , true
2015-03-04 00:24:29 +00:00
}
2015-05-14 04:35:11 +00:00
return fmt . Sprintf ( "Unable to connect to the server: %v" , t . Err ) , true
2015-04-02 03:20:09 +00:00
}
2015-05-14 04:35:11 +00:00
return "" , false
2015-04-02 03:20:09 +00:00
}
2015-05-14 04:35:11 +00:00
// MultilineError returns a string representing an error that splits sub errors into their own
// lines. The returned string will end with a newline.
2015-04-02 03:20:09 +00:00
func MultilineError ( prefix string , err error ) string {
if agg , ok := err . ( utilerrors . Aggregate ) ; ok {
2015-05-14 04:35:11 +00:00
errs := utilerrors . Flatten ( agg ) . Errors ( )
2015-04-02 03:20:09 +00:00
buf := & bytes . Buffer { }
switch len ( errs ) {
case 0 :
2015-05-14 04:35:11 +00:00
return fmt . Sprintf ( "%s%v\n" , prefix , err )
2015-04-02 03:20:09 +00:00
case 1 :
2015-05-14 04:35:11 +00:00
return fmt . Sprintf ( "%s%v\n" , prefix , messageForError ( errs [ 0 ] ) )
2015-04-02 03:20:09 +00:00
default :
fmt . Fprintln ( buf , prefix )
for _ , err := range errs {
2015-05-14 04:35:11 +00:00
fmt . Fprintf ( buf , "* %v\n" , messageForError ( err ) )
2015-04-02 03:20:09 +00:00
}
return buf . String ( )
}
}
2015-05-14 04:35:11 +00:00
return fmt . Sprintf ( "%s%s\n" , prefix , err )
}
// MultipleErrors returns a newline delimited string containing
// the prefix and referenced errors in standard form.
func MultipleErrors ( prefix string , errs [ ] error ) string {
buf := & bytes . Buffer { }
for _ , err := range errs {
fmt . Fprintf ( buf , "%s%v\n" , prefix , messageForError ( err ) )
}
return buf . String ( )
}
// messageForError returns the string representing the error.
func messageForError ( err error ) string {
msg , ok := StandardErrorMessage ( err )
if ! ok {
msg = err . Error ( )
}
return msg
2015-04-02 03:20:09 +00:00
}
2015-03-09 22:08:16 +00:00
func UsageError ( cmd * cobra . Command , format string , args ... interface { } ) error {
msg := fmt . Sprintf ( format , args ... )
return fmt . Errorf ( "%s\nsee '%s -h' for help." , msg , cmd . CommandPath ( ) )
2015-02-05 00:14:48 +00:00
}
2015-03-09 22:08:16 +00:00
func getFlag ( cmd * cobra . Command , flag string ) * pflag . Flag {
2014-11-10 17:09:32 +00:00
f := cmd . Flags ( ) . Lookup ( flag )
if f == nil {
2015-03-09 22:08:16 +00:00
glog . Fatalf ( "flag accessed but not defined for command %s: %s" , cmd . Name ( ) , flag )
2014-11-10 17:09:32 +00:00
}
2015-03-09 22:08:16 +00:00
return f
}
func GetFlagString ( cmd * cobra . Command , flag string ) string {
2015-08-05 14:21:47 +00:00
s , err := cmd . Flags ( ) . GetString ( flag )
if err != nil {
2015-08-08 01:52:23 +00:00
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
2015-08-05 14:21:47 +00:00
}
return s
2014-11-10 17:09:32 +00:00
}
2015-06-16 16:30:11 +00:00
// GetFlagStringList can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
2015-08-05 14:21:47 +00:00
func GetFlagStringSlice ( cmd * cobra . Command , flag string ) [ ] string {
s , err := cmd . Flags ( ) . GetStringSlice ( flag )
if err != nil {
2015-08-08 01:52:23 +00:00
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
2015-06-16 16:30:11 +00:00
}
2015-08-05 14:21:47 +00:00
return s
2015-06-16 16:30:11 +00:00
}
2015-06-29 18:36:06 +00:00
// GetWideFlag is used to determine if "-o wide" is used
func GetWideFlag ( cmd * cobra . Command ) bool {
f := cmd . Flags ( ) . Lookup ( "output" )
if f . Value . String ( ) == "wide" {
return true
}
return false
}
2014-11-10 17:09:32 +00:00
func GetFlagBool ( cmd * cobra . Command , flag string ) bool {
2015-08-05 14:21:47 +00:00
b , err := cmd . Flags ( ) . GetBool ( flag )
2015-03-07 10:00:45 +00:00
if err != nil {
2015-08-08 01:52:23 +00:00
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
2014-11-10 17:09:32 +00:00
}
2015-08-05 14:21:47 +00:00
return b
2014-11-10 17:09:32 +00:00
}
// Assumes the flag has a default value.
func GetFlagInt ( cmd * cobra . Command , flag string ) int {
2015-08-05 14:21:47 +00:00
i , err := cmd . Flags ( ) . GetInt ( flag )
2015-03-09 22:08:16 +00:00
if err != nil {
2015-08-08 01:52:23 +00:00
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
2015-03-09 22:08:16 +00:00
}
2015-08-05 14:21:47 +00:00
return i
2014-11-10 17:09:32 +00:00
}
2015-09-10 03:46:11 +00:00
// Assumes the flag has a default value.
func GetFlagInt64 ( cmd * cobra . Command , flag string ) int64 {
i , err := cmd . Flags ( ) . GetInt64 ( flag )
if err != nil {
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
}
return i
}
2014-12-10 21:48:48 +00:00
func GetFlagDuration ( cmd * cobra . Command , flag string ) time . Duration {
2015-08-05 14:21:47 +00:00
d , err := cmd . Flags ( ) . GetDuration ( flag )
2015-03-09 22:08:16 +00:00
if err != nil {
2015-08-08 01:52:23 +00:00
glog . Fatalf ( "err accessing flag %s for command %s: %v" , flag , cmd . Name ( ) , err )
2015-03-09 22:08:16 +00:00
}
2015-08-05 14:21:47 +00:00
return d
2014-12-10 21:48:48 +00:00
}
2015-09-10 21:58:09 +00:00
func AddValidateFlags ( cmd * cobra . Command ) {
2015-08-18 01:15:34 +00:00
cmd . Flags ( ) . Bool ( "validate" , true , "If true, use a schema to validate the input before sending it" )
2015-09-16 23:31:45 +00:00
cmd . Flags ( ) . String ( "schema-cache-dir" , fmt . Sprintf ( "~/%s/%s" , clientcmd . RecommendedHomeDir , clientcmd . RecommendedSchemaName ) , fmt . Sprintf ( "If non-empty, load/store cached API schemas in this directory, default is '$HOME/%s/%s'" , clientcmd . RecommendedHomeDir , clientcmd . RecommendedSchemaName ) )
2015-08-18 01:15:34 +00:00
}
2015-03-07 10:00:45 +00:00
func ReadConfigDataFromReader ( reader io . Reader , source string ) ( [ ] byte , error ) {
data , err := ioutil . ReadAll ( reader )
if err != nil {
return nil , err
}
if len ( data ) == 0 {
return nil , fmt . Errorf ( ` Read from %s but no data found ` , source )
}
return data , nil
}
2014-11-10 17:09:32 +00:00
// ReadConfigData reads the bytes from the specified filesytem or network
// location or from stdin if location == "-".
2014-12-27 21:48:27 +00:00
// TODO: replace with resource.Builder
2014-11-10 17:09:32 +00:00
func ReadConfigData ( location string ) ( [ ] byte , error ) {
if len ( location ) == 0 {
2014-11-20 10:00:36 +00:00
return nil , fmt . Errorf ( "location given but empty" )
2014-11-10 17:09:32 +00:00
}
if location == "-" {
// Read from stdin.
2015-03-07 10:00:45 +00:00
return ReadConfigDataFromReader ( os . Stdin , "stdin ('-')" )
2014-11-10 17:09:32 +00:00
}
// Use the location as a file path or URL.
return ReadConfigDataFromLocation ( location )
}
2014-12-27 21:48:27 +00:00
// TODO: replace with resource.Builder
2014-11-10 17:09:32 +00:00
func ReadConfigDataFromLocation ( location string ) ( [ ] byte , error ) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings . Index ( location , "http://" ) == 0 || strings . Index ( location , "https://" ) == 0 {
resp , err := http . Get ( location )
if err != nil {
2014-11-20 10:00:36 +00:00
return nil , fmt . Errorf ( "unable to access URL %s: %v\n" , location , err )
2014-11-10 17:09:32 +00:00
}
defer resp . Body . Close ( )
if resp . StatusCode != 200 {
2014-11-20 10:00:36 +00:00
return nil , fmt . Errorf ( "unable to read URL, server reported %d %s" , resp . StatusCode , resp . Status )
2014-11-10 17:09:32 +00:00
}
2015-03-07 10:00:45 +00:00
return ReadConfigDataFromReader ( resp . Body , location )
2014-11-10 17:09:32 +00:00
} else {
2015-03-07 10:00:45 +00:00
file , err := os . Open ( location )
2014-11-10 17:09:32 +00:00
if err != nil {
2014-11-20 10:00:36 +00:00
return nil , fmt . Errorf ( "unable to read %s: %v\n" , location , err )
2014-11-10 17:09:32 +00:00
}
2015-03-07 10:00:45 +00:00
return ReadConfigDataFromReader ( file , location )
2014-11-10 17:09:32 +00:00
}
}
2015-01-15 03:29:13 +00:00
2015-01-31 18:59:08 +00:00
func Merge ( dst runtime . Object , fragment , kind string ) ( runtime . Object , error ) {
2015-01-15 03:29:13 +00:00
// Ok, this is a little hairy, we'd rather not force the user to specify a kind for their JSON
// So we pull it into a map, add the Kind field, and then reserialize.
// We also pull the apiVersion for proper parsing
var intermediate interface { }
if err := json . Unmarshal ( [ ] byte ( fragment ) , & intermediate ) ; err != nil {
2015-01-31 18:59:08 +00:00
return nil , err
2015-01-15 03:29:13 +00:00
}
dataMap , ok := intermediate . ( map [ string ] interface { } )
if ! ok {
2015-01-31 18:59:08 +00:00
return nil , fmt . Errorf ( "Expected a map, found something else: %s" , fragment )
2015-01-15 03:29:13 +00:00
}
version , found := dataMap [ "apiVersion" ]
if ! found {
2015-01-31 18:59:08 +00:00
return nil , fmt . Errorf ( "Inline JSON requires an apiVersion field" )
2015-01-15 03:29:13 +00:00
}
versionString , ok := version . ( string )
if ! ok {
2015-01-31 18:59:08 +00:00
return nil , fmt . Errorf ( "apiVersion must be a string" )
2015-01-15 03:29:13 +00:00
}
2015-09-10 19:30:47 +00:00
i , err := latest . GroupOrDie ( "" ) . InterfacesFor ( versionString )
2015-02-25 18:06:18 +00:00
if err != nil {
return nil , err
}
2015-01-15 03:29:13 +00:00
2015-01-31 18:59:08 +00:00
// encode dst into versioned json and apply fragment directly too it
2015-02-25 18:06:18 +00:00
target , err := i . Codec . Encode ( dst )
2015-01-15 03:29:13 +00:00
if err != nil {
2015-01-31 18:59:08 +00:00
return nil , err
2015-01-15 03:29:13 +00:00
}
2015-02-23 19:47:06 +00:00
patched , err := jsonpatch . MergePatch ( target , [ ] byte ( fragment ) )
2015-01-15 03:29:13 +00:00
if err != nil {
2015-01-31 18:59:08 +00:00
return nil , err
}
2015-02-25 18:06:18 +00:00
out , err := i . Codec . Decode ( patched )
2015-01-31 18:59:08 +00:00
if err != nil {
return nil , err
2015-01-15 03:29:13 +00:00
}
2015-01-31 18:59:08 +00:00
return out , nil
2015-01-15 03:29:13 +00:00
}
2015-07-15 12:10:47 +00:00
// DumpReaderToFile writes all data from the given io.Reader to the specified file
// (usually for temporary use).
func DumpReaderToFile ( reader io . Reader , filename string ) error {
f , err := os . OpenFile ( filename , os . O_WRONLY | os . O_CREATE | os . O_TRUNC , 0600 )
defer f . Close ( )
if err != nil {
return err
}
buffer := make ( [ ] byte , 1024 )
for {
count , err := reader . Read ( buffer )
if err == io . EOF {
break
}
if err != nil {
return err
}
_ , err = f . Write ( buffer [ : count ] )
if err != nil {
return err
}
}
return nil
}
2015-07-23 22:43:48 +00:00
// UpdateObject updates resource object with updateFn
func UpdateObject ( info * resource . Info , updateFn func ( runtime . Object ) error ) ( runtime . Object , error ) {
helper := resource . NewHelper ( info . Client , info . Mapping )
2015-09-10 21:32:57 +00:00
if err := updateFn ( info . Object ) ; err != nil {
return nil , err
}
// Update the annotation used by kubectl apply
if err := kubectl . UpdateApplyAnnotation ( info ) ; err != nil {
2015-07-23 22:43:48 +00:00
return nil , err
}
2015-09-10 21:32:57 +00:00
2015-07-23 22:43:48 +00:00
data , err := helper . Codec . Encode ( info . Object )
if err != nil {
return nil , err
}
2015-09-10 21:32:57 +00:00
if _ , err := helper . Replace ( info . Namespace , info . Name , true , data ) ; err != nil {
2015-07-23 22:43:48 +00:00
return nil , err
}
2015-09-10 21:32:57 +00:00
2015-07-23 22:43:48 +00:00
return info . Object , nil
}