update helm-controller

This commit is contained in:
galal-hussein 2020-04-23 00:34:19 +02:00
parent 553517e194
commit 94da8b8e12
35 changed files with 1101 additions and 480 deletions

7
go.mod
View File

@ -61,6 +61,7 @@ replace (
)
require (
github.com/Azure/go-autorest v14.0.1+incompatible // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect
@ -100,11 +101,11 @@ require (
github.com/pkg/errors v0.9.1
github.com/rakelkar/gonetsh v0.0.0-20190719023240-501daadcadf8 // indirect
github.com/rancher/dynamiclistener v0.2.0
github.com/rancher/helm-controller v0.4.2-0.20200326195131-eb51d4fa9d8d
github.com/rancher/helm-controller v0.5.0
github.com/rancher/kine v0.3.5
github.com/rancher/remotedialer v0.2.0
github.com/rancher/wrangler v0.5.4-0.20200326191509-4054411d9736
github.com/rancher/wrangler-api v0.5.1-0.20200326194427-c13310506d04
github.com/rancher/wrangler v0.6.1
github.com/rancher/wrangler-api v0.6.0
github.com/rootless-containers/rootlesskit v0.7.2
github.com/sirupsen/logrus v1.4.2
github.com/spf13/pflag v1.0.5

11
go.sum
View File

@ -7,6 +7,10 @@ github.com/Azure/azure-sdk-for-go v35.0.0+incompatible h1:PkmdmQUmeSdQQ5258f4SyC
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA=
github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.0.1+incompatible h1:YhojO9jolWIvvTW7ORhz2ZSNF6Q1TbLqUunKd3jrtyw=
github.com/Azure/go-autorest v14.0.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
@ -640,6 +644,8 @@ github.com/rancher/flannel v0.11.0-k3s.2 h1:0GVr5ORAIvcri1LYTE8eMQ+NrRbuPeIniPaW
github.com/rancher/flannel v0.11.0-k3s.2/go.mod h1:Hn4ZV+eq0LhLZP63xZnxdGwXEoRSxs5sxELxu27M3UA=
github.com/rancher/helm-controller v0.4.2-0.20200326195131-eb51d4fa9d8d h1:6w5gCRgJzWEGdGi/0Xv4XXuGZY8wgWduRA9A+4c1N8I=
github.com/rancher/helm-controller v0.4.2-0.20200326195131-eb51d4fa9d8d/go.mod h1:3jCGmvjp3bFnbeuHL4HiODje9ZYJ/ujUBNtXHFXrwlM=
github.com/rancher/helm-controller v0.5.0 h1:BY5PG3dz6GWct2O9r8mFv73tZ7E5U9uI89QwMBXV83E=
github.com/rancher/helm-controller v0.5.0/go.mod h1:kEtAI/0AylXIplxWkIRR2xl3nhd4jZ6Wke1nvE/sKUs=
github.com/rancher/kine v0.3.5 h1:Tm4eOtejpnzs1WFBrXj76lCLvX9czLlTkgqUk9luCQk=
github.com/rancher/kine v0.3.5/go.mod h1:xEMl0tLCva9/9me7mXJ3m9Vo6yqHgC4OU3NiK4CPrGQ=
github.com/rancher/kubernetes v1.18.2-k3s.1 h1:LhWNObWF7dL/+T57LkYpuRKtsCBpt0P5G6dRVFG+Ncs=
@ -693,9 +699,14 @@ github.com/rancher/wrangler v0.4.0 h1:iLvuJcZkd38E3RGG74dFMMNEju0PeTzfT1PQiv5okV
github.com/rancher/wrangler v0.4.0/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU4wnQvTN8=
github.com/rancher/wrangler v0.5.4-0.20200326191509-4054411d9736 h1:hqpVLgNUxU5sQUV6SzJPMY8Fy7T9Qht2QkA2Q7O/SH0=
github.com/rancher/wrangler v0.5.4-0.20200326191509-4054411d9736/go.mod h1:L4HtjPeX8iqLgsxfJgz+JjKMcX2q3qbRXSeTlC/CSd4=
github.com/rancher/wrangler v0.6.0/go.mod h1:L4HtjPeX8iqLgsxfJgz+JjKMcX2q3qbRXSeTlC/CSd4=
github.com/rancher/wrangler v0.6.1 h1:7tyLk/FV2zCQkYg5SEtT4lSlsHNwa5yMOa797/VJhiQ=
github.com/rancher/wrangler v0.6.1/go.mod h1:L4HtjPeX8iqLgsxfJgz+JjKMcX2q3qbRXSeTlC/CSd4=
github.com/rancher/wrangler-api v0.2.0/go.mod h1:zTPdNLZO07KvRaVOx6XQbKBSV55Fnn4s7nqmrMPJqd8=
github.com/rancher/wrangler-api v0.5.1-0.20200326194427-c13310506d04 h1:y55e+kUaz/UswjN/oJdqHWMuoCG1FxwZJkxJEUONZZE=
github.com/rancher/wrangler-api v0.5.1-0.20200326194427-c13310506d04/go.mod h1:R3nemXoECcrDqXDSHdY7yJay4j42TeEkU79Hep0rdJ8=
github.com/rancher/wrangler-api v0.6.0 h1:d/b0AkgZ+x41EYLIcUJiogtU3Y11Mqss2zr9VEKycRk=
github.com/rancher/wrangler-api v0.6.0/go.mod h1:RbuDkPNHhxcXuwAbLVvEAhH+UPAh+MIkpEd2fcGc0MM=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY=
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=

View File

@ -39,9 +39,10 @@ type Controller struct {
}
const (
image = "rancher/klipper-helm:v0.2.3"
label = "helmcharts.helm.cattle.io/chart"
name = "helm-controller"
image = "rancher/klipper-helm:v0.2.5"
Label = "helmcharts.helm.cattle.io/chart"
CRDName = "helmcharts.helm.cattle.io"
Name = "helm-controller"
)
func Register(ctx context.Context, apply apply.Apply,
@ -50,7 +51,7 @@ func Register(ctx context.Context, apply apply.Apply,
crbs rbaccontroller.ClusterRoleBindingController,
sas corecontroller.ServiceAccountController,
cm corecontroller.ConfigMapController) {
apply = apply.WithSetID(name).
apply = apply.WithSetID(Name).
WithCacheTypes(helms, jobs, crbs, sas, cm).
WithStrictCaching().WithPatcher(batch.SchemeGroupVersion.WithKind("Job"), func(namespace, name string, pt types.PatchType, data []byte) (runtime.Object, error) {
err := jobs.Delete(namespace, name, &metav1.DeleteOptions{})
@ -63,7 +64,7 @@ func Register(ctx context.Context, apply apply.Apply,
relatedresource.Watch(ctx, "helm-pod-watch",
func(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) {
if job, ok := obj.(*batch.Job); ok {
name := job.Labels[label]
name := job.Labels[Label]
if name != "" {
return []relatedresource.Key{
{
@ -84,8 +85,8 @@ func Register(ctx context.Context, apply apply.Apply,
apply: apply,
}
helms.OnChange(ctx, name, controller.OnHelmChanged)
helms.OnRemove(ctx, name, controller.OnHelmRemove)
helms.OnChange(ctx, Name, controller.OnHelmChanged)
helms.OnRemove(ctx, Name, controller.OnHelmRemove)
}
func (c *Controller) OnHelmChanged(key string, chart *helmv1.HelmChart) (*helmv1.HelmChart, error) {
@ -163,7 +164,7 @@ func job(chart *helmv1.HelmChart) (*batch.Job, *core.ConfigMap) {
Name: fmt.Sprintf("helm-%s-%s", action, chart.Name),
Namespace: chart.Namespace,
Labels: map[string]string{
label: chart.Name,
Label: chart.Name,
},
},
Spec: batch.JobSpec{
@ -171,7 +172,7 @@ func job(chart *helmv1.HelmChart) (*batch.Job, *core.ConfigMap) {
Template: core.PodTemplateSpec{
ObjectMeta: meta.ObjectMeta{
Labels: map[string]string{
label: chart.Name,
Label: chart.Name,
},
},
Spec: core.PodSpec{

View File

@ -52,18 +52,23 @@ func NewFactoryFromConfigOrDie(config *rest.Config) *Factory {
}
func NewFactoryFromConfig(config *rest.Config) (*Factory, error) {
cs, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(cs, 2*time.Hour)
return NewFactory(cs, informerFactory), nil
return NewFactoryFromConfigWithOptions(config, nil)
}
func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) {
if namespace == "" {
return NewFactoryFromConfig(config)
return NewFactoryFromConfigWithOptions(config, &FactoryOptions{
Namespace: namespace,
})
}
type FactoryOptions struct {
Namespace string
Resync time.Duration
}
func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) {
if opts == nil {
opts = &FactoryOptions{}
}
cs, err := clientset.NewForConfig(config)
@ -71,7 +76,17 @@ func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*
return nil, err
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, 2*time.Hour, informers.WithNamespace(namespace))
resync := opts.Resync
if resync == 0 {
resync = 2 * time.Hour
}
if opts.Namespace == "" {
informerFactory := informers.NewSharedInformerFactory(cs, resync)
return NewFactory(cs, informerFactory), nil
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, resync, informers.WithNamespace(opts.Namespace))
return NewFactory(cs, informerFactory), nil
}

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterDaemonSetGeneratingHandler(ctx context.Context, controller DaemonSe
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterDaemonSetStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *daemonSetStatusHandler) sync(key string, obj *v1.DaemonSet) (*v1.Daemon
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *daemonSetStatusHandler) sync(key string, obj *v1.DaemonSet) (*v1.Daemon
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type daemonSetGeneratingHandler struct {
name string
}
func (a *daemonSetGeneratingHandler) Remove(key string, obj *v1.DaemonSet) (*v1.DaemonSet, error) {
if obj != nil {
return obj, nil
}
obj = &v1.DaemonSet{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *daemonSetGeneratingHandler) Handle(obj *v1.DaemonSet, status v1.DaemonSetStatus) (v1.DaemonSetStatus, error) {
objs, newStatus, err := a.DaemonSetGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterDeploymentGeneratingHandler(ctx context.Context, controller Deploym
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterDeploymentStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *deploymentStatusHandler) sync(key string, obj *v1.Deployment) (*v1.Depl
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *deploymentStatusHandler) sync(key string, obj *v1.Deployment) (*v1.Depl
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type deploymentGeneratingHandler struct {
name string
}
func (a *deploymentGeneratingHandler) Remove(key string, obj *v1.Deployment) (*v1.Deployment, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Deployment{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *deploymentGeneratingHandler) Handle(obj *v1.Deployment, status v1.DeploymentStatus) (v1.DeploymentStatus, error) {
objs, newStatus, err := a.DeploymentGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterStatefulSetGeneratingHandler(ctx context.Context, controller Statef
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterStatefulSetStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *statefulSetStatusHandler) sync(key string, obj *v1.StatefulSet) (*v1.St
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *statefulSetStatusHandler) sync(key string, obj *v1.StatefulSet) (*v1.St
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type statefulSetGeneratingHandler struct {
name string
}
func (a *statefulSetGeneratingHandler) Remove(key string, obj *v1.StatefulSet) (*v1.StatefulSet, error) {
if obj != nil {
return obj, nil
}
obj = &v1.StatefulSet{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *statefulSetGeneratingHandler) Handle(obj *v1.StatefulSet, status v1.StatefulSetStatus) (v1.StatefulSetStatus, error) {
objs, newStatus, err := a.StatefulSetGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -52,18 +52,23 @@ func NewFactoryFromConfigOrDie(config *rest.Config) *Factory {
}
func NewFactoryFromConfig(config *rest.Config) (*Factory, error) {
cs, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(cs, 2*time.Hour)
return NewFactory(cs, informerFactory), nil
return NewFactoryFromConfigWithOptions(config, nil)
}
func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) {
if namespace == "" {
return NewFactoryFromConfig(config)
return NewFactoryFromConfigWithOptions(config, &FactoryOptions{
Namespace: namespace,
})
}
type FactoryOptions struct {
Namespace string
Resync time.Duration
}
func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) {
if opts == nil {
opts = &FactoryOptions{}
}
cs, err := clientset.NewForConfig(config)
@ -71,7 +76,17 @@ func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*
return nil, err
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, 2*time.Hour, informers.WithNamespace(namespace))
resync := opts.Resync
if resync == 0 {
resync = 2 * time.Hour
}
if opts.Namespace == "" {
informerFactory := informers.NewSharedInformerFactory(cs, resync)
return NewFactory(cs, informerFactory), nil
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, resync, informers.WithNamespace(opts.Namespace))
return NewFactory(cs, informerFactory), nil
}

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterJobGeneratingHandler(ctx context.Context, controller JobController,
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterJobStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *jobStatusHandler) sync(key string, obj *v1.Job) (*v1.Job, error) {
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *jobStatusHandler) sync(key string, obj *v1.Job) (*v1.Job, error) {
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type jobGeneratingHandler struct {
name string
}
func (a *jobGeneratingHandler) Remove(key string, obj *v1.Job) (*v1.Job, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Job{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *jobGeneratingHandler) Handle(obj *v1.Job, status v1.JobStatus) (v1.JobStatus, error) {
objs, newStatus, err := a.JobGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -52,18 +52,23 @@ func NewFactoryFromConfigOrDie(config *rest.Config) *Factory {
}
func NewFactoryFromConfig(config *rest.Config) (*Factory, error) {
cs, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(cs, 2*time.Hour)
return NewFactory(cs, informerFactory), nil
return NewFactoryFromConfigWithOptions(config, nil)
}
func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) {
if namespace == "" {
return NewFactoryFromConfig(config)
return NewFactoryFromConfigWithOptions(config, &FactoryOptions{
Namespace: namespace,
})
}
type FactoryOptions struct {
Namespace string
Resync time.Duration
}
func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) {
if opts == nil {
opts = &FactoryOptions{}
}
cs, err := clientset.NewForConfig(config)
@ -71,7 +76,17 @@ func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*
return nil, err
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, 2*time.Hour, informers.WithNamespace(namespace))
resync := opts.Resync
if resync == 0 {
resync = 2 * time.Hour
}
if opts.Namespace == "" {
informerFactory := informers.NewSharedInformerFactory(cs, resync)
return NewFactory(cs, informerFactory), nil
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, resync, informers.WithNamespace(opts.Namespace))
return NewFactory(cs, informerFactory), nil
}

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterNamespaceGeneratingHandler(ctx context.Context, controller Namespac
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterNamespaceStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *namespaceStatusHandler) sync(key string, obj *v1.Namespace) (*v1.Namesp
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *namespaceStatusHandler) sync(key string, obj *v1.Namespace) (*v1.Namesp
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type namespaceGeneratingHandler struct {
name string
}
func (a *namespaceGeneratingHandler) Remove(key string, obj *v1.Namespace) (*v1.Namespace, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Namespace{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *namespaceGeneratingHandler) Handle(obj *v1.Namespace, status v1.NamespaceStatus) (v1.NamespaceStatus, error) {
objs, newStatus, err := a.NamespaceGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterNodeGeneratingHandler(ctx context.Context, controller NodeControlle
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterNodeStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *nodeStatusHandler) sync(key string, obj *v1.Node) (*v1.Node, error) {
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *nodeStatusHandler) sync(key string, obj *v1.Node) (*v1.Node, error) {
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type nodeGeneratingHandler struct {
name string
}
func (a *nodeGeneratingHandler) Remove(key string, obj *v1.Node) (*v1.Node, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Node{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *nodeGeneratingHandler) Handle(obj *v1.Node, status v1.NodeStatus) (v1.NodeStatus, error) {
objs, newStatus, err := a.NodeGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterPersistentVolumeClaimGeneratingHandler(ctx context.Context, control
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterPersistentVolumeClaimStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *persistentVolumeClaimStatusHandler) sync(key string, obj *v1.Persistent
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *persistentVolumeClaimStatusHandler) sync(key string, obj *v1.Persistent
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type persistentVolumeClaimGeneratingHandler struct {
name string
}
func (a *persistentVolumeClaimGeneratingHandler) Remove(key string, obj *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaim, error) {
if obj != nil {
return obj, nil
}
obj = &v1.PersistentVolumeClaim{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *persistentVolumeClaimGeneratingHandler) Handle(obj *v1.PersistentVolumeClaim, status v1.PersistentVolumeClaimStatus) (v1.PersistentVolumeClaimStatus, error) {
objs, newStatus, err := a.PersistentVolumeClaimGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterPodGeneratingHandler(ctx context.Context, controller PodController,
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterPodStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *podStatusHandler) sync(key string, obj *v1.Pod) (*v1.Pod, error) {
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *podStatusHandler) sync(key string, obj *v1.Pod) (*v1.Pod, error) {
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type podGeneratingHandler struct {
name string
}
func (a *podGeneratingHandler) Remove(key string, obj *v1.Pod) (*v1.Pod, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Pod{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *podGeneratingHandler) Handle(obj *v1.Pod, status v1.PodStatus) (v1.PodStatus, error) {
objs, newStatus, err := a.PodGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -25,6 +25,7 @@ import (
"github.com/rancher/wrangler/pkg/apply"
"github.com/rancher/wrangler/pkg/condition"
"github.com/rancher/wrangler/pkg/generic"
"github.com/rancher/wrangler/pkg/kv"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
@ -267,6 +268,7 @@ func RegisterServiceGeneratingHandler(ctx context.Context, controller ServiceCon
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
RegisterServiceStatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -281,7 +283,7 @@ func (a *serviceStatusHandler) sync(key string, obj *v1.Service) (*v1.Service, e
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -289,16 +291,16 @@ func (a *serviceStatusHandler) sync(key string, obj *v1.Service) (*v1.Service, e
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -315,29 +317,28 @@ type serviceGeneratingHandler struct {
name string
}
func (a *serviceGeneratingHandler) Remove(key string, obj *v1.Service) (*v1.Service, error) {
if obj != nil {
return obj, nil
}
obj = &v1.Service{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *serviceGeneratingHandler) Handle(obj *v1.Service, status v1.ServiceStatus) (v1.ServiceStatus, error) {
objs, newStatus, err := a.ServiceGeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -52,18 +52,23 @@ func NewFactoryFromConfigOrDie(config *rest.Config) *Factory {
}
func NewFactoryFromConfig(config *rest.Config) (*Factory, error) {
cs, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(cs, 2*time.Hour)
return NewFactory(cs, informerFactory), nil
return NewFactoryFromConfigWithOptions(config, nil)
}
func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) {
if namespace == "" {
return NewFactoryFromConfig(config)
return NewFactoryFromConfigWithOptions(config, &FactoryOptions{
Namespace: namespace,
})
}
type FactoryOptions struct {
Namespace string
Resync time.Duration
}
func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) {
if opts == nil {
opts = &FactoryOptions{}
}
cs, err := clientset.NewForConfig(config)
@ -71,7 +76,17 @@ func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*
return nil, err
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, 2*time.Hour, informers.WithNamespace(namespace))
resync := opts.Resync
if resync == 0 {
resync = 2 * time.Hour
}
if opts.Namespace == "" {
informerFactory := informers.NewSharedInformerFactory(cs, resync)
return NewFactory(cs, informerFactory), nil
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, resync, informers.WithNamespace(opts.Namespace))
return NewFactory(cs, informerFactory), nil
}

View File

@ -28,18 +28,45 @@ type Reconciler func(oldObj runtime.Object, newObj runtime.Object) (bool, error)
type ClientFactory func(gvr schema.GroupVersionResource) (dynamic.NamespaceableResourceInterface, error)
type InformerFactory interface {
Get(gvk schema.GroupVersionKind, gvr schema.GroupVersionResource) (cache.SharedIndexInformer, error)
}
type InformerGetter interface {
Informer() cache.SharedIndexInformer
GroupVersionKind() schema.GroupVersionKind
}
type PatchByGVK map[schema.GroupVersionKind]map[objectset.ObjectKey]string
func (p PatchByGVK) Add(gvk schema.GroupVersionKind, namespace, name, patch string) {
d, ok := p[gvk]
if !ok {
d = map[objectset.ObjectKey]string{}
p[gvk] = d
}
d[objectset.ObjectKey{
Name: name,
Namespace: namespace,
}] = patch
}
type Plan struct {
Create objectset.ObjectKeyByGVK
Delete objectset.ObjectKeyByGVK
Update PatchByGVK
Objects []runtime.Object
}
type Apply interface {
Apply(set *objectset.ObjectSet) error
ApplyObjects(objs ...runtime.Object) error
WithContext(ctx context.Context) Apply
WithCacheTypes(igs ...InformerGetter) Apply
WithCacheTypeFactory(factory InformerFactory) Apply
WithSetID(id string) Apply
WithOwner(obj runtime.Object) Apply
WithOwnerKey(key string, gvk schema.GroupVersionKind) Apply
WithInjector(injs ...injectors.ConfigInjector) Apply
WithInjectorName(injs ...string) Apply
WithPatcher(gvk schema.GroupVersionKind, patchers Patcher) Apply
@ -53,6 +80,10 @@ type Apply interface {
WithNoDelete() Apply
WithGVK(gvks ...schema.GroupVersionKind) Apply
WithSetOwnerReference(controller, block bool) Apply
FindOwner(obj runtime.Object) (runtime.Object, error)
PurgeOrphan(obj runtime.Object) error
DryRun(objs ...runtime.Object) (Plan, error)
}
func NewForConfig(cfg *rest.Config) (Apply, error) {
@ -70,6 +101,7 @@ func New(discovery discovery.DiscoveryInterface, cf ClientFactory, igs ...Inform
clientFactory: cf,
discovery: discovery,
namespaced: map[schema.GroupVersionKind]bool{},
gvkToGVR: map[schema.GroupVersionKind]schema.GroupVersionResource{},
clients: map[schema.GroupVersionKind]dynamic.NamespaceableResourceInterface{},
},
informers: map[schema.GroupVersionKind]cache.SharedIndexInformer{},
@ -93,6 +125,7 @@ type clients struct {
clientFactory ClientFactory
discovery discovery.DiscoveryInterface
namespaced map[schema.GroupVersionKind]bool
gvkToGVR map[schema.GroupVersionKind]schema.GroupVersionResource
clients map[schema.GroupVersionKind]dynamic.NamespaceableResourceInterface
}
@ -102,6 +135,12 @@ func (c *clients) IsNamespaced(gvk schema.GroupVersionKind) bool {
return c.namespaced[gvk]
}
func (c *clients) gvr(gvk schema.GroupVersionKind) schema.GroupVersionResource {
c.Lock()
defer c.Unlock()
return c.gvkToGVR[gvk]
}
func (c *clients) client(gvk schema.GroupVersionKind) (dynamic.NamespaceableResourceInterface, error) {
c.Lock()
defer c.Unlock()
@ -127,6 +166,7 @@ func (c *clients) client(gvk schema.GroupVersionKind) (dynamic.NamespaceableReso
c.namespaced[gvk] = resource.Namespaced
c.clients[gvk] = client
c.gvkToGVR[gvk] = gvk.GroupVersion().WithResource(resource.Name)
return client, nil
}
@ -144,6 +184,10 @@ func (a *apply) newDesiredSet() desiredSet {
}
}
func (a *apply) DryRun(objs ...runtime.Object) (Plan, error) {
return a.newDesiredSet().DryRun(objs...)
}
func (a *apply) Apply(set *objectset.ObjectSet) error {
return a.newDesiredSet().Apply(set)
}
@ -162,6 +206,10 @@ func (a *apply) WithOwner(obj runtime.Object) Apply {
return a.newDesiredSet().WithOwner(obj)
}
func (a *apply) WithOwnerKey(key string, gvk schema.GroupVersionKind) Apply {
return a.newDesiredSet().WithOwnerKey(key, gvk)
}
func (a *apply) WithInjector(injs ...injectors.ConfigInjector) Apply {
return a.newDesiredSet().WithInjector(injs...)
}
@ -174,6 +222,10 @@ func (a *apply) WithCacheTypes(igs ...InformerGetter) Apply {
return a.newDesiredSet().WithCacheTypes(igs...)
}
func (a *apply) WithCacheTypeFactory(factory InformerFactory) Apply {
return a.newDesiredSet().WithCacheTypeFactory(factory)
}
func (a *apply) WithGVK(gvks ...schema.GroupVersionKind) Apply {
return a.newDesiredSet().WithGVK(gvks...)
}
@ -221,3 +273,11 @@ func (a *apply) WithSetOwnerReference(controller, block bool) Apply {
func (a *apply) WithContext(ctx context.Context) Apply {
return a.newDesiredSet().WithContext(ctx)
}
func (a *apply) FindOwner(obj runtime.Object) (runtime.Object, error) {
return a.newDesiredSet().FindOwner(obj)
}
func (a *apply) PurgeOrphan(obj runtime.Object) error {
return a.newDesiredSet().PurgeOrphan(obj)
}

View File

@ -3,8 +3,10 @@ package apply
import (
"context"
"github.com/rancher/wrangler/pkg/apply/injectors"
"github.com/rancher/wrangler/pkg/kv"
"github.com/rancher/wrangler/pkg/merr"
"github.com/rancher/wrangler/pkg/objectset"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
@ -12,7 +14,7 @@ import (
type desiredSet struct {
a *apply
ctx context.Context
ctx context.Context
defaultNamespace string
listerNamespace string
setOwnerReference bool
@ -23,6 +25,7 @@ type desiredSet struct {
pruneTypes map[schema.GroupVersionKind]cache.SharedIndexInformer
patchers map[schema.GroupVersionKind]Patcher
reconcilers map[schema.GroupVersionKind]Reconciler
informerFactory InformerFactory
remove bool
noDelete bool
setID string
@ -33,6 +36,9 @@ type desiredSet struct {
ratelimitingQps float32
injectorNames []string
errs []error
createPlan bool
plan Plan
}
func (o *desiredSet) err(err error) error {
@ -44,6 +50,12 @@ func (o desiredSet) Err() error {
return merr.NewErrors(append(o.errs, o.objs.Err())...)
}
func (o desiredSet) DryRun(objs ...runtime.Object) (Plan, error) {
o.objs = objectset.NewObjectSet()
o.objs.Add(objs...)
return o.dryRun()
}
func (o desiredSet) Apply(set *objectset.ObjectSet) error {
if set == nil {
set = objectset.NewObjectSet()
@ -76,6 +88,14 @@ func (o desiredSet) WithSetID(id string) Apply {
return o
}
func (o desiredSet) WithOwnerKey(key string, gvk schema.GroupVersionKind) Apply {
obj := &v1.PartialObjectMetadata{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(gvk)
o.owner = obj
return o
}
func (o desiredSet) WithOwner(obj runtime.Object) Apply {
o.owner = obj
return o
@ -98,6 +118,11 @@ func (o desiredSet) WithInjectorName(injs ...string) Apply {
return o
}
func (o desiredSet) WithCacheTypeFactory(factory InformerFactory) Apply {
o.informerFactory = factory
return o
}
func (o desiredSet) WithCacheTypes(igs ...InformerGetter) Apply {
pruneTypes := make(map[schema.GroupVersionKind]cache.SharedIndexInformer, len(igs))
for k, v := range o.pruneTypes {
@ -147,7 +172,11 @@ func (o desiredSet) WithRestrictClusterScoped() Apply {
}
func (o desiredSet) WithDefaultNamespace(ns string) Apply {
o.defaultNamespace = ns
if ns == "" {
o.defaultNamespace = defaultNamespace
} else {
o.defaultNamespace = ns
}
return o
}

View File

@ -27,6 +27,7 @@ const (
LabelName = "objectset.rio.cattle.io/owner-name"
LabelNamespace = "objectset.rio.cattle.io/owner-namespace"
LabelHash = "objectset.rio.cattle.io/hash"
LabelPrefix = "objectset.rio.cattle.io/"
)
var (
@ -58,6 +59,15 @@ func (o *desiredSet) getRateLimit(labelHash string) flowcontrol.RateLimiter {
return rl
}
func (o *desiredSet) dryRun() (Plan, error) {
o.createPlan = true
o.plan.Create = objectset.ObjectKeyByGVK{}
o.plan.Update = PatchByGVK{}
o.plan.Delete = objectset.ObjectKeyByGVK{}
err := o.apply()
return o.plan, err
}
func (o *desiredSet) apply() error {
if o.objs == nil || o.objs.Len() == 0 {
o.remove = true
@ -67,7 +77,7 @@ func (o *desiredSet) apply() error {
return err
}
labelSet, annotationSet, err := o.getLabelsAndAnnotations()
labelSet, annotationSet, err := GetLabelsAndAnnotations(o.setID, o.owner)
if err != nil {
return o.err(err)
}
@ -90,13 +100,13 @@ func (o *desiredSet) apply() error {
objs := o.collect(objList)
debugID := o.debugID()
req, err := labels.NewRequirement(LabelHash, selection.Equals, []string{labelSet[LabelHash]})
sel, err := GetSelector(labelSet)
if err != nil {
return o.err(err)
}
for _, gvk := range o.objs.GVKOrder(o.knownGVK()...) {
o.process(debugID, labels.NewSelector().Add(*req), gvk, objs[gvk])
o.process(debugID, sel, gvk, objs[gvk])
}
return o.Err()
@ -161,18 +171,26 @@ func (o *desiredSet) runInjectors(objList []runtime.Object) ([]runtime.Object, e
return objList, nil
}
func (o *desiredSet) getLabelsAndAnnotations() (map[string]string, map[string]string, error) {
func GetSelector(labelSet map[string]string) (labels.Selector, error) {
req, err := labels.NewRequirement(LabelHash, selection.Equals, []string{labelSet[LabelHash]})
if err != nil {
return nil, err
}
return labels.NewSelector().Add(*req), nil
}
func GetLabelsAndAnnotations(setID string, owner runtime.Object) (map[string]string, map[string]string, error) {
annotations := map[string]string{
LabelID: o.setID,
LabelID: setID,
}
if o.owner != nil {
gvk, err := gvk2.Get(o.owner)
if owner != nil {
gvk, err := gvk2.Get(owner)
if err != nil {
return nil, nil, err
}
annotations[LabelGVK] = gvk.String()
metadata, err := meta.Accessor(o.owner)
metadata, err := meta.Accessor(owner)
if err != nil {
return nil, nil, fmt.Errorf("failed to get metadata for %s", gvk)
}

View File

@ -5,6 +5,9 @@ import (
"compress/gzip"
"encoding/base64"
"io/ioutil"
"strings"
data2 "github.com/rancher/wrangler/pkg/data"
"github.com/pkg/errors"
"github.com/rancher/wrangler/pkg/data/convert"
@ -95,7 +98,7 @@ func emptyMaps(data map[string]interface{}, keys ...string) bool {
return true
}
func sanitizePatch(patch []byte) ([]byte, error) {
func sanitizePatch(patch []byte, removeObjectSetAnnotation bool) ([]byte, error) {
mod := false
data := map[string]interface{}{}
err := json.Unmarshal(patch, &data)
@ -117,6 +120,23 @@ func sanitizePatch(patch []byte) ([]byte, error) {
mod = true
}
if removeObjectSetAnnotation {
metadata := convert.ToMapInterface(data2.GetValueN(data, "metadata"))
annotations := convert.ToMapInterface(data2.GetValueN(data, "metadata", "annotations"))
for k := range annotations {
if strings.HasPrefix(k, LabelPrefix) {
mod = true
delete(annotations, k)
}
}
if mod && len(annotations) == 0 {
delete(metadata, "annotations")
if len(metadata) == 0 {
delete(data, "metadata")
}
}
}
if emptyMaps(data, "metadata", "annotations") {
return []byte("{}"), nil
}
@ -152,7 +172,7 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc
return false, nil
}
patch, err = sanitizePatch(patch)
patch, err = sanitizePatch(patch, false)
if err != nil {
return false, err
}
@ -172,6 +192,9 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc
if err != nil {
return false, err
}
if originalObject == nil {
originalObject = oldObject
}
handled, err := reconciler(originalObject, newObject)
if err != nil {
return false, err
@ -187,13 +210,17 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc
return true, err
}
func (o *desiredSet) compareObjects(gvk schema.GroupVersionKind, patcher Patcher, client dynamic.NamespaceableResourceInterface, debugID string, oldObject, newObject runtime.Object, force bool) error {
func (o *desiredSet) compareObjects(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patcher, client dynamic.NamespaceableResourceInterface, debugID string, oldObject, newObject runtime.Object, force bool) error {
oldMetadata, err := meta.Accessor(oldObject)
if err != nil {
return err
}
if ran, err := applyPatch(gvk, o.reconcilers[gvk], patcher, debugID, oldObject, newObject); err != nil {
if o.createPlan {
o.plan.Objects = append(o.plan.Objects, oldObject)
}
if ran, err := applyPatch(gvk, reconciler, patcher, debugID, oldObject, newObject); err != nil {
return err
} else if !ran {
logrus.Debugf("DesiredSet - No change(2) %s %s/%s for %s", gvk, oldMetadata.GetNamespace(), oldMetadata.GetName(), debugID)

View File

@ -0,0 +1,152 @@
package apply
import (
"fmt"
"strings"
"github.com/rancher/wrangler/pkg/gvk"
"github.com/rancher/wrangler/pkg/kv"
"github.com/pkg/errors"
namer "github.com/rancher/wrangler/pkg/name"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/cache"
)
var (
ErrOwnerNotFound = errors.New("owner not found")
)
func notFound(name string, gvk schema.GroupVersionKind) error {
// this is not proper, but does it really matter that much? If you find this
// line while researching a bug, then the answer is probably yes.
resource := namer.GuessPluralName(strings.ToLower(gvk.Kind))
return apierrors.NewNotFound(schema.GroupResource{
Group: gvk.Group,
Resource: resource,
}, name)
}
func getGVK(gvkLabel string, gvk *schema.GroupVersionKind) error {
parts := strings.Split(gvkLabel, ", Kind=")
if len(parts) != 2 {
return fmt.Errorf("invalid GVK format: %s", gvkLabel)
}
gvk.Group, gvk.Version = kv.Split(parts[0], "/")
gvk.Kind = parts[1]
return nil
}
func (o desiredSet) FindOwner(obj runtime.Object) (runtime.Object, error) {
if obj == nil {
return nil, ErrOwnerNotFound
}
meta, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
var (
debugID = fmt.Sprintf("%s/%s", meta.GetNamespace(), meta.GetName())
gvkLabel = meta.GetAnnotations()[LabelGVK]
namespace = meta.GetAnnotations()[LabelNamespace]
name = meta.GetAnnotations()[LabelName]
gvk schema.GroupVersionKind
)
if gvkLabel == "" {
return nil, ErrOwnerNotFound
}
if err := getGVK(gvkLabel, &gvk); err != nil {
return nil, err
}
cache, client, err := o.getControllerAndClient(debugID, gvk)
if err != nil {
return nil, err
}
if cache != nil {
return o.fromCache(cache, namespace, name, gvk)
}
return o.fromClient(client, namespace, name, gvk)
}
func (o *desiredSet) fromClient(client dynamic.NamespaceableResourceInterface, namespace, name string, gvk schema.GroupVersionKind) (runtime.Object, error) {
var (
err error
obj interface{}
)
if namespace == "" {
obj, err = client.Get(o.ctx, name, metav1.GetOptions{})
} else {
obj, err = client.Namespace(namespace).Get(o.ctx, name, metav1.GetOptions{})
}
if err != nil {
return nil, err
}
if ro, ok := obj.(runtime.Object); ok {
return ro, nil
}
return nil, notFound(name, gvk)
}
func (o *desiredSet) fromCache(cache cache.SharedInformer, namespace, name string, gvk schema.GroupVersionKind) (runtime.Object, error) {
var key string
if namespace == "" {
key = name
} else {
key = namespace + "/" + name
}
item, ok, err := cache.GetStore().GetByKey(key)
if err != nil {
return nil, err
} else if !ok {
return nil, notFound(name, gvk)
} else if ro, ok := item.(runtime.Object); ok {
return ro, nil
}
return nil, notFound(name, gvk)
}
func (o desiredSet) PurgeOrphan(obj runtime.Object) error {
if obj == nil {
return nil
}
meta, err := meta.Accessor(obj)
if err != nil {
return err
}
if _, err := o.FindOwner(obj); apierrors.IsNotFound(err) {
gvk, err := gvk.Get(obj)
if err != nil {
return err
}
o.strictCaching = false
_, client, err := o.getControllerAndClient(meta.GetName(), gvk)
if err != nil {
return err
}
if meta.GetNamespace() == "" {
return client.Delete(o.ctx, meta.GetName(), metav1.DeleteOptions{})
} else {
return client.Namespace(meta.GetNamespace()).Delete(o.ctx, meta.GetName(), metav1.DeleteOptions{})
}
} else if err == ErrOwnerNotFound {
return nil
} else if err != nil {
return err
}
return nil
}

View File

@ -28,19 +28,27 @@ var (
)
func (o *desiredSet) getControllerAndClient(debugID string, gvk schema.GroupVersionKind) (cache.SharedIndexInformer, dynamic.NamespaceableResourceInterface, error) {
// client needs to be accessed first so that the gvk->gvr mapping gets cached
client, err := o.a.clients.client(gvk)
if err != nil {
return nil, nil, err
}
informer, ok := o.pruneTypes[gvk]
if !ok {
informer = o.a.informers[gvk]
}
if informer == nil && o.informerFactory != nil {
newInformer, err := o.informerFactory.Get(gvk, o.a.clients.gvr(gvk))
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to construct informer for %v for %s", gvk, debugID)
}
informer = newInformer
}
if informer == nil && o.strictCaching {
return nil, nil, fmt.Errorf("failed to find informer for %s for %s", gvk, debugID)
}
client, err := o.a.clients.client(gvk)
if err != nil {
return nil, nil, err
}
return informer, client, nil
}
@ -206,6 +214,8 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro
patcher = o.createPatcher(client)
}
reconciler := o.reconcilers[gvk]
existing, err := o.list(controller, client, set)
if err != nil {
o.err(errors.Wrapf(err, "failed to list %s for %s", gvk, debugID))
@ -214,6 +224,26 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro
toCreate, toDelete, toUpdate := compareSets(existing, objs)
if o.createPlan {
o.plan.Create[gvk] = toCreate
o.plan.Delete[gvk] = toDelete
reconciler = nil
patcher = func(namespace, name string, pt types2.PatchType, data []byte) (runtime.Object, error) {
data, err := sanitizePatch(data, true)
if err != nil {
return nil, err
}
if string(data) != "{}" {
o.plan.Update.Add(gvk, namespace, name, string(data))
}
return nil, nil
}
toCreate = nil
toDelete = nil
}
createF := func(k objectset.ObjectKey) {
obj := objs[k]
obj, err := prepareObjectForCreate(gvk, obj)
@ -248,7 +278,7 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro
}
updateF := func(k objectset.ObjectKey) {
err := o.compareObjects(gvk, patcher, client, debugID, existing[k], objs[k], len(toCreate) > 0 || len(toDelete) > 0)
err := o.compareObjects(gvk, reconciler, patcher, client, debugID, existing[k], objs[k], len(toCreate) > 0 || len(toDelete) > 0)
if err == ErrReplace {
deleteF(k, true)
o.err(fmt.Errorf("DesiredSet - Replace Wait %s %s for %s", gvk, k, debugID))

View File

@ -4,6 +4,7 @@ import (
"reflect"
"time"
"github.com/rancher/wrangler/pkg/generic"
"github.com/sirupsen/logrus"
)
@ -14,7 +15,7 @@ func (c Cond) GetStatus(obj interface{}) string {
}
func (c Cond) SetError(obj interface{}, reason string, err error) {
if err == nil {
if err == nil || err == generic.ErrSkip {
c.True(obj)
c.Message(obj, "")
c.Reason(obj, reason)
@ -153,6 +154,9 @@ func getTS(obj interface{}, condName string) string {
}
func setStatus(obj interface{}, condName, status string) {
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
panic("obj passed must be a pointer")
}
cond := findOrCreateCond(obj, condName)
setValue(cond, "Status", status)
}
@ -167,6 +171,9 @@ func setValue(cond reflect.Value, fieldName, newValue string) {
func findOrNotCreateCond(obj interface{}, condName string) *reflect.Value {
condSlice := getValue(obj, "Status", "Conditions")
if !condSlice.IsValid() {
condSlice = getValue(obj, "Conditions")
}
return findCond(obj, condSlice, condName)
}
@ -224,6 +231,9 @@ func getValue(obj interface{}, name ...string) reflect.Value {
}
func getFieldValue(v reflect.Value, name ...string) reflect.Value {
if !v.IsValid() {
return v
}
field := v.FieldByName(name[0])
if len(name) == 1 {
return field

View File

@ -85,18 +85,23 @@ func NewFactoryFromConfigOrDie(config *rest.Config) *Factory {
}
func NewFactoryFromConfig(config *rest.Config) (*Factory, error) {
cs, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
informerFactory := informers.NewSharedInformerFactory(cs, 2*time.Hour)
return NewFactory(cs, informerFactory), nil
return NewFactoryFromConfigWithOptions(config, nil)
}
func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) {
if namespace == "" {
return NewFactoryFromConfig(config)
return NewFactoryFromConfigWithOptions(config, &FactoryOptions{
Namespace: namespace,
})
}
type FactoryOptions struct {
Namespace string
Resync time.Duration
}
func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) {
if opts == nil {
opts = &FactoryOptions{}
}
cs, err := clientset.NewForConfig(config)
@ -104,7 +109,17 @@ func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*
return nil, err
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, 2*time.Hour, informers.WithNamespace(namespace))
resync := opts.Resync
if resync == 0 {
resync = 2*time.Hour
}
if opts.Namespace == "" {
informerFactory := informers.NewSharedInformerFactory(cs, resync)
return NewFactory(cs, informerFactory), nil
}
informerFactory := informers.NewSharedInformerFactoryWithOptions(cs, resync, informers.WithNamespace(opts.Namespace))
return NewFactory(cs, informerFactory), nil
}

View File

@ -333,6 +333,7 @@ func Register{{.type}}GeneratingHandler(ctx context.Context, controller {{.type}
if opts != nil {
statusHandler.opts = *opts
}
controller.OnChange(ctx, name, statusHandler.Remove)
Register{{.type}}StatusHandler(ctx, controller, condition, name, statusHandler.Handle)
}
@ -347,7 +348,7 @@ func (a *{{.lowerName}}StatusHandler) sync(key string, obj *{{.version}}.{{.type
return obj, nil
}
origStatus := obj.Status
origStatus := obj.Status.DeepCopy()
obj = obj.DeepCopy()
newStatus, err := a.handler(obj, obj.Status)
if err != nil {
@ -355,16 +356,16 @@ func (a *{{.lowerName}}StatusHandler) sync(key string, obj *{{.version}}.{{.type
newStatus = *origStatus.DeepCopy()
}
obj.Status = newStatus
if a.condition != "" {
if errors.IsConflict(err) {
a.condition.SetError(obj, "", nil)
a.condition.SetError(&newStatus, "", nil)
} else {
a.condition.SetError(obj, "", err)
a.condition.SetError(&newStatus, "", err)
}
}
if !equality.Semantic.DeepEqual(origStatus, obj.Status) {
if !equality.Semantic.DeepEqual(origStatus, &newStatus) {
var newErr error
obj.Status = newStatus
obj, newErr = a.client.UpdateStatus(obj)
if err == nil {
err = newErr
@ -381,29 +382,28 @@ type {{.lowerName}}GeneratingHandler struct {
name string
}
func (a *{{.lowerName}}GeneratingHandler) Remove(key string, obj *{{.version}}.{{.type}}) (*{{.version}}.{{.type}}, error) {
if obj != nil {
return obj, nil
}
obj = &{{.version}}.{{.type}}{}
obj.Namespace, obj.Name = kv.RSplit(key, "/")
obj.SetGroupVersionKind(a.gvk)
return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects()
}
func (a *{{.lowerName}}GeneratingHandler) Handle(obj *{{.version}}.{{.type}}, status {{.version}}.{{.statusType}}) ({{.version}}.{{.statusType}}, error) {
objs, newStatus, err := a.{{.type}}GeneratingHandler(obj, status)
if err != nil {
return newStatus, err
}
apply := a.apply
if !a.opts.DynamicLookup {
apply = apply.WithStrictCaching()
}
if !a.opts.AllowCrossNamespace && !a.opts.AllowClusterScoped {
apply = apply.WithSetOwnerReference(true, false).
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !a.opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return newStatus, apply.
return newStatus, generic.ConfigureApplyForObject(a.apply, obj, &a.opts).
WithOwner(obj).
WithSetID(a.name).
ApplyObjects(objs...)

View File

@ -3,6 +3,7 @@ package crd
import (
"context"
"reflect"
"strconv"
"strings"
"sync"
"time"
@ -16,7 +17,7 @@ import (
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
@ -52,8 +53,107 @@ func (c CRD) WithSchemaFromStruct(obj interface{}) CRD {
return c
}
func (c CRD) WithCustomColumn(columns []v1beta1.CustomResourceColumnDefinition) CRD {
c.Columns = columns
func (c CRD) WithColumn(name, path string) CRD {
c.Columns = append(c.Columns, v1beta1.CustomResourceColumnDefinition{
Name: name,
Type: "string",
Priority: 0,
JSONPath: path,
})
return c
}
func getType(obj interface{}) reflect.Type {
if t, ok := obj.(reflect.Type); ok {
return t
}
t := reflect.TypeOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
func (c CRD) WithColumnsFromStruct(obj interface{}) CRD {
c.Columns = append(c.Columns, readCustomColumns(getType(obj), ".")...)
return c
}
func fieldName(f reflect.StructField) string {
jsonTag := f.Tag.Get("json")
if jsonTag == "-" {
return ""
}
name := strings.Split(jsonTag, ",")[0]
if name == "" {
return f.Name
}
return name
}
func tagToColumn(f reflect.StructField) (v1beta1.CustomResourceColumnDefinition, bool) {
c := v1beta1.CustomResourceColumnDefinition{
Name: f.Name,
Type: "string",
}
columnDef, ok := f.Tag.Lookup("column")
if !ok {
return c, false
}
for k, v := range kv.SplitMap(columnDef, ",") {
switch k {
case "name":
c.Name = v
case "type":
c.Type = v
case "format":
c.Format = v
case "description":
c.Description = v
case "priority":
p, _ := strconv.Atoi(v)
c.Priority = int32(p)
case "jsonpath":
c.JSONPath = v
}
}
return c, true
}
func readCustomColumns(t reflect.Type, path string) (result []v1beta1.CustomResourceColumnDefinition) {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fieldName := fieldName(f)
if fieldName == "" {
continue
}
t := f.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() == reflect.Struct {
if f.Anonymous {
result = append(result, readCustomColumns(t, path)...)
} else {
result = append(result, readCustomColumns(t, path+"."+fieldName)...)
}
} else {
if col, ok := tagToColumn(f); ok {
result = append(result, col)
}
}
}
return result
}
func (c CRD) WithCustomColumn(columns ...v1beta1.CustomResourceColumnDefinition) CRD {
c.Columns = append(c.Columns, columns...)
return c
}
@ -79,10 +179,7 @@ func (c CRD) WithShortNames(shortNames ...string) CRD {
func (c CRD) ToCustomResourceDefinition() (apiext.CustomResourceDefinition, error) {
if c.SchemaObject != nil && c.GVK.Kind == "" {
t := reflect.TypeOf(c.SchemaObject)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
t := getType(c.SchemaObject)
c.GVK.Kind = t.Name()
}
@ -250,6 +347,13 @@ func (f *Factory) CreateCRDs(ctx context.Context, crds ...CRD) (map[schema.Group
return nil, nil
}
if ok, err := f.ensureAccess(ctx); err != nil {
return nil, err
} else if !ok {
logrus.Infof("No access to list CRDs, assuming CRDs are pre-created.")
return nil, err
}
crdStatus := map[schema.GroupVersionKind]*apiext.CustomResourceDefinition{}
ready, err := f.getReadyCRDs(ctx)
@ -341,7 +445,7 @@ func (f *Factory) createCRD(ctx context.Context, crdDef CRD, ready map[string]*a
}
logrus.Infof("Creating CRD %s", crd.Name)
if newCrd, err := f.CRDClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(ctx, &crd, metav1.CreateOptions{}); errors.IsAlreadyExists(err) {
if newCrd, err := f.CRDClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(ctx, &crd, metav1.CreateOptions{}); apierrors.IsAlreadyExists(err) {
return f.CRDClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(ctx, crd.Name, metav1.GetOptions{})
} else if err != nil {
return nil, err
@ -350,6 +454,14 @@ func (f *Factory) createCRD(ctx context.Context, crdDef CRD, ready map[string]*a
}
}
func (f *Factory) ensureAccess(ctx context.Context) (bool, error) {
_, err := f.CRDClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
if apierrors.IsForbidden(err) {
return false, nil
}
return true, err
}
func (f *Factory) getReadyCRDs(ctx context.Context) (map[string]*apiext.CustomResourceDefinition, error) {
list, err := f.CRDClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
if err != nil {

24
vendor/github.com/rancher/wrangler/pkg/data/merge.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package data
func MergeMaps(base, overlay map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range base {
result[k] = v
}
for k, v := range overlay {
if baseMap, overlayMap, bothMaps := bothMaps(result[k], v); bothMaps {
v = MergeMaps(baseMap, overlayMap)
}
result[k] = v
}
return result
}
func bothMaps(left, right interface{}) (map[string]interface{}, map[string]interface{}, bool) {
leftMap, ok := left.(map[string]interface{})
if !ok {
return nil, nil, false
}
rightMap, ok := right.(map[string]interface{})
return leftMap, rightMap, ok
}

View File

@ -1,7 +1,39 @@
package generic
import (
"github.com/rancher/wrangler/pkg/apply"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type GeneratingHandlerOptions struct {
AllowCrossNamespace bool
AllowClusterScoped bool
NoOwnerReference bool
DynamicLookup bool
}
func ConfigureApplyForObject(apply apply.Apply, obj metav1.Object, opts *GeneratingHandlerOptions) apply.Apply {
if opts == nil {
opts = &GeneratingHandlerOptions{}
}
if opts.DynamicLookup {
apply = apply.WithDynamicLookup()
}
if opts.NoOwnerReference {
apply = apply.WithSetOwnerReference(true, false)
}
if opts.AllowCrossNamespace && !opts.AllowClusterScoped {
apply = apply.
WithDefaultNamespace(obj.GetNamespace()).
WithListerNamespace(obj.GetNamespace())
}
if !opts.AllowClusterScoped {
apply = apply.WithRestrictClusterScoped()
}
return apply
}

View File

@ -5,6 +5,7 @@ import (
"reflect"
"strings"
errors2 "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
)
@ -25,7 +26,7 @@ func (h *Handlers) Handle(key string, obj runtime.Object) (runtime.Object, error
for _, handler := range h.handlers {
newObj, err := handler.handler(key, obj)
if err != nil {
if err != nil && errors2.Cause(err) != ErrSkip {
errs = append(errs, &handlerError{
HandlerName: handler.name,
Err: err,

View File

@ -27,7 +27,16 @@ func Get(obj runtime.Object) (schema.GroupVersionKind, error) {
return gvks[0], nil
}
func Set(obj runtime.Object) error {
func Set(objs ...runtime.Object) error {
for _, obj := range objs {
if err := setObject(obj); err != nil {
return err
}
}
return nil
}
func setObject(obj runtime.Object) error {
gvk := obj.GetObjectKind().GroupVersionKind()
if gvk.Kind != "" {
return nil

View File

@ -34,6 +34,8 @@ func (o ObjectKey) String() string {
return fmt.Sprintf("%s/%s", o.Namespace, o.Name)
}
type ObjectKeyByGVK map[schema.GroupVersionKind][]ObjectKey
type ObjectByGVK map[schema.GroupVersionKind]map[ObjectKey]runtime.Object
func (o ObjectByGVK) Add(obj runtime.Object) (schema.GroupVersionKind, error) {
@ -69,14 +71,19 @@ type ObjectSet struct {
gvkSeen map[schema.GroupVersionKind]bool
}
func NewObjectSet() *ObjectSet {
return &ObjectSet{
func NewObjectSet(objs ...runtime.Object) *ObjectSet {
os := &ObjectSet{
objects: ObjectByGVK{},
gvkSeen: map[schema.GroupVersionKind]bool{},
}
os.Add(objs...)
return os
}
func (o *ObjectSet) ObjectsByGVK() ObjectByGVK {
if o == nil {
return nil
}
return o.objects
}
@ -126,6 +133,10 @@ func (o *ObjectSet) Len() int {
return len(o.objects)
}
func (o *ObjectSet) GVKs() []schema.GroupVersionKind {
return o.GVKOrder()
}
func (o *ObjectSet) GVKOrder(known ...schema.GroupVersionKind) []schema.GroupVersionKind {
var rest []schema.GroupVersionKind

View File

@ -36,7 +36,7 @@ func applyStrategicMergePatch(original, patch []byte, lookup strategicpatch.Look
if err := json.Unmarshal(patch, &patchMap); err != nil {
return nil, err
}
patchedMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, lookup)
patchedMap, err := strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, lookup)
if err != nil {
return nil, err
}

View File

@ -1,6 +1,7 @@
package openapi
import (
"encoding/json"
"fmt"
types "github.com/rancher/wrangler/pkg/schemas"
@ -8,6 +9,14 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
)
var (
blacklistFields = map[string]bool{
"kind": true,
"apiVersion": true,
"metadata": true,
}
)
func MustGenerate(obj interface{}) *v1beta1.JSONSchemaProps {
if obj == nil {
return nil
@ -42,178 +51,169 @@ func toOpenAPI(name string, schemas *types.Schemas) (*v1beta1.JSONSchemaProps, e
delete(newSchema.ResourceFields, "kind")
delete(newSchema.ResourceFields, "apiVersion")
delete(newSchema.ResourceFields, "metadata")
return parseSchema(newSchema, schemas)
return schemaToProps(newSchema, schemas, map[string]bool{})
}
func parseSchema(schema *types.Schema, schemas *types.Schemas) (*v1beta1.JSONSchemaProps, error) {
jsp := &v1beta1.JSONSchemaProps{
Description: schema.Description,
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{},
func populateField(fieldJSP *v1beta1.JSONSchemaProps, f *types.Field) error {
fieldJSP.Description = f.Description
// don't reset this to not nullable
if f.Nullable {
fieldJSP.Nullable = f.Nullable
}
fieldJSP.MinLength = f.MinLength
fieldJSP.MaxLength = f.MaxLength
if f.Type == "string" && len(f.Options) > 0 {
for _, opt := range append(f.Options, "") {
bytes, err := json.Marshal(&opt)
if err != nil {
return err
}
fieldJSP.Enum = append(fieldJSP.Enum, v1beta1.JSON{
Raw: bytes,
})
}
}
for name, f := range schema.ResourceFields {
fieldJSP := v1beta1.JSONSchemaProps{
Description: f.Description,
Nullable: f.Nullable,
MinLength: f.MinLength,
MaxLength: f.MaxLength,
if len(f.InvalidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[^%s]*$", f.InvalidChars)
}
if len(f.ValidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[%s]*$", f.ValidChars)
}
if f.Min != nil {
fl := float64(*f.Min)
fieldJSP.Minimum = &fl
}
if f.Max != nil {
fl := float64(*f.Max)
fieldJSP.Maximum = &fl
}
return nil
}
func typeToProps(typeName string, schemas *types.Schemas, inflight map[string]bool) (*v1beta1.JSONSchemaProps, error) {
t, subType, schema, err := typeAndSchema(typeName, schemas)
if err != nil {
return nil, err
}
if schema != nil {
return schemaToProps(schema, schemas, inflight)
}
jsp := &v1beta1.JSONSchemaProps{}
switch t {
case "map":
additionalProps, err := typeToProps(subType, schemas, inflight)
if err != nil {
return nil, err
}
if len(f.Options) > 0 {
for _, opt := range f.Options {
fieldJSP.Enum = append(fieldJSP.Enum, v1beta1.JSON{
Raw: []byte(opt),
})
}
jsp.Type = "object"
jsp.Nullable = true
jsp.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
Schema: additionalProps,
}
if len(f.InvalidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[^%s]*$", f.InvalidChars)
case "array":
items, err := typeToProps(subType, schemas, inflight)
if err != nil {
return nil, err
}
if len(f.ValidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[%s]*$", f.ValidChars)
jsp.Type = "array"
jsp.Nullable = true
jsp.Items = &v1beta1.JSONSchemaPropsOrArray{
Schema: items,
}
if f.Min != nil {
fl := float64(*f.Min)
fieldJSP.Minimum = &fl
}
if f.Max != nil {
fl := float64(*f.Max)
fieldJSP.Maximum = &fl
}
// default is not support by k8s
//
//if f.Default != nil {
// bytes, err := json.Marshal(f.Default)
// if err != nil {
// return nil, err
// }
// fieldJSP.Default = &v1beta1.JSON{
// Raw: bytes,
// }
//}
if f.Required {
fieldJSP.Required = append(fieldJSP.Required, name)
}
if definition.IsMapType(f.Type) {
fieldJSP.Type = "object"
subType := definition.SubType(f.Type)
subType, schema, err := typeAndSchema(subType, schemas)
if err != nil {
return nil, err
}
if schema == nil {
fieldJSP.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
Schema: &v1beta1.JSONSchemaProps{
Type: subType,
},
}
} else {
subObject, err := parseSchema(schema, schemas)
if err != nil {
return nil, err
}
fieldJSP.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
Schema: subObject,
}
}
} else if definition.IsArrayType(f.Type) {
fieldJSP.Type = "array"
subType := definition.SubType(f.Type)
subType, schema, err := typeAndSchema(subType, schemas)
if err != nil {
return nil, err
}
if schema == nil {
fieldJSP.Items = &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: subType,
},
}
} else {
subObject, err := parseSchema(schema, schemas)
if err != nil {
return nil, err
}
fieldJSP.Items = &v1beta1.JSONSchemaPropsOrArray{
Schema: subObject,
}
}
} else {
typeName, schema, err := typeAndSchema(f.Type, schemas)
if err != nil {
return nil, err
}
if schema == nil {
fieldJSP.Type = typeName
} else {
fieldJSP.Type = "object"
subObject, err := parseSchema(schema, schemas)
if err != nil {
return nil, err
}
fieldJSP.Properties = subObject.Properties
}
}
jsp.Properties[name] = fieldJSP
default:
jsp.Type = t
}
return jsp, nil
}
func typeAndSchema(typeName string, schemas *types.Schemas) (string, *types.Schema, error) {
switch typeName {
// TODO: in v1 set the x- header for this
case "intOrString":
return "string", nil, nil
case "int":
return "integer", nil, nil
case "float":
return "number", nil, nil
case "string":
return "string", nil, nil
case "date":
return "string", nil, nil
case "enum":
return "string", nil, nil
case "password":
return "string", nil, nil
case "hostname":
return "string", nil, nil
case "boolean":
return "boolean", nil, nil
case "json":
return "object", nil, nil
func schemaToProps(schema *types.Schema, schemas *types.Schemas, inflight map[string]bool) (*v1beta1.JSONSchemaProps, error) {
jsp := &v1beta1.JSONSchemaProps{
Description: schema.Description,
Type: "object",
}
if inflight[schema.ID] {
return jsp, nil
}
inflight[schema.ID] = true
defer delete(inflight, schema.ID)
jsp.Properties = map[string]v1beta1.JSONSchemaProps{}
for name, f := range schema.ResourceFields {
fieldJSP, err := typeToProps(f.Type, schemas, inflight)
if err != nil {
return nil, err
}
if err := populateField(fieldJSP, &f); err != nil {
return nil, err
}
if f.Required {
jsp.Required = append(jsp.Required, name)
}
jsp.Properties[name] = *fieldJSP
}
return jsp, nil
}
func typeAndSchema(typeName string, schemas *types.Schemas) (string, string, *types.Schema, error) {
if definition.IsReferenceType(typeName) {
return "string", nil, nil
return "string", "", nil, nil
}
if definition.IsArrayType(typeName) {
return "array", nil, nil
return "array", definition.SubType(typeName), nil, nil
}
if definition.IsMapType(typeName) {
return "map", definition.SubType(typeName), nil, nil
}
switch typeName {
// TODO: in v1 set the x- header for this
case "intOrString":
return "string", "", nil, nil
case "int":
return "integer", "", nil, nil
case "float":
return "number", "", nil, nil
case "string":
return "string", "", nil, nil
case "date":
return "string", "", nil, nil
case "enum":
return "string", "", nil, nil
case "base64":
return "string", "", nil, nil
case "password":
return "string", "", nil, nil
case "hostname":
return "string", "", nil, nil
case "boolean":
return "boolean", "", nil, nil
case "json":
return "object", "", nil, nil
}
schema := schemas.Schema(typeName)
if schema == nil {
return "", nil, fmt.Errorf("failed to find schema %s", typeName)
return "", "", nil, fmt.Errorf("failed to find schema %s", typeName)
}
if schema.InternalSchema != nil {
return "", schema.InternalSchema, nil
return "", "", schema.InternalSchema, nil
}
return "", schema, nil
return "", "", schema, nil
}

View File

@ -64,23 +64,25 @@ func (s *Schemas) MustImportAndCustomize(obj interface{}, f func(*Schema), exter
MustCustomizeType(obj, f)
}
func getType(obj interface{}) reflect.Type {
if t, ok := obj.(reflect.Type); ok {
return t
}
t := reflect.TypeOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
func (s *Schemas) Import(obj interface{}, externalOverrides ...interface{}) (*Schema, error) {
var types []reflect.Type
for _, override := range externalOverrides {
types = append(types, reflect.TypeOf(override))
}
var (
v = reflect.ValueOf(obj)
t reflect.Type
)
if v.Kind() == reflect.Ptr {
t = v.Elem().Type()
} else {
t = v.Type()
types = append(types, getType(override))
}
t := getType(obj)
return s.importType(t, types...)
}
@ -320,6 +322,9 @@ func (s *Schemas) readFields(schema *Schema, t reflect.Type) error {
}
if hasType && hasMeta {
delete(schema.ResourceFields, "kind")
delete(schema.ResourceFields, "apiVersion")
delete(schema.ResourceFields, "metadata")
schema.CollectionMethods = []string{"GET", "POST"}
schema.ResourceMethods = []string{"GET", "PUT", "DELETE"}
}
@ -357,7 +362,11 @@ func (s *Schemas) processFieldsMappers(t reflect.Type, fieldName string, schema
}
func applyTag(structField *reflect.StructField, field *Field) error {
for _, part := range strings.Split(structField.Tag.Get("norman"), ",") {
t, ok := structField.Tag.Lookup("wrangler")
if !ok {
t = structField.Tag.Get("norman")
}
for _, part := range strings.Split(t, ",") {
if part == "" {
continue
}

6
vendor/modules.txt vendored
View File

@ -716,7 +716,7 @@ github.com/rancher/dynamiclistener/factory
github.com/rancher/dynamiclistener/storage/file
github.com/rancher/dynamiclistener/storage/kubernetes
github.com/rancher/dynamiclistener/storage/memory
# github.com/rancher/helm-controller v0.4.2-0.20200326195131-eb51d4fa9d8d
# github.com/rancher/helm-controller v0.5.0
github.com/rancher/helm-controller/pkg/apis/helm.cattle.io
github.com/rancher/helm-controller/pkg/apis/helm.cattle.io/v1
github.com/rancher/helm-controller/pkg/generated/clientset/versioned
@ -745,7 +745,7 @@ github.com/rancher/kine/pkg/server
github.com/rancher/kine/pkg/tls
# github.com/rancher/remotedialer v0.2.0
github.com/rancher/remotedialer
# github.com/rancher/wrangler v0.5.4-0.20200326191509-4054411d9736
# github.com/rancher/wrangler v0.6.1
github.com/rancher/wrangler/pkg/apply
github.com/rancher/wrangler/pkg/apply/injectors
github.com/rancher/wrangler/pkg/cleanup
@ -773,7 +773,7 @@ github.com/rancher/wrangler/pkg/schemes
github.com/rancher/wrangler/pkg/signals
github.com/rancher/wrangler/pkg/slice
github.com/rancher/wrangler/pkg/start
# github.com/rancher/wrangler-api v0.5.1-0.20200326194427-c13310506d04
# github.com/rancher/wrangler-api v0.6.0
github.com/rancher/wrangler-api/pkg/generated/controllers/apps
github.com/rancher/wrangler-api/pkg/generated/controllers/apps/v1
github.com/rancher/wrangler-api/pkg/generated/controllers/batch