func TestLabelsForObject(t *testing.T) { f := NewFactory(nil) tests := []struct { name string object runtime.Object expected string err error }{ { name: "successful re-use of labels", object: &api.Service{ ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", Labels: map[string]string{"svc": "test"}}, TypeMeta: unversioned.TypeMeta{Kind: "Service", APIVersion: "v1"}, }, expected: "svc=test", err: nil, }, { name: "empty labels", object: &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", Labels: map[string]string{}}, TypeMeta: unversioned.TypeMeta{Kind: "Service", APIVersion: "v1"}, }, expected: "", err: nil, }, { name: "nil labels", object: &api.Service{ ObjectMeta: api.ObjectMeta{Name: "zen", Namespace: "test", Labels: nil}, TypeMeta: unversioned.TypeMeta{Kind: "Service", APIVersion: "v1"}, }, expected: "", err: nil, }, } for _, test := range tests { gotLabels, err := f.LabelsForObject(test.object) if err != test.err { t.Fatalf("%s: Error mismatch: Expected %v, got %v", test.name, test.err, err) } got := kubectl.MakeLabels(gotLabels) if test.expected != got { t.Fatalf("%s: Labels mismatch! Expected %s, got %s", test.name, test.expected, got) } } }
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *ExposeOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() infos, err := r.Infos() if err != nil { return err } if len(infos) > 1 { return fmt.Errorf("multiple resources provided: %v", args) } info := infos[0] mapping := info.ResourceMapping() if err := f.CanBeExposed(mapping.Kind); err != nil { return err } // Get the input object inputObject, err := r.Object() if err != nil { return err } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") generator, found := f.Generator(generatorName) if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) params["default-name"] = info.Name // For objects that need a pod selector, derive it from the exposed object in case a user // didn't explicitly specify one via --selector if s, found := params["selector"]; found && kubectl.IsZero(s) { s, err := f.PodSelectorForObject(inputObject) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find selectors via --selector flag or introspection: %s", err)) } params["selector"] = s } if cmdutil.GetFlagInt(cmd, "port") < 1 { noPorts := true for _, param := range names { if param.Name == "port" { noPorts = false break } } if cmdutil.GetFlagInt(cmd, "port") < 0 && !noPorts { ports, err := f.PortsForObject(inputObject) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find port via --port flag or introspection: %s", err)) } switch len(ports) { case 0: return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection") case 1: params["port"] = ports[0] default: return cmdutil.UsageError(cmd, fmt.Sprintf("multiple ports to choose from: %v, please explicitly specify a port using the --port flag.", ports)) } } } if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") { params["create-external-load-balancer"] = "true" } if kubectl.IsZero(params["labels"]) { labels, err := f.LabelsForObject(inputObject) if err != nil { return err } params["labels"] = kubectl.MakeLabels(labels) } if v := cmdutil.GetFlagString(cmd, "type"); v != "" { params["type"] = v } err = kubectl.ValidateParams(names, params) if err != nil { return err } // Expose new object object, err := generator.Generate(params) if err != nil { return err } inline := cmdutil.GetFlagString(cmd, "overrides") if len(inline) > 0 { object, err = cmdutil.Merge(object, inline, mapping.Kind) if err != nil { return err } } resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()} info, err = resourceMapper.InfoForObject(object) if err != nil { return err } // TODO: extract this flag to a central location, when such a location exists. if cmdutil.GetFlagBool(cmd, "dry-run") { fmt.Fprintln(out, "running in dry-run mode...") } else { // Serialize the configuration into an annotation. if err := kubectl.UpdateApplyAnnotation(info); err != nil { return err } object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, info.Object) if err != nil { return err } } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { return f.PrintObject(cmd, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "exposed") return nil }
// 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. func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { mapper := kubectl.ShortcutExpander{RESTMapper: api.RESTMapper} flags := pflag.NewFlagSet("", pflag.ContinueOnError) flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags generators := map[string]kubectl.Generator{ "run/v1": kubectl.BasicReplicationController{}, "run-pod/v1": kubectl.BasicPod{}, "service/v1": kubectl.ServiceGeneratorV1{}, "service/v2": kubectl.ServiceGeneratorV2{}, "horizontalpodautoscaler/v1beta1": kubectl.HorizontalPodAutoscalerV1Beta1{}, } clientConfig := optionalClientConfig if optionalClientConfig == nil { clientConfig = DefaultClientConfig(flags) } clients := NewClientCache(clientConfig) return &Factory{ clients: clients, flags: flags, generators: generators, Object: func() (meta.RESTMapper, runtime.ObjectTyper) { cfg, err := clientConfig.ClientConfig() CheckErr(err) cmdApiVersion := cfg.Version return kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersion: cmdApiVersion}, api.Scheme }, Client: func() (*client.Client, error) { return clients.ClientForVersion("") }, ClientConfig: func() (*client.Config, error) { return clients.ClientConfigForVersion("") }, RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { group, err := api.RESTMapper.GroupForResource(mapping.Resource) client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } switch group { case "": return client.RESTClient, nil case "extensions": return client.ExtensionsClient.RESTClient, nil } return nil, fmt.Errorf("unable to get RESTClient for resource '%s'", mapping.Resource) }, Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) { group, err := api.RESTMapper.GroupForResource(mapping.Resource) if err != nil { return nil, err } client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } if describer, ok := kubectl.DescriberFor(group, mapping.Kind, client); ok { return describer, nil } return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind) }, Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, showAll bool, columnLabels []string) (kubectl.ResourcePrinter, error) { return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, wide, showAll, columnLabels), nil }, PodSelectorForObject: func(object runtime.Object) (string, error) { // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) switch t := object.(type) { case *api.ReplicationController: return kubectl.MakeLabels(t.Spec.Selector), nil case *api.Pod: if len(t.Labels) == 0 { return "", fmt.Errorf("the pod has no labels and cannot be exposed") } return kubectl.MakeLabels(t.Labels), nil case *api.Service: if t.Spec.Selector == nil { return "", fmt.Errorf("the service has no pod selector set") } return kubectl.MakeLabels(t.Spec.Selector), nil default: _, kind, err := api.Scheme.ObjectVersionAndKind(object) if err != nil { return "", err } return "", fmt.Errorf("cannot extract pod selector from %s", kind) } }, PortsForObject: func(object runtime.Object) ([]string, error) { // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) switch t := object.(type) { case *api.ReplicationController: return getPorts(t.Spec.Template.Spec), nil case *api.Pod: return getPorts(t.Spec), nil case *api.Service: return getServicePorts(t.Spec), nil default: _, kind, err := api.Scheme.ObjectVersionAndKind(object) if err != nil { return nil, err } return nil, fmt.Errorf("cannot extract ports from %s", kind) } }, LabelsForObject: func(object runtime.Object) (map[string]string, error) { return meta.NewAccessor().Labels(object) }, Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } return kubectl.ScalerFor(mapping.Kind, client) }, Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } return kubectl.ReaperFor(mapping.Kind, client) }, Validator: func(validate bool, cacheDir string) (validation.Schema, error) { if validate { client, err := clients.ClientForVersion("") if err != nil { return nil, err } dir := cacheDir if len(dir) > 0 { version, err := client.ServerVersion() if err != nil { return nil, err } dir = path.Join(cacheDir, version.String()) } return &clientSwaggerSchema{ c: client, cacheDir: dir, mapper: api.RESTMapper, }, nil } return validation.NullSchema{}, nil }, DefaultNamespace: func() (string, bool, error) { return clientConfig.Namespace() }, Generator: func(name string) (kubectl.Generator, bool) { generator, ok := generators[name] return generator, ok }, CanBeExposed: func(kind string) error { if kind != "ReplicationController" && kind != "Service" && kind != "Pod" { return fmt.Errorf("invalid resource provided: %v, only a replication controller, service or pod is accepted", kind) } return nil }, CanBeAutoscaled: func(kind string) error { // TODO: support autoscale for deployments if kind != "ReplicationController" { return fmt.Errorf("invalid resource provided: %v, only a replication controller is accepted", kind) } return nil }, } }
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *ExposeOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() infos, err := r.Infos() if err != nil { return err } if len(infos) > 1 { return fmt.Errorf("multiple resources provided: %v", args) } info := infos[0] mapping := info.ResourceMapping() if err := f.CanBeExposed(mapping.Kind); err != nil { return err } // Get the input object inputObject, err := r.Object() if err != nil { return err } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") generator, found := f.Generator(generatorName) if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) name := info.Name if len(name) > validation.DNS952LabelMaxLength { name = name[:validation.DNS952LabelMaxLength] } params["default-name"] = name // For objects that need a pod selector, derive it from the exposed object in case a user // didn't explicitly specify one via --selector if s, found := params["selector"]; found && kubectl.IsZero(s) { s, err := f.PodSelectorForObject(inputObject) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find selectors via --selector flag or introspection: %s", err)) } params["selector"] = s } // For objects that need a port, derive it from the exposed object in case a user // didn't explicitly specify one via --port if port, found := params["port"]; found && kubectl.IsZero(port) { ports, err := f.PortsForObject(inputObject) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find port via --port flag or introspection: %s", err)) } switch len(ports) { case 0: return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection") case 1: params["port"] = ports[0] default: params["ports"] = strings.Join(ports, ",") } } if kubectl.IsZero(params["labels"]) { labels, err := f.LabelsForObject(inputObject) if err != nil { return err } params["labels"] = kubectl.MakeLabels(labels) } if err = kubectl.ValidateParams(names, params); err != nil { return err } // Generate new object object, err := generator.Generate(params) if err != nil { return err } if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 { object, err = cmdutil.Merge(object, inline, mapping.Kind) if err != nil { return err } } resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()} info, err = resourceMapper.InfoForObject(object) if err != nil { return err } // 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) } // Serialize the configuration into an annotation. if err := kubectl.UpdateApplyAnnotation(info); err != nil { return err } // Serialize the object with the annotation applied. data, err := info.Mapping.Codec.Encode(object) if err != nil { return err } object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, data) if err != nil { return err } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return f.PrintObject(cmd, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "exposed") return nil }