220 lines
6.2 KiB
Go
220 lines
6.2 KiB
Go
package reconcile
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
|
|
"github.com/go-logr/logr"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
// Deployment reconciles a k8s deployment object.
|
|
func Deployment(ctx context.Context, r client.Client, deployment *appsv1.Deployment, log logr.Logger) error {
|
|
foundDeployment := &appsv1.Deployment{}
|
|
justCreated := false
|
|
if err := r.Get(ctx, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, foundDeployment); err != nil {
|
|
if apierrs.IsNotFound(err) {
|
|
log.Info("Creating Deployment", "namespace", deployment.Namespace, "name", deployment.Name)
|
|
if err := r.Create(ctx, deployment); err != nil {
|
|
log.Error(err, "unable to create deployment")
|
|
return err
|
|
}
|
|
justCreated = true
|
|
} else {
|
|
log.Error(err, "error getting deployment")
|
|
return err
|
|
}
|
|
}
|
|
if !justCreated && CopyDeploymentSetFields(deployment, foundDeployment) {
|
|
log.Info("Updating Deployment", "namespace", deployment.Namespace, "name", deployment.Name)
|
|
if err := r.Update(ctx, foundDeployment); err != nil {
|
|
log.Error(err, "unable to update deployment")
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Service reconciles a k8s service object.
|
|
func Service(ctx context.Context, r client.Client, service *corev1.Service, log logr.Logger) error {
|
|
foundService := &corev1.Service{}
|
|
justCreated := false
|
|
if err := r.Get(ctx, types.NamespacedName{Name: service.Name, Namespace: service.Namespace}, foundService); err != nil {
|
|
if apierrs.IsNotFound(err) {
|
|
log.Info("Creating Service", "namespace", service.Namespace, "name", service.Name)
|
|
if err = r.Create(ctx, service); err != nil {
|
|
log.Error(err, "unable to create service")
|
|
return err
|
|
}
|
|
justCreated = true
|
|
} else {
|
|
log.Error(err, "error getting service")
|
|
return err
|
|
}
|
|
}
|
|
if !justCreated && CopyServiceFields(service, foundService) {
|
|
log.Info("Updating Service\n", "namespace", service.Namespace, "name", service.Name)
|
|
if err := r.Update(ctx, foundService); err != nil {
|
|
log.Error(err, "unable to update Service")
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VirtualService reconciles an Istio virtual service object.
|
|
func VirtualService(ctx context.Context, r client.Client, virtualServiceName, namespace string, virtualservice *unstructured.Unstructured, log logr.Logger) error {
|
|
foundVirtualService := &unstructured.Unstructured{}
|
|
foundVirtualService.SetAPIVersion("networking.istio.io/v1alpha3")
|
|
foundVirtualService.SetKind("VirtualService")
|
|
justCreated := false
|
|
if err := r.Get(ctx, types.NamespacedName{Name: virtualServiceName, Namespace: namespace}, foundVirtualService); err != nil {
|
|
if apierrs.IsNotFound(err) {
|
|
log.Info("Creating virtual service", "namespace", namespace, "name", virtualServiceName)
|
|
if err := r.Create(ctx, virtualservice); err != nil {
|
|
log.Error(err, "unable to create virtual service")
|
|
return err
|
|
}
|
|
justCreated = true
|
|
} else {
|
|
log.Error(err, "error getting virtual service")
|
|
return err
|
|
}
|
|
}
|
|
if !justCreated && CopyVirtualService(virtualservice, foundVirtualService) {
|
|
log.Info("Updating virtual service", "namespace", namespace, "name", virtualServiceName)
|
|
if err := r.Update(ctx, foundVirtualService); err != nil {
|
|
log.Error(err, "unable to update virtual service")
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reference: https://github.com/pwittrock/kubebuilder-workshop/blob/master/pkg/util/util.go
|
|
|
|
// CopyStatefulSetFields copies the owned fields from one StatefulSet to another
|
|
// Returns true if the fields copied from don't match to.
|
|
func CopyStatefulSetFields(from, to *appsv1.StatefulSet) bool {
|
|
requireUpdate := false
|
|
for k, v := range to.Labels {
|
|
if from.Labels[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Labels = from.Labels
|
|
|
|
for k, v := range to.Annotations {
|
|
if from.Annotations[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Annotations = from.Annotations
|
|
|
|
if *from.Spec.Replicas != *to.Spec.Replicas {
|
|
*to.Spec.Replicas = *from.Spec.Replicas
|
|
requireUpdate = true
|
|
}
|
|
|
|
if !reflect.DeepEqual(to.Spec.Template.Spec, from.Spec.Template.Spec) {
|
|
requireUpdate = true
|
|
}
|
|
to.Spec.Template.Spec = from.Spec.Template.Spec
|
|
|
|
return requireUpdate
|
|
}
|
|
|
|
func CopyDeploymentSetFields(from, to *appsv1.Deployment) bool {
|
|
requireUpdate := false
|
|
for k, v := range to.Labels {
|
|
if from.Labels[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Labels = from.Labels
|
|
|
|
for k, v := range to.Annotations {
|
|
if from.Annotations[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Annotations = from.Annotations
|
|
|
|
if from.Spec.Replicas != to.Spec.Replicas {
|
|
to.Spec.Replicas = from.Spec.Replicas
|
|
requireUpdate = true
|
|
}
|
|
|
|
if !reflect.DeepEqual(to.Spec.Template.Spec, from.Spec.Template.Spec) {
|
|
requireUpdate = true
|
|
}
|
|
to.Spec.Template.Spec = from.Spec.Template.Spec
|
|
|
|
return requireUpdate
|
|
}
|
|
|
|
// CopyServiceFields copies the owned fields from one Service to another
|
|
func CopyServiceFields(from, to *corev1.Service) bool {
|
|
requireUpdate := false
|
|
for k, v := range to.Labels {
|
|
if from.Labels[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Labels = from.Labels
|
|
|
|
for k, v := range to.Annotations {
|
|
if from.Annotations[k] != v {
|
|
requireUpdate = true
|
|
}
|
|
}
|
|
to.Annotations = from.Annotations
|
|
|
|
// Don't copy the entire Spec, because we can't overwrite the clusterIp field
|
|
|
|
if !reflect.DeepEqual(to.Spec.Selector, from.Spec.Selector) {
|
|
requireUpdate = true
|
|
}
|
|
to.Spec.Selector = from.Spec.Selector
|
|
|
|
if !reflect.DeepEqual(to.Spec.Ports, from.Spec.Ports) {
|
|
requireUpdate = true
|
|
}
|
|
to.Spec.Ports = from.Spec.Ports
|
|
|
|
return requireUpdate
|
|
}
|
|
|
|
// Copy configuration related fields to another instance and returns true if there
|
|
// is a diff and thus needs to update.
|
|
func CopyVirtualService(from, to *unstructured.Unstructured) bool {
|
|
fromSpec, found, err := unstructured.NestedMap(from.Object, "spec")
|
|
if !found {
|
|
return false
|
|
}
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
toSpec, found, err := unstructured.NestedMap(to.Object, "spec")
|
|
if !found || err != nil {
|
|
unstructured.SetNestedMap(to.Object, fromSpec, "spec")
|
|
return true
|
|
}
|
|
|
|
requiresUpdate := !reflect.DeepEqual(fromSpec, toSpec)
|
|
if requiresUpdate {
|
|
unstructured.SetNestedMap(to.Object, fromSpec, "spec")
|
|
}
|
|
return requiresUpdate
|
|
}
|