Example #1
0
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:   api.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:   api.TypeMeta{Kind: "Service", APIVersion: "v1"},
			},
			expected: "",
			err:      nil,
		},
		{
			name: "nil labels",
			object: &api.Service{
				ObjectMeta: api.ObjectMeta{Name: "zen", Namespace: "test", Labels: nil},
				TypeMeta:   api.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 := qingctl.MakeLabels(gotLabels)
		if test.expected != got {
			t.Fatalf("%s: Labels mismatch! Expected %s, got %s", test.name, test.expected, got)
		}

	}
}
Example #2
0
// NewFactory creates a factory with the default QingYuan 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 := qingctl.ShortcutExpander{latest.RESTMapper}

	flags := pflag.NewFlagSet("", pflag.ContinueOnError)
	flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags

	generators := map[string]qingctl.Generator{
		"run/v1":     qingctl.BasicReplicationController{},
		"service/v1": qingctl.ServiceGenerator{},
	}

	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 qingctl.OutputVersionMapper{mapper, 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) {
			client, err := clients.ClientForVersion(mapping.APIVersion)
			if err != nil {
				return nil, err
			}
			return client.RESTClient, nil
		},
		Describer: func(mapping *meta.RESTMapping) (qingctl.Describer, error) {
			client, err := clients.ClientForVersion(mapping.APIVersion)
			if err != nil {
				return nil, err
			}
			describer, ok := qingctl.DescriberFor(mapping.Kind, client)
			if !ok {
				return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind)
			}
			return describer, nil
		},
		Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, columnLabels []string) (qingctl.ResourcePrinter, error) {
			return qingctl.NewHumanReadablePrinter(noHeaders, withNamespace, 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 qingctl.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 qingctl.MakeLabels(t.Labels), nil
			case *api.Service:
				if t.Spec.Selector == nil {
					return "", fmt.Errorf("the service has no pod selector set")
				}
				return qingctl.MakeLabels(t.Spec.Selector), nil
			default:
				kind, err := meta.NewAccessor().Kind(object)
				if err != nil {
					return "", err
				}
				return "", fmt.Errorf("it is not possible to get a 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
			default:
				kind, err := meta.NewAccessor().Kind(object)
				if err != nil {
					return nil, err
				}
				return nil, fmt.Errorf("it is not possible to get ports from %s", kind)
			}
		},
		LabelsForObject: func(object runtime.Object) (map[string]string, error) {
			return meta.NewAccessor().Labels(object)
		},
		Scaler: func(mapping *meta.RESTMapping) (qingctl.Scaler, error) {
			client, err := clients.ClientForVersion(mapping.APIVersion)
			if err != nil {
				return nil, err
			}
			return qingctl.ScalerFor(mapping.Kind, qingctl.NewScalerClient(client))
		},
		Reaper: func(mapping *meta.RESTMapping) (qingctl.Reaper, error) {
			client, err := clients.ClientForVersion(mapping.APIVersion)
			if err != nil {
				return nil, err
			}
			return qingctl.ReaperFor(mapping.Kind, client)
		},
		Validator: func() (validation.Schema, error) {
			if flags.Lookup("validate").Value.String() == "true" {
				client, err := clients.ClientForVersion("")
				if err != nil {
					return nil, err
				}
				return &clientSwaggerSchema{client, api.Scheme}, nil
			}
			return validation.NullSchema{}, nil
		},
		DefaultNamespace: func() (string, error) {
			return clientConfig.Namespace()
		},
		Generator: func(name string) (qingctl.Generator, bool) {
			generator, ok := generators[name]
			return generator, ok
		},
	}
}
Example #3
0
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
	namespace, err := f.DefaultNamespace()
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
		ContinueOnError().
		NamespaceParam(namespace).DefaultNamespace().
		ResourceTypeOrNameArgs(false, args...).
		Flatten().
		Do()
	err = r.Err()
	if err != nil {
		return err
	}
	mapping, err := r.ResourceMapping()
	if err != nil {
		return err
	}
	infos, err := r.Infos()
	if err != nil {
		return err
	}
	if len(infos) > 1 {
		return fmt.Errorf("multiple resources provided: %v", args)
	}
	info := infos[0]

	// Get the input object
	client, err := f.RESTClient(mapping)
	if err != nil {
		return err
	}
	inputObject, err := resource.NewHelper(client, mapping).Get(info.Namespace, info.Name)
	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 := qingctl.MakeParams(cmd, names)
	params["default-name"] = info.Name
	if s, found := params["selector"]; !found || len(s) == 0 || cmdutil.GetFlagInt(cmd, "port") < 1 {
		if len(s) == 0 {
			s, err := f.PodSelectorForObject(inputObject)
			if err != nil {
				return err
			}
			params["selector"] = s
		}
		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 err
			}
			switch len(ports) {
			case 0:
				return cmdutil.UsageError(cmd, "couldn't find a suitable port via --port flag or introspection")
			case 1:
				params["port"] = ports[0]
			default:
				return cmdutil.UsageError(cmd, "more than one port to choose from, please explicitly specify a port using the --port flag.")
			}
		}
	}
	if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") {
		params["create-external-load-balancer"] = "true"
	}
	if len(params["labels"]) == 0 {
		labels, err := f.LabelsForObject(inputObject)
		if err != nil {
			return err
		}
		params["labels"] = qingctl.MakeLabels(labels)
	}
	if v := cmdutil.GetFlagString(cmd, "type"); v != "" {
		params["type"] = v
	}
	err = qingctl.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
		}
	}

	// TODO: extract this flag to a central location, when such a location exists.
	if !cmdutil.GetFlagBool(cmd, "dry-run") {
		resourceMapper := &resource.Mapper{typer, mapper, f.ClientMapperForCommand()}
		info, err := resourceMapper.InfoForObject(object)
		if err != nil {
			return err
		}
		data, err := info.Mapping.Codec.Encode(object)
		if err != nil {
			return err
		}
		_, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, data)
		if err != nil {
			return err
		}
	}

	if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") {
		msg := fmt.Sprintf(`
			An external load-balanced service was created.  On many platforms (e.g. Google Compute Engine),
			you will also need to explicitly open a firewall rule for the service port (%d) to serve traffic.
			
			See https://github.com/qingyuancloud/QingYuan/tree/master/docs/services-firewall.md for more details.
			`, cmdutil.GetFlagInt(cmd, "port"))
		out.Write([]byte(msg))
	}

	return f.PrintObject(cmd, object, out)
}