Merge pull request #18835 from brendandburns/3rdparty

Automatic merge from submit-queue

Add support for 3rd party objects to kubectl

@deads2k @jlowdermilk

Instructions for playing around with this:

Run an apiserver with third party resources turned on (`--runtime-config=extensions/v1beta1=true,extensions/v1beta1/thirdpartyresources=true`)

Then you should be able to:

```
kubectl create -f rsrc.json
```

```json
{
  "metadata": {
    "name": "foo.company.com"
  },
  "apiVersion": "extensions/v1beta1",
  "kind": "ThirdPartyResource",
  "versions": [
    {
      "apiGroup": "group",
      "name": "v1"
    },
    {
      "apiGroup": "group",
      "name": "v2"
    }
  ]
}
```

Once that is done, you should be able to:

```
curl http://<server>/apis/company.com/v1/foos
```

```
curl -X POST -d @${HOME}/foo.json http://localhost:8080/apis/company.com/v1/namespaces/default/foos
```

```json
{
  "kind": "Foo",
  "apiVersion": "company.com/v1",
  "metadata": {
    "name": "baz"
  },
  "someField": "hello world",
  "otherField": 1
}
```

After this PR, you can do:

```
kubectl create -f foo.json
```

```
kubectl get foos
```

etc.
This commit is contained in:
k8s-merge-robot
2016-03-31 18:09:56 -07:00
115 changed files with 697 additions and 148 deletions

View File

@@ -35,6 +35,10 @@ var (
// registeredGroupVersions stores all API group versions for which RegisterGroup is called.
registeredVersions = map[unversioned.GroupVersion]struct{}{}
// thirdPartyGroupVersions are API versions which are dynamically
// registered (and unregistered) via API calls to the apiserver
thirdPartyGroupVersions []unversioned.GroupVersion
// enabledVersions represents all enabled API versions. It should be a
// subset of registeredVersions. Please call EnableVersions() to add
// enabled versions.
@@ -158,6 +162,51 @@ func IsRegistered(group string) bool {
return found
}
// IsRegisteredVersion returns if a version is registered.
func IsRegisteredVersion(v unversioned.GroupVersion) bool {
_, found := registeredVersions[v]
return found
}
// IsThirdPartyAPIGroupVersion returns true if the api version is a user-registered group/version.
func IsThirdPartyAPIGroupVersion(gv unversioned.GroupVersion) bool {
for ix := range thirdPartyGroupVersions {
if thirdPartyGroupVersions[ix] == gv {
return true
}
}
return false
}
// AddThirdPartyAPIGroupVersions sets the list of third party versions,
// registers them in the API machinery and enables them.
// Skips GroupVersions that are already registered.
// Returns the list of GroupVersions that were skipped.
func AddThirdPartyAPIGroupVersions(gvs ...unversioned.GroupVersion) []unversioned.GroupVersion {
filteredGVs := []unversioned.GroupVersion{}
skippedGVs := []unversioned.GroupVersion{}
for ix := range gvs {
if !IsRegisteredVersion(gvs[ix]) {
filteredGVs = append(filteredGVs, gvs[ix])
} else {
glog.V(3).Infof("Skipping %s, because its already registered", gvs[ix].String())
skippedGVs = append(skippedGVs, gvs[ix])
}
}
if len(filteredGVs) == 0 {
return skippedGVs
}
RegisterVersions(filteredGVs)
EnableVersions(filteredGVs...)
next := make([]unversioned.GroupVersion, len(gvs))
for ix := range filteredGVs {
next[ix] = filteredGVs[ix]
}
thirdPartyGroupVersions = next
return skippedGVs
}
// TODO: This is an expedient function, because we don't check if a Group is
// supported throughout the code base. We will abandon this function and
// checking the error returned by the Group() function.

View File

@@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/flowcontrol"
)
@@ -159,3 +160,7 @@ func (c *RESTClient) Delete() *Request {
func (c *RESTClient) APIVersion() unversioned.GroupVersion {
return *c.contentConfig.GroupVersion
}
func (c *RESTClient) Codec() runtime.Codec {
return c.contentConfig.Codec
}

View File

@@ -110,6 +110,7 @@ func NewCmdAnnotate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
},
}
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.Flags().StringVarP(&options.selector, "selector", "l", "", "Selector (label query) to filter on")
cmd.Flags().BoolVar(&options.overwrite, "overwrite", false, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().BoolVar(&options.all, "all", false, "select all resources in the namespace of the specified resource types")
@@ -161,7 +162,7 @@ func (o *AnnotateOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra
o.recordChangeCause = cmdutil.GetRecordFlag(cmd)
o.changeCause = f.Command()
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
@@ -246,11 +247,13 @@ func (o AnnotateOptions) RunAnnotate() error {
if err != nil {
return err
}
mapper, _ := o.f.Object(cmdutil.GetIncludeThirdPartyAPIs(o.cmd))
outputFormat := cmdutil.GetFlagString(o.cmd, "output")
if outputFormat != "" {
return o.f.PrintObject(o.cmd, outputObj, o.out)
return o.f.PrintObject(o.cmd, mapper, outputObj, o.out)
}
mapper, _ := o.f.Object()
cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "annotated")
return nil
})

View File

@@ -72,6 +72,7 @@ func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -95,7 +96,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
Schema(schema).
ContinueOnError().

View File

@@ -74,6 +74,7 @@ func NewCmdAutoscale(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -88,7 +89,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
@@ -157,7 +158,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
}
// TODO: extract this flag to a central location, when such a location exists.
if cmdutil.GetFlagBool(cmd, "dry-run") {
return f.PrintObject(cmd, object, out)
return f.PrintObject(cmd, mapper, object, out)
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil {
@@ -170,7 +171,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
}
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return f.PrintObject(cmd, object, out)
return f.PrintObject(cmd, mapper, object, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "autoscaled")
return nil

View File

@@ -42,6 +42,7 @@ func NewCmdClusterInfo(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.CheckErr(err)
},
}
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -56,7 +57,7 @@ func RunClusterInfo(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error
}
printService(out, "Kubernetes master", client.Host)
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
if cmdNamespace == "" {
cmdNamespace = api.NamespaceSystem

View File

@@ -180,7 +180,7 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Typer: scheme,
}
return &cmdutil.Factory{
Object: func() (meta.RESTMapper, runtime.ObjectTyper) {
Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) {
priorityRESTMapper := meta.PriorityRESTMapper{
Delegate: t.Mapper,
ResourcePriority: []unversioned.GroupVersionResource{
@@ -221,7 +221,7 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
func NewMixedFactory(apiClient resource.RESTClient) (*cmdutil.Factory, *testFactory, runtime.Codec) {
f, t, c := NewTestFactory()
f.Object = func() (meta.RESTMapper, runtime.ObjectTyper) {
f.Object = func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) {
priorityRESTMapper := meta.PriorityRESTMapper{
Delegate: meta.MultiRESTMapper{t.Mapper, testapi.Default.RESTMapper()},
ResourcePriority: []unversioned.GroupVersionResource{
@@ -248,7 +248,7 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
}
f := &cmdutil.Factory{
Object: func() (meta.RESTMapper, runtime.ObjectTyper) {
Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) {
return testapi.Default.RESTMapper(), api.Scheme
},
Client: func() (*client.Client, error) {
@@ -380,7 +380,8 @@ func ExamplePrintReplicationControllerWithNamespace() {
Replicas: 1,
},
}
err := f.PrintObject(cmd, ctrl, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -428,7 +429,8 @@ func ExamplePrintMultiContainersReplicationControllerWithWide() {
Replicas: 1,
},
}
err := f.PrintObject(cmd, ctrl, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -476,7 +478,8 @@ func ExamplePrintReplicationController() {
Replicas: 1,
},
}
err := f.PrintObject(cmd, ctrl, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -511,7 +514,8 @@ func ExamplePrintPodWithWideFormat() {
},
},
}
err := f.PrintObject(cmd, pod, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -550,7 +554,8 @@ func ExamplePrintPodWithShowLabels() {
},
},
}
err := f.PrintObject(cmd, pod, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -660,7 +665,8 @@ func ExamplePrintPodHideTerminated() {
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
err := f.PrintObject(cmd, podList, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, podList, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -680,7 +686,8 @@ func ExamplePrintPodShowAll() {
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
err := f.PrintObject(cmd, podList, os.Stdout)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, podList, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
@@ -748,7 +755,9 @@ func ExamplePrintServiceWithNamespacesAndLabels() {
}
ld := util.NewLineDelimiter(os.Stdout, "|")
defer ld.Flush()
err := f.PrintObject(cmd, svc, ld)
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, svc, ld)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}

View File

@@ -79,7 +79,7 @@ func NewCmdConvert(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmd.Flags().BoolVar(&options.local, "local", true, "If true, convert will NOT try to contact api-server but run locally.")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -109,8 +109,9 @@ func (o *ConvertOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.
}
// build the builder
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
clientMapper := resource.ClientMapperFunc(f.ClientForMapping)
if o.local {
fmt.Fprintln(out, "running in local mode...")
o.builder = resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{ClientMapper: clientMapper}, f.Decoder(true))

View File

@@ -76,6 +76,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
// create subcommands
cmd.AddCommand(NewCmdCreateNamespace(f, out))
@@ -103,7 +104,7 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
Schema(schema).
ContinueOnError().
@@ -223,7 +224,7 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer,
if err != nil {
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
gvk, err := typer.ObjectKind(obj)
mapping, err := mapper.RESTMapping(unversioned.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
if err != nil {
@@ -257,5 +258,5 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer,
return nil
}
return f.PrintObject(cmd, obj, out)
return f.PrintObject(cmd, mapper, obj, out)
}

View File

@@ -51,6 +51,7 @@ func NewCmdCreateNamespace(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.NamespaceV1GeneratorName)
return cmd
}

View File

@@ -38,6 +38,7 @@ func NewCmdCreateSecret(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
}
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, cmdOut))
cmd.AddCommand(NewCmdCreateSecretGeneric(f, cmdOut))
return cmd
}
@@ -155,6 +156,7 @@ func NewCmdCreateSecretDockerRegistry(f *cmdutil.Factory, cmdOut io.Writer) *cob
cmd.Flags().String("docker-email", "", "Email for Docker registry")
cmd.MarkFlagRequired("docker-email")
cmd.Flags().String("docker-server", "https://index.docker.io/v1/", "Server location for Docker registry")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}

View File

@@ -50,6 +50,7 @@ func NewCmdCreateServiceAccount(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Com
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceAccountV1GeneratorName)
return cmd
}

View File

@@ -100,6 +100,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -109,7 +110,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err
}
deleteAll := cmdutil.GetFlagBool(cmd, "all")
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -89,6 +89,7 @@ func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command {
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -103,7 +104,7 @@ func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
return cmdutil.UsageError(cmd, "Required resource not specified.")
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -60,7 +60,7 @@ kubectl cordon foo
func NewCmdCordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DrainOptions{factory: f, out: out}
return &cobra.Command{
cmd := &cobra.Command{
Use: "cordon NODE",
Short: "Mark node as unschedulable",
Long: cordon_long,
@@ -70,6 +70,7 @@ func NewCmdCordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.CheckErr(options.RunCordonOrUncordon(true))
},
}
return cmd
}
const (
@@ -83,7 +84,7 @@ $ kubectl uncordon foo
func NewCmdUncordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
options := &DrainOptions{factory: f, out: out}
return &cobra.Command{
cmd := &cobra.Command{
Use: "uncordon NODE",
Short: "Mark node as schedulable",
Long: uncordon_long,
@@ -93,6 +94,7 @@ func NewCmdUncordon(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.CheckErr(options.RunCordonOrUncordon(false))
},
}
return cmd
}
const (
@@ -149,14 +151,14 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error {
return err
}
o.mapper, o.typer = o.factory.Object()
o.mapper, o.typer = o.factory.Object(false)
cmdNamespace, _, err := o.factory.DefaultNamespace()
if err != nil {
return err
}
r := o.factory.NewBuilder().
r := o.factory.NewBuilder(cmdutil.GetIncludeThirdPartyAPIs(cmd)).
NamespaceParam(cmdNamespace).DefaultNamespace().
ResourceNames("node", args[0]).
Do()

View File

@@ -107,6 +107,7 @@ func NewCmdEdit(f *cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.Flags().Bool("windows-line-endings", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)")
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -129,7 +130,7 @@ func RunEdit(f *cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,

View File

@@ -52,6 +52,7 @@ func NewCmdExplain(f *cmdutil.Factory, out io.Writer) *cobra.Command {
},
}
cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -65,7 +66,7 @@ func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
apiVersionString := cmdutil.GetFlagString(cmd, "api-version")
apiVersion := unversioned.GroupVersion{}
mapper, _ := f.Object()
mapper, _ := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
// TODO: After we figured out the new syntax to separate group and resource, allow
// the users to use it in explain (kubectl explain <group><syntax><resource>).
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.

View File

@@ -115,7 +115,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(false)
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
@@ -229,7 +229,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
info.Refresh(object, true)
// TODO: extract this flag to a central location, when such a location exists.
if cmdutil.GetFlagBool(cmd, "dry-run") {
return f.PrintObject(cmd, object, out)
return f.PrintObject(cmd, mapper, object, out)
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
return err
@@ -242,7 +242,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
}
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
return f.PrintObject(cmd, object, out)
return f.PrintObject(cmd, mapper, object, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "exposed")
return nil

View File

@@ -102,6 +102,7 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
usage := "Filename, directory, or URL to a file identifying the resource to get from a server."
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -110,7 +111,7 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error {
selector := cmdutil.GetFlagString(cmd, "selector")
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {

View File

@@ -100,6 +100,7 @@ func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -204,8 +205,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
if err != nil {
return cmdutil.UsageError(cmd, err.Error())
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
@@ -296,7 +296,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
}
outputFormat := cmdutil.GetFlagString(cmd, "output")
if outputFormat != "" {
return f.PrintObject(cmd, outputObj, out)
return f.PrintObject(cmd, mapper, outputObj, out)
}
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, dataChangeMsg)
return nil

View File

@@ -101,6 +101,7 @@ func NewCmdLogs(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.")
cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.")
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -155,7 +156,7 @@ func (o *LogsOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Com
o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping)
o.Out = out
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
decoder := f.Decoder(true)
if o.Object == nil {
infos, err := resource.NewBuilder(mapper, typer, o.ClientMapper, decoder).

View File

@@ -80,6 +80,7 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
usage := "Filename, directory, or URL to a file identifying the resource to update"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
@@ -112,7 +113,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
return fmt.Errorf("unable to parse %q: %v", patch, err)
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -87,6 +87,8 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -114,7 +116,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st
return forceReplace(f, out, cmd, args, shortOutput, options)
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
Schema(schema).
ContinueOnError().
@@ -182,7 +184,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
}
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -102,6 +102,8 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Bool("rollback", false, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout")
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -190,7 +192,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
var keepOldName bool
var replicasDefaulted bool
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
if len(filename) != 0 {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
@@ -311,10 +313,10 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
oldRcData.WriteString(oldRc.Name)
newRcData.WriteString(newRc.Name)
} else {
if err := f.PrintObject(cmd, oldRc, oldRcData); err != nil {
if err := f.PrintObject(cmd, mapper, oldRc, oldRcData); err != nil {
return err
}
if err := f.PrintObject(cmd, newRc, newRcData); err != nil {
if err := f.PrintObject(cmd, mapper, newRc, newRcData); err != nil {
return err
}
}
@@ -359,7 +361,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
return err
}
if outputFormat != "" {
return f.PrintObject(cmd, newRc, out)
return f.PrintObject(cmd, mapper, newRc, out)
}
kind, err := api.Scheme.ObjectKind(newRc)
if err != nil {

View File

@@ -43,7 +43,6 @@ func NewCmdRollout(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Help()
},
}
// subcommands
cmd.AddCommand(NewCmdRolloutHistory(f, out))
cmd.AddCommand(NewCmdRolloutPause(f, out))

View File

@@ -70,7 +70,7 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st
}
revisionDetail := cmdutil.GetFlagInt64(cmd, "revision")
mapper, typer := f.Object()
mapper, typer := f.Object(false)
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {

View File

@@ -80,7 +80,7 @@ func (o *PauseConfig) CompletePause(f *cmdutil.Factory, cmd *cobra.Command, out
return cmdutil.UsageError(cmd, cmd.Use)
}
o.Mapper, o.Typer = f.Object()
o.Mapper, o.Typer = f.Object(false)
o.PauseObject = f.PauseObject
o.Out = out

View File

@@ -78,7 +78,7 @@ func (o *ResumeConfig) CompleteResume(f *cmdutil.Factory, cmd *cobra.Command, ou
return cmdutil.UsageError(cmd, cmd.Use)
}
o.Mapper, o.Typer = f.Object()
o.Mapper, o.Typer = f.Object(false)
o.ResumeObject = f.ResumeObject
o.Out = out

View File

@@ -79,7 +79,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i
}
o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision")
o.Mapper, o.Typer = f.Object()
o.Mapper, o.Typer = f.Object(false)
o.Out = out
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()

View File

@@ -87,6 +87,7 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
addRunFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -256,7 +257,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
if err != nil {
return err
}
_, typer := f.Object()
_, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
@@ -270,7 +271,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
outputFormat := cmdutil.GetFlagString(cmd, "output")
if outputFormat != "" {
return f.PrintObject(cmd, obj, cmdOut)
return f.PrintObject(cmd, mapper, obj, cmdOut)
}
cmdutil.PrintSuccess(mapper, false, cmdOut, mapping.Resource, args[0], "created")
return nil
@@ -421,7 +422,7 @@ func generateService(f *cmdutil.Factory, cmd *cobra.Command, args []string, serv
}
if cmdutil.GetFlagString(cmd, "output") != "" {
return f.PrintObject(cmd, obj, out)
return f.PrintObject(cmd, mapper, obj, out)
}
cmdutil.PrintSuccess(mapper, false, out, mapping.Resource, args[0], "created")
@@ -440,7 +441,7 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub
return nil, "", nil, nil, err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
groupVersionKind, err := typer.ObjectKind(obj)
if err != nil {
return nil, "", nil, nil, err

View File

@@ -302,6 +302,7 @@ func TestGenerateService(t *testing.T) {
cmd.Flags().String("output", "", "")
cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "")
cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation.")
cmdutil.AddInclude3rdPartyFlags(cmd)
addRunFlags(cmd)
if !test.expectPOST {

View File

@@ -85,6 +85,7 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait.")
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
usage := "Filename, directory, or URL to a file identifying the resource to set a new size"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
@@ -108,7 +109,7 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -77,6 +77,7 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
@@ -86,7 +87,7 @@ func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, out io.Write
return err
}
mapper, typer := f.Object()
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@@ -39,6 +39,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
@@ -51,6 +52,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
utilflag "k8s.io/kubernetes/pkg/util/flag"
@@ -69,8 +71,9 @@ type Factory struct {
clients *ClientCache
flags *pflag.FlagSet
// Returns interfaces for dealing with arbitrary runtime.Objects.
Object func() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for dealing with arbitrary runtime.Objects. If thirdPartyDiscovery is true, performs API calls
// to discovery dynamic API objects registered by third parties.
Object func(thirdPartyDiscovery bool) (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted
// into their internal form (if possible). Eventually the internal form will be removed as an option,
// and only versioned objects will be returned.
@@ -179,6 +182,31 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
return generators[cmdName]
}
func getGroupVersionKinds(gvks []unversioned.GroupVersionKind, group string) []unversioned.GroupVersionKind {
result := []unversioned.GroupVersionKind{}
for ix := range gvks {
if gvks[ix].Group == group {
result = append(result, gvks[ix])
}
}
return result
}
func makeInterfacesFor(versionList []unversioned.GroupVersion) func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
accessor := meta.NewAccessor()
return func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range versionList {
if versionList[ix].String() == version.String() {
return &meta.VersionInterfaces{
ObjectConvertor: thirdpartyresourcedata.NewThirdPartyObjectConverter(api.Scheme),
MetadataAccessor: accessor,
}, nil
}
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, versionList)
}
}
// NewFactory creates a factory with the default Kubernetes resources defined
// if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig.
// if optionalClientConfig is not nil, then this factory will make use of it.
@@ -199,17 +227,61 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
clients: clients,
flags: flags,
Object: func() (meta.RESTMapper, runtime.ObjectTyper) {
// If discoverDynamicAPIs is true, make API calls to the discovery service to find APIs that
// have been dynamically added to the apiserver
Object: func(discoverDynamicAPIs bool) (meta.RESTMapper, runtime.ObjectTyper) {
cfg, err := clientConfig.ClientConfig()
CheckErr(err)
cmdApiVersion := unversioned.GroupVersion{}
if cfg.GroupVersion != nil {
cmdApiVersion = *cfg.GroupVersion
}
if discoverDynamicAPIs {
client, err := clients.ClientForVersion(&unversioned.GroupVersion{Version: "v1"})
CheckErr(err)
versions, gvks, err := GetThirdPartyGroupVersions(client.Discovery())
CheckErr(err)
if len(versions) > 0 {
priorityMapper, ok := mapper.RESTMapper.(meta.PriorityRESTMapper)
if !ok {
CheckErr(fmt.Errorf("expected PriorityMapper, saw: %v", mapper.RESTMapper))
return nil, nil
}
multiMapper, ok := priorityMapper.Delegate.(meta.MultiRESTMapper)
if !ok {
CheckErr(fmt.Errorf("unexpected type: %v", mapper.RESTMapper))
return nil, nil
}
groupsMap := map[string][]unversioned.GroupVersion{}
for _, version := range versions {
groupsMap[version.Group] = append(groupsMap[version.Group], version)
}
for group, versionList := range groupsMap {
preferredExternalVersion := versionList[0]
thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group))
CheckErr(err)
accessor := meta.NewAccessor()
groupMeta := apimachinery.GroupMeta{
GroupVersion: preferredExternalVersion,
GroupVersions: versionList,
RESTMapper: thirdPartyMapper,
SelfLinker: runtime.SelfLinker(accessor),
InterfacesFor: makeInterfacesFor(versionList),
}
CheckErr(registered.RegisterGroup(groupMeta))
registered.AddThirdPartyAPIGroupVersions(versionList...)
multiMapper = append(meta.MultiRESTMapper{thirdPartyMapper}, multiMapper...)
}
priorityMapper.Delegate = multiMapper
// Re-assign to the RESTMapper here because priorityMapper is actually a copy, so if we
// don't re-assign, the above assignement won't actually update mapper.RESTMapper
mapper.RESTMapper = priorityMapper
}
}
outputRESTMapper := kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}
// eventually this should allow me choose a group priority based on the order of the discovery doc, for now hardcode a given order
priorityRESTMapper := meta.PriorityRESTMapper{
Delegate: outputRESTMapper,
ResourcePriority: []unversioned.GroupVersionResource{
@@ -223,7 +295,6 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
{Group: metrics.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind},
},
}
return priorityRESTMapper, api.Scheme
},
Client: func() (*client.Client, error) {
@@ -233,22 +304,39 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return clients.ClientConfigForVersion(nil)
},
ClientForMapping: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
gvk := mapping.GroupVersionKind
mappingVersion := mapping.GroupVersionKind.GroupVersion()
client, err := clients.ClientForVersion(&mappingVersion)
c, err := clients.ClientForVersion(&mappingVersion)
if err != nil {
return nil, err
}
switch mapping.GroupVersionKind.Group {
switch gvk.Group {
case api.GroupName:
return client.RESTClient, nil
return c.RESTClient, nil
case autoscaling.GroupName:
return client.AutoscalingClient.RESTClient, nil
return c.AutoscalingClient.RESTClient, nil
case batch.GroupName:
return client.BatchClient.RESTClient, nil
return c.BatchClient.RESTClient, nil
case extensions.GroupName:
return client.ExtensionsClient.RESTClient, nil
return c.ExtensionsClient.RESTClient, nil
case api.SchemeGroupVersion.Group:
return c.RESTClient, nil
case extensions.SchemeGroupVersion.Group:
return c.ExtensionsClient.RESTClient, nil
default:
if !registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
return nil, fmt.Errorf("unknown api group/version: %s", gvk.String())
}
cfg, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
gv := gvk.GroupVersion()
cfg.GroupVersion = &gv
cfg.APIPath = "/apis"
cfg.Codec = thirdpartyresourcedata.NewCodec(c.ExtensionsClient.RESTClient.Codec(), gvk.Kind)
return restclient.RESTClientFor(cfg)
}
return nil, fmt.Errorf("unable to get RESTClient for resource '%s'", mapping.Resource)
},
Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
mappingVersion := mapping.GroupVersionKind.GroupVersion()
@@ -810,6 +898,10 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
}
return getSchemaAndValidate(c.c.BatchClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir)
}
if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
// Don't attempt to validate third party objects
return nil
}
if gvk.Group == extensions.GroupName {
if c.c.ExtensionsClient == nil {
return errors.New("unable to validate: no experimental client")
@@ -874,8 +966,7 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
}
// PrintObject prints an api object given command line flags to modify the output format
func (f *Factory) PrintObject(cmd *cobra.Command, obj runtime.Object, out io.Writer) error {
mapper, _ := f.Object()
func (f *Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
gvk, err := api.Scheme.ObjectKind(obj)
if err != nil {
return err
@@ -936,8 +1027,8 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin
}
// One stop shopping for a Builder
func (f *Factory) NewBuilder() *resource.Builder {
mapper, typer := f.Object()
func (f *Factory) NewBuilder(thirdPartyDiscovery bool) *resource.Builder {
mapper, typer := f.Object(thirdPartyDiscovery)
return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true))
}

View File

@@ -32,6 +32,8 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
@@ -514,3 +516,53 @@ func ContainsChangeCause(info *resource.Info) bool {
func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || ContainsChangeCause(info)
}
func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) {
result := []unversioned.GroupVersion{}
gvks := []unversioned.GroupVersionKind{}
groupList, err := discovery.ServerGroups()
if err != nil {
// On forbidden or not found, just return empty lists.
if errors.IsForbidden(err) || errors.IsNotFound(err) {
return result, gvks, nil
}
return nil, nil, err
}
for ix := range groupList.Groups {
group := &groupList.Groups[ix]
for jx := range group.Versions {
gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion)
if err2 != nil {
return nil, nil, err
}
// Skip GroupVersionKinds that have been statically registered.
if registered.IsRegisteredVersion(gv) {
continue
}
result = append(result, gv)
resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion)
if err != nil {
return nil, nil, err
}
for kx := range resourceList.APIResources {
gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind))
}
}
}
return result, gvks, nil
}
func GetIncludeThirdPartyAPIs(cmd *cobra.Command) bool {
if cmd.Flags().Lookup("include-extended-apis") == nil {
return false
}
return GetFlagBool(cmd, "include-extended-apis")
}
func AddInclude3rdPartyFlags(cmd *cobra.Command) {
cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
}

View File

@@ -54,6 +54,28 @@ func makeImageList(spec *api.PodSpec) string {
return strings.Join(listOfImages(spec), ",")
}
func NewThirdPartyResourceMapper(gvs []unversioned.GroupVersion, gvks []unversioned.GroupVersionKind) (meta.RESTMapper, error) {
mapper := meta.NewDefaultRESTMapper(gvs, func(gv unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range gvs {
if gvs[ix].Group == gv.Group && gvs[ix].Version == gv.Version {
return &meta.VersionInterfaces{
ObjectConvertor: api.Scheme,
MetadataAccessor: meta.NewAccessor(),
}, nil
}
}
groupVersions := []string{}
for ix := range gvs {
groupVersions = append(groupVersions, gvs[ix].String())
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", gv.String(), strings.Join(groupVersions, ", "))
})
for ix := range gvks {
mapper.Add(gvks[ix], meta.RESTScopeNamespace)
}
return mapper, nil
}
// OutputVersionMapper is a RESTMapper that will prefer mappings that
// correspond to a preferred output version (if feasible)
type OutputVersionMapper struct {

View File

@@ -22,6 +22,8 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
"k8s.io/kubernetes/pkg/runtime"
)
@@ -50,8 +52,16 @@ type Mapper struct {
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
versions := &runtime.VersionedObjects{}
_, gvk, err := m.Decode(data, nil, versions)
var obj runtime.Object
var versioned runtime.Object
if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
obj, err = runtime.Decode(thirdpartyresourcedata.NewCodec(nil, gvk.Kind), data)
versioned = obj
} else {
obj, versioned = versions.Last(), versions.First()
}
if err != nil {
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
return nil, fmt.Errorf("unable to decode %q: %v [%v]", source, err, gvk)
}
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
@@ -63,10 +73,6 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
}
// TODO: decoding the version object is convenient, but questionable. This is used by apply
// and rolling-update today, but both of those cases should probably be requesting the raw
// object and performing their own decoding.
obj, versioned := versions.Last(), versions.First()
name, _ := mapping.MetadataAccessor.Name(obj)
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)

View File

@@ -24,6 +24,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
@@ -241,6 +242,11 @@ func AsVersionedObjects(infos []*Info, version string, encoder runtime.Encoder)
}
// TODO: use info.VersionedObject as the value?
switch obj := info.Object.(type) {
case *extensions.ThirdPartyResourceData:
objects = append(objects, &runtime.Unknown{Raw: obj.Data})
continue
}
// objects that are not part of api.Scheme must be converted to JSON
// TODO: convert to map[string]interface{}, attach to runtime.Unknown?

View File

@@ -255,6 +255,12 @@ type JSONPrinter struct {
// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
switch obj := obj.(type) {
case *runtime.Unknown:
_, err := w.Write(obj.Raw)
return err
}
data, err := json.Marshal(obj)
if err != nil {
return err
@@ -281,6 +287,16 @@ type YAMLPrinter struct {
// PrintObj prints the data as YAML.
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
switch obj := obj.(type) {
case *runtime.Unknown:
data, err := yaml.JSONToYAML(obj.Raw)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
output, err := yaml.Marshal(obj)
if err != nil {
return err
@@ -414,6 +430,9 @@ var persistentVolumeColumns = []string{"NAME", "CAPACITY", "ACCESSMODES", "STATU
var persistentVolumeClaimColumns = []string{"NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESSMODES", "AGE"}
var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
var thirdPartyResourceColumns = []string{"NAME", "DESCRIPTION", "VERSION(S)"}
// TODO: consider having 'KIND' for third party resource data
var thirdPartyResourceDataColumns = []string{"NAME", "LABELS", "DATA"}
var horizontalPodAutoscalerColumns = []string{"NAME", "REFERENCE", "TARGET", "CURRENT", "MINPODS", "MAXPODS", "AGE"}
var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
var deploymentColumns = []string{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"}
@@ -470,6 +489,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(configMapColumns, printConfigMapList)
h.Handler(podSecurityPolicyColumns, printPodSecurityPolicy)
h.Handler(podSecurityPolicyColumns, printPodSecurityPolicyList)
h.Handler(thirdPartyResourceDataColumns, printThirdPartyResourceData)
h.Handler(thirdPartyResourceDataColumns, printThirdPartyResourceDataList)
}
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
@@ -1471,6 +1492,35 @@ func printThirdPartyResourceList(list *extensions.ThirdPartyResourceList, w io.W
return nil
}
func truncate(str string, maxLen int) string {
if len(str) > maxLen {
return str[0:maxLen] + "..."
}
return str
}
func printThirdPartyResourceData(rsrc *extensions.ThirdPartyResourceData, w io.Writer, options PrintOptions) error {
l := labels.FormatLabels(rsrc.Labels)
truncateCols := 50
if options.Wide {
truncateCols = 100
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", rsrc.Name, l, truncate(string(rsrc.Data), truncateCols)); err != nil {
return err
}
return nil
}
func printThirdPartyResourceDataList(list *extensions.ThirdPartyResourceDataList, w io.Writer, options PrintOptions) error {
for _, item := range list.Items {
if err := printThirdPartyResourceData(&item, w, options); err != nil {
return err
}
}
return nil
}
func printDeployment(deployment *extensions.Deployment, w io.Writer, options PrintOptions) error {
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", deployment.Namespace); err != nil {

View File

@@ -370,7 +370,7 @@ func TestAPIVersionOfDiscoveryEndpoints(t *testing.T) {
}
func TestDiscoveryAtAPIS(t *testing.T) {
// TODO(caesarxuchao): make this pass now that batch is added,
// TODO(caesarxuchao): make this pass now that batch is added,
// and rewrite it so that the indexes do not need to change each time a new api group is added.
/*
master, etcdserver, config, assert := newMaster(t)

View File

@@ -31,9 +31,37 @@ import (
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/runtime"
)
type thirdPartyObjectConverter struct {
converter runtime.ObjectConvertor
}
func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersion string) (out runtime.Object, err error) {
switch in.(type) {
// This seems weird, but in this case the ThirdPartyResourceData is really just a wrapper on the raw 3rd party data.
// The actual thing printed/sent to server is the actual raw third party resource data, which only has one version.
case *extensions.ThirdPartyResourceData:
return in, nil
default:
return t.converter.ConvertToVersion(in, outVersion)
}
}
func (t *thirdPartyObjectConverter) Convert(in, out interface{}) error {
return t.converter.Convert(in, out)
}
func (t *thirdPartyObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
return t.converter.ConvertFieldLabel(version, kind, label, value)
}
func NewThirdPartyObjectConverter(converter runtime.ObjectConvertor) runtime.ObjectConvertor {
return &thirdPartyObjectConverter{converter}
}
type thirdPartyResourceDataMapper struct {
mapper meta.RESTMapper
kind string
@@ -117,6 +145,7 @@ func (t *thirdPartyResourceDataMapper) RESTMapping(gk unversioned.GroupKind, ver
if err != nil {
return nil, err
}
mapping.ObjectConvertor = &thirdPartyObjectConverter{mapping.ObjectConvertor}
return mapping, nil
}
@@ -177,28 +206,51 @@ func NewCodec(codec runtime.Codec, kind string) runtime.Codec {
return &thirdPartyResourceDataCodec{codec, kind}
}
func (t *thirdPartyResourceDataCodec) populate(objIn *extensions.ThirdPartyResourceData, data []byte) error {
func parseObject(data []byte) (map[string]interface{}, error) {
var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil {
fmt.Printf("Invalid JSON:\n%s\n", string(data))
return err
return nil, err
}
mapObj, ok := obj.(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected object: %#v", obj)
return nil, fmt.Errorf("unexpected object: %#v", obj)
}
return t.populateFromObject(objIn, mapObj, data)
return mapObj, nil
}
func (t *thirdPartyResourceDataCodec) populateFromObject(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error {
func (t *thirdPartyResourceDataCodec) populate(data []byte) (runtime.Object, error) {
mapObj, err := parseObject(data)
if err != nil {
return nil, err
}
return t.populateFromObject(mapObj, data)
}
func (t *thirdPartyResourceDataCodec) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, error) {
typeMeta := unversioned.TypeMeta{}
if err := json.Unmarshal(data, &typeMeta); err != nil {
return err
return nil, err
}
if typeMeta.Kind != t.kind {
return fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind)
switch typeMeta.Kind {
case t.kind:
result := &extensions.ThirdPartyResourceData{}
if err := t.populateResource(result, mapObj, data); err != nil {
return nil, err
}
return result, nil
case t.kind + "List":
list := &extensions.ThirdPartyResourceDataList{}
if err := t.populateListResource(list, mapObj); err != nil {
return nil, err
}
return list, nil
default:
return nil, fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind)
}
}
func (t *thirdPartyResourceDataCodec) populateResource(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error {
metadata, ok := mapObj["metadata"].(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"])
@@ -212,6 +264,9 @@ func (t *thirdPartyResourceDataCodec) populateFromObject(objIn *extensions.Third
if err := json.Unmarshal(metadataData, &objIn.ObjectMeta); err != nil {
return err
}
// Override API Version with the ThirdPartyResourceData value
// TODO: fix this hard code
objIn.APIVersion = v1beta1.SchemeGroupVersion.String()
objIn.Data = data
return nil
@@ -219,17 +274,12 @@ func (t *thirdPartyResourceDataCodec) populateFromObject(objIn *extensions.Third
func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
if into == nil {
obj := &extensions.ThirdPartyResourceData{}
if err := t.populate(obj, data); err != nil {
return nil, nil, err
}
objData, err := runtime.Encode(t.delegate, obj)
obj, err := t.populate(data)
if err != nil {
return nil, nil, err
}
return t.delegate.Decode(objData, gvk, into)
return obj, gvk, nil
}
thirdParty, ok := into.(*extensions.ThirdPartyResourceData)
if !ok {
return nil, nil, fmt.Errorf("unexpected object: %#v", into)
@@ -241,7 +291,8 @@ func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.Group
}
mapObj, ok := dataObj.(map[string]interface{})
if !ok {
return nil, nil, fmt.Errorf("unexpcted object: %#v", dataObj)
return nil, nil, fmt.Errorf("unexpected object: %#v", dataObj)
}
/*if gvk.Kind != "ThirdPartyResourceData" {
return nil, nil, fmt.Errorf("unexpected kind: %s", gvk.Kind)
@@ -284,12 +335,38 @@ func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.Group
actual.Group, actual.Version = gv.Group, gv.Version
}
if err := t.populate(thirdParty, data); err != nil {
mapObj, err := parseObject(data)
if err != nil {
return nil, actual, err
}
if err := t.populateResource(thirdParty, mapObj, data); err != nil {
return nil, actual, err
}
return thirdParty, actual, nil
}
func (t *thirdPartyResourceDataCodec) populateListResource(objIn *extensions.ThirdPartyResourceDataList, mapObj map[string]interface{}) error {
items, ok := mapObj["items"].([]interface{})
if !ok {
return fmt.Errorf("unexpected object for items: %#v", mapObj["items"])
}
objIn.Items = make([]extensions.ThirdPartyResourceData, len(items))
for ix := range items {
objData, err := json.Marshal(items[ix])
if err != nil {
return err
}
objMap, err := parseObject(objData)
if err != nil {
return err
}
if err := t.populateResource(&objIn.Items[ix], objMap, objData); err != nil {
return err
}
}
return nil
}
const template = `{
"kind": "%s",
"items": [ %s ]

View File

@@ -20,9 +20,28 @@ import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
)
func ExtractGroupVersionKind(list *extensions.ThirdPartyResourceList) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) {
gvs := []unversioned.GroupVersion{}
gvks := []unversioned.GroupVersionKind{}
for ix := range list.Items {
rsrc := &list.Items[ix]
kind, group, err := ExtractApiGroupAndKind(rsrc)
if err != nil {
return nil, nil, err
}
for _, version := range rsrc.Versions {
gv := unversioned.GroupVersion{Group: group, Version: version.Name}
gvs = append(gvs, gv)
gvks = append(gvks, unversioned.GroupVersionKind{Group: group, Version: version.Name, Kind: kind})
}
}
return gvs, gvks, nil
}
func convertToCamelCase(input string) string {
result := ""
toUpper := true