mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
Add helm controller
This commit is contained in:
parent
e832588662
commit
1d666d9515
7
manifests/nginx-ingress.yaml
Normal file
7
manifests/nginx-ingress.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: k3s.cattle.io/v1
|
||||||
|
kind: HelmChart
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
chart: stable/nginx-ingress
|
309
pkg/helm/controller.go
Normal file
309
pkg/helm/controller.go
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
batchclient "github.com/rancher/k3s/types/apis/batch/v1"
|
||||||
|
coreclient "github.com/rancher/k3s/types/apis/core/v1"
|
||||||
|
k3s "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1"
|
||||||
|
rbacclients "github.com/rancher/k3s/types/apis/rbac.authorization.k8s.io/v1"
|
||||||
|
"github.com/rancher/norman/pkg/changeset"
|
||||||
|
"github.com/rancher/norman/pkg/objectset"
|
||||||
|
batch "k8s.io/api/batch/v1"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
rbac "k8s.io/api/rbac/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
namespace = "kube-system"
|
||||||
|
image = "rancher/klipper-helm:v0.1.0"
|
||||||
|
label = "helm.k3s.cattle.io/chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
trueVal = true
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(ctx context.Context) error {
|
||||||
|
k3sClients := k3s.ClientsFrom(ctx)
|
||||||
|
coreClients := coreclient.ClientsFrom(ctx)
|
||||||
|
jobClients := batchclient.ClientsFrom(ctx)
|
||||||
|
rbacClients := rbacclients.ClientsFrom(ctx)
|
||||||
|
|
||||||
|
h := &handler{
|
||||||
|
jobs: jobClients.Job,
|
||||||
|
jobCache: jobClients.Job.Cache(),
|
||||||
|
processor: objectset.NewProcessor("k3s.helm").
|
||||||
|
Client(coreClients.ConfigMap).
|
||||||
|
Client(coreClients.ServiceAccount).
|
||||||
|
Client(jobClients.Job).
|
||||||
|
Client(rbacClients.ClusterRoleBinding).
|
||||||
|
Patcher(batch.SchemeGroupVersion.WithKind("Job"), objectset.ReplaceOnChange),
|
||||||
|
}
|
||||||
|
|
||||||
|
k3sClients.HelmChart.OnChange(ctx, "helm", h.onChange)
|
||||||
|
k3sClients.HelmChart.OnRemove(ctx, "helm", h.onRemove)
|
||||||
|
|
||||||
|
changeset.Watch(ctx, "helm-pod-watch",
|
||||||
|
func(namespace, name string, obj runtime.Object) ([]changeset.Key, error) {
|
||||||
|
if job, ok := obj.(*batch.Job); ok {
|
||||||
|
name := job.Labels[label]
|
||||||
|
if name != "" {
|
||||||
|
return []changeset.Key{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
k3sClients.HelmChart,
|
||||||
|
jobClients.Job)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
jobCache batchclient.JobClientCache
|
||||||
|
jobs batchclient.JobClient
|
||||||
|
processor *objectset.Processor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) onChange(chart *k3s.HelmChart) (runtime.Object, error) {
|
||||||
|
if chart.Namespace != namespace || chart.Spec.Chart == "" {
|
||||||
|
return chart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
objs := objectset.NewObjectSet()
|
||||||
|
job, configMap := job(chart)
|
||||||
|
objs.Add(serviceAccount(chart))
|
||||||
|
objs.Add(roleBinding(chart))
|
||||||
|
objs.Add(job)
|
||||||
|
if configMap != nil {
|
||||||
|
objs.Add(configMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.processor.NewDesiredSet(chart, objs).Apply(); err != nil {
|
||||||
|
return chart, err
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.Status.JobName = job.Name
|
||||||
|
return chart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) onRemove(chart *k3s.HelmChart) (runtime.Object, error) {
|
||||||
|
if chart.Namespace != namespace || chart.Spec.Chart == "" {
|
||||||
|
return chart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
job, _ := job(chart)
|
||||||
|
|
||||||
|
job, err := h.jobCache.Get(chart.Namespace, job.Name)
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
_, err := h.onChange(chart)
|
||||||
|
if err != nil {
|
||||||
|
return chart, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if job.Status.Succeeded <= 0 {
|
||||||
|
return nil, fmt.Errorf("waiting for delete of helm chart %s", chart.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chart, h.processor.NewDesiredSet(chart, objectset.NewObjectSet()).Apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
func job(chart *k3s.HelmChart) (*batch.Job, *core.ConfigMap) {
|
||||||
|
action := "install"
|
||||||
|
if chart.DeletionTimestamp != nil {
|
||||||
|
action = "delete"
|
||||||
|
}
|
||||||
|
job := &batch.Job{
|
||||||
|
TypeMeta: meta.TypeMeta{
|
||||||
|
APIVersion: "batch/v1",
|
||||||
|
Kind: "Job",
|
||||||
|
},
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("helm-%s-%s", action, chart.Name),
|
||||||
|
Namespace: chart.Namespace,
|
||||||
|
Labels: map[string]string{
|
||||||
|
label: chart.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: batch.JobSpec{
|
||||||
|
Template: core.PodTemplateSpec{
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Labels: map[string]string{
|
||||||
|
label: chart.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
RestartPolicy: core.RestartPolicyOnFailure,
|
||||||
|
Containers: []core.Container{
|
||||||
|
{
|
||||||
|
Name: "helm",
|
||||||
|
Image: image,
|
||||||
|
ImagePullPolicy: core.PullAlways,
|
||||||
|
Args: args(chart),
|
||||||
|
Env: []core.EnvVar{
|
||||||
|
{
|
||||||
|
Name: "NAME",
|
||||||
|
Value: chart.Name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "VERSION",
|
||||||
|
Value: chart.Spec.Version,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "REPO",
|
||||||
|
Value: chart.Spec.Repo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServiceAccountName: fmt.Sprintf("helm-%s", chart.Name),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
configMap := configMap(chart)
|
||||||
|
if configMap == nil {
|
||||||
|
return job, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
job.Spec.Template.Spec.Volumes = []core.Volume{
|
||||||
|
{
|
||||||
|
Name: "values",
|
||||||
|
VolumeSource: core.VolumeSource{
|
||||||
|
ConfigMap: &core.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{
|
||||||
|
Name: configMap.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
job.Spec.Template.Spec.Containers[0].VolumeMounts = []core.VolumeMount{
|
||||||
|
{
|
||||||
|
MountPath: "/config",
|
||||||
|
Name: "values",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return job, configMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func configMap(chart *k3s.HelmChart) *core.ConfigMap {
|
||||||
|
if chart.Spec.ValuesContent == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &core.ConfigMap{
|
||||||
|
TypeMeta: meta.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("chart-values-%s", chart.Name),
|
||||||
|
Namespace: chart.Namespace,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"values.yaml": chart.Spec.ValuesContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleBinding(chart *k3s.HelmChart) *rbac.ClusterRoleBinding {
|
||||||
|
return &rbac.ClusterRoleBinding{
|
||||||
|
TypeMeta: meta.TypeMeta{
|
||||||
|
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||||
|
Kind: "ClusterRoleBinding",
|
||||||
|
},
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("helm-%s-%s", chart.Namespace, chart.Name),
|
||||||
|
},
|
||||||
|
RoleRef: rbac.RoleRef{
|
||||||
|
Kind: "ClusterRole",
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Name: "cluster-admin",
|
||||||
|
},
|
||||||
|
Subjects: []rbac.Subject{
|
||||||
|
{
|
||||||
|
Name: fmt.Sprintf("helm-%s", chart.Name),
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Namespace: chart.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceAccount(chart *k3s.HelmChart) *core.ServiceAccount {
|
||||||
|
return &core.ServiceAccount{
|
||||||
|
TypeMeta: meta.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
},
|
||||||
|
ObjectMeta: meta.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("helm-%s", chart.Name),
|
||||||
|
Namespace: chart.Namespace,
|
||||||
|
},
|
||||||
|
AutomountServiceAccountToken: &trueVal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func args(chart *k3s.HelmChart) []string {
|
||||||
|
if chart.DeletionTimestamp != nil {
|
||||||
|
return []string{
|
||||||
|
"delete",
|
||||||
|
"--purge", chart.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := chart.Spec
|
||||||
|
args := []string{
|
||||||
|
"install",
|
||||||
|
"--name", chart.Name,
|
||||||
|
spec.Chart,
|
||||||
|
}
|
||||||
|
if spec.TargetNamespace != "" {
|
||||||
|
args = append(args, "--namespace", spec.TargetNamespace)
|
||||||
|
}
|
||||||
|
if spec.Repo != "" {
|
||||||
|
args = append(args, "--repo", spec.Repo)
|
||||||
|
}
|
||||||
|
if spec.Version != "" {
|
||||||
|
args = append(args, "--version", spec.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys(spec.Set) {
|
||||||
|
val := spec.Set[k]
|
||||||
|
if val.StrVal != "" {
|
||||||
|
args = append(args, "--set-string", fmt.Sprintf("%s=%s", k, val.StrVal))
|
||||||
|
} else {
|
||||||
|
args = append(args, "--set", fmt.Sprintf("%s=%d", k, val.IntVal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func keys(val map[string]intstr.IntOrString) []string {
|
||||||
|
var keys []string
|
||||||
|
for k := range val {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
@ -17,11 +17,14 @@ import (
|
|||||||
"github.com/rancher/k3s/pkg/daemons/config"
|
"github.com/rancher/k3s/pkg/daemons/config"
|
||||||
"github.com/rancher/k3s/pkg/daemons/control"
|
"github.com/rancher/k3s/pkg/daemons/control"
|
||||||
"github.com/rancher/k3s/pkg/deploy"
|
"github.com/rancher/k3s/pkg/deploy"
|
||||||
|
"github.com/rancher/k3s/pkg/helm"
|
||||||
"github.com/rancher/k3s/pkg/servicelb"
|
"github.com/rancher/k3s/pkg/servicelb"
|
||||||
"github.com/rancher/k3s/pkg/tls"
|
"github.com/rancher/k3s/pkg/tls"
|
||||||
appsv1 "github.com/rancher/k3s/types/apis/apps/v1"
|
appsv1 "github.com/rancher/k3s/types/apis/apps/v1"
|
||||||
|
batchv1 "github.com/rancher/k3s/types/apis/batch/v1"
|
||||||
corev1 "github.com/rancher/k3s/types/apis/core/v1"
|
corev1 "github.com/rancher/k3s/types/apis/core/v1"
|
||||||
v1 "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1"
|
v1 "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1"
|
||||||
|
rbacv1 "github.com/rancher/k3s/types/apis/rbac.authorization.k8s.io/v1"
|
||||||
"github.com/rancher/norman"
|
"github.com/rancher/norman"
|
||||||
"github.com/rancher/norman/pkg/clientaccess"
|
"github.com/rancher/norman/pkg/clientaccess"
|
||||||
"github.com/rancher/norman/pkg/dynamiclistener"
|
"github.com/rancher/norman/pkg/dynamiclistener"
|
||||||
@ -91,6 +94,8 @@ func startNorman(ctx context.Context, config *Config) (string, error) {
|
|||||||
v1.Factory,
|
v1.Factory,
|
||||||
appsv1.Factory,
|
appsv1.Factory,
|
||||||
corev1.Factory,
|
corev1.Factory,
|
||||||
|
batchv1.Factory,
|
||||||
|
rbacv1.Factory,
|
||||||
},
|
},
|
||||||
Schemas: []*types.Schemas{
|
Schemas: []*types.Schemas{
|
||||||
v1.Schemas,
|
v1.Schemas,
|
||||||
@ -99,6 +104,7 @@ func startNorman(ctx context.Context, config *Config) (string, error) {
|
|||||||
&v1.APIVersion: {
|
&v1.APIVersion: {
|
||||||
v1.ListenerConfigGroupVersionKind.Kind,
|
v1.ListenerConfigGroupVersionKind.Kind,
|
||||||
v1.AddonGroupVersionKind.Kind,
|
v1.AddonGroupVersionKind.Kind,
|
||||||
|
v1.HelmChartGroupVersionKind.Kind,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IgnoredKubeConfigEnv: true,
|
IgnoredKubeConfigEnv: true,
|
||||||
@ -108,6 +114,7 @@ func startNorman(ctx context.Context, config *Config) (string, error) {
|
|||||||
},
|
},
|
||||||
DisableLeaderElection: true,
|
DisableLeaderElection: true,
|
||||||
MasterControllers: []norman.ControllerRegister{
|
MasterControllers: []norman.ControllerRegister{
|
||||||
|
helm.Register,
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
return servicelb.Register(ctx, norman.GetServer(ctx).K8sClient, !config.DisableServiceLB)
|
return servicelb.Register(ctx, norman.GetServer(ctx).K8sClient, !config.DisableServiceLB)
|
||||||
},
|
},
|
||||||
|
@ -13,6 +13,7 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
Schemas = factory.Schemas(&APIVersion).
|
Schemas = factory.Schemas(&APIVersion).
|
||||||
MustImport(&APIVersion, ListenerConfig{}).
|
MustImport(&APIVersion, Addon{}).
|
||||||
MustImport(&APIVersion, Addon{})
|
MustImport(&APIVersion, HelmChart{}).
|
||||||
|
MustImport(&APIVersion, ListenerConfig{})
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/rancher/norman/types"
|
"github.com/rancher/norman/types"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListenerConfig struct {
|
type ListenerConfig struct {
|
||||||
@ -34,3 +35,26 @@ type AddonSpec struct {
|
|||||||
type AddonStatus struct {
|
type AddonStatus struct {
|
||||||
GVKs []schema.GroupVersionKind `json:"gvks,omitempty"`
|
GVKs []schema.GroupVersionKind `json:"gvks,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HelmChart struct {
|
||||||
|
types.Namespaced
|
||||||
|
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec HelmChartSpec `json:"spec,omitempty"`
|
||||||
|
Status HelmChartStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelmChartSpec struct {
|
||||||
|
TargetNamespace string `json:"targetNamespace,omitempty"`
|
||||||
|
Chart string `json:"chart,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
Repo string `json:"repo,omitempty"`
|
||||||
|
Set map[string]intstr.IntOrString `json:"set,omitempty"`
|
||||||
|
ValuesContent string `json:"valuesContent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelmChartStatus struct {
|
||||||
|
JobName string `json:"jobName,omitempty"`
|
||||||
|
}
|
||||||
|
@ -5,8 +5,10 @@ import (
|
|||||||
v1 "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1"
|
v1 "github.com/rancher/k3s/types/apis/k3s.cattle.io/v1"
|
||||||
"github.com/rancher/norman/generator"
|
"github.com/rancher/norman/generator"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
v13 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
v12 "k8s.io/api/core/v1"
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -68,18 +70,32 @@ func main() {
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := generator.ControllersForForeignTypes(basePackage, v12.SchemeGroupVersion, []interface{}{
|
if err := generator.ControllersForForeignTypes(basePackage, corev1.SchemeGroupVersion, []interface{}{
|
||||||
v12.Service{},
|
corev1.ServiceAccount{},
|
||||||
v12.Pod{},
|
corev1.Service{},
|
||||||
|
corev1.Pod{},
|
||||||
|
corev1.ConfigMap{},
|
||||||
}, []interface{}{
|
}, []interface{}{
|
||||||
v12.Node{},
|
corev1.Node{},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := generator.ControllersForForeignTypes(basePackage, v13.SchemeGroupVersion, []interface{}{
|
if err := generator.ControllersForForeignTypes(basePackage, appsv1.SchemeGroupVersion, []interface{}{
|
||||||
v13.Deployment{},
|
appsv1.Deployment{},
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := generator.ControllersForForeignTypes(basePackage, batchv1.SchemeGroupVersion, []interface{}{
|
||||||
|
batchv1.Job{},
|
||||||
|
}, nil); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := generator.ControllersForForeignTypes(basePackage, rbacv1.SchemeGroupVersion, nil, []interface{}{
|
||||||
|
rbacv1.ClusterRoleBinding{},
|
||||||
|
}); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user