1
0
Fork 0
forked from barak/tarpoon

Add glide.yaml and vendor deps

This commit is contained in:
Dalton Hubble 2016-12-03 22:43:32 -08:00
parent db918f12ad
commit 5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions

114
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/BUILD generated vendored Normal file
View file

@ -0,0 +1,114 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"cached_discovery.go",
"clientcache.go",
"factory.go",
"helpers.go",
"printing.go",
"shortcut_restmapper.go",
],
tags = ["automanaged"],
deps = [
"//federation/apis/federation:go_default_library",
"//federation/client/clientset_generated/federation_internalclientset:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/errors:go_default_library",
"//pkg/api/meta:go_default_library",
"//pkg/api/service:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/typed/dynamic:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/labels:go_default_library",
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/runtime/serializer/json:go_default_library",
"//pkg/util/errors:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/flag:go_default_library",
"//pkg/util/homedir:go_default_library",
"//pkg/util/sets:go_default_library",
"//pkg/util/strategicpatch:go_default_library",
"//pkg/version:go_default_library",
"//pkg/watch:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger",
"//vendor:github.com/evanphx/json-patch",
"//vendor:github.com/golang/glog",
"//vendor:github.com/spf13/cobra",
"//vendor:github.com/spf13/pflag",
],
)
go_test(
name = "go_default_test",
srcs = [
"cached_discovery_test.go",
"factory_test.go",
"helpers_test.go",
"shortcut_restmapper_test.go",
],
data = [
"//api/swagger-spec",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/errors:go_default_library",
"//pkg/api/meta:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/testing:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/restclient/fake:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/client/typed/discovery:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/labels:go_default_library",
"//pkg/runtime:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/flag:go_default_library",
"//pkg/util/validation/field:go_default_library",
"//pkg/version:go_default_library",
"//pkg/watch:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger",
"//vendor:github.com/stretchr/testify/assert",
],
)

View file

@ -0,0 +1,253 @@
/*
Copyright 2016 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 util
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/version"
)
// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
// versions and resources.
type CachedDiscoveryClient struct {
delegate discovery.DiscoveryInterface
// cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
cacheDirectory string
// ttl is how long the cache should be considered valid
ttl time.Duration
// mutex protects the variables below
mutex sync.Mutex
// ourFiles are all filenames of cache files created by this process
ourFiles map[string]struct{}
// invalidated is true if all cache files should be ignored that are not ours (e.g. after Invalidate() was called)
invalidated bool
// fresh is true if all used cache files were ours
fresh bool
}
var _ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedResources := &metav1.APIResourceList{}
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
glog.V(6).Infof("returning cached discovery info from %v", filename)
return cachedResources, nil
}
}
liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return liveResources, err
}
if err := d.writeCachedFile(filename, liveResources); err != nil {
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveResources, nil
}
// ServerResources returns the supported resources for all groups and versions.
func (d *CachedDiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := metav1.ExtractGroupVersions(apiGroups)
result := map[string]*metav1.APIResourceList{}
for _, groupVersion := range groupVersions {
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return nil, err
}
result[groupVersion] = resources
}
return result, nil
}
func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
filename := filepath.Join(d.cacheDirectory, "servergroups.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedGroups := &metav1.APIGroupList{}
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
glog.V(6).Infof("returning cached discovery info from %v", filename)
return cachedGroups, nil
}
}
liveGroups, err := d.delegate.ServerGroups()
if err != nil {
return liveGroups, err
}
if err := d.writeCachedFile(filename, liveGroups); err != nil {
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveGroups, nil
}
func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
// after invalidation ignore cache files not created by this process
d.mutex.Lock()
_, ourFile := d.ourFiles[filename]
if d.invalidated && !ourFile {
d.mutex.Unlock()
return nil, errors.New("cache invalidated")
}
d.mutex.Unlock()
file, err := os.Open(filename)
if err != nil {
return nil, err
}
fileInfo, err := file.Stat()
if err != nil {
return nil, err
}
if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
return nil, errors.New("cache expired")
}
// the cache is present and its valid. Try to read and use it.
cachedBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.fresh = d.fresh && ourFile
return cachedBytes, nil
}
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return err
}
bytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj)
if err != nil {
return err
}
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
if err != nil {
return err
}
defer os.Remove(f.Name())
_, err = f.Write(bytes)
if err != nil {
return err
}
err = f.Chmod(0755)
if err != nil {
return err
}
name := f.Name()
err = f.Close()
if err != nil {
return err
}
// atomic rename
d.mutex.Lock()
defer d.mutex.Unlock()
err = os.Rename(name, filename)
if err == nil {
d.ourFiles[filename] = struct{}{}
}
return err
}
func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
return d.delegate.RESTClient()
}
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
return d.delegate.ServerPreferredResources()
}
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
return d.delegate.ServerPreferredNamespacedResources()
}
func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
return d.delegate.ServerVersion()
}
func (d *CachedDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
return d.delegate.SwaggerSchema(version)
}
func (d *CachedDiscoveryClient) Fresh() bool {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.fresh
}
func (d *CachedDiscoveryClient) Invalidate() {
d.mutex.Lock()
defer d.mutex.Unlock()
d.ourFiles = map[string]struct{}{}
d.fresh = true
d.invalidated = true
}
// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
func NewCachedDiscoveryClient(delegate discovery.DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
return &CachedDiscoveryClient{
delegate: delegate,
cacheDirectory: cacheDirectory,
ttl: ttl,
ourFiles: map[string]struct{}{},
fresh: true,
}
}

View file

@ -0,0 +1,165 @@
/*
Copyright 2016 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 util
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/emicklei/go-restful/swagger"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api/errors"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/version"
)
func TestCachedDiscoveryClient_Fresh(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := NewCachedDiscoveryClient(&c, d, 60*time.Second)
assert.True(cdc.Fresh(), "should be fresh after creation")
cdc.ServerGroups()
assert.True(cdc.Fresh(), "should be fresh after groups call without cache")
assert.Equal(c.groupCalls, 1)
cdc.ServerGroups()
assert.True(cdc.Fresh(), "should be fresh after another groups call")
assert.Equal(c.groupCalls, 1)
cdc.ServerResources()
assert.True(cdc.Fresh(), "should be fresh after resources call")
assert.Equal(c.resourceCalls, 1)
cdc.ServerResources()
assert.True(cdc.Fresh(), "should be fresh after another resources call")
assert.Equal(c.resourceCalls, 1)
cdc = NewCachedDiscoveryClient(&c, d, 60*time.Second)
cdc.ServerGroups()
assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing groups cache")
assert.Equal(c.groupCalls, 1)
cdc.ServerResources()
assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing resources cache")
assert.Equal(c.resourceCalls, 1)
cdc.Invalidate()
assert.True(cdc.Fresh(), "should be fresh after cache invalidation")
cdc.ServerResources()
assert.True(cdc.Fresh(), "should ignore existing resources cache after invalidation")
assert.Equal(c.resourceCalls, 2)
}
func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := NewCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
cdc.ServerGroups()
assert.Equal(c.groupCalls, 1)
time.Sleep(1 * time.Second)
cdc.ServerGroups()
assert.Equal(c.groupCalls, 2)
}
type fakeDiscoveryClient struct {
groupCalls int
resourceCalls int
versionCalls int
swaggerCalls int
}
var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
return &fake.RESTClient{}
}
func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
c.groupCalls = c.groupCalls + 1
return &metav1.APIGroupList{
Groups: []metav1.APIGroup{
{
Name: "a",
Versions: []metav1.GroupVersionForDiscovery{
{
GroupVersion: "a/v1",
Version: "v1",
},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "a/v1",
Version: "v1",
},
},
},
}, nil
}
func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
if groupVersion == "a/v1" {
return &metav1.APIResourceList{}, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "")
}
func (c *fakeDiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return map[string]*metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
c.resourceCalls = c.resourceCalls + 1
return []schema.GroupVersionResource{}, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
c.resourceCalls = c.resourceCalls + 1
return []schema.GroupVersionResource{}, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
c.versionCalls = c.versionCalls + 1
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) {
c.swaggerCalls = c.swaggerCalls + 1
return &swagger.ApiDeclaration{}, nil
}

View file

@ -0,0 +1,197 @@
/*
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 util
import (
"sync"
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/typed/discovery"
oldclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/runtime/schema"
)
func NewClientCache(loader clientcmd.ClientConfig) *ClientCache {
return &ClientCache{
clientsets: make(map[schema.GroupVersion]*internalclientset.Clientset),
configs: make(map[schema.GroupVersion]*restclient.Config),
fedClientSets: make(map[schema.GroupVersion]fedclientset.Interface),
loader: loader,
}
}
// ClientCache caches previously loaded clients for reuse, and ensures MatchServerVersion
// is invoked only once
type ClientCache struct {
loader clientcmd.ClientConfig
clientsets map[schema.GroupVersion]*internalclientset.Clientset
fedClientSets map[schema.GroupVersion]fedclientset.Interface
configs map[schema.GroupVersion]*restclient.Config
matchVersion bool
defaultConfigLock sync.Mutex
defaultConfig *restclient.Config
discoveryClient discovery.DiscoveryInterface
}
// also looks up the discovery client. We can't do this during init because the flags won't have been set
// because this is constructed pre-command execution before the command tree is even set up
func (c *ClientCache) getDefaultConfig() (restclient.Config, discovery.DiscoveryInterface, error) {
c.defaultConfigLock.Lock()
defer c.defaultConfigLock.Unlock()
if c.defaultConfig != nil && c.discoveryClient != nil {
return *c.defaultConfig, c.discoveryClient, nil
}
config, err := c.loader.ClientConfig()
if err != nil {
return restclient.Config{}, nil, err
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return restclient.Config{}, nil, err
}
if c.matchVersion {
if err := discovery.MatchesServerVersion(discoveryClient); err != nil {
return restclient.Config{}, nil, err
}
}
c.defaultConfig = config
c.discoveryClient = discoveryClient
return *c.defaultConfig, c.discoveryClient, nil
}
// ClientConfigForVersion returns the correct config for a server
func (c *ClientCache) ClientConfigForVersion(requiredVersion *schema.GroupVersion) (*restclient.Config, error) {
// TODO: have a better config copy method
config, discoveryClient, err := c.getDefaultConfig()
if err != nil {
return nil, err
}
if requiredVersion == nil && config.GroupVersion != nil {
// if someone has set the values via flags, our config will have the groupVersion set
// that means it is required.
requiredVersion = config.GroupVersion
}
// required version may still be nil, since config.GroupVersion may have been nil. Do the check
// before looking up from the cache
if requiredVersion != nil {
if config, ok := c.configs[*requiredVersion]; ok {
return config, nil
}
}
negotiatedVersion, err := discovery.NegotiateVersion(discoveryClient, requiredVersion, registered.EnabledVersions())
if err != nil {
return nil, err
}
config.GroupVersion = negotiatedVersion
// TODO this isn't what we want. Each clientset should be setting defaults as it sees fit.
oldclient.SetKubernetesDefaults(&config)
if requiredVersion != nil {
c.configs[*requiredVersion] = &config
}
// `version` does not necessarily equal `config.Version`. However, we know that we call this method again with
// `config.Version`, we should get the config we've just built.
configCopy := config
c.configs[*config.GroupVersion] = &configCopy
return &config, nil
}
// ClientSetForVersion initializes or reuses a clientset for the specified version, or returns an
// error if that is not possible
func (c *ClientCache) ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error) {
if requiredVersion != nil {
if clientset, ok := c.clientsets[*requiredVersion]; ok {
return clientset, nil
}
}
config, err := c.ClientConfigForVersion(requiredVersion)
if err != nil {
return nil, err
}
clientset, err := internalclientset.NewForConfig(config)
if err != nil {
return nil, err
}
c.clientsets[*config.GroupVersion] = clientset
// `version` does not necessarily equal `config.Version`. However, we know that if we call this method again with
// `version`, we should get a client based on the same config we just found. There's no guarantee that a client
// is copiable, so create a new client and save it in the cache.
if requiredVersion != nil {
configCopy := *config
clientset, err := internalclientset.NewForConfig(&configCopy)
if err != nil {
return nil, err
}
c.clientsets[*requiredVersion] = clientset
}
return clientset, nil
}
func (c *ClientCache) FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) {
if version != nil {
if clientSet, found := c.fedClientSets[*version]; found {
return clientSet, nil
}
}
config, err := c.ClientConfigForVersion(version)
if err != nil {
return nil, err
}
// TODO: support multi versions of client with clientset
clientSet, err := fedclientset.NewForConfig(config)
if err != nil {
return nil, err
}
c.fedClientSets[*config.GroupVersion] = clientSet
if version != nil {
configCopy := *config
clientSet, err := fedclientset.NewForConfig(&configCopy)
if err != nil {
return nil, err
}
c.fedClientSets[*version] = clientSet
}
return clientSet, nil
}
func (c *ClientCache) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) {
fedClientSet, err := c.FederationClientSetForVersion(version)
if err != nil {
return nil, err
}
return fedClientSet.Federation().RESTClient().(*restclient.RESTClient), nil
}

View file

@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["editor.go"],
tags = ["automanaged"],
deps = [
"//pkg/util/term:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = ["editor_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [],
)

View file

@ -0,0 +1,192 @@
/*
Copyright 2015 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 editor
import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/term"
)
const (
// sorry, blame Git
// TODO: on Windows rely on 'start' to launch the editor associated
// with the given file type. If we can't because of the need of
// blocking, use a script with 'ftype' and 'assoc' to detect it.
defaultEditor = "vi"
defaultShell = "/bin/bash"
windowsEditor = "notepad"
windowsShell = "cmd"
)
type Editor struct {
Args []string
Shell bool
}
// NewDefaultEditor creates a struct Editor that uses the OS environment to
// locate the editor program, looking at EDITOR environment variable to find
// the proper command line. If the provided editor has no spaces, or no quotes,
// it is treated as a bare command to be loaded. Otherwise, the string will
// be passed to the user's shell for execution.
func NewDefaultEditor(envs []string) Editor {
args, shell := defaultEnvEditor(envs)
return Editor{
Args: args,
Shell: shell,
}
}
func defaultEnvShell() []string {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = platformize(defaultShell, windowsShell)
}
flag := "-c"
if shell == windowsShell {
flag = "/C"
}
return []string{shell, flag}
}
func defaultEnvEditor(envs []string) ([]string, bool) {
var editor string
for _, env := range envs {
if len(env) > 0 {
editor = os.Getenv(env)
}
if len(editor) > 0 {
break
}
}
if len(editor) == 0 {
editor = platformize(defaultEditor, windowsEditor)
}
if !strings.Contains(editor, " ") {
return []string{editor}, false
}
if !strings.ContainsAny(editor, "\"'\\") {
return strings.Split(editor, " "), false
}
// rather than parse the shell arguments ourselves, punt to the shell
shell := defaultEnvShell()
return append(shell, editor), true
}
func (e Editor) args(path string) []string {
args := make([]string, len(e.Args))
copy(args, e.Args)
if e.Shell {
last := args[len(args)-1]
args[len(args)-1] = fmt.Sprintf("%s %q", last, path)
} else {
args = append(args, path)
}
return args
}
// Launch opens the described or returns an error. The TTY will be protected, and
// SIGQUIT, SIGTERM, and SIGINT will all be trapped.
func (e Editor) Launch(path string) error {
if len(e.Args) == 0 {
return fmt.Errorf("no editor defined, can't open %s", path)
}
abs, err := filepath.Abs(path)
if err != nil {
return err
}
args := e.args(abs)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
glog.V(5).Infof("Opening file with editor %v", args)
if err := (term.TTY{In: os.Stdin, TryDev: true}).Safe(cmd.Run); err != nil {
if err, ok := err.(*exec.Error); ok {
if err.Err == exec.ErrNotFound {
return fmt.Errorf("unable to launch the editor %q", strings.Join(e.Args, " "))
}
}
return fmt.Errorf("there was a problem with the editor %q", strings.Join(e.Args, " "))
}
return nil
}
// LaunchTempFile reads the provided stream into a temporary file in the given directory
// and file prefix, and then invokes Launch with the path of that file. It will return
// the contents of the file after launch, any errors that occur, and the path of the
// temporary file so the caller can clean it up as needed.
func (e Editor) LaunchTempFile(prefix, suffix string, r io.Reader) ([]byte, string, error) {
f, err := tempFile(prefix, suffix)
if err != nil {
return nil, "", err
}
defer f.Close()
path := f.Name()
if _, err := io.Copy(f, r); err != nil {
os.Remove(path)
return nil, path, err
}
// This file descriptor needs to close so the next process (Launch) can claim it.
f.Close()
if err := e.Launch(path); err != nil {
return nil, path, err
}
bytes, err := ioutil.ReadFile(path)
return bytes, path, err
}
func tempFile(prefix, suffix string) (f *os.File, err error) {
dir := os.TempDir()
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, prefix+randSeq(5)+suffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
continue
}
break
}
return
}
var letters = []rune("abcdefghijklmnopqrstuvwxyz0123456789")
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func platformize(linux, windows string) string {
if runtime.GOOS == "windows" {
return windows
}
return linux
}

View file

@ -0,0 +1,63 @@
/*
Copyright 2015 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 editor
import (
"bytes"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
)
func TestArgs(t *testing.T) {
if e, a := []string{"/bin/bash", "-c \"test\""}, (Editor{Args: []string{"/bin/bash", "-c"}, Shell: true}).args("test"); !reflect.DeepEqual(e, a) {
t.Errorf("unexpected args: %v", a)
}
if e, a := []string{"/bin/bash", "-c", "test"}, (Editor{Args: []string{"/bin/bash", "-c"}, Shell: false}).args("test"); !reflect.DeepEqual(e, a) {
t.Errorf("unexpected args: %v", a)
}
if e, a := []string{"/bin/bash", "-i -c \"test\""}, (Editor{Args: []string{"/bin/bash", "-i -c"}, Shell: true}).args("test"); !reflect.DeepEqual(e, a) {
t.Errorf("unexpected args: %v", a)
}
if e, a := []string{"/test", "test"}, (Editor{Args: []string{"/test"}}).args("test"); !reflect.DeepEqual(e, a) {
t.Errorf("unexpected args: %v", a)
}
}
func TestEditor(t *testing.T) {
edit := Editor{Args: []string{"cat"}}
testStr := "test something\n"
contents, path, err := edit.LaunchTempFile("", "someprefix", bytes.NewBufferString(testStr))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := os.Stat(path); err != nil {
t.Fatalf("no temp file: %s", path)
}
defer os.Remove(path)
if disk, err := ioutil.ReadFile(path); err != nil || !bytes.Equal(contents, disk) {
t.Errorf("unexpected file on disk: %v %s", err, string(disk))
}
if !bytes.Equal(contents, []byte(testStr)) {
t.Errorf("unexpected contents: %s", string(contents))
}
if !strings.Contains(path, "someprefix") {
t.Errorf("path not expected: %s", path)
}
}

1343
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go generated vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,381 @@
/*
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 util
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"syscall"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/schema"
uexec "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/validation/field"
)
func TestMerge(t *testing.T) {
grace := int64(30)
tests := []struct {
obj runtime.Object
fragment string
expected runtime.Object
expectErr bool
kind string
}{
{
kind: "Pod",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
},
fragment: fmt.Sprintf(`{ "apiVersion": "%s" }`, registered.GroupOrDie(api.GroupName).GroupVersion.String()),
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: apitesting.DeepEqualSafePodSpec(),
},
},
/* TODO: uncomment this test once Merge is updated to use
strategic-merge-patch. See #8449.
{
kind: "Pod",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
Containers: []api.Container{
api.Container{
Name: "c1",
Image: "red-image",
},
api.Container{
Name: "c2",
Image: "blue-image",
},
},
},
},
fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "containers": [ { "name": "c1", "image": "green-image" } ] } }`, registered.GroupOrDie(api.GroupName).GroupVersion.String()),
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
Containers: []api.Container{
api.Container{
Name: "c1",
Image: "green-image",
},
api.Container{
Name: "c2",
Image: "blue-image",
},
},
},
},
}, */
{
kind: "Pod",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
},
fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "volumes": [ {"name": "v1"}, {"name": "v2"} ] } }`, registered.GroupOrDie(api.GroupName).GroupVersion.String()),
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{
Name: "v1",
VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}},
},
{
Name: "v2",
VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}},
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &api.PodSecurityContext{},
},
},
},
{
kind: "Pod",
obj: &api.Pod{},
fragment: "invalid json",
expected: &api.Pod{},
expectErr: true,
},
{
kind: "Service",
obj: &api.Service{},
fragment: `{ "apiVersion": "badVersion" }`,
expectErr: true,
},
{
kind: "Service",
obj: &api.Service{
Spec: api.ServiceSpec{},
},
fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "ports": [ { "port": 0 } ] } }`, registered.GroupOrDie(api.GroupName).GroupVersion.String()),
expected: &api.Service{
Spec: api.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
Ports: []api.ServicePort{
{
Protocol: api.ProtocolTCP,
Port: 0,
},
},
},
},
},
{
kind: "Service",
obj: &api.Service{
Spec: api.ServiceSpec{
Selector: map[string]string{
"version": "v1",
},
},
},
fragment: fmt.Sprintf(`{ "apiVersion": "%s", "spec": { "selector": { "version": "v2" } } }`, registered.GroupOrDie(api.GroupName).GroupVersion.String()),
expected: &api.Service{
Spec: api.ServiceSpec{
SessionAffinity: "None",
Type: api.ServiceTypeClusterIP,
Selector: map[string]string{
"version": "v2",
},
},
},
},
}
for i, test := range tests {
out, err := Merge(testapi.Default.Codec(), test.obj, test.fragment, test.kind)
if !test.expectErr {
if err != nil {
t.Errorf("testcase[%d], unexpected error: %v", i, err)
} else if !reflect.DeepEqual(out, test.expected) {
t.Errorf("\n\ntestcase[%d]\nexpected:\n%+v\nsaw:\n%+v", i, test.expected, out)
}
}
if test.expectErr && err == nil {
t.Errorf("testcase[%d], unexpected non-error", i)
}
}
}
type fileHandler struct {
data []byte
}
func (f *fileHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/error" {
res.WriteHeader(http.StatusNotFound)
return
}
res.WriteHeader(http.StatusOK)
res.Write(f.data)
}
type checkErrTestCase struct {
err error
expectedErr string
expectedCode int
}
func TestCheckInvalidErr(t *testing.T) {
testCheckError(t, []checkErrTestCase{
{
errors.NewInvalid(api.Kind("Invalid1"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field"), "single", "details")}),
"The Invalid1 \"invalidation\" is invalid: field: Invalid value: \"single\": details\n",
DefaultErrorExitCode,
},
{
errors.NewInvalid(api.Kind("Invalid2"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field1"), "multi1", "details"), field.Invalid(field.NewPath("field2"), "multi2", "details")}),
"The Invalid2 \"invalidation\" is invalid: \n* field1: Invalid value: \"multi1\": details\n* field2: Invalid value: \"multi2\": details\n",
DefaultErrorExitCode,
},
{
errors.NewInvalid(api.Kind("Invalid3"), "invalidation", field.ErrorList{}),
"The Invalid3 \"invalidation\" is invalid",
DefaultErrorExitCode,
},
{
errors.NewInvalid(api.Kind("Invalid4"), "invalidation", field.ErrorList{field.Invalid(field.NewPath("field4"), "multi4", "details"), field.Invalid(field.NewPath("field4"), "multi4", "details")}),
"The Invalid4 \"invalidation\" is invalid: field4: Invalid value: \"multi4\": details\n",
DefaultErrorExitCode,
},
})
}
func TestCheckNoResourceMatchError(t *testing.T) {
testCheckError(t, []checkErrTestCase{
{
&meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}},
`the server doesn't have a resource type "foo"`,
DefaultErrorExitCode,
},
{
&meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Version: "theversion", Resource: "foo"}},
`the server doesn't have a resource type "foo" in version "theversion"`,
DefaultErrorExitCode,
},
{
&meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: "thegroup", Version: "theversion", Resource: "foo"}},
`the server doesn't have a resource type "foo" in group "thegroup" and version "theversion"`,
DefaultErrorExitCode,
},
{
&meta.NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: "thegroup", Resource: "foo"}},
`the server doesn't have a resource type "foo" in group "thegroup"`,
DefaultErrorExitCode,
},
})
}
func TestCheckExitError(t *testing.T) {
testCheckError(t, []checkErrTestCase{
{
uexec.CodeExitError{Err: fmt.Errorf("pod foo/bar terminated"), Code: 42},
"",
42,
},
})
}
func testCheckError(t *testing.T, tests []checkErrTestCase) {
var errReturned string
var codeReturned int
errHandle := func(err string, code int) {
errReturned = err
codeReturned = code
}
for _, test := range tests {
checkErr("", test.err, errHandle)
if errReturned != test.expectedErr {
t.Fatalf("Got: %s, expected: %s", errReturned, test.expectedErr)
}
if codeReturned != test.expectedCode {
t.Fatalf("Got: %d, expected: %d", codeReturned, test.expectedCode)
}
}
}
func TestDumpReaderToFile(t *testing.T) {
testString := "TEST STRING"
tempFile, err := ioutil.TempFile("", "hlpers_test_dump_")
if err != nil {
t.Errorf("unexpected error setting up a temporary file %v", err)
}
defer syscall.Unlink(tempFile.Name())
defer tempFile.Close()
defer func() {
if !t.Failed() {
os.Remove(tempFile.Name())
}
}()
err = DumpReaderToFile(strings.NewReader(testString), tempFile.Name())
if err != nil {
t.Errorf("error in DumpReaderToFile: %v", err)
}
data, err := ioutil.ReadFile(tempFile.Name())
if err != nil {
t.Errorf("error when reading %s: %v", tempFile.Name(), err)
}
stringData := string(data)
if stringData != testString {
t.Fatalf("Wrong file content %s != %s", testString, stringData)
}
}
func TestMaybeConvert(t *testing.T) {
tests := []struct {
input runtime.Object
gv schema.GroupVersion
expected runtime.Object
}{
{
input: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
},
gv: schema.GroupVersion{Group: "", Version: "v1"},
expected: &v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
},
},
},
{
input: &extensions.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Data: []byte("this is some data"),
},
expected: &extensions.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Data: []byte("this is some data"),
},
},
}
for _, test := range tests {
obj, err := MaybeConvertObject(test.input, test.gv, testapi.Default.Converter())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expected, obj) {
t.Errorf("expected:\n%#v\nsaw:\n%#v\n", test.expected, obj)
}
}
}

View file

@ -0,0 +1,23 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["jsonmerge.go"],
tags = ["automanaged"],
deps = [
"//pkg/util/strategicpatch:go_default_library",
"//pkg/util/yaml:go_default_library",
"//vendor:github.com/evanphx/json-patch",
"//vendor:github.com/golang/glog",
],
)

View file

@ -0,0 +1,193 @@
/*
Copyright 2015 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 jsonmerge
import (
"encoding/json"
"fmt"
"github.com/evanphx/json-patch"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/util/yaml"
)
// Delta represents a change between two JSON documents.
type Delta struct {
original []byte
edit []byte
preconditions []PreconditionFunc
}
// PreconditionFunc is a test to verify that an incompatible change
// has occurred before an Apply can be successful.
type PreconditionFunc func(interface{}) (hold bool, message string)
// AddPreconditions adds precondition checks to a change which must
// be satisfied before an Apply is considered successful. If a
// precondition returns false, the Apply is failed with
// ErrPreconditionFailed.
func (d *Delta) AddPreconditions(fns ...PreconditionFunc) {
d.preconditions = append(d.preconditions, fns...)
}
// RequireKeyUnchanged creates a precondition function that fails
// if the provided key is present in the diff (indicating its value
// has changed).
func RequireKeyUnchanged(key string) PreconditionFunc {
return func(diff interface{}) (bool, string) {
m, ok := diff.(map[string]interface{})
if !ok {
return true, ""
}
// the presence of key in a diff means that its value has been changed, therefore
// we should fail the precondition.
_, ok = m[key]
if ok {
return false, key + " should not be changed\n"
} else {
return true, ""
}
}
}
// RequireMetadataKeyUnchanged creates a precondition function that fails
// if the metadata.key is present in the diff (indicating its value
// has changed).
func RequireMetadataKeyUnchanged(key string) PreconditionFunc {
return func(diff interface{}) (bool, string) {
m, ok := diff.(map[string]interface{})
if !ok {
return true, ""
}
m1, ok := m["metadata"]
if !ok {
return true, ""
}
m2, ok := m1.(map[string]interface{})
if !ok {
return true, ""
}
_, ok = m2[key]
if ok {
return false, "metadata." + key + " should not be changed\n"
} else {
return true, ""
}
}
}
// TestPreconditions test if preconditions hold given the edit
func TestPreconditionsHold(edit []byte, preconditions []PreconditionFunc) (bool, string) {
diff := make(map[string]interface{})
if err := json.Unmarshal(edit, &diff); err != nil {
return false, err.Error()
}
for _, fn := range preconditions {
if hold, msg := fn(diff); !hold {
return false, msg
}
}
return true, ""
}
// NewDelta accepts two JSON or YAML documents and calculates the difference
// between them. It returns a Delta object which can be used to resolve
// conflicts against a third version with a common parent, or an error
// if either document is in error.
func NewDelta(from, to []byte) (*Delta, error) {
d := &Delta{}
before, err := yaml.ToJSON(from)
if err != nil {
return nil, err
}
after, err := yaml.ToJSON(to)
if err != nil {
return nil, err
}
diff, err := jsonpatch.CreateMergePatch(before, after)
if err != nil {
return nil, err
}
glog.V(6).Infof("Patch created from:\n%s\n%s\n%s", string(before), string(after), string(diff))
d.original = before
d.edit = diff
return d, nil
}
// Apply attempts to apply the changes described by Delta onto latest,
// returning an error if the changes cannot be applied cleanly.
// IsConflicting will be true if the changes overlap, otherwise a
// generic error will be returned.
func (d *Delta) Apply(latest []byte) ([]byte, error) {
base, err := yaml.ToJSON(latest)
if err != nil {
return nil, err
}
changes, err := jsonpatch.CreateMergePatch(d.original, base)
if err != nil {
return nil, err
}
diff1 := make(map[string]interface{})
if err := json.Unmarshal(d.edit, &diff1); err != nil {
return nil, err
}
diff2 := make(map[string]interface{})
if err := json.Unmarshal(changes, &diff2); err != nil {
return nil, err
}
for _, fn := range d.preconditions {
hold1, _ := fn(diff1)
hold2, _ := fn(diff2)
if !hold1 || !hold2 {
return nil, ErrPreconditionFailed
}
}
glog.V(6).Infof("Testing for conflict between:\n%s\n%s", string(d.edit), string(changes))
hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2)
if err != nil {
return nil, err
}
if hasConflicts {
return nil, ErrConflict
}
return jsonpatch.MergePatch(base, d.edit)
}
// IsConflicting returns true if the provided error indicates a
// conflict exists between the original changes and the applied
// changes.
func IsConflicting(err error) bool {
return err == ErrConflict
}
// IsPreconditionFailed returns true if the provided error indicates
// a Delta precondition did not succeed.
func IsPreconditionFailed(err error) bool {
return err == ErrPreconditionFailed
}
var ErrPreconditionFailed = fmt.Errorf("a precondition failed")
var ErrConflict = fmt.Errorf("changes are in conflict")
func (d *Delta) Edit() []byte {
return d.edit
}

View file

@ -0,0 +1,150 @@
/*
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 util
import (
"fmt"
"io"
"strings"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime/schema"
"github.com/spf13/cobra"
)
// AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path)
func AddPrinterFlags(cmd *cobra.Command) {
AddOutputFlags(cmd)
cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').")
AddNoHeadersFlags(cmd)
cmd.Flags().Bool("show-labels", false, "When printing, show all labels as the last column (default hide labels column)")
cmd.Flags().String("template", "", "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
cmd.MarkFlagFilename("template")
cmd.Flags().String("sort-by", "", "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.")
cmd.Flags().BoolP("show-all", "a", false, "When printing, show all resources (default hide terminated pods.)")
}
// AddOutputFlagsForMutation adds output related flags to a command. Used by mutations only.
func AddOutputFlagsForMutation(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).")
}
// AddOutputFlags adds output related flags to a command.
func AddOutputFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
}
// AddNoHeadersFlags adds no-headers flags to a command.
func AddNoHeadersFlags(cmd *cobra.Command) {
cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers.")
}
// PrintSuccess prints message after finishing mutating operations
func PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource string, name string, dryRun bool, operation string) {
resource, _ = mapper.ResourceSingularizer(resource)
dryRunMsg := ""
if dryRun {
dryRunMsg = " (dry run)"
}
if shortOutput {
// -o name: prints resource/name
if len(resource) > 0 {
fmt.Fprintf(out, "%s/%s\n", resource, name)
} else {
fmt.Fprintf(out, "%s\n", name)
}
} else {
// understandable output by default
if len(resource) > 0 {
fmt.Fprintf(out, "%s \"%s\" %s%s\n", resource, name, operation, dryRunMsg)
} else {
fmt.Fprintf(out, "\"%s\" %s%s\n", name, operation, dryRunMsg)
}
}
}
// ValidateOutputArgs validates -o flag args for mutations
func ValidateOutputArgs(cmd *cobra.Command) error {
outputMode := GetFlagString(cmd, "output")
if outputMode != "" && outputMode != "name" {
return UsageError(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", outputMode)
}
return nil
}
// OutputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
// defaultVersion is never mutated. Nil simply allows clean passing in common usage from client.Config
func OutputVersion(cmd *cobra.Command, defaultVersion *schema.GroupVersion) (schema.GroupVersion, error) {
outputVersionString := GetFlagString(cmd, "output-version")
if len(outputVersionString) == 0 {
if defaultVersion == nil {
return schema.GroupVersion{}, nil
}
return *defaultVersion, nil
}
return schema.ParseGroupVersion(outputVersionString)
}
// PrinterForCommand returns the default printer for this command.
// Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) {
outputFormat := GetFlagString(cmd, "output")
// templates are logically optional for specifying a format.
// TODO once https://github.com/kubernetes/kubernetes/issues/12668 is fixed, this should fall back to GetFlagString
templateFile, _ := cmd.Flags().GetString("template")
if len(outputFormat) == 0 && len(templateFile) != 0 {
outputFormat = "template"
}
templateFormat := []string{
"go-template=", "go-template-file=", "jsonpath=", "jsonpath-file=", "custom-columns=", "custom-columns-file=",
}
for _, format := range templateFormat {
if strings.HasPrefix(outputFormat, format) {
templateFile = outputFormat[len(format):]
outputFormat = format[:len(format)-1]
}
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"))
if err != nil {
return nil, generic, err
}
return maybeWrapSortingPrinter(cmd, printer), generic, nil
}
func maybeWrapSortingPrinter(cmd *cobra.Command, printer kubectl.ResourcePrinter) kubectl.ResourcePrinter {
sorting, err := cmd.Flags().GetString("sort-by")
if err != nil {
// error can happen on missing flag or bad flag type. In either case, this command didn't intent to sort
return printer
}
if len(sorting) != 0 {
return &kubectl.SortingPrinter{
Delegate: printer,
SortField: fmt.Sprintf("{%s}", sorting),
}
}
return printer
}

View file

@ -0,0 +1,21 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["cmd_sanity.go"],
tags = ["automanaged"],
deps = [
"//pkg/kubectl/cmd/templates:go_default_library",
"//vendor:github.com/spf13/cobra",
],
)

View file

@ -0,0 +1,94 @@
/*
Copyright 2016 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 sanity
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
)
type CmdCheck func(cmd *cobra.Command) []error
var (
AllCmdChecks = []CmdCheck{
CheckLongDesc,
CheckExamples,
}
)
func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error {
cmdPath := cmd.CommandPath()
for _, skipCmdPath := range skip {
if cmdPath == skipCmdPath {
fmt.Fprintf(os.Stdout, "-----+ skipping command %s\n", cmdPath)
return []error{}
}
}
errors := []error{}
if cmd.HasSubCommands() {
for _, subCmd := range cmd.Commands() {
errors = append(errors, CheckCmdTree(subCmd, checks, skip)...)
}
}
fmt.Fprintf(os.Stdout, "-----+ checking command %s\n", cmdPath)
for _, check := range checks {
if err := check(cmd); err != nil && len(err) > 0 {
errors = append(errors, err...)
}
}
return errors
}
func CheckLongDesc(cmd *cobra.Command) []error {
cmdPath := cmd.CommandPath()
long := cmd.Long
if len(long) > 0 {
if strings.Trim(long, " \t\n") != long {
return []error{fmt.Errorf(`command %q: long description is not normalized
make sure you are calling templates.LongDesc (from pkg/cmd/templates) before assigning cmd.Long`, cmdPath)}
}
}
return nil
}
func CheckExamples(cmd *cobra.Command) []error {
cmdPath := cmd.CommandPath()
examples := cmd.Example
errors := []error{}
if len(examples) > 0 {
for _, line := range strings.Split(examples, "\n") {
if !strings.HasPrefix(line, templates.Indentation) {
errors = append(errors, fmt.Errorf(`command %q: examples are not normalized
make sure you are calling templates.Examples (from pkg/cmd/templates) before assigning cmd.Example`, cmdPath))
}
if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") {
errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath))
}
}
}
return errors
}

View file

@ -0,0 +1,147 @@
/*
Copyright 2016 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 util
import (
"strings"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime/schema"
)
// ShortcutExpander is a RESTMapper that can be used for Kubernetes resources. It expands the resource first, then invokes the wrapped
type ShortcutExpander struct {
RESTMapper meta.RESTMapper
All []schema.GroupResource
discoveryClient discovery.DiscoveryInterface
}
var _ meta.RESTMapper = &ShortcutExpander{}
func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) ShortcutExpander {
return ShortcutExpander{All: userResources, RESTMapper: delegate, discoveryClient: client}
}
func (e ShortcutExpander) getAll() []schema.GroupResource {
if e.discoveryClient == nil {
return e.All
}
// Check if we have access to server resources
apiResources, err := e.discoveryClient.ServerResources()
if err != nil {
return e.All
}
availableResources := []schema.GroupVersionResource{}
for groupVersionString, resourceList := range apiResources {
currVersion, err := schema.ParseGroupVersion(groupVersionString)
if err != nil {
return e.All
}
for _, resource := range resourceList.APIResources {
availableResources = append(availableResources, currVersion.WithResource(resource.Name))
}
}
availableAll := []schema.GroupResource{}
for _, requestedResource := range e.All {
for _, availableResource := range availableResources {
if requestedResource.Group == availableResource.Group &&
requestedResource.Resource == availableResource.Resource {
availableAll = append(availableAll, requestedResource)
break
}
}
}
return availableAll
}
func (e ShortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
return e.RESTMapper.KindFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
return e.RESTMapper.KindsFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
return e.RESTMapper.ResourcesFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
return e.RESTMapper.ResourceFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceSingularizer(resource string) (string, error) {
return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(schema.GroupVersionResource{Resource: resource}).Resource)
}
func (e ShortcutExpander) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
return e.RESTMapper.RESTMapping(gk, versions...)
}
func (e ShortcutExpander) RESTMappings(gk schema.GroupKind) ([]*meta.RESTMapping, error) {
return e.RESTMapper.RESTMappings(gk)
}
// userResources are the resource names that apply to the primary, user facing resources used by
// client tools. They are in deletion-first order - dependent resources should be last.
var userResources = []schema.GroupResource{
{Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"},
{Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "extensions", Resource: "jobs"},
{Group: "extensions", Resource: "deployments"},
{Group: "extensions", Resource: "replicasets"},
}
// AliasesForResource returns whether a resource has an alias or not
func (e ShortcutExpander) AliasesForResource(resource string) ([]string, bool) {
if strings.ToLower(resource) == "all" {
var resources []schema.GroupResource
if resources = e.getAll(); len(resources) == 0 {
resources = userResources
}
aliases := []string{}
for _, r := range resources {
aliases = append(aliases, r.Resource)
}
return aliases, true
}
expanded := expandResourceShortcut(schema.GroupVersionResource{Resource: resource}).Resource
return []string{expanded}, (expanded != resource)
}
// expandResourceShortcut will return the expanded version of resource
// (something that a pkg/api/meta.RESTMapper can understand), if it is
// indeed a shortcut. Otherwise, will return resource unmodified.
func expandResourceShortcut(resource schema.GroupVersionResource) schema.GroupVersionResource {
if expanded, ok := kubectl.ShortForms[resource.Resource]; ok {
resource.Resource = expanded
return resource
}
return resource
}

View file

@ -0,0 +1,61 @@
/*
Copyright 2016 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 util
import (
"strings"
"testing"
"k8s.io/kubernetes/pkg/api/testapi"
)
func TestReplaceAliases(t *testing.T) {
tests := []struct {
name string
arg string
expected string
}{
{
name: "no-replacement",
arg: "service",
expected: "service",
},
{
name: "all-replacement",
arg: "all",
expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets",
},
{
name: "alias-in-comma-separated-arg",
arg: "all,secrets",
expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets,secrets",
},
}
mapper := NewShortcutExpander(testapi.Default.RESTMapper(), nil)
for _, test := range tests {
resources := []string{}
for _, arg := range strings.Split(test.arg, ",") {
curr, _ := mapper.AliasesForResource(arg)
resources = append(resources, curr...)
}
if strings.Join(resources, ",") != test.expected {
t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, resources)
}
}
}