Merge pull request #631 from justinsb/move_apis_3

Experimental support for federation
This commit is contained in:
Justin Santa Barbara 2016-10-12 00:45:18 -04:00 committed by GitHub
commit 591a85056a
38 changed files with 2430 additions and 110 deletions

View File

@ -34,12 +34,15 @@ ifdef STATIC_BUILD
EXTRA_LDFLAGS=-s
endif
kops: gobindata
kops: kops-gobindata
go install ${EXTRA_BUILDFLAGS} -ldflags "-X main.BuildVersion=${VERSION} ${EXTRA_LDFLAGS}" k8s.io/kops/cmd/kops/...
gobindata:
gobindata-tool:
go build ${EXTRA_BUILDFLAGS} -ldflags "${EXTRA_LDFLAGS}" -o ${GOPATH_1ST}/bin/go-bindata k8s.io/kops/vendor/github.com/jteeuwen/go-bindata/go-bindata
cd ${GOPATH_1ST}/src/k8s.io/kops; ${GOPATH_1ST}/bin/go-bindata -o upup/models/bindata.go -pkg models -ignore="\\.DS_Store" -ignore=".*\\.go" -prefix upup/models/ upup/models/...
kops-gobindata: gobindata-tool
cd ${GOPATH_1ST}/src/k8s.io/kops; ${GOPATH_1ST}/bin/go-bindata -o upup/models/bindata.go -pkg models -ignore="\\.DS_Store" -ignore="bindata\\.go" -ignore="vfs\\.go" -prefix upup/models/ upup/models/...
cd ${GOPATH_1ST}/src/k8s.io/kops; ${GOPATH_1ST}/bin/go-bindata -o federation/model/bindata.go -pkg model -ignore="\\.DS_Store" -ignore="bindata\\.go" -prefix federation/model/ federation/model/...
# Build in a docker container with golang 1.X
# Used to test we have not broken 1.X
@ -52,7 +55,7 @@ check-builds-in-go16:
check-builds-in-go17:
docker run -v ${GOPATH_1ST}/src/k8s.io/kops:/go/src/k8s.io/kops golang:1.7 make -f /go/src/k8s.io/kops/Makefile kops
codegen: gobindata
codegen: kops-gobindata
go install k8s.io/kops/upup/tools/generators/...
PATH=${GOPATH_1ST}/bin:${PATH} go generate k8s.io/kops/upup/pkg/fi/cloudup/awstasks
PATH=${GOPATH_1ST}/bin:${PATH} go generate k8s.io/kops/upup/pkg/fi/cloudup/gcetasks
@ -136,7 +139,7 @@ protokube-push: protokube-image
nodeup: nodeup-dist
nodeup-gocode: gobindata
nodeup-gocode: kops-gobindata
go install ${EXTRA_BUILDFLAGS} -ldflags "${EXTRA_LDFLAGS} -X main.BuildVersion=${VERSION}" k8s.io/kops/cmd/nodeup
nodeup-dist:
@ -172,6 +175,7 @@ copydeps:
gofmt:
gofmt -w -s cmd/
gofmt -w -s channels/
gofmt -w -s examples/
gofmt -w -s util/
gofmt -w -s cmd/
gofmt -w -s upup/pkg/

View File

@ -25,6 +25,7 @@ import (
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/util/pkg/vfs"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
@ -68,6 +69,11 @@ func NewCmdCreate(f *util.Factory, out io.Writer) *cobra.Command {
}
func RunCreate(f *util.Factory, cmd *cobra.Command, out io.Writer, c *CreateOptions) error {
clientset, err := f.Clientset()
if err != nil {
return err
}
// Codecs provides access to encoding and decoding for the scheme
codecs := k8sapi.Codecs //serializer.NewCodecFactory(scheme)
@ -85,6 +91,14 @@ func RunCreate(f *util.Factory, cmd *cobra.Command, out io.Writer, c *CreateOpti
}
switch v := o.(type) {
case *kopsapi.Federation:
_, err = clientset.Federations().Create(v)
if err != nil {
if errors.IsAlreadyExists(err) {
return fmt.Errorf("federation %q already exists", v.Name)
}
return fmt.Errorf("error creating federation: %v", err)
}
default:
glog.V(2).Infof("Type of object was %T", v)
return fmt.Errorf("Unhandled kind %q in %q", gvk, f)

View File

@ -15,6 +15,7 @@ func NewCmdEdit(f *util.Factory, out io.Writer) *cobra.Command {
// create subcommands
cmd.AddCommand(NewCmdEditCluster(f, out))
cmd.AddCommand(NewCmdEditInstanceGroup(f, out))
cmd.AddCommand(NewCmdEditFederation(f, out))
return cmd
}

121
cmd/kops/edit_federation.go Normal file
View File

@ -0,0 +1,121 @@
package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"io"
"k8s.io/kops/cmd/kops/util"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/v1alpha1"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/editor"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
)
type EditFederationOptions struct {
}
func NewCmdEditFederation(f *util.Factory, out io.Writer) *cobra.Command {
options := &EditFederationOptions{}
cmd := &cobra.Command{
Use: "federation",
Aliases: []string{"federations"},
Short: "Edit federation",
Long: `Edit a federation configuration.`,
Run: func(cmd *cobra.Command, args []string) {
err := RunEditFederation(f, cmd, args, os.Stdout, options)
if err != nil {
exitWithError(err)
}
},
}
return cmd
}
func RunEditFederation(f *util.Factory, cmd *cobra.Command, args []string, out io.Writer, options *EditFederationOptions) error {
if len(args) == 0 {
return fmt.Errorf("Specify name of Federation to edit")
}
if len(args) != 1 {
return fmt.Errorf("Can only edit one Federation at a time")
}
name := args[0]
clientset, err := f.Clientset()
if err != nil {
return err
}
if name == "" {
return fmt.Errorf("name is required")
}
old, err := clientset.Federations().Get(name)
if err != nil {
return fmt.Errorf("error reading Federation %q: %v", name, err)
}
if old == nil {
return fmt.Errorf("Federation %q not found", name)
}
var (
edit = editor.NewDefaultEditor(editorEnvs)
)
ext := "yaml"
var b bytes.Buffer
yamlSerde := json.NewYAMLSerializer(json.DefaultMetaFactory, k8sapi.Scheme, k8sapi.Scheme)
encoder := k8sapi.Codecs.EncoderForVersion(yamlSerde, v1alpha1.SchemeGroupVersion)
if err := encoder.Encode(old, &b); err != nil {
return fmt.Errorf("error parsing Federation: %v", err)
}
raw := b.Bytes()
// launch the editor
edited, file, err := edit.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(os.Args[0])), ext, bytes.NewReader(raw))
defer func() {
if file != "" {
os.Remove(file)
}
}()
if err != nil {
return fmt.Errorf("error launching editor: %v", err)
}
if bytes.Equal(edited, raw) {
fmt.Fprintln(os.Stderr, "Edit cancelled, no changes made.")
return nil
}
codec := k8sapi.Codecs.UniversalDecoder(kopsapi.SchemeGroupVersion)
newObj, _, err := codec.Decode(edited, nil, nil)
if err != nil {
return fmt.Errorf("error parsing: %v", err)
}
newFed := newObj.(*kopsapi.Federation)
err = newFed.Validate()
if err != nil {
return err
}
// Note we perform as much validation as we can, before writing a bad config
_, err = clientset.Federations().Update(newFed)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,84 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"io"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/util/pkg/tables"
k8sapi "k8s.io/kubernetes/pkg/api"
"os"
"strings"
)
type GetFederationOptions struct {
}
func init() {
var options GetFederationOptions
cmd := &cobra.Command{
Use: "federations",
Aliases: []string{"federation"},
Short: "get federations",
Long: `List or get federations.`,
Run: func(cmd *cobra.Command, args []string) {
err := RunGetFederations(&rootCommand, os.Stdout, &options)
if err != nil {
exitWithError(err)
}
},
}
getCmd.cobraCommand.AddCommand(cmd)
}
func RunGetFederations(context Factory, out io.Writer, options *GetFederationOptions) error {
client, err := context.Clientset()
if err != nil {
return err
}
list, err := client.Federations().List(k8sapi.ListOptions{})
if err != nil {
return err
}
var federations []*api.Federation
for i := range list.Items {
federations = append(federations, &list.Items[i])
}
if len(federations) == 0 {
fmt.Fprintf(out, "No federations found\n")
return nil
}
output := getCmd.output
if output == OutputTable {
t := &tables.Table{}
t.AddColumn("NAME", func(f *api.Federation) string {
return f.Name
})
t.AddColumn("CONTROLLERS", func(f *api.Federation) string {
return strings.Join(f.Spec.Controllers, ",")
})
t.AddColumn("MEMBERS", func(f *api.Federation) string {
return strings.Join(f.Spec.Members, ",")
})
return t.Render(federations, out, "NAME", "CONTROLLERS", "MEMBERS")
} else if output == OutputYaml {
for _, f := range federations {
y, err := api.ToYaml(f)
if err != nil {
return fmt.Errorf("error marshaling yaml for %q: %v", f.Name, err)
}
_, err = out.Write(y)
if err != nil {
return fmt.Errorf("error writing to output: %v", err)
}
}
return nil
} else {
return fmt.Errorf("Unknown output format: %q", output)
}
}

View File

@ -9,6 +9,7 @@ import (
"io"
"k8s.io/kops/cmd/kops/util"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/v1alpha1"
"k8s.io/kops/pkg/client/simple"
"k8s.io/kops/upup/pkg/kutil"
k8sapi "k8s.io/kubernetes/pkg/api"
@ -60,6 +61,9 @@ func initializeSchemas() error {
if err := kopsapi.AddToScheme(scheme); err != nil {
return err
}
if err := v1alpha1.AddToScheme(scheme); err != nil {
return err
}
return nil
}

View File

@ -15,6 +15,7 @@ func NewCmdUpdate(f *util.Factory, out io.Writer) *cobra.Command {
// subcommands
cmd.AddCommand(NewCmdUpdateCluster(f, out))
cmd.AddCommand(NewCmdUpdateFederation(f, out))
return cmd
}

View File

@ -0,0 +1,77 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"io"
"k8s.io/kops/cmd/kops/util"
"k8s.io/kops/federation"
"os"
)
type UpdateFederationOptions struct {
}
func NewCmdUpdateFederation(f *util.Factory, out io.Writer) *cobra.Command {
options := &UpdateFederationOptions{}
cmd := &cobra.Command{
Use: "federation",
Short: "Update federation",
Long: `Updates a k8s federation.`,
Run: func(cmd *cobra.Command, args []string) {
err := RunUpdateFederation(f, cmd, args, os.Stdout, options)
if err != nil {
exitWithError(err)
}
},
}
return cmd
}
func RunUpdateFederation(factory *util.Factory, cmd *cobra.Command, args []string, out io.Writer, options *UpdateFederationOptions) error {
if len(args) == 0 {
return fmt.Errorf("Specify name of Federation to update")
}
if len(args) != 1 {
return fmt.Errorf("Can only update one Federation at a time")
}
name := args[0]
clientset, err := factory.Clientset()
if err != nil {
return err
}
f, err := clientset.Federations().Get(name)
if err != nil {
return fmt.Errorf("error reading federation %q: %v", name, err)
}
applyCmd := &federation.ApplyFederationOperation{
Federation: f,
KopsClient: clientset,
}
err = applyCmd.Run()
if err != nil {
return err
}
kubecfg, err := applyCmd.FindKubecfg()
if err != nil {
return err
}
if kubecfg == nil {
return fmt.Errorf("cannot find configuration for federation")
}
err = kubecfg.WriteKubecfg()
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,6 @@
kind: Cluster
apiVersion: kops/v1alpha1
metadata:
name: control1.federation.com
spec:
channel: stable

View File

@ -0,0 +1,9 @@
kind: Federation
apiVersion: kops/v1alpha1
metadata:
name: federation
spec:
dnsName: test.federation.com
controllers:
- control1.federation.com
- control2.federation.com

View File

@ -24,4 +24,4 @@ func apply() error {
}
return nil
}
}

View File

@ -1,10 +1,10 @@
package main
import (
"os"
"flag"
"k8s.io/kops/util/pkg/vfs"
"fmt"
"k8s.io/kops/util/pkg/vfs"
"os"
"strings"
)
@ -16,6 +16,7 @@ var clusterName string
// nodeZones is the set of zones in which we will run nodes
var nodeZones []string
// masterZones is the set of zones in which we will run masters
var masterZones []string
@ -64,7 +65,7 @@ func parseFlags() error {
return fmt.Errorf("Must pass -zones with comma-separated list of zones")
}
nodeZones = strings.Split(*flagZones, ",")
masterZones = []string{nodeZones[0] }
masterZones = []string{nodeZones[0]}
return nil
}
}

View File

@ -0,0 +1,405 @@
package federation
import (
"fmt"
"k8s.io/kops/upup/pkg/fi/fitasks"
"k8s.io/kops/upup/pkg/fi"
"crypto/rsa"
crypto_rand "crypto/rand"
k8sapiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kops/federation/tasks"
"text/template"
"bytes"
"k8s.io/kops/federation/model"
"k8s.io/kops/federation/targets/kubernetes"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/client/simple"
"k8s.io/kops/pkg/apis/kops/registry"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kops/upup/pkg/kutil"
"k8s.io/kops/upup/pkg/fi/k8sapi"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
"k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4"
"github.com/golang/glog"
"strings"
)
type ApplyFederationOperation struct {
Federation *kopsapi.Federation
KopsClient simple.Clientset
namespace string
name string
apiserverDeploymentName string
apiserverServiceName string
apiserverHostName string
dnsZoneName string
apiserverSecretName string
}
func (o*ApplyFederationOperation) FindKubecfg() (*kutil.KubeconfigBuilder, error) {
// TODO: Only if not yet set?
// hasKubecfg, err := hasKubecfg(f.Name)
// if err != nil {
// glog.Warningf("error reading kubecfg: %v", err)
// hasKubecfg = true
// }
// Loop through looking for a configured cluster
for _, controller := range o.Federation.Spec.Controllers {
cluster, err := o.KopsClient.Clusters().Get(controller)
if err != nil {
return nil, fmt.Errorf("error reading cluster %q: %v", controller, err)
}
context, err := o.federationContextForCluster(cluster)
if err != nil {
return nil, err
}
apiserverKeypair := o.buildApiserverKeypair()
federationConfiguration := &FederationConfiguration{
Namespace: o.namespace,
ApiserverSecretName: o.apiserverSecretName,
ApiserverServiceName: o.apiserverServiceName,
ApiserverKeypair: apiserverKeypair,
KubeconfigSecretName:"federation-apiserver-kubeconfig",
}
k, err := federationConfiguration.extractKubecfg(context, o.Federation)
if err != nil {
return nil, err
}
if k == nil {
continue
}
return k, nil
}
return nil, nil
}
func (o*ApplyFederationOperation) Run() error {
o.namespace = "federation"
o.name = "federation"
o.apiserverDeploymentName = "federation-apiserver"
o.apiserverServiceName = o.apiserverDeploymentName
o.apiserverSecretName = "federation-apiserver-secrets"
o.dnsZoneName = o.Federation.Spec.DNSName
o.apiserverHostName = "api." + o.dnsZoneName
// TODO: sync clusters
var controllerKubernetesClients []release_1_3.Interface
for _, controller := range o.Federation.Spec.Controllers {
cluster, err := o.KopsClient.Clusters().Get(controller)
if err != nil {
return fmt.Errorf("error reading cluster %q: %v", controller, err)
}
context, err := o.federationContextForCluster(cluster)
if err != nil {
return err
}
err = o.runOnCluster(context, cluster)
if err != nil {
return err
}
k8s := context.Target.(*kubernetes.KubernetesTarget).KubernetesClient
controllerKubernetesClients = append(controllerKubernetesClients, k8s)
}
federationKubecfg, err := o.FindKubecfg()
if err != nil {
return err
}
federationRestConfig, err := federationKubecfg.BuildRestConfig()
if err != nil {
return err
}
federationControllerClient, err := federation_release_1_4.NewForConfig(federationRestConfig)
if err != nil {
return err
}
//k8sControllerClient, err := release_1_3.NewForConfig(federationRestConfig)
//if err != nil {
// return err
//}
for _, member := range o.Federation.Spec.Members {
glog.V(2).Infof("configuring member cluster %q", member)
cluster, err := o.KopsClient.Clusters().Get(member)
if err != nil {
return fmt.Errorf("error reading cluster %q: %v", member, err)
}
clusterName := strings.Replace(cluster.Name, ".", "-", -1)
a := &FederationCluster{
FederationNamespace : o.namespace,
ControllerKubernetesClients: controllerKubernetesClients,
FederationClient: federationControllerClient,
ClusterSecretName: "secret-" + cluster.Name,
ClusterName: clusterName,
ApiserverHostname: cluster.Spec.MasterPublicName,
}
err = a.Run(cluster)
if err != nil {
return err
}
}
// Create default namespace
glog.V(2).Infof("Ensuring default namespace exists")
if _, err := o.ensureFederationNamespace(federationControllerClient, "default"); err != nil {
return err
}
return nil
}
// Builds a fi.Context applying to the federation namespace in the specified cluster
// Note that this operates inside the cluster, for example the KeyStore is backed by secrets in the namespace
func (o*ApplyFederationOperation) federationContextForCluster(cluster *kopsapi.Cluster) (*fi.Context, error) {
clusterKeystore, err := registry.KeyStore(cluster)
if err != nil {
return nil, err
}
target, err := kubernetes.NewKubernetesTarget(o.KopsClient, clusterKeystore, cluster)
if err != nil {
return nil, err
}
federationKeystore := k8sapi.NewKubernetesKeystore(target.KubernetesClient, o.namespace)
checkExisting := true
context, err := fi.NewContext(target, nil, federationKeystore, nil, nil, checkExisting, nil)
if err != nil {
return nil, err
}
return context, nil
}
func (o*ApplyFederationOperation) buildApiserverKeypair() (*fitasks.Keypair) {
keypairName := "secret-" + o.apiserverHostName
keypair := &fitasks.Keypair{
Name: fi.String(keypairName),
Subject: "cn=" + o.Federation.Name,
Type: "server",
}
// So it has a valid cert inside the cluster
if o.apiserverServiceName != "" {
keypair.AlternateNames = append(keypair.AlternateNames, o.apiserverServiceName)
}
// So it has a valid cert outside the cluster
if o.apiserverHostName != "" {
keypair.AlternateNames = append(keypair.AlternateNames, o.apiserverHostName)
}
return keypair
}
func (o*ApplyFederationOperation) runOnCluster(context *fi.Context, cluster *kopsapi.Cluster) error {
_, _, err := EnsureCASecret(context.Keystore)
if err != nil {
return err
}
apiserverKeypair := o.buildApiserverKeypair()
err = apiserverKeypair.Run(context)
if err != nil {
return err
}
err = o.EnsureNamespace(context)
if err != nil {
return err
}
federationConfiguration := &FederationConfiguration{
ApiserverServiceName: o.apiserverServiceName,
Namespace: o.namespace,
ApiserverSecretName: o.apiserverSecretName,
ApiserverKeypair: apiserverKeypair,
KubeconfigSecretName:"federation-apiserver-kubeconfig",
}
err = federationConfiguration.EnsureConfiguration(context)
if err != nil {
return err
}
templateData, err := model.Asset("manifest.yaml")
if err != nil {
return fmt.Errorf("error loading manifest: %v", err)
}
manifest, err := o.executeTemplate("manifest", string(templateData))
if err != nil {
return fmt.Errorf("error expanding manifest template: %v", err)
}
applyManifestTask := tasks.KubernetesResource{
Name: fi.String(o.name),
Manifest: fi.WrapResource(fi.NewStringResource(manifest)),
}
err = applyManifestTask.Run(context)
if err != nil {
return err
}
return nil
}
func (o*ApplyFederationOperation) buildTemplateData() map[string]string {
namespace := o.namespace
name := o.name
dnsZoneName := o.dnsZoneName
apiserverHostname := o.apiserverHostName
// The names of the k8s apiserver & controller-manager objects
apiserverDeploymentName := "federation-apiserver"
controllerDeploymentName := "federation-controller-manager"
imageRepo := "gcr.io/google_containers/hyperkube-amd64"
imageTag := "v1.4.0"
federationDNSProvider := "aws-route53"
federationDNSProviderConfig := ""
// TODO: define exactly what these do...
serviceCIDR := "10.10.0.0/24"
federationAdmissionControl := "NamespaceLifecycle"
data := make(map[string]string)
data["FEDERATION_NAMESPACE"] = namespace
data["FEDERATION_NAME"] = name
data["FEDERATION_APISERVER_DEPLOYMENT_NAME"] = apiserverDeploymentName
data["FEDERATION_CONTROLLER_MANAGER_DEPLOYMENT_NAME"] = controllerDeploymentName
data["FEDERATION_APISERVER_IMAGE_REPO"] = imageRepo
data["FEDERATION_APISERVER_IMAGE_TAG"] = imageTag
data["FEDERATION_CONTROLLER_MANAGER_IMAGE_REPO"] = imageRepo
data["FEDERATION_CONTROLLER_MANAGER_IMAGE_TAG"] = imageTag
data["FEDERATION_SERVICE_CIDR"] = serviceCIDR
data["EXTERNAL_HOSTNAME"] = apiserverHostname
data["FEDERATION_ADMISSION_CONTROL"] = federationAdmissionControl
data["FEDERATION_DNS_PROVIDER"] = federationDNSProvider
data["FEDERATION_DNS_PROVIDER_CONFIG"] = federationDNSProviderConfig
data["DNS_ZONE_NAME"] = dnsZoneName
return data
}
func (o*ApplyFederationOperation) executeTemplate(key string, templateDefinition string) (string, error) {
data := o.buildTemplateData()
t := template.New(key)
funcMap := make(template.FuncMap)
//funcMap["Args"] = func() []string {
// return args
//}
//funcMap["RenderResource"] = func(resourceName string, args []string) (string, error) {
// return l.renderResource(resourceName, args)
//}
//for k, fn := range l.TemplateFunctions {
// funcMap[k] = fn
//}
t.Funcs(funcMap)
t.Option("missingkey=zero")
_, err := t.Parse(templateDefinition)
if err != nil {
return "", fmt.Errorf("error parsing template %q: %v", key, err)
}
var buffer bytes.Buffer
err = t.ExecuteTemplate(&buffer, key, data)
if err != nil {
return "", fmt.Errorf("error executing template %q: %v", key, err)
}
return buffer.String(), nil
}
func (o*ApplyFederationOperation) EnsureNamespace(c *fi.Context) error {
k8s := c.Target.(*kubernetes.KubernetesTarget).KubernetesClient
ns, err := k8s.Core().Namespaces().Get(o.namespace)
if err != nil {
if errors.IsNotFound(err) {
ns = nil
} else {
return fmt.Errorf("error reading namespace: %v", err)
}
}
if ns == nil {
ns = &k8sapiv1.Namespace{}
ns.Name = o.namespace
ns, err = k8s.Core().Namespaces().Create(ns)
if err != nil {
return fmt.Errorf("error creating namespace: %v", err)
}
}
return nil
}
func (o*ApplyFederationOperation) ensureFederationNamespace(k8s federation_release_1_4.Interface, name string) (*k8sapiv1.Namespace, error) {
return mutateNamespace(k8s, name, func(n *k8sapiv1.Namespace) (*k8sapiv1.Namespace, error) {
if n == nil {
n = &k8sapiv1.Namespace{}
n.Name = name
}
return n, nil
})
}
func EnsureCASecret(keystore fi.Keystore) (*fi.Certificate, *fi.PrivateKey, error) {
id := fi.CertificateId_CA
caCert, caPrivateKey, err := keystore.FindKeypair(id)
if err != nil {
return nil, nil, err
}
if caPrivateKey == nil {
template := fi.BuildCAX509Template()
caRsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
if err != nil {
return nil, nil, fmt.Errorf("error generating RSA private key: %v", err)
}
caPrivateKey = &fi.PrivateKey{Key: caRsaKey}
caCert, err = fi.SignNewCertificate(caPrivateKey, template, nil, nil)
if err != nil {
return nil, nil, err
}
err = keystore.StoreKeypair(id, caCert, caPrivateKey)
if err != nil {
return nil, nil, err
}
}
return caCert, caPrivateKey, nil
}

73
federation/auth_file.go Normal file
View File

@ -0,0 +1,73 @@
package federation
import (
"strings"
"fmt"
"bytes"
)
type AuthFile struct {
Lines []*AuthFileLine
}
type AuthFileLine struct {
User string
Secret string
Role string
}
func ParseAuthFile(data []byte) (*AuthFile, error) {
parsed := &AuthFile{}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parsedLine, err := ParseAuthFileLine(line)
if err != nil {
return nil, err
}
parsed.Lines = append(parsed.Lines, parsedLine)
}
return parsed, nil
}
func (a*AuthFile) FindUser(user string) *AuthFileLine {
for _, line := range a.Lines {
if line.User == user {
return line
}
}
return nil
}
func (a*AuthFile) Add(line *AuthFileLine) error {
existing := a.FindUser(line.User)
if existing != nil {
return fmt.Errorf("user %q already exists in file", line.User)
}
a.Lines = append(a.Lines, line)
return nil
}
func (a*AuthFile) Encode() string {
var b bytes.Buffer
for _, line := range a.Lines {
b.WriteString(fmt.Sprintf("%s,%s,%s\n", line.Secret, line.User, line.Role))
}
return b.String()
}
func ParseAuthFileLine(line string) (*AuthFileLine, error) {
tokens := strings.Split(line, ",")
if len(tokens) != 3 {
return nil, fmt.Errorf("unexpected line: expected exactly 3 tokens, found %d", len(tokens))
}
parsed := &AuthFileLine{
Secret: tokens[0],
User: tokens[1],
Role: tokens[2],
}
return parsed, nil
}

View File

@ -0,0 +1,204 @@
package federation
import (
"fmt"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/errors"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/kutil"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
"k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4"
"k8s.io/kubernetes/federation/apis/federation/v1beta1"
"k8s.io/kops/pkg/apis/kops/registry"
)
type FederationCluster struct {
FederationNamespace string
ControllerKubernetesClients []release_1_3.Interface
FederationClient federation_release_1_4.Interface
ClusterSecretName string
ClusterName string
ApiserverHostname string
}
func (o*FederationCluster) Run(cluster *kopsapi.Cluster) error {
keyStore, err := registry.KeyStore(cluster)
if err != nil {
return err
}
secretStore, err := registry.SecretStore(cluster)
if err != nil {
return err
}
k := kutil.CreateKubecfg{
ContextName: cluster.Name,
KeyStore: keyStore,
SecretStore: secretStore,
KubeMasterIP: cluster.Spec.MasterPublicName,
}
kubeconfig, err := k.ExtractKubeconfig()
if err != nil {
return fmt.Errorf("error building connection information for cluster %q: %v", cluster.Name, err)
}
user := kutil.KubectlUser{
ClientCertificateData: kubeconfig.ClientCert,
ClientKeyData : kubeconfig.ClientKey,
}
// username/password or bearer token may be set, but not both
if kubeconfig.KubeBearerToken != "" {
user.Token = kubeconfig.KubeBearerToken
} else {
user.Username = kubeconfig.KubeUser
user.Password = kubeconfig.KubePassword
}
for _, k8s := range o.ControllerKubernetesClients {
if err := o.ensureFederationSecret(k8s, kubeconfig.CACert, user); err != nil {
return err
}
}
if err := o.ensureFederationCluster(o.FederationClient); err != nil {
return err
}
return nil
}
func (o*FederationCluster) ensureFederationSecret(k8s release_1_3.Interface, caCertData []byte, user kutil.KubectlUser) error {
_, err := mutateSecret(k8s, o.FederationNamespace, o.ClusterSecretName, func(s *v1.Secret) (*v1.Secret, error) {
var kubeconfigData []byte
var err error
{
kubeconfig := &kutil.KubectlConfig{
ApiVersion: "v1",
Kind: "Config",
}
cluster := &kutil.KubectlClusterWithName{
Name: o.ClusterName,
Cluster: kutil.KubectlCluster{
Server: "https://" + o.ApiserverHostname,
},
}
if caCertData != nil {
cluster.Cluster.CertificateAuthorityData = caCertData
}
kubeconfig.Clusters = append(kubeconfig.Clusters, cluster)
user := &kutil.KubectlUserWithName{
Name: o.ClusterName,
User: user,
}
kubeconfig.Users = append(kubeconfig.Users, user)
context := &kutil.KubectlContextWithName{
Name: o.ClusterName,
Context: kutil.KubectlContext{
Cluster: cluster.Name,
User: user.Name,
},
}
kubeconfig.CurrentContext = o.ClusterName
kubeconfig.Contexts = append(kubeconfig.Contexts, context)
kubeconfigData, err = kopsapi.ToYaml(kubeconfig)
if err != nil {
return nil, fmt.Errorf("error building kubeconfig: %v", err)
}
}
if s == nil {
s = &v1.Secret{}
s.Type = v1.SecretTypeOpaque
}
if s.Data == nil {
s.Data = make(map[string][]byte)
}
s.Data["kubeconfig"] = kubeconfigData
return s, nil
})
return err
}
func (o*FederationCluster) ensureFederationCluster(federationClient federation_release_1_4.Interface) error {
_, err := mutateCluster(federationClient, o.ClusterName, func(c *v1beta1.Cluster) (*v1beta1.Cluster, error) {
if c == nil {
c = &v1beta1.Cluster{}
}
// How to connect to the member cluster
c.Spec.ServerAddressByClientCIDRs = []v1beta1.ServerAddressByClientCIDR{
{
// The CIDR with which clients can match their IP to figure out the server address that they should use.
ClientCIDR: "0.0.0.0/0",
// Address of this server, suitable for a client that matches the above CIDR.
// This can be a hostname, hostname:port, IP or IP:port.
ServerAddress: "https://" + o.ApiserverHostname,
},
}
// Secret containing credentials for connecting to cluster
c.Spec.SecretRef = &v1.LocalObjectReference{
Name: o.ClusterSecretName,
}
return c, nil
})
return err
}
func findCluster(k8s federation_release_1_4.Interface, name string) (*v1beta1.Cluster, error) {
glog.V(2).Infof("querying k8s for federation cluster %s", name)
c, err := k8s.Federation().Clusters().Get(name)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
} else {
return nil, fmt.Errorf("error reading federation cluster %s: %v", name, err)
}
}
return c, nil
}
func mutateCluster(k8s federation_release_1_4.Interface, name string, fn func(s *v1beta1.Cluster) (*v1beta1.Cluster, error)) (*v1beta1.Cluster, error) {
existing, err := findCluster(k8s, name)
if err != nil {
return nil, err
}
createObject := existing == nil
updated, err := fn(existing)
if err != nil {
return nil, err
}
updated.Name = name
if createObject {
glog.V(2).Infof("creating federation cluster %s", name)
created, err := k8s.Federation().Clusters().Create(updated)
if err != nil {
return nil, fmt.Errorf("error creating federation cluster %s: %v", name, err)
}
return created, nil
} else {
glog.V(2).Infof("updating federation cluster %s", name)
created, err := k8s.Federation().Clusters().Update(updated)
if err != nil {
return nil, fmt.Errorf("error updating federation cluster %s: %v", name, err)
}
return created, nil
}
}

View File

@ -0,0 +1,369 @@
package federation
import (
"fmt"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kops/federation/targets/kubernetes"
"k8s.io/kubernetes/pkg/api/errors"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/kutil"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi/fitasks"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
)
const UserAdmin = "admin"
type FederationConfiguration struct {
Namespace string
ApiserverKeypair *fitasks.Keypair
ApiserverServiceName string
ApiserverSecretName string
KubeconfigSecretName string
}
func (o*FederationConfiguration) extractKubecfg(c *fi.Context, f *kopsapi.Federation) (*kutil.KubeconfigBuilder, error) {
// TODO: move this
masterName := "api." + f.Spec.DNSName
k := kutil.NewKubeconfigBuilder()
k.KubeMasterIP = masterName
k.Context = "federation-" + f.Name
// CA Cert
caCert, _, err := c.Keystore.FindKeypair(fi.CertificateId_CA)
if err != nil {
return nil, err
}
if caCert == nil {
glog.Infof("No CA certificate in cluster %q", c)
return nil, nil
}
k.CACert, err = caCert.AsBytes()
if err != nil {
return nil, err
}
k8s := c.Target.(*kubernetes.KubernetesTarget).KubernetesClient
// Basic auth
secret, err := findSecret(k8s, o.Namespace, o.ApiserverSecretName)
if err != nil {
return nil, err
}
if secret == nil {
glog.Infof("No federation configuration in cluster %q", c)
return nil, nil
}
{
basicAuthData, err := o.findBasicAuth(secret)
if err != nil {
return nil, err
}
if basicAuthData == nil {
glog.Infof("No auth data in cluster %q", c)
return nil, nil
}
user := basicAuthData.FindUser(UserAdmin)
if user == nil {
glog.Infof("No auth data for user %q in cluster %q", UserAdmin, c)
return nil, nil
}
k.KubeUser = user.User
k.KubePassword = user.Secret
}
{
knownTokens, err := o.findKnownTokens(secret)
if err != nil {
return nil, err
}
if knownTokens == nil {
glog.Infof("No token data in cluster %q", c)
return nil, nil
}
user := knownTokens.FindUser(UserAdmin)
if user == nil {
glog.Infof("No token data for user %q in cluster %q", UserAdmin, c)
return nil, nil
}
k.KubeBearerToken = user.Secret
}
return k, nil
}
func (o*FederationConfiguration) findBasicAuth(secret *v1.Secret) (*AuthFile, error) {
var basicAuthData *AuthFile
var err error
if secret == nil {
return nil, nil
}
if secret.Data["basic-auth.csv"] != nil {
basicAuthData, err = ParseAuthFile(secret.Data["basic-auth.csv"])
if err != nil {
return nil, fmt.Errorf("error parsing auth file basic-auth.csv in secret %s/%s: %v", secret.Namespace, secret.Name, err)
}
}
return basicAuthData, nil
}
func (o*FederationConfiguration) findKnownTokens(secret *v1.Secret) (*AuthFile, error) {
var knownTokens *AuthFile
var err error
if secret == nil {
return nil, nil
}
if secret.Data["known-tokens.csv"] != nil {
knownTokens, err = ParseAuthFile(secret.Data["known-tokens.csv"])
if err != nil {
return nil, fmt.Errorf("error parsing auth file known-tokens.csv in secret %s/%s: %v", secret.Namespace, secret.Name, err)
}
}
return knownTokens, nil
}
func (o*FederationConfiguration) EnsureConfiguration(c *fi.Context) error {
caCert, _, err := c.Keystore.FindKeypair(fi.CertificateId_CA)
if err != nil {
return err
}
if caCert == nil {
return fmt.Errorf("cannot find CA certificate")
}
serverCert, serverKey, err := c.Keystore.FindKeypair(fi.StringValue(o.ApiserverKeypair.Name))
if err != nil {
return err
}
if serverCert == nil || serverKey == nil {
return fmt.Errorf("cannot find server keypair")
}
k8s := c.Target.(*kubernetes.KubernetesTarget).KubernetesClient
adminPassword := ""
adminToken := ""
_, err = mutateSecret(k8s, o.Namespace, o.ApiserverSecretName, func(s *v1.Secret) (*v1.Secret, error) {
basicAuthData, err := o.findBasicAuth(s)
if err != nil {
return nil, err
}
knownTokens, err := o.findKnownTokens(s)
if err != nil {
return nil, err
}
{
if basicAuthData == nil {
basicAuthData = &AuthFile{}
}
u := basicAuthData.FindUser(UserAdmin)
if u == nil {
s, err := fi.CreateSecret()
if err != nil {
return nil, err
}
err = basicAuthData.Add(&AuthFileLine{User: UserAdmin, Secret: string(s.Data), Role: "admin"})
if err != nil {
return nil, err
}
adminPassword = string(s.Data)
} else {
adminPassword = u.Secret
}
}
{
if knownTokens == nil {
knownTokens = &AuthFile{}
}
u := knownTokens.FindUser(UserAdmin)
if u == nil {
s, err := fi.CreateSecret()
if err != nil {
return nil, err
}
err = knownTokens.Add(&AuthFileLine{User: UserAdmin, Secret: string(s.Data), Role: "admin"})
if err != nil {
return nil, err
}
adminToken = string(s.Data)
} else {
adminToken = u.Secret
}
}
if s == nil {
s = &v1.Secret{}
s.Type = v1.SecretTypeOpaque
}
if s.Data == nil {
s.Data = make(map[string][]byte)
}
{
b, err := caCert.AsBytes()
if err != nil {
return nil, err
}
s.Data["ca.crt"] = b
}
{
b, err := serverCert.AsBytes()
if err != nil {
return nil, err
}
s.Data["server.cert"] = b
}
{
b, err := serverKey.AsBytes()
if err != nil {
return nil, err
}
s.Data["server.key"] = b
}
s.Data["basic-auth.csv"] = []byte(basicAuthData.Encode())
s.Data["known-tokens.csv"] = []byte(knownTokens.Encode())
return s, nil
})
// TODO: Prefer username / password or token?
user := kutil.KubectlUser{
Username:UserAdmin,
Password: adminPassword,
//Token: adminToken,
}
err = o.ensureSecretKubeconfig(c, caCert, user)
if err != nil {
return err
}
return nil
}
func (o*FederationConfiguration) ensureSecretKubeconfig(c *fi.Context, caCert *fi.Certificate, user kutil.KubectlUser) error {
k8s := c.Target.(*kubernetes.KubernetesTarget).KubernetesClient
_, err := mutateSecret(k8s, o.Namespace, o.KubeconfigSecretName, func(s *v1.Secret) (*v1.Secret, error) {
var kubeconfigData []byte
var err error
{
kubeconfig := &kutil.KubectlConfig{
ApiVersion: "v1",
Kind: "Config",
}
cluster := &kutil.KubectlClusterWithName{
Name: o.ApiserverServiceName,
Cluster: kutil.KubectlCluster{
Server: "https://" + o.ApiserverServiceName,
},
}
if caCert != nil {
caCertData, err := caCert.AsBytes()
if err != nil {
return nil, err
}
cluster.Cluster.CertificateAuthorityData = caCertData
}
kubeconfig.Clusters = append(kubeconfig.Clusters, cluster)
user := &kutil.KubectlUserWithName{
Name: o.ApiserverServiceName,
User: user,
}
kubeconfig.Users = append(kubeconfig.Users, user)
context := &kutil.KubectlContextWithName{
Name: o.ApiserverServiceName,
Context: kutil.KubectlContext{
Cluster: cluster.Name,
User: user.Name,
},
}
kubeconfig.CurrentContext = o.ApiserverServiceName
kubeconfig.Contexts = append(kubeconfig.Contexts, context)
kubeconfigData, err = kopsapi.ToYaml(kubeconfig)
if err != nil {
return nil, fmt.Errorf("error building kubeconfig: %v", err)
}
}
if s == nil {
s = &v1.Secret{}
s.Type = v1.SecretTypeOpaque
}
if s.Data == nil {
s.Data = make(map[string][]byte)
}
s.Data["kubeconfig"] = kubeconfigData
return s, nil
})
return err
}
func findSecret(k8s release_1_3.Interface, namespace, name string) (*v1.Secret, error) {
glog.V(2).Infof("querying k8s for secret %s/%s", namespace, name)
s, err := k8s.Core().Secrets(namespace).Get(name)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
} else {
return nil, fmt.Errorf("error reading secret %s/%s: %v", namespace, name, err)
}
}
return s, nil
}
func mutateSecret(k8s release_1_3.Interface, namespace string, name string, fn func(s *v1.Secret) (*v1.Secret, error)) (*v1.Secret, error) {
existing, err := findSecret(k8s, namespace, name)
if err != nil {
return nil, err
}
createObject := existing == nil
updated, err := fn(existing)
if err != nil {
return nil, err
}
updated.Namespace = namespace
updated.Name = name
if createObject {
glog.V(2).Infof("creating k8s secret %s/%s", namespace, name)
created, err := k8s.Core().Secrets(namespace).Create(updated)
if err != nil {
return nil, fmt.Errorf("error creating secret %s/%s: %v", namespace, name, err)
}
return created, nil
} else {
// TODO: Check dirty?
glog.V(2).Infof("updating k8s secret %s/%s", namespace, name)
updated, err := k8s.Core().Secrets(namespace).Update(updated)
if err != nil {
return nil, fmt.Errorf("error updating secret %s/%s: %v", namespace, name, err)
}
return updated, nil
}
}

View File

@ -0,0 +1,54 @@
package federation
import (
"k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/errors"
"fmt"
"k8s.io/kubernetes/pkg/api/v1"
)
func findNamespace(k8s federation_release_1_4.Interface, name string) (*v1.Namespace, error) {
glog.V(2).Infof("querying k8s for federation Namespace %s", name)
c, err := k8s.Core().Namespaces().Get(name)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
} else {
return nil, fmt.Errorf("error reading federation Namespace %s: %v", name, err)
}
}
return c, nil
}
func mutateNamespace(k8s federation_release_1_4.Interface, name string, fn func(s *v1.Namespace) (*v1.Namespace, error)) (*v1.Namespace, error) {
existing, err := findNamespace(k8s, name)
if err != nil {
return nil, err
}
createObject := existing == nil
updated, err := fn(existing)
if err != nil {
return nil, err
}
updated.Name = name
if createObject {
glog.V(2).Infof("creating federation Namespace %s", name)
created, err := k8s.Core().Namespaces().Create(updated)
if err != nil {
return nil, fmt.Errorf("error creating federation Namespace %s: %v", name, err)
}
return created, nil
} else {
glog.V(2).Infof("updating federation Namespace %s", name)
created, err := k8s.Core().Namespaces().Update(updated)
if err != nil {
return nil, fmt.Errorf("error updating federation Namespace %s: %v", name, err)
}
return created, nil
}
}

1
federation/model/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bindata.go

View File

@ -0,0 +1,153 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{.FEDERATION_NAMESPACE}}
---
apiVersion: v1
kind: Service
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
annotations:
dns.alpha.kubernetes.io/external: {{.EXTERNAL_HOSTNAME}}
spec:
type: LoadBalancer
selector:
app: federated-cluster
module: federation-apiserver
ports:
- name: https
protocol: TCP
port: 443
targetPort: 443
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}-etcd-claim
annotations:
volume.alpha.kubernetes.io/storage-class: "yes"
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
template:
metadata:
name: federation-apiserver
labels:
app: federated-cluster
module: federation-apiserver
spec:
containers:
- name: apiserver
image: {{.FEDERATION_APISERVER_IMAGE_REPO}}:{{.FEDERATION_APISERVER_IMAGE_TAG}}
command:
- /hyperkube
- federation-apiserver
- --bind-address=0.0.0.0
- --etcd-servers=http://localhost:2379
- --service-cluster-ip-range={{.FEDERATION_SERVICE_CIDR}}
- --secure-port=443
- --external-hostname={{.EXTERNAL_HOSTNAME}}
- --client-ca-file=/srv/kubernetes/ca.crt
- --basic-auth-file=/srv/kubernetes/basic-auth.csv
- --tls-cert-file=/srv/kubernetes/server.cert
- --tls-private-key-file=/srv/kubernetes/server.key
- --admission-control={{.FEDERATION_ADMISSION_CONTROL}}
- --token-auth-file=/srv/kubernetes/known-tokens.csv
ports:
- containerPort: 443
name: https
- containerPort: 8080
name: local
volumeMounts:
- name: federation-apiserver-secrets
mountPath: /srv/kubernetes/
readOnly: true
- name: etcd
image: quay.io/coreos/etcd:v2.3.3
command:
- /etcd
- --data-dir
- /var/etcd/data
volumeMounts:
- mountPath: /var/etcd
name: varetcd
volumes:
- name: federation-apiserver-secrets
secret:
secretName: federation-apiserver-secrets
- name: varetcd
persistentVolumeClaim:
claimName: {{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}-etcd-claim
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{.FEDERATION_CONTROLLER_MANAGER_DEPLOYMENT_NAME}}
namespace: {{.FEDERATION_NAMESPACE}}
labels:
app: federated-cluster
spec:
template:
metadata:
name: federation-controller-manager
labels:
app: federated-cluster
module: federation-controller-manager
spec:
volumes:
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
containers:
- name: controller-manager
volumeMounts:
- name: ssl-certs
readOnly: true
mountPath: /etc/ssl/certs
image: {{.FEDERATION_CONTROLLER_MANAGER_IMAGE_REPO}}:{{.FEDERATION_CONTROLLER_MANAGER_IMAGE_TAG}}
command:
- /hyperkube
- federation-controller-manager
- --master=https://{{.FEDERATION_APISERVER_DEPLOYMENT_NAME}}:443
- --dns-provider={{.FEDERATION_DNS_PROVIDER}}
- --dns-provider-config={{.FEDERATION_DNS_PROVIDER_CONFIG}}
- --federation-name={{.FEDERATION_NAME}}
- --zone-name={{.DNS_ZONE_NAME}}
ports:
- containerPort: 443
name: https
- containerPort: 8080
name: local
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace

View File

@ -0,0 +1,70 @@
package kubernetes
import (
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/pkg/client/simple"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/kutil"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
"fmt"
)
type KubernetesTarget struct {
//kubectlContext string
//keystore *k8sapi.KubernetesKeystore
KubernetesClient release_1_3.Interface
cluster *kopsapi.Cluster
}
func NewKubernetesTarget(clientset simple.Clientset, keystore fi.Keystore, cluster *kopsapi.Cluster) (*KubernetesTarget, error) {
b := &kutil.CreateKubecfg{
ContextName: cluster.Name,
KeyStore: keystore,
SecretStore: nil,
KubeMasterIP: cluster.Spec.MasterPublicName,
}
kubeconfig, err := b.ExtractKubeconfig()
if err != nil {
return nil, fmt.Errorf("error building credentials for cluster %q: %v", cluster.Name, err)
}
clientConfig, err := kubeconfig.BuildRestConfig()
if err != nil {
return nil, fmt.Errorf("error building configuration for cluster %q: %v", cluster.Name, err)
}
k8sClient, err := release_1_3.NewForConfig(clientConfig)
if err != nil {
return nil, fmt.Errorf("cannot build k8s client: %v", err)
}
target := &KubernetesTarget{
cluster: cluster,
KubernetesClient: k8sClient,
}
return target, nil
}
var _ fi.Target = &KubernetesTarget{}
func (t *KubernetesTarget) Finish(taskMap map[string]fi.Task) error {
return nil
}
func (t *KubernetesTarget) Apply(manifest []byte) error {
context := t.cluster.Name
// Would be nice if we could use RunApply from kubectl's code directly...
// ... but that seems really hard
kubectl := &kutil.Kubectl{}
err := kubectl.Apply(context, manifest)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,60 @@
package tasks
import (
"fmt"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/federation/targets/kubernetes"
"k8s.io/kubernetes/pkg/util/validation/field"
)
//go:generate fitask -type=KubernetesResource
type KubernetesResource struct {
Name *string
Manifest *fi.ResourceHolder
}
//var _ fi.HasCheckExisting = &KubernetesResource{}
//
//// It's important always to check for the existing key, so we don't regenerate keys e.g. on terraform
//func (e *KubernetesResource) CheckExisting(c *fi.Context) bool {
// return true
//}
func (e *KubernetesResource) Find(c *fi.Context) (*KubernetesResource, error) {
// We always apply...
// TODO: parse the existing kubectl apply annotations
return nil, nil
}
func (e *KubernetesResource) Run(c *fi.Context) error {
return fi.DefaultDeltaRunMethod(e, c)
}
func (s *KubernetesResource) CheckChanges(a, e, changes *KubernetesResource) error {
return nil
}
func (_ *KubernetesResource) Render(c *fi.Context, a, e, changes *KubernetesResource) error {
name := fi.StringValue(e.Name)
if name == "" {
return field.Required(field.NewPath("Name"), "")
}
target, ok := c.Target.(*kubernetes.KubernetesTarget)
if !ok {
return fmt.Errorf("Expected KubernetesTarget, got %T", c.Target)
}
manifestData, err := e.Manifest.AsBytes()
if err != nil {
return fmt.Errorf("error rending manifest template: %v", err)
}
err = target.Apply(manifestData)
if err != nil {
return fmt.Errorf("error applying manifest %q: %v", name, err)
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/util/pkg/vfs"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"net/url"
)
@ -15,7 +14,7 @@ const DefaultChannel = "stable"
type Channel struct {
unversioned.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty"`
Spec ChannelSpec `json:"spec,omitempty"`
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/golang/glog"
"k8s.io/kops/util/pkg/vfs"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"net"
"strconv"
@ -14,7 +13,7 @@ import (
type Cluster struct {
unversioned.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty"`
Spec ClusterSpec `json:"spec,omitempty"`
}

13
pkg/apis/kops/common.go Normal file
View File

@ -0,0 +1,13 @@
package kops
import (
"k8s.io/kubernetes/pkg/runtime"
)
// ApiType adds a Validate() method to runtime.Object
// TODO: use the real Validation infrastructure here
type ApiType interface {
runtime.Object
Validate() error
}

View File

@ -1,7 +1,6 @@
package kops
import (
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
)
@ -10,7 +9,7 @@ import (
type KubeletConfig struct {
unversioned.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty"`
Spec ClusterSpec `json:"spec,omitempty"`
}

21
pkg/apis/kops/doc.go Normal file
View File

@ -0,0 +1,21 @@
/*
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.
*/
// +k8s:deepcopy-gen=package,register
// +k8s:conversion-gen=k8s.io/kops/pkg/apis/kops
// +groupName=kops.k8s.io
package kops // import "k8s.io/kops/pkg/apis/kops"

View File

@ -0,0 +1,32 @@
package kops
import (
"k8s.io/kubernetes/pkg/api/unversioned"
)
// Federation represents a federated set of kubernetes clusters
type Federation struct {
unversioned.TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
Spec FederationSpec `json:"spec,omitempty"`
}
type FederationSpec struct {
Controllers []string `json:"controllers,omitempty"`
Members []string `json:"members,omitempty"`
DNSName string `json:"dnsName,omitempty"`
}
type FederationList struct {
unversioned.TypeMeta `json:",inline"`
unversioned.ListMeta `json:"metadata,omitempty"`
Items []Federation `json:"items"`
}
func (f *Federation) Validate() error {
return nil
}

109
pkg/apis/kops/hack.go Normal file
View File

@ -0,0 +1,109 @@
package kops
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
)
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name is unique within a namespace. Name is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
Name string `json:"name,omitempty"`
// GenerateName indicates that the name should be made unique by the server prior to persisting
// it. A non-empty value for the field indicates the name will be made unique (and the name
// returned to the client will be different than the name passed). The value of this field will
// be combined with a unique suffix on the server if the Name field has not been provided.
// The provided value must be valid within the rules for Name, and may be truncated by the length
// of the suffix required to make the value unique on the server.
//
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
// generated name exists - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
GenerateName string `json:"generateName,omitempty"`
// Namespace defines the space within which name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
Namespace string `json:"namespace,omitempty"`
// SelfLink is a URL representing this object.
SelfLink string `json:"selfLink,omitempty"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
UID types.UID `json:"uid,omitempty"`
// An opaque value that represents the version of this resource. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and values may only be valid for a particular
// resource or set of resources. Only servers will generate resource versions.
ResourceVersion string `json:"resourceVersion,omitempty"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
CreationTimestamp unversioned.Time `json:"creationTimestamp,omitempty"`
// DeletionTimestamp is the time after which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
DeletionTimestamp *unversioned.Time `json:"deletionTimestamp,omitempty"`
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
// was requested. Represents the most recent grace period, and may only be shortened once set.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
// Labels are key value pairs that may be used to scope and select individual resources.
// Label keys are of the form:
// label-key ::= prefixed-name | name
// prefixed-name ::= prefix '/' name
// prefix ::= DNS_SUBDOMAIN
// name ::= DNS_LABEL
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
// to the user. Other system components that wish to use labels must specify a prefix. The
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
Labels map[string]string `json:"labels,omitempty"`
// Annotations are unstructured key value data stored with a resource that may be set by
// external tooling. They are not queryable and should be preserved when modifying
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
// List of objects depended by this object. If ALL objects in the list have
// been deleted, this object will be garbage collected. If this object is managed by a controller,
// then an entry in this list will point to this controller, with the controller field set to true.
// There cannot be more than one managing controller.
OwnerReferences []api.OwnerReference `json:"ownerReferences,omitempty"`
// Must be empty before the object is deleted from the registry. Each entry
// is an identifier for the responsible component that will remove the entry
// from the list. If the deletionTimestamp of the object is non-nil, entries
// in this list can only be removed.
Finalizers []string `json:"finalizers,omitempty"`
// The name of the cluster which the object belongs to.
// This is used to distinguish resources with same name and namespace in different clusters.
// This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.
ClusterName string `json:"clusterName,omitempty"`
}

View File

@ -3,7 +3,6 @@ package kops
import (
"fmt"
"github.com/golang/glog"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/validation/field"
)
@ -11,7 +10,7 @@ import (
// InstanceGroup represents a group of instances (either nodes or masters) with the same configuration
type InstanceGroup struct {
unversioned.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty"`
Spec InstanceGroupSpec `json:"spec,omitempty"`
}

View File

@ -46,6 +46,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Cluster{},
&InstanceGroup{},
&Federation{},
)
return nil
}
@ -55,4 +56,7 @@ func (obj *Cluster) GetObjectKind() unversioned.ObjectKind {
}
func (obj *InstanceGroup) GetObjectKind() unversioned.ObjectKind {
return &obj.TypeMeta
}
func (obj *Federation) GetObjectKind() unversioned.ObjectKind {
return &obj.TypeMeta
}

View File

@ -3,4 +3,5 @@ package simple
type Clientset interface {
Clusters() ClusterInterface
InstanceGroups(cluster string) InstanceGroupInterface
Federations() FederationInterface
}

View File

@ -0,0 +1,21 @@
package simple
import (
api "k8s.io/kops/pkg/apis/kops"
k8sapi "k8s.io/kubernetes/pkg/api"
)
// FederationInterface has methods to work with Federation resources.
type FederationInterface interface {
Create(*api.Federation) (*api.Federation, error)
Update(*api.Federation) (*api.Federation, error)
//UpdateStatus(*api.Federation) (*api.Federation, error)
Delete(name string, options *k8sapi.DeleteOptions) error
//DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error
Get(name string) (*api.Federation, error)
List(opts k8sapi.ListOptions) (*api.FederationList, error)
//Watch(opts k8sapi.ListOptions) (watch.Interface, error)
//Patch(name string, pt api.PatchType, data []byte, subresources ...string) (result *api.Federation, err error)
//FederationExpansion
}

View File

@ -2,7 +2,6 @@ package vfsclientset
import (
"k8s.io/kops/pkg/client/simple"
"github.com/golang/glog"
"k8s.io/kops/util/pkg/vfs"
)
@ -17,14 +16,11 @@ func (c *VFSClientset) Clusters() simple.ClusterInterface {
}
func (c *VFSClientset) InstanceGroups(clusterName string) simple.InstanceGroupInterface {
if clusterName == "" {
glog.Fatalf("clusterName is required")
}
clusterBasePath := c.basePath.Join(clusterName)
return newInstanceGroupVFS(c, clusterName)
}
return &InstanceGroupVFS{
clusterBasePath: clusterBasePath,
}
func (c *VFSClientset) Federations() simple.FederationInterface {
return newFederationVFS(c)
}
func NewVFSClientset(basePath vfs.Path) (simple.Clientset) {

View File

@ -0,0 +1,199 @@
package vfsclientset
import (
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"time"
"fmt"
"os"
"k8s.io/kops/util/pkg/vfs"
"k8s.io/kops/pkg/apis/kops/registry"
"reflect"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
"bytes"
"k8s.io/kubernetes/pkg/runtime"
)
type commonVFS struct {
key string
basePath vfs.Path
encoder runtime.Encoder
}
func (c*commonVFS) init(key string, basePath vfs.Path, storeVersion runtime.GroupVersioner) {
yamlSerde := json.NewYAMLSerializer(json.DefaultMetaFactory, k8sapi.Scheme, k8sapi.Scheme)
encoder := k8sapi.Codecs.EncoderForVersion(yamlSerde, storeVersion)
c.key = key
c.basePath = basePath
c.encoder = encoder
}
func (c *commonVFS) get(name string, dest interface{}) (bool, error) {
err := registry.ReadConfig(c.basePath.Join(name), dest)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("error reading %s %q: %v", c.key, name, err)
}
return true, nil
}
func (c *commonVFS) list(items interface{}, options k8sapi.ListOptions) (interface{}, error) {
return c.readAll(items)
}
func (c *commonVFS) create(i api.ApiType) (error) {
objectMeta, err := k8sapi.ObjectMetaFor(i)
if err != nil {
return err
}
err = i.Validate()
if err != nil {
return err
}
if objectMeta.CreationTimestamp.IsZero() {
objectMeta.CreationTimestamp = unversioned.NewTime(time.Now().UTC())
}
err = c.writeConfig(c.basePath.Join(objectMeta.Name), i, vfs.WriteOptionCreate)
if err != nil {
if os.IsExist(err) {
return err
}
return fmt.Errorf("error writing %s: %v", c.key, err)
}
return nil
}
func (c*commonVFS) serialize(o runtime.Object) ([]byte, error) {
var b bytes.Buffer
err := c.encoder.Encode(o, &b)
if err != nil {
return nil, fmt.Errorf("error encoding object: %v", err)
}
return b.Bytes(), nil
}
func (c*commonVFS) writeConfig(configPath vfs.Path, o runtime.Object, writeOptions ...vfs.WriteOption) error {
data, err := c.serialize(o)
if err != nil {
return fmt.Errorf("error marshalling object: %v", err)
}
create := false
for _, writeOption := range writeOptions {
switch writeOption {
case vfs.WriteOptionCreate:
create = true
case vfs.WriteOptionOnlyIfExists:
_, err = configPath.ReadFile()
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("cannot update configuration file %s: does not exist", configPath)
}
return fmt.Errorf("error checking if configuration file %s exists already: %v", configPath, err)
}
default:
return fmt.Errorf("unknown write option: %q", writeOption)
}
}
if create {
err = configPath.CreateFile(data)
} else {
err = configPath.WriteFile(data)
}
if err != nil {
if create && os.IsExist(err) {
return err
}
return fmt.Errorf("error writing configuration file %s: %v", configPath, err)
}
return nil
}
func (c *commonVFS) update(i api.ApiType) (error) {
objectMeta, err := k8sapi.ObjectMetaFor(i)
if err != nil {
return err
}
err = i.Validate()
if err != nil {
return err
}
if objectMeta.CreationTimestamp.IsZero() {
objectMeta.CreationTimestamp = unversioned.NewTime(time.Now().UTC())
}
err = registry.WriteConfig(c.basePath.Join(objectMeta.Name), i, vfs.WriteOptionOnlyIfExists)
if err != nil {
return fmt.Errorf("error writing %s: %v", c.key, err)
}
return nil
}
func (c *commonVFS) delete(name string, options *k8sapi.DeleteOptions) (error) {
p := c.basePath.Join(name)
err := p.Remove()
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("error deleting %s configuration %q: %v", c.key, name, err)
}
return nil
}
func (c *commonVFS) listNames() ([]string, error) {
keys, err := listChildNames(c.basePath)
if err != nil {
return nil, fmt.Errorf("error listing %s in state store: %v", c.key, err)
}
return keys, nil
}
func (c *commonVFS) readAll(items interface{}) (interface{}, error) {
sliceValue := reflect.ValueOf(items)
sliceType := reflect.TypeOf(items)
if sliceType.Kind() != reflect.Slice {
return nil, fmt.Errorf("expected slice, got %T", items)
}
elemType := sliceType.Elem()
names, err := c.listNames()
if err != nil {
return nil, err
}
for _, name := range names {
elemValue := reflect.New(elemType)
found, err := c.get(name, elemValue.Interface())
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("%s was listed, but then not found %q", c.key, name)
}
sliceValue = reflect.Append(sliceValue, elemValue.Elem())
}
return sliceValue.Interface(), nil
}

View File

@ -0,0 +1,64 @@
package vfsclientset
import (
"k8s.io/kops/pkg/client/simple"
api "k8s.io/kops/pkg/apis/kops"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kops/pkg/apis/kops/v1alpha1"
)
type FederationVFS struct {
commonVFS
}
func newFederationVFS(c *VFSClientset) *FederationVFS {
key := "_federation"
r := &FederationVFS{}
r.init(key, c.basePath.Join(key), v1alpha1.SchemeGroupVersion)
return r
}
var _ simple.FederationInterface = &FederationVFS{}
func (c *FederationVFS) Get(name string) (*api.Federation, error) {
v := &api.Federation{}
found, err := c.get(name, v)
if err != nil {
return nil ,err
}
if !found {
return nil, nil
}
return v, nil
}
func (c *FederationVFS) List(options k8sapi.ListOptions) (*api.FederationList, error) {
list := &api.FederationList{}
items, err := c.list(list.Items, options)
if err != nil {
return nil, err
}
list.Items = items.([]api.Federation)
return list, nil
}
func (c *FederationVFS) Create(g *api.Federation) (*api.Federation, error) {
err := c.create(g)
if err != nil {
return nil, err
}
return g, nil
}
func (c *FederationVFS) Update(g *api.Federation) (*api.Federation, error) {
err := c.update(g)
if err != nil {
return nil, err
}
return g, nil
}
func (c *FederationVFS) Delete(name string, options *k8sapi.DeleteOptions) (error) {
return c.delete(name, options)
}

View File

@ -1,120 +1,69 @@
package vfsclientset
import (
"k8s.io/kops/pkg/client/simple"
api "k8s.io/kops/pkg/apis/kops"
k8sapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"time"
"fmt"
"os"
"github.com/golang/glog"
"k8s.io/kops/util/pkg/vfs"
"k8s.io/kops/pkg/apis/kops/registry"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/v1alpha1"
"k8s.io/kops/pkg/client/simple"
k8sapi "k8s.io/kubernetes/pkg/api"
)
type InstanceGroupVFS struct {
clusterBasePath vfs.Path
commonVFS
}
var _ simple.ClusterInterface = &ClusterVFS{}
func newInstanceGroupVFS(c *VFSClientset, clusterName string) *InstanceGroupVFS {
if clusterName == "" {
glog.Fatalf("clusterName is required")
}
key := "instancegroup"
r := &InstanceGroupVFS{}
r.init(key, c.basePath.Join(clusterName, key), v1alpha1.SchemeGroupVersion)
return r
}
var _ simple.InstanceGroupInterface = &InstanceGroupVFS{}
func (c *InstanceGroupVFS) Get(name string) (*api.InstanceGroup, error) {
group := &api.InstanceGroup{}
err := registry.ReadConfig(c.clusterBasePath.Join("instancegroup", name), group)
v := &api.InstanceGroup{}
found, err := c.get(name, v)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("error reading InstanceGroup %q: %v", name, err)
return nil, err
}
return group, nil
if !found {
return nil, nil
}
return v, nil
}
func (c *InstanceGroupVFS) List(options k8sapi.ListOptions) (*api.InstanceGroupList, error) {
items, err := c.readAll()
list := &api.InstanceGroupList{}
items, err := c.list(list.Items, options)
if err != nil {
return nil, err
}
ret := &api.InstanceGroupList{}
for _, i := range items {
ret.Items = append(ret.Items, *i)
}
return ret, nil
list.Items = items.([]api.InstanceGroup)
return list, nil
}
func (c *InstanceGroupVFS) Create(g *api.InstanceGroup) (*api.InstanceGroup, error) {
err := g.Validate()
err := c.create(g)
if err != nil {
return nil, err
}
if g.CreationTimestamp.IsZero() {
g.CreationTimestamp = unversioned.NewTime(time.Now().UTC())
}
err = registry.WriteConfig(c.clusterBasePath.Join("instancegroup", g.Name), g, vfs.WriteOptionCreate)
if err != nil {
return nil, fmt.Errorf("error writing InstanceGroup: %v", err)
}
return g, nil
}
func (c *InstanceGroupVFS) Update(g *api.InstanceGroup) (*api.InstanceGroup, error) {
err := g.Validate()
err := c.update(g)
if err != nil {
return nil, err
}
err = registry.WriteConfig(c.clusterBasePath.Join("instancegroup", g.Name), g, vfs.WriteOptionOnlyIfExists)
if err != nil {
return nil, fmt.Errorf("error writing InstanceGroup %q: %v", g.Name, err)
}
return g, nil
}
func (c *InstanceGroupVFS) Delete(name string, options *k8sapi.DeleteOptions) (error) {
p := c.clusterBasePath.Join("instancegroup", name)
err := p.Remove()
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("error deleting instancegroup configuration %q: %v", name, err)
}
return nil
func (c *InstanceGroupVFS) Delete(name string, options *k8sapi.DeleteOptions) error {
return c.delete(name, options)
}
func (c *InstanceGroupVFS) listNames() ([]string, error) {
keys, err := listChildNames(c.clusterBasePath.Join("instancegroup"))
if err != nil {
return nil, fmt.Errorf("error listing instancegroups in state store: %v", err)
}
return keys, nil
}
func (r *InstanceGroupVFS) readAll() ([]*api.InstanceGroup, error) {
names, err := r.listNames()
if err != nil {
return nil, err
}
var instancegroups []*api.InstanceGroup
for _, name := range names {
g, err := r.Get(name)
if err != nil {
return nil, err
}
if g == nil {
glog.Warningf("InstanceGroup was listed, but then not found %q", name)
}
instancegroups = append(instancegroups, g)
}
return instancegroups, nil
}

View File

@ -0,0 +1,70 @@
package k8sapi
import (
"fmt"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kubernetes/pkg/api/v1"
)
// KeypairSecret is a wrapper around a k8s Secret object that holds a TLS keypair
type KeypairSecret struct {
Namespace string
Name string
Certificate *fi.Certificate
PrivateKey *fi.PrivateKey
}
// ParseKeypairSecret parses the secret object, decoding the certificate & private-key, if present
func ParseKeypairSecret(secret *v1.Secret) (*KeypairSecret, error) {
k := &KeypairSecret{}
k.Namespace = secret.Namespace
k.Name = secret.Name
certData := secret.Data[v1.TLSCertKey]
if certData != nil {
cert, err := fi.LoadPEMCertificate(certData)
if err != nil {
return nil, fmt.Errorf("error parsing certificate in %s/%s: %q", k.Namespace, k.Name, err)
}
k.Certificate = cert
}
keyData := secret.Data[v1.TLSPrivateKeyKey]
if keyData != nil {
key, err := fi.ParsePEMPrivateKey(keyData)
if err != nil {
return nil, fmt.Errorf("error parsing key in %s/%s: %q", k.Namespace, k.Name, err)
}
k.PrivateKey = key
}
return k, nil
}
// Encode maps a KeypairSecret into a k8s Secret
func (k *KeypairSecret) Encode() (*v1.Secret, error) {
secret := &v1.Secret{}
secret.Namespace = k.Namespace
secret.Name = k.Name
secret.Type = v1.SecretTypeTLS
secret.Data = make(map[string][]byte)
if k.Certificate != nil {
data, err := k.Certificate.AsBytes()
if err != nil {
return nil, err
}
secret.Data[v1.TLSCertKey] = data
}
if k.PrivateKey != nil {
data, err := k.PrivateKey.AsBytes()
if err != nil {
return nil, err
}
secret.Data[v1.TLSPrivateKeyKey] = data
}
return secret, nil
}

View File

@ -0,0 +1,134 @@
package k8sapi
import (
crypto_rand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
"math/big"
"time"
)
type KubernetesKeystore struct {
client release_1_3.Interface
namespace string
//mutex sync.Mutex
//cacheCaCertificates *certificates
//cacheCaPrivateKeys *privateKeys
}
var _ fi.Keystore = &KubernetesKeystore{}
func NewKubernetesKeystore(client release_1_3.Interface, namespace string) fi.Keystore {
c := &KubernetesKeystore{
client: client,
namespace: namespace,
}
return c
}
func (c *KubernetesKeystore) issueCert(id string, serial *big.Int, privateKey *fi.PrivateKey, template *x509.Certificate) (*fi.Certificate, error) {
glog.Infof("Issuing new certificate: %q", id)
template.SerialNumber = serial
caCert, caKey, err := c.FindKeypair(fi.CertificateId_CA)
if err != nil {
return nil, err
}
if caCert == nil || caCert.Certificate == nil || caKey == nil || caKey.Key == nil {
return nil, fmt.Errorf("CA keypair was not found; cannot issue certificates")
}
cert, err := fi.SignNewCertificate(privateKey, template, caCert.Certificate, caKey)
if err != nil {
return nil, err
}
err = c.StoreKeypair(id, cert, privateKey)
if err != nil {
return nil, err
}
return cert, nil
}
func (c *KubernetesKeystore) findSecret(id string) (*v1.Secret, error) {
secret, err := c.client.Core().Secrets(c.namespace).Get(id)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, fmt.Errorf("error reading secret %s/%s from kubernetes: %v", c.namespace, id, err)
}
return secret, nil
}
func (c *KubernetesKeystore) FindKeypair(id string) (*fi.Certificate, *fi.PrivateKey, error) {
secret, err := c.findSecret(id)
if err != nil {
return nil, nil, err
}
if secret == nil {
return nil, nil, nil
}
keypair, err := ParseKeypairSecret(secret)
if err != nil {
return nil, nil, fmt.Errorf("error parsing secret %s/%s from kubernetes: %v", c.namespace, id, err)
}
return keypair.Certificate, keypair.PrivateKey, nil
}
func (c *KubernetesKeystore) CreateKeypair(id string, template *x509.Certificate) (*fi.Certificate, *fi.PrivateKey, error) {
t := time.Now().UnixNano()
serial := fi.BuildPKISerial(t)
rsaKey, err := rsa.GenerateKey(crypto_rand.Reader, 2048)
if err != nil {
return nil, nil, fmt.Errorf("error generating RSA private key: %v", err)
}
privateKey := &fi.PrivateKey{Key: rsaKey}
cert, err := c.issueCert(id, serial, privateKey, template)
if err != nil {
return nil, nil, err
}
return cert, privateKey, nil
}
func (c *KubernetesKeystore) StoreKeypair(id string, cert *fi.Certificate, privateKey *fi.PrivateKey) error {
keypair := &KeypairSecret{
Namespace: c.namespace,
Name: id,
Certificate: cert,
PrivateKey: privateKey,
}
secret, err := keypair.Encode()
createdSecret, err := c.client.Core().Secrets(c.namespace).Create(secret)
if err != nil {
return fmt.Errorf("error creating secret %s/%s: %v", secret.Namespace, secret.Name, err)
}
created, err := ParseKeypairSecret(createdSecret)
if err != nil {
return fmt.Errorf("created secret did not round-trip (%s/%s): %v", c.namespace, id, err)
}
if created == nil || created.Certificate == nil || created.PrivateKey == nil {
return fmt.Errorf("created secret did not round-trip (%s/%s): could not read back", c.namespace, id)
}
return err
}