mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
[Federation][join-01] Implement kubefed join
command.
Also, add unit tests for `kubefed join`.
This commit is contained in:
parent
edab99bf87
commit
2342f6eefb
52
federation/pkg/kubefed/BUILD
Normal file
52
federation/pkg/kubefed/BUILD
Normal file
@ -0,0 +1,52 @@
|
||||
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 = ["join.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/cmd:go_default_library",
|
||||
"//pkg/kubectl/cmd/templates:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/spf13/cobra",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["join_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/unversioned:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apimachinery/registered:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/restclient/fake:go_default_library",
|
||||
"//pkg/client/typed/dynamic:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
300
federation/pkg/kubefed/join.go
Normal file
300
federation/pkg/kubefed/join.go
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
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 kubefed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
// KubeconfigSecretDataKey is the key name used in the secret to
|
||||
// stores a cluster's credentials.
|
||||
KubeconfigSecretDataKey = "kubeconfig"
|
||||
|
||||
// defaultClusterCIDR is the default CIDR range accepted by the
|
||||
// joining API server. See `apis/federation.ClusterSpec` for
|
||||
// details.
|
||||
// TODO(madhusudancs): Make this value customizable.
|
||||
defaultClientCIDR = "0.0.0.0/0"
|
||||
)
|
||||
|
||||
var (
|
||||
join_long = templates.LongDesc(`
|
||||
Join a cluster to a federation.
|
||||
|
||||
Current context is assumed to be a federation API
|
||||
server. Please use the --context flag otherwise.`)
|
||||
join_example = templates.Examples(`
|
||||
# Join a cluster to a federation by specifying the
|
||||
# cluster context name and the context name of the
|
||||
# federation control plane's host cluster.
|
||||
kubectl join foo --host=bar`)
|
||||
)
|
||||
|
||||
// JoinFederationConfig provides a filesystem based kubeconfig (via
|
||||
// `PathOptions()`) and a mechanism to talk to the federation host
|
||||
// cluster.
|
||||
type JoinFederationConfig interface {
|
||||
// PathOptions provides filesystem based kubeconfig access.
|
||||
PathOptions() *clientcmd.PathOptions
|
||||
// HostFactory provides a mechanism to communicate with the
|
||||
// cluster where federation control plane is hosted.
|
||||
HostFactory(host, kubeconfigPath string) cmdutil.Factory
|
||||
}
|
||||
|
||||
// joinFederationConfig implements JoinFederationConfig interface.
|
||||
type joinFederationConfig struct {
|
||||
pathOptions *clientcmd.PathOptions
|
||||
}
|
||||
|
||||
// Assert that `joinFederationConfig` implements the
|
||||
// `JoinFederationConfig` interface.
|
||||
var _ JoinFederationConfig = &joinFederationConfig{}
|
||||
|
||||
func NewJoinFederationConfig(pathOptions *clientcmd.PathOptions) JoinFederationConfig {
|
||||
return &joinFederationConfig{
|
||||
pathOptions: pathOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *joinFederationConfig) PathOptions() *clientcmd.PathOptions {
|
||||
return j.pathOptions
|
||||
}
|
||||
|
||||
func (j *joinFederationConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory {
|
||||
loadingRules := *j.pathOptions.LoadingRules
|
||||
loadingRules.Precedence = j.pathOptions.GetLoadingPrecedence()
|
||||
loadingRules.ExplicitPath = kubeconfigPath
|
||||
overrides := &clientcmd.ConfigOverrides{
|
||||
CurrentContext: host,
|
||||
}
|
||||
|
||||
hostClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, overrides)
|
||||
|
||||
return cmdutil.NewFactory(hostClientConfig)
|
||||
}
|
||||
|
||||
// NewCmdJoin defines the `join` command that joins a cluster to a
|
||||
// federation.
|
||||
func NewCmdJoin(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationConfig) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "join CLUSTER_CONTEXT --host=HOST_CONTEXT",
|
||||
Short: "Join a cluster to a federation",
|
||||
Long: join_long,
|
||||
Example: join_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := joinFederation(f, cmdOut, config, cmd, args)
|
||||
cmdutil.CheckErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ClusterV1Beta1GeneratorName)
|
||||
addJoinFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// joinFederation is the implementation of the `join federation` command.
|
||||
func joinFederation(f cmdutil.Factory, cmdOut io.Writer, config JoinFederationConfig, cmd *cobra.Command, args []string) error {
|
||||
name, err := kubectlcmd.NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host := cmdutil.GetFlagString(cmd, "host")
|
||||
hostSystemNamespace := cmdutil.GetFlagString(cmd, "host-system-namespace")
|
||||
kubeconfig := cmdutil.GetFlagString(cmd, "kubeconfig")
|
||||
dryRun := cmdutil.GetDryRunFlag(cmd)
|
||||
|
||||
glog.V(2).Infof("Args and flags: name %s, host: %s, host-system-namespace: %s, kubeconfig: %s, dry-run: %s", name, host, hostSystemNamespace, kubeconfig, dryRun)
|
||||
|
||||
po := config.PathOptions()
|
||||
po.LoadingRules.ExplicitPath = kubeconfig
|
||||
clientConfig, err := po.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
generator, err := clusterGenerator(clientConfig, name)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed creating cluster generator: %v", err)
|
||||
return err
|
||||
}
|
||||
glog.V(2).Infof("Created cluster generator: %#v", generator)
|
||||
|
||||
// We are not using the `kubectl create secret` machinery through
|
||||
// `RunCreateSubcommand` as we do to the cluster resource below
|
||||
// because we have a bunch of requirements that the machinery does
|
||||
// not satisfy.
|
||||
// 1. We want to create the secret in a specific namespace, which
|
||||
// is neither the "default" namespace nor the one specified
|
||||
// via the `--namespace` flag.
|
||||
// 2. `SecretGeneratorV1` requires LiteralSources in a string-ified
|
||||
// form that it parses to generate the secret data key-value
|
||||
// pairs. We, however, have the key-value pairs ready without a
|
||||
// need for parsing.
|
||||
// 3. The result printing mechanism needs to be mostly quiet. We
|
||||
// don't have to print the created secret in the default case.
|
||||
// Having said that, secret generation machinery could be altered to
|
||||
// suit our needs, but it is far less invasive and readable this way.
|
||||
hostFactory := config.HostFactory(host, kubeconfig)
|
||||
_, err = createSecret(hostFactory, clientConfig, hostSystemNamespace, name, dryRun)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed creating the cluster credentials secret: %v", err)
|
||||
return err
|
||||
}
|
||||
glog.V(2).Infof("Cluster credentials secret created")
|
||||
|
||||
return kubectlcmd.RunCreateSubcommand(f, cmd, cmdOut, &kubectlcmd.CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: dryRun,
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
||||
func addJoinFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().String("kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
|
||||
cmd.Flags().String("host", "", "Host cluster context")
|
||||
cmd.Flags().String("host-system-namespace", "federation-system", "Namespace in the host cluster where the federation system components are installed")
|
||||
}
|
||||
|
||||
// minifyConfig is a wrapper around `clientcmdapi.MinifyConfig()` that
|
||||
// sets the current context to the given context before calling
|
||||
// `clientcmdapi.MinifyConfig()`.
|
||||
func minifyConfig(clientConfig *clientcmdapi.Config, context string) (*clientcmdapi.Config, error) {
|
||||
// MinifyConfig inline-modifies the passed clientConfig. So we make a
|
||||
// copy of it before passing the config to it. A shallow copy is
|
||||
// sufficient because the underlying fields will be reconstructed by
|
||||
// MinifyConfig anyway.
|
||||
newClientConfig := *clientConfig
|
||||
newClientConfig.CurrentContext = context
|
||||
err := clientcmdapi.MinifyConfig(&newClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newClientConfig, nil
|
||||
}
|
||||
|
||||
// createSecret extracts the kubeconfig for a given cluster and populates
|
||||
// a secret with that kubeconfig.
|
||||
func createSecret(hostFactory cmdutil.Factory, clientConfig *clientcmdapi.Config, namespace, name string, dryRun bool) (runtime.Object, error) {
|
||||
// Minify the kubeconfig to ensure that there is only information
|
||||
// relevant to the cluster we are registering.
|
||||
newClientConfig, err := minifyConfig(clientConfig, name)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed to minify the kubeconfig for the given context %q: %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Flatten the kubeconfig to ensure that all the referenced file
|
||||
// contents are inlined.
|
||||
err = clientcmdapi.FlattenConfig(newClientConfig)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed to flatten the kubeconfig for the given context %q: %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configBytes, err := clientcmd.Write(*newClientConfig)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed to serialize the kubeconfig for the given context %q: %v", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the secret object with the minified and flattened
|
||||
// kubeconfig content.
|
||||
secret := &api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
KubeconfigSecretDataKey: configBytes,
|
||||
},
|
||||
}
|
||||
|
||||
if !dryRun {
|
||||
// Boilerplate to create the secret in the host cluster.
|
||||
clientset, err := hostFactory.ClientSet()
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed to retrieve the cluster clientset: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return clientset.Core().Secrets(namespace).Create(secret)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// clusterGenerator extracts the cluster information from the supplied
|
||||
// kubeconfig and builds a StructuredGenerator for the
|
||||
// `federation/cluster` API resource.
|
||||
func clusterGenerator(clientConfig *clientcmdapi.Config, name string) (kubectl.StructuredGenerator, error) {
|
||||
// Get the context from the config.
|
||||
ctx, found := clientConfig.Contexts[name]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("cluster context %q not found", name)
|
||||
}
|
||||
|
||||
// Get the cluster object corresponding to the supplied context.
|
||||
cluster, found := clientConfig.Clusters[ctx.Cluster]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("cluster endpoint not found for %q", name)
|
||||
}
|
||||
|
||||
// Extract the scheme portion of the cluster APIServer endpoint and
|
||||
// default it to `https` if it isn't specified.
|
||||
scheme := extractScheme(cluster.Server)
|
||||
serverAddress := cluster.Server
|
||||
if scheme == "" {
|
||||
// Use "https" as the default scheme.
|
||||
scheme := "https"
|
||||
serverAddress = strings.Join([]string{scheme, serverAddress}, "://")
|
||||
}
|
||||
|
||||
generator := &kubectl.ClusterGeneratorV1Beta1{
|
||||
Name: name,
|
||||
ClientCIDR: defaultClientCIDR,
|
||||
ServerAddress: serverAddress,
|
||||
SecretName: name,
|
||||
}
|
||||
return generator, nil
|
||||
}
|
||||
|
||||
// extractScheme parses the given URL to extract the scheme portion
|
||||
// out of it.
|
||||
func extractScheme(url string) string {
|
||||
scheme := ""
|
||||
segs := strings.SplitN(url, "://", 2)
|
||||
if len(segs) == 2 {
|
||||
scheme = segs[0]
|
||||
}
|
||||
return scheme
|
||||
}
|
392
federation/pkg/kubefed/join_test.go
Normal file
392
federation/pkg/kubefed/join_test.go
Normal file
@ -0,0 +1,392 @@
|
||||
/*
|
||||
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 kubefed
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/restclient/fake"
|
||||
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestJoinFederation(t *testing.T) {
|
||||
cmdErrMsg := ""
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
cmdErrMsg = str
|
||||
})
|
||||
|
||||
fakeKubeFiles, err := fakeKubeconfigFiles()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer rmFakeKubeconfigFiles(fakeKubeFiles)
|
||||
|
||||
testCases := []struct {
|
||||
cluster string
|
||||
server string
|
||||
token string
|
||||
kubeconfigGlobal string
|
||||
kubeconfigExplicit string
|
||||
expectedServer string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
cluster: "syndicate",
|
||||
server: "https://10.20.30.40",
|
||||
token: "badge",
|
||||
kubeconfigGlobal: fakeKubeFiles[0],
|
||||
kubeconfigExplicit: "",
|
||||
expectedServer: "https://10.20.30.40",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
cluster: "ally",
|
||||
server: "ally256.example.com:80",
|
||||
token: "souvenir",
|
||||
kubeconfigGlobal: fakeKubeFiles[0],
|
||||
kubeconfigExplicit: fakeKubeFiles[1],
|
||||
expectedServer: "https://ally256.example.com:80",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
cluster: "confederate",
|
||||
server: "10.8.8.8",
|
||||
token: "totem",
|
||||
kubeconfigGlobal: fakeKubeFiles[1],
|
||||
kubeconfigExplicit: fakeKubeFiles[2],
|
||||
expectedServer: "https://10.8.8.8",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
cluster: "affiliate",
|
||||
server: "https://10.20.30.40",
|
||||
token: "badge",
|
||||
kubeconfigGlobal: fakeKubeFiles[0],
|
||||
kubeconfigExplicit: "",
|
||||
expectedServer: "https://10.20.30.40",
|
||||
expectedErr: fmt.Sprintf("error: cluster context %q not found", "affiliate"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
cmdErrMsg = ""
|
||||
f := testJoinFederationFactory(tc.cluster, tc.expectedServer)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
hostFactory, err := fakeJoinHostFactory(tc.cluster, tc.server, tc.token)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] unexpected error: %v", i, err)
|
||||
}
|
||||
|
||||
joinConfig, err := newFakeJoinFederationConfig(hostFactory, tc.kubeconfigGlobal)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] unexpected error: %v", i, err)
|
||||
}
|
||||
|
||||
cmd := NewCmdJoin(f, buf, joinConfig)
|
||||
|
||||
cmd.Flags().Set("kubeconfig", tc.kubeconfigExplicit)
|
||||
cmd.Flags().Set("host", "substrate")
|
||||
cmd.Run(cmd, []string{tc.cluster})
|
||||
|
||||
if tc.expectedErr == "" {
|
||||
// uses the name from the cluster, not the response
|
||||
// Actual data passed are tested in the fake secret and cluster
|
||||
// REST clients.
|
||||
if msg := buf.String(); msg != fmt.Sprintf("cluster %q created\n", tc.cluster) {
|
||||
t.Errorf("[%d] unexpected output: %s", i, msg)
|
||||
if cmdErrMsg != "" {
|
||||
t.Errorf("[%d] unexpected error message: %s", i, cmdErrMsg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cmdErrMsg != tc.expectedErr {
|
||||
t.Errorf("[%d] expected error: %s, got: %s, output: %s", i, tc.expectedErr, cmdErrMsg, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testJoinFederationFactory(name, server string) cmdutil.Factory {
|
||||
want := fakeCluster(name, server)
|
||||
f, tf, _, _ := cmdtesting.NewAPIFactory()
|
||||
codec := testapi.Federation.Codec()
|
||||
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/clusters" && m == http.MethodPost:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var got federationapi.Cluster
|
||||
_, _, err = codec.Decode(body, nil, &got)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !api.Semantic.DeepEqual(got, want) {
|
||||
return nil, fmt.Errorf("unexpected cluster object\n\tgot: %#v\n\twant: %#v", got, want)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &want)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
}),
|
||||
}
|
||||
tf.Namespace = "test"
|
||||
return f
|
||||
}
|
||||
|
||||
type fakeJoinFederationConfig struct {
|
||||
pathOptions *clientcmd.PathOptions
|
||||
hostFactory cmdutil.Factory
|
||||
}
|
||||
|
||||
func newFakeJoinFederationConfig(f cmdutil.Factory, kubeconfigGlobal string) (JoinFederationConfig, error) {
|
||||
pathOptions := clientcmd.NewDefaultPathOptions()
|
||||
pathOptions.GlobalFile = kubeconfigGlobal
|
||||
pathOptions.EnvVar = ""
|
||||
|
||||
return &fakeJoinFederationConfig{
|
||||
pathOptions: pathOptions,
|
||||
hostFactory: f,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *fakeJoinFederationConfig) PathOptions() *clientcmd.PathOptions {
|
||||
return r.pathOptions
|
||||
}
|
||||
|
||||
func (r *fakeJoinFederationConfig) HostFactory(host, kubeconfigPath string) cmdutil.Factory {
|
||||
return r.hostFactory
|
||||
}
|
||||
|
||||
func fakeJoinHostFactory(name, server, token string) (cmdutil.Factory, error) {
|
||||
kubeconfig := clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
name: {
|
||||
Server: server,
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
name: {
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
name: {
|
||||
Cluster: name,
|
||||
AuthInfo: name,
|
||||
},
|
||||
},
|
||||
CurrentContext: name,
|
||||
}
|
||||
configBytes, err := clientcmd.Write(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretObject := v1.Secret{
|
||||
TypeMeta: unversioned.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: "federation-system",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"kubeconfig": configBytes,
|
||||
},
|
||||
}
|
||||
|
||||
f, tf, codec, _ := cmdtesting.NewAPIFactory()
|
||||
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||
tf.ClientConfig = defaultClientConfig()
|
||||
tf.Client = &fake.RESTClient{
|
||||
NegotiatedSerializer: ns,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api/v1/namespaces/federation-system/secrets" && m == http.MethodPost:
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var got v1.Secret
|
||||
_, _, err = codec.Decode(body, nil, &got)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !api.Semantic.DeepEqual(got, secretObject) {
|
||||
return nil, fmt.Errorf("Unexpected secret object\n\tgot: %#v\n\twant: %#v", got, secretObject)
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &secretObject)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
}),
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func fakeCluster(name, server string) federationapi.Cluster {
|
||||
return federationapi.Cluster{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: federationapi.ClusterSpec{
|
||||
ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
|
||||
{
|
||||
ClientCIDR: defaultClientCIDR,
|
||||
ServerAddress: server,
|
||||
},
|
||||
},
|
||||
SecretRef: &v1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fakeKubeconfigFiles() ([]string, error) {
|
||||
kubeconfigs := []clientcmdapi.Config{
|
||||
{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"syndicate": {
|
||||
Server: "https://10.20.30.40",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"syndicate": {
|
||||
Token: "badge",
|
||||
},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"syndicate": {
|
||||
Cluster: "syndicate",
|
||||
AuthInfo: "syndicate",
|
||||
},
|
||||
},
|
||||
CurrentContext: "syndicate",
|
||||
},
|
||||
{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"ally": {
|
||||
Server: "ally256.example.com:80",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"ally": {
|
||||
Token: "souvenir",
|
||||
},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"ally": {
|
||||
Cluster: "ally",
|
||||
AuthInfo: "ally",
|
||||
},
|
||||
},
|
||||
CurrentContext: "ally",
|
||||
},
|
||||
{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"ally": {
|
||||
Server: "https://ally64.example.com",
|
||||
},
|
||||
"confederate": {
|
||||
Server: "10.8.8.8",
|
||||
},
|
||||
},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{
|
||||
"ally": {
|
||||
Token: "souvenir",
|
||||
},
|
||||
"confederate": {
|
||||
Token: "totem",
|
||||
},
|
||||
},
|
||||
Contexts: map[string]*clientcmdapi.Context{
|
||||
"ally": {
|
||||
Cluster: "ally",
|
||||
AuthInfo: "ally",
|
||||
},
|
||||
"confederate": {
|
||||
Cluster: "confederate",
|
||||
AuthInfo: "confederate",
|
||||
},
|
||||
},
|
||||
CurrentContext: "confederate",
|
||||
},
|
||||
}
|
||||
kubefiles := []string{}
|
||||
for _, cfg := range kubeconfigs {
|
||||
fakeKubeFile, _ := ioutil.TempFile("", "")
|
||||
err := clientcmd.WriteToFile(cfg, fakeKubeFile.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kubefiles = append(kubefiles, fakeKubeFile.Name())
|
||||
}
|
||||
return kubefiles, nil
|
||||
}
|
||||
|
||||
func rmFakeKubeconfigFiles(kubefiles []string) {
|
||||
for _, file := range kubefiles {
|
||||
os.Remove(file)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHeader() http.Header {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return header
|
||||
}
|
||||
|
||||
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
||||
}
|
||||
|
||||
func defaultClientConfig() *restclient.Config {
|
||||
return &restclient.Config{
|
||||
APIPath: "/api",
|
||||
ContentConfig: restclient.ContentConfig{
|
||||
NegotiatedSerializer: api.Codecs,
|
||||
ContentType: runtime.ContentTypeJSON,
|
||||
GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion,
|
||||
},
|
||||
}
|
||||
}
|
@ -242,6 +242,7 @@ host-ipc-sources
|
||||
host-network-sources
|
||||
host-pid-sources
|
||||
host-port-endpoints
|
||||
host-system-namespace
|
||||
hostname-override
|
||||
http-check-frequency
|
||||
http-port
|
||||
|
@ -480,6 +480,7 @@ k8s.io/kubernetes/federation/pkg/federation-controller/util,bgrant0607,1
|
||||
k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink,luxas,1
|
||||
k8s.io/kubernetes/federation/pkg/federation-controller/util/planner,Q-Lee,1
|
||||
k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer,caesarxuchao,1
|
||||
k8s.io/kubernetes/federation/pkg/kubefed,madhusudancs,0
|
||||
k8s.io/kubernetes/federation/registry/cluster,nikhiljindal,0
|
||||
k8s.io/kubernetes/federation/registry/cluster/etcd,nikhiljindal,0
|
||||
k8s.io/kubernetes/hack/cmd/teststale,thockin,1
|
||||
|
|
Loading…
Reference in New Issue
Block a user