|
|
|
@ -17,23 +17,22 @@ limitations under the License.
|
|
|
|
|
package installer // import "k8s.io/helm/cmd/helm/installer"
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/Masterminds/semver"
|
|
|
|
|
"github.com/ghodss/yaml"
|
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
|
|
|
"k8s.io/api/core/v1"
|
|
|
|
|
"k8s.io/api/extensions/v1beta1"
|
|
|
|
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
|
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
|
|
|
|
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
|
|
|
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
|
|
|
|
|
"k8s.io/helm/pkg/version"
|
|
|
|
|
|
|
|
|
|
"k8s.io/helm/pkg/chartutil"
|
|
|
|
|
"k8s.io/helm/pkg/tiller/environment"
|
|
|
|
@ -43,7 +42,7 @@ import (
|
|
|
|
|
//
|
|
|
|
|
// Returns an error if the command failed.
|
|
|
|
|
func Install(client kubernetes.Interface, opts *Options) error {
|
|
|
|
|
if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil {
|
|
|
|
|
if err := createDeployment(client.AppsV1(), opts); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := createService(client.CoreV1(), opts.Namespace); err != nil {
|
|
|
|
@ -61,51 +60,108 @@ func Install(client kubernetes.Interface, opts *Options) error {
|
|
|
|
|
//
|
|
|
|
|
// Returns an error if the command failed.
|
|
|
|
|
func Upgrade(client kubernetes.Interface, opts *Options) error {
|
|
|
|
|
obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
appsobj, err := client.AppsV1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
|
|
|
|
|
if err == nil {
|
|
|
|
|
// Can happen in two cases:
|
|
|
|
|
// 1. helm init inserted an apps/v1 Deployment up front in Kubernetes
|
|
|
|
|
// 2. helm init inserted an extensions/v1beta1 Deployment against a K8s cluster already
|
|
|
|
|
// supporting apps/v1 Deployment. In such a case K8s is returning the apps/v1 object anyway.`
|
|
|
|
|
// (for the same reason "kubectl convert" is being deprecated)
|
|
|
|
|
return upgradeAppsTillerDeployment(client, opts, appsobj)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extensionsobj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
|
|
|
|
|
if err == nil {
|
|
|
|
|
// User performed helm init against older version of kubernetes (Previous to 1.9)
|
|
|
|
|
return upgradeExtensionsTillerDeployment(client, opts, extensionsobj)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func upgradeAppsTillerDeployment(client kubernetes.Interface, opts *Options, obj *appsv1.Deployment) error {
|
|
|
|
|
// Update the PodTemplateSpec section of the deployment
|
|
|
|
|
if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
tillerImage := obj.Spec.Template.Spec.Containers[0].Image
|
|
|
|
|
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
|
|
|
|
|
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
|
|
|
|
|
|
|
|
|
|
if _, err := client.AppsV1().Deployments(opts.Namespace).Update(obj); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
obj.Spec.Template.Spec.Containers[0].Image = opts.SelectImage()
|
|
|
|
|
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
|
|
|
|
|
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
|
|
|
|
|
|
|
|
|
|
// If the service does not exist that would mean we are upgrading from a Tiller version
|
|
|
|
|
// that didn't deploy the service, so install it.
|
|
|
|
|
_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
|
|
|
|
|
if apierrors.IsNotFound(err) {
|
|
|
|
|
return createService(client.CoreV1(), opts.Namespace)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func upgradeExtensionsTillerDeployment(client kubernetes.Interface, opts *Options, obj *extensionsv1beta1.Deployment) error {
|
|
|
|
|
// Update the PodTemplateSpec section of the deployment
|
|
|
|
|
if err := updatePodTemplate(&obj.Spec.Template.Spec, opts); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the service does not exist that would mean we are upgrading from a Tiller version
|
|
|
|
|
// that didn't deploy the service, so install it.
|
|
|
|
|
_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
|
|
|
|
|
_, err := client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
|
|
|
|
|
if apierrors.IsNotFound(err) {
|
|
|
|
|
return createService(client.CoreV1(), opts.Namespace)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
|
|
|
|
|
func semverCompare(image string) int {
|
|
|
|
|
split := strings.Split(image, ":")
|
|
|
|
|
if len(split) < 2 {
|
|
|
|
|
// If we don't know the version, we consider the client version newer.
|
|
|
|
|
return 1
|
|
|
|
|
func updatePodTemplate(podSpec *v1.PodSpec, opts *Options) error {
|
|
|
|
|
tillerImage := podSpec.Containers[0].Image
|
|
|
|
|
clientImage := opts.SelectImage()
|
|
|
|
|
|
|
|
|
|
if semverCompare(tillerImage, clientImage) == -1 && !opts.ForceUpgrade {
|
|
|
|
|
return fmt.Errorf("current Tiller version %s is newer than client version %s, use --force-upgrade to downgrade", tillerImage, clientImage)
|
|
|
|
|
}
|
|
|
|
|
tillerVersion, err := semver.NewVersion(split[1])
|
|
|
|
|
podSpec.Containers[0].Image = clientImage
|
|
|
|
|
podSpec.Containers[0].ImagePullPolicy = opts.pullPolicy()
|
|
|
|
|
podSpec.ServiceAccountName = opts.ServiceAccount
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
|
|
|
|
|
func semverCompare(tillerImage, clientImage string) int {
|
|
|
|
|
tillerVersion, err := string2semver(tillerImage)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// same thing with unparsable tiller versions (e.g. canary releases).
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
clientVersion, err := semver.NewVersion(version.Version)
|
|
|
|
|
|
|
|
|
|
// clientVersion, err := semver.NewVersion(currentVersion)
|
|
|
|
|
clientVersion, err := string2semver(clientImage)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return clientVersion.Compare(tillerVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func string2semver(image string) (*semver.Version, error) {
|
|
|
|
|
split := strings.Split(image, ":")
|
|
|
|
|
if len(split) < 2 {
|
|
|
|
|
// If we don't know the version, we consider the client version newer.
|
|
|
|
|
return nil, fmt.Errorf("no repository in image %s", image)
|
|
|
|
|
}
|
|
|
|
|
return semver.NewVersion(split[1])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createDeployment creates the Tiller Deployment resource.
|
|
|
|
|
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
|
|
|
|
|
func createDeployment(client appsv1client.DeploymentsGetter, opts *Options) error {
|
|
|
|
|
obj, err := generateDeployment(opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
@ -118,7 +174,7 @@ func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options)
|
|
|
|
|
// Deployment gets a deployment object that can be used to generate a manifest
|
|
|
|
|
// as a string. This object should not be submitted directly to the Kubernetes
|
|
|
|
|
// api
|
|
|
|
|
func Deployment(opts *Options) (*v1beta1.Deployment, error) {
|
|
|
|
|
func Deployment(opts *Options) (*appsv1.Deployment, error) {
|
|
|
|
|
dep, err := generateDeployment(opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
@ -197,7 +253,7 @@ func parseNodeSelectorsInto(labels string, m map[string]string) error {
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
|
|
|
|
|
func generateDeployment(opts *Options) (*appsv1.Deployment, error) {
|
|
|
|
|
labels := generateLabels(map[string]string{"name": "tiller"})
|
|
|
|
|
nodeSelectors := map[string]string{}
|
|
|
|
|
if len(opts.NodeSelectors) > 0 {
|
|
|
|
@ -206,14 +262,17 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
d := &v1beta1.Deployment{
|
|
|
|
|
d := &appsv1.Deployment{
|
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
|
Namespace: opts.Namespace,
|
|
|
|
|
Name: deploymentName,
|
|
|
|
|
Labels: labels,
|
|
|
|
|
},
|
|
|
|
|
Spec: v1beta1.DeploymentSpec{
|
|
|
|
|
Spec: appsv1.DeploymentSpec{
|
|
|
|
|
Replicas: opts.getReplicas(),
|
|
|
|
|
Selector: &metav1.LabelSelector{
|
|
|
|
|
MatchLabels: labels,
|
|
|
|
|
},
|
|
|
|
|
Template: v1.PodTemplateSpec{
|
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
|
Labels: labels,
|
|
|
|
@ -297,7 +356,7 @@ func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
|
|
|
|
|
// merge them and convert back to Deployment
|
|
|
|
|
if len(opts.Values) > 0 {
|
|
|
|
|
// base deployment struct
|
|
|
|
|
var dd v1beta1.Deployment
|
|
|
|
|
var dd appsv1.Deployment
|
|
|
|
|
// get YAML from original deployment
|
|
|
|
|
dy, err := yaml.Marshal(d)
|
|
|
|
|
if err != nil {
|
|
|
|
|