Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy external CCMs #364

Merged
merged 4 commits into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ issues:
- "don't use underscores in Go names; func SetDefaults_ClusterNetwork should be SetDefaultsClusterNetwork"
- "don't use underscores in Go names; func SetDefaults_Features should be SetDefaultsFeatures"
- "don't use underscores in Go names; func SetDefaults_MachineController should be SetDefaultsMachineController"
- "G101: Potential hardcoded credentials"
49 changes: 33 additions & 16 deletions pkg/config/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ import (
"github.com/pkg/errors"
)

// ENV variable names with credential in them that machine-controller expects to see
const (
AWSAccessKeyID = "AWS_ACCESS_KEY_ID"
AWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
DigitalOceanTokenKey = "DO_TOKEN"
GoogleServiceAccountKey = "GOOGLE_SERVICE_ACCOUNT"
HetznerTokenKey = "HZ_TOKEN"
OpenStackAuthURL = "OS_AUTH_URL"
OpenStackDomainName = "OS_DOMAIN_NAME"
OpenStackPassword = "OS_PASSWORD"
OpenStackTenantName = "OS_TENANT_NAME"
OpenStackUserName = "OS_USER_NAME"
VSphereAddress = "VSPHERE_ADDRESS"
VSpherePasswords = "VSPHERE_PASSWORD"
VSphereUsername = "VSPHERE_USERNAME"
)

// Cluster describes our entire configuration.
type Cluster struct {
Name string `json:"name"`
Expand Down Expand Up @@ -462,8 +479,8 @@ func (p ProviderName) ProviderCredentials() (map[string]string, error) {
return nil, err
}
if envCreds.AccessKeyID != "" && envCreds.SecretAccessKey != "" {
creds["AWS_ACCESS_KEY_ID"] = envCreds.AccessKeyID
creds["AWS_SECRET_ACCESS_KEY"] = envCreds.SecretAccessKey
creds[AWSAccessKeyID] = envCreds.AccessKeyID
creds[AWSSecretAccessKey] = envCreds.SecretAccessKey
return creds, nil
}

Expand All @@ -474,44 +491,44 @@ func (p ProviderName) ProviderCredentials() (map[string]string, error) {
return nil, err
}
if configCreds.AccessKeyID != "" && configCreds.SecretAccessKey != "" {
creds["AWS_ACCESS_KEY_ID"] = configCreds.AccessKeyID
creds["AWS_SECRET_ACCESS_KEY"] = configCreds.SecretAccessKey
creds[AWSAccessKeyID] = configCreds.AccessKeyID
creds[AWSSecretAccessKey] = configCreds.SecretAccessKey
return creds, nil
}

return nil, errors.New("error parsing aws credentials")
case ProviderNameOpenStack:
return parseCredentialVariables([]ProviderEnvironmentVariable{
{Name: "OS_AUTH_URL"},
{Name: "OS_USERNAME", MachineControllerName: "OS_USER_NAME"},
{Name: "OS_PASSWORD"},
{Name: "OS_DOMAIN_NAME"},
{Name: "OS_TENANT_NAME"},
{Name: OpenStackAuthURL},
{Name: "OS_USERNAME", MachineControllerName: OpenStackUserName},
{Name: OpenStackPassword},
{Name: OpenStackDomainName},
{Name: OpenStackTenantName},
})
case ProviderNameHetzner:
return parseCredentialVariables([]ProviderEnvironmentVariable{
{Name: "HCLOUD_TOKEN", MachineControllerName: "HZ_TOKEN"},
{Name: "HCLOUD_TOKEN", MachineControllerName: HetznerTokenKey},
})
case ProviderNameDigitalOcean:
return parseCredentialVariables([]ProviderEnvironmentVariable{
{Name: "DIGITALOCEAN_TOKEN", MachineControllerName: "DO_TOKEN"},
{Name: "DIGITALOCEAN_TOKEN", MachineControllerName: DigitalOceanTokenKey},
})
case ProviderNameGCE:
gsa, err := parseCredentialVariables([]ProviderEnvironmentVariable{
{Name: "GOOGLE_CREDENTIALS", MachineControllerName: "GOOGLE_SERVICE_ACCOUNT"},
{Name: "GOOGLE_CREDENTIALS", MachineControllerName: GoogleServiceAccountKey},
})
if err != nil {
return nil, errors.WithStack(err)
}
// encode it before sending to secret to be consumed by
// machine-controller, as machine-controller assumes it will be double encoded
gsa["GOOGLE_SERVICE_ACCOUNT"] = base64.StdEncoding.EncodeToString([]byte(gsa["GOOGLE_SERVICE_ACCOUNT"]))
gsa[GoogleServiceAccountKey] = base64.StdEncoding.EncodeToString([]byte(gsa[GoogleServiceAccountKey]))
return gsa, nil
case ProviderNameVSphere:
return parseCredentialVariables([]ProviderEnvironmentVariable{
{Name: "VSPHERE_ADDRESS"},
{Name: "VSPHERE_USERNAME"},
{Name: "VSPHERE_PASSWORD"},
{Name: VSphereAddress},
{Name: VSphereUsername},
{Name: VSpherePasswords},
})
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/installer/installation/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/kubermatic/kubeone/pkg/certificate"
"github.com/kubermatic/kubeone/pkg/features"
"github.com/kubermatic/kubeone/pkg/task"
"github.com/kubermatic/kubeone/pkg/templates/externalccm"
"github.com/kubermatic/kubeone/pkg/templates/machinecontroller"
"github.com/kubermatic/kubeone/pkg/util"
)
Expand All @@ -42,9 +43,10 @@ func Install(ctx *util.Context) error {
{Fn: saveKubeconfig, ErrMsg: "unable to save kubeconfig to the local machine", Retries: 3},
{Fn: util.BuildKubernetesClientset, ErrMsg: "unable to build kubernetes clientset", Retries: 3},
{Fn: features.Activate, ErrMsg: "unable to activate features"},
{Fn: applyCanalCNI, ErrMsg: "failed to install cni plugin canal", Retries: 3},
{Fn: externalccm.Ensure, ErrMsg: "failed to install external CCM"},
{Fn: patchCoreDNS, ErrMsg: "failed to patch CoreDNS", Retries: 3},
{Fn: machinecontroller.EnsureMachineController, ErrMsg: "failed to install machine-controller", Retries: 3},
{Fn: applyCanalCNI, ErrMsg: "failed to install cni plugin canal", Retries: 3},
{Fn: machinecontroller.Ensure, ErrMsg: "failed to install machine-controller", Retries: 3},
{Fn: machinecontroller.WaitReady, ErrMsg: "failed to wait for machine-controller", Retries: 3},
{Fn: createWorkerMachines, ErrMsg: "failed to create worker machines", Retries: 3},
}
Expand Down
93 changes: 93 additions & 0 deletions pkg/templates/externalccm/ccm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
Copyright 2019 The KubeOne Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package externalccm

import (
"context"
"strings"

"github.com/Masterminds/semver"
"github.com/pkg/errors"

"github.com/kubermatic/kubeone/pkg/config"
"github.com/kubermatic/kubeone/pkg/util"

appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

// Ensure external CCM deployen if Provider.External
func Ensure(ctx *util.Context) error {
if !ctx.Cluster.Provider.External {
return nil
}

ctx.Logger.Info("Ensure external CCM is up to date")

switch ctx.Cluster.Provider.Name {
case config.ProviderNameHetzner:
return ensureHetzner(ctx)
case config.ProviderNameDigitalOcean:
return ensureDigitalOcean(ctx)
default:
ctx.Logger.Infof("External CCM for %q not yet supported, skipping", ctx.Cluster.Provider.Name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a message that the user should deploy it manually, e.g. External CCM for %q not yet supported, please deploy CCM manually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please deploy CCM manually is implied, since we can't install them 🤷‍♂️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer being explicit in the user-facing error messages.

Copy link
Member Author

@kron4eg kron4eg Apr 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to run pipeline again just for "notice", as then it easily can take days to stabilize because everything is flaky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering you have to rebase the PR, you can fix this. 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too late, already rebased :P

return nil
}
}

func simpleCreateOrUpdate(ctx context.Context, client dynclient.Client, obj runtime.Object) error {
okFunc := func(runtime.Object) error { return nil }
_, err := controllerutil.CreateOrUpdate(ctx, client, obj, okFunc)
return err
}

func mutateDeploymentWithVersionCheck(want *semver.Constraints) func(obj runtime.Object) error {
return func(obj runtime.Object) error {
dep, ok := obj.(*appsv1.Deployment)
if !ok {
return errors.Errorf("unknown object type %T passed", obj)
}

if dep.ObjectMeta.CreationTimestamp.IsZero() {
// let it create deployment
return nil
}

if len(dep.Spec.Template.Spec.Containers) != 1 {
return errors.New("unable to choose a CCM container, as number of containers > 1")
}

imageSpec := strings.SplitN(dep.Spec.Template.Spec.Containers[0].Image, ":", 2)
if len(imageSpec) != 2 {
return errors.New("unable to grab CCM image version")
}

existing, err := semver.NewVersion(imageSpec[1])
if err != nil {
return errors.Wrap(err, "failed to parse deployed CCM version")
}

if !want.Check(existing) {
return errors.New("newer version deployed, skipping")
xmudrii marked this conversation as resolved.
Show resolved Hide resolved
}

// OK to update the deployment
return nil
}
}
Loading