mirror of https://github.com/kubernetes/kops.git
Merge pull request #631 from justinsb/move_apis_3
Experimental support for federation
This commit is contained in:
commit
591a85056a
14
Makefile
14
Makefile
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
kind: Cluster
|
||||
apiVersion: kops/v1alpha1
|
||||
metadata:
|
||||
name: control1.federation.com
|
||||
spec:
|
||||
channel: stable
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
kind: Federation
|
||||
apiVersion: kops/v1alpha1
|
||||
metadata:
|
||||
name: federation
|
||||
spec:
|
||||
dnsName: test.federation.com
|
||||
controllers:
|
||||
- control1.federation.com
|
||||
- control2.federation.com
|
||||
|
|
@ -24,4 +24,4 @@ func apply() error {
|
|||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
bindata.go
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@ package simple
|
|||
type Clientset interface {
|
||||
Clusters() ClusterInterface
|
||||
InstanceGroups(cluster string) InstanceGroupInterface
|
||||
Federations() FederationInterface
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue