func (r *endpointConfigReconciler) determineActionForEndpointConfig(desired *endpointconfigv1.EndpointConfig, actualEndpointConfigs map[string]*endpointconfigv1.EndpointConfig) map[ReconcileAction]map[string]*endpointconfigv1.EndpointConfig {
	desiredEndpointConfigs := map[string]*endpointconfigv1.EndpointConfig{}
	if desired != nil {
		desiredEndpointConfigs[desired.ObjectMeta.GetName()] = desired
	}
	actions := map[ReconcileAction]map[string]*endpointconfigv1.EndpointConfig{
		NeedsCreate: map[string]*endpointconfigv1.EndpointConfig{},
		NeedsDelete: map[string]*endpointconfigv1.EndpointConfig{},
		NeedsNoop:   map[string]*endpointconfigv1.EndpointConfig{},
		NeedsUpdate: map[string]*endpointconfigv1.EndpointConfig{},
	}
	visited := map[string]*endpointconfigv1.EndpointConfig{}
	for name, desiredEndpointConfig := range desiredEndpointConfigs {
		if actualEndpointConfig, exists := actualEndpointConfigs[name]; exists {
			if reflect.DeepEqual(desiredEndpointConfig.Spec, actualEndpointConfig.Spec) {
				actions[NeedsNoop][name] = desiredEndpointConfig
			} else {
				targetEndpointConfig := actualEndpointConfigs[name].DeepCopy()
				targetEndpointConfig.Spec = desiredEndpointConfig.Spec
				actions[NeedsUpdate][name] = targetEndpointConfig
			}
		} else {
			actions[NeedsCreate][name] = desiredEndpointConfig
		}
		visited[name] = desiredEndpointConfig
	}
	for name, actualEndpointConfig := range actualEndpointConfigs {
		if _, visited := visited[name]; visited {
			continue
		}
		actions[NeedsDelete][name] = actualEndpointConfig
	}
	return actions
}
Determine the action necessary to bring actual state to desired state.
package hosting
import (
	"context"
	"fmt"
	"reflect"
	"github.com/go-logr/logr"
	"github.com/pkg/errors"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	commonv1 "github.com/aws/amazon-sagemaker-operator-for-k8s/api/v1/common"
	endpointconfigv1 "github.com/aws/amazon-sagemaker-operator-for-k8s/api/v1/endpointconfig"
	hostingv1 "github.com/aws/amazon-sagemaker-operator-for-k8s/api/v1/hostingdeployment"
	modelv1 "github.com/aws/amazon-sagemaker-operator-for-k8s/api/v1/model"
	. "github.com/aws/amazon-sagemaker-operator-for-k8s/controllers"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type EndpointConfigReconcilerProvider func(client.Client, logr.Logger) EndpointConfigReconciler
func NewEndpointConfigReconciler(client client.Client, log logr.Logger) EndpointConfigReconciler {
	return &endpointConfigReconciler{
		k8sClient: client,
		log:       log.WithName("EndpointConfigReconciler"),
	}
}
type EndpointConfigReconciler interface {
	Reconcile(ctx context.Context, desiredDeployment *hostingv1.HostingDeployment, shouldDeleteUnusedResources bool) error
	GetSageMakerEndpointConfigName(ctx context.Context, desiredDeployment *hostingv1.HostingDeployment) (string, error)
}
type endpointConfigReconciler struct {
	k8sClient client.Client
	log       logr.Logger
}
var _ EndpointConfigReconciler = (*endpointConfigReconciler)(nil)
func (r *endpointConfigReconciler) Reconcile(ctx context.Context, desiredDeployment *hostingv1.HostingDeployment, shouldDeleteUnusedResources bool) error {
	r.log.Info("Reconciling EndpointConfigs")
	var err error
	var desiredEndpointConfig *endpointconfigv1.EndpointConfig
	if desiredEndpointConfig, err = r.extractDesiredEndpointConfigFromHostingDeployment(ctx, desiredDeployment); err != nil {
		return errors.Wrap(err, "Unable to interpret HostingDeployment endpoint config")
	}
	if desiredEndpointConfig != nil {
		r.log.Info("Desired endpoint config", "desired", desiredEndpointConfig)
	}
	var actualEndpointConfigs map[string]*endpointconfigv1.EndpointConfig
	if actualEndpointConfigs, err = r.getActualEndpointConfigsForHostingDeployment(ctx, desiredDeployment); err != nil {
		return errors.Wrap(err, "Unable to get actual endpoint config")
	}
	r.log.Info("Actual endpoint config", "actual", actualEndpointConfigs)
	actions := r.determineActionForEndpointConfig(desiredEndpointConfig, actualEndpointConfigs)
	for action, endpointConfigs := range actions {
		r.log.Info("action", "action", action, "ecs", getEndpointConfigNamesFromMap(endpointConfigs))
		switch action {
		case NeedsNoop:
		case NeedsCreate:
			for _, endpointConfig := range endpointConfigs {
				if err := r.k8sClient.Create(ctx, endpointConfig); err != nil {
					return errors.Wrapf(err, "Unable to create Kubernetes EndpointConfig '%s'", types.NamespacedName{
						Name:      endpointConfig.ObjectMeta.Name,
						Namespace: endpointConfig.ObjectMeta.Namespace,
					})
				}
			}
		case NeedsDelete:
			if !shouldDeleteUnusedResources {
				r.log.Info("Not deleting unused resources", "shouldDeleteUnusedResources", shouldDeleteUnusedResources)
				break
			}
			for _, endpointConfig := range endpointConfigs {
				if err := r.k8sClient.Delete(ctx, endpointConfig); err != nil {
					if !apierrs.IsNotFound(err) {
						return errors.Wrapf(err, "Unable to delete Kubernetes EndpointConfig '%s'", types.NamespacedName{
							Name:      endpointConfig.ObjectMeta.Name,
							Namespace: endpointConfig.ObjectMeta.Namespace,
						})
					}
				}
			}
		case NeedsUpdate:
			for _, endpointConfig := range endpointConfigs {
				if err = r.k8sClient.Update(ctx, endpointConfig); err != nil {
					return errors.Wrapf(err, "Unable to update Kubernetes EndpointConfig '%s'", types.NamespacedName{
						Name:      endpointConfig.ObjectMeta.Name,
						Namespace: endpointConfig.ObjectMeta.Namespace,
					})
				}
			}
		}
	}
	return nil
}
func getEndpointConfigNamesFromMap(m map[string]*endpointconfigv1.EndpointConfig) []string {
	keys := []string{}
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}
func (r *endpointConfigReconciler) extractDesiredEndpointConfigFromHostingDeployment(ctx context.Context, desiredDeployment *hostingv1.HostingDeployment) (*endpointconfigv1.EndpointConfig, error) {
	if !desiredDeployment.ObjectMeta.GetDeletionTimestamp().IsZero() {
		return nil, nil
	}
	productionVariants := []commonv1.ProductionVariant{}
	for _, variant := range desiredDeployment.Spec.ProductionVariants {
		if variant.VariantName == nil {
			return nil, fmt.Errorf("ProductionVariant has nil VariantName")
		}
		if variant.ModelName == nil {
			return nil, fmt.Errorf("ProductionVariant '%s' has nil ModelName", *variant.VariantName)
		}
		if resolved, err := r.resolveSageMakerModelName(ctx, *variant.ModelName, desiredDeployment); err != nil {
			return nil, err
		} else {
			variant.ModelName = resolved
		}
		productionVariants = append(productionVariants, variant)
	}
	namespacedName := GetSubresourceNamespacedName(desiredDeployment.ObjectMeta.GetName(), *desiredDeployment)
	ownershipLabels := GetResourceOwnershipLabelsForHostingDeployment(*desiredDeployment)
	desiredEndpointConfig := endpointconfigv1.EndpointConfig{
		ObjectMeta: metav1.ObjectMeta{
			Name:      namespacedName.Name,
			Namespace: namespacedName.Namespace,
			Labels:    ownershipLabels,
		},
		Spec: endpointconfigv1.EndpointConfigSpec{
			ProductionVariants: productionVariants,
			KmsKeyId:           GetOrDefault(desiredDeployment.Spec.KmsKeyId, ""),
			Tags:               commonv1.DeepCopyTagSlice(desiredDeployment.Spec.Tags),
			Region:             desiredDeployment.Spec.Region,
			SageMakerEndpoint:  desiredDeployment.Spec.SageMakerEndpoint,
		},
	}
	return &desiredEndpointConfig, nil
}
func (r *endpointConfigReconciler) resolveSageMakerModelName(ctx context.Context, k8sModelName string, desiredDeployment *hostingv1.HostingDeployment) (*string, error) {
	if desiredDeployment == nil {
		return nil, fmt.Errorf("Unable to resolve SageMaker model name for nil deployment")
	}
	namespacedName := GetSubresourceNamespacedName(k8sModelName, *desiredDeployment)
	var model modelv1.Model
	if err := r.k8sClient.Get(ctx, namespacedName, &model); err != nil {
		return nil, errors.Wrapf(err, "Unable to resolve SageMaker model name for model '%s'", namespacedName)
	}
	if model.Status.SageMakerModelName == "" {
		causedBy := ""
		if model.Status.Additional != "" {
			causedBy = fmt.Sprintf("Caused by: %s", model.Status.Additional)
		}
		return nil, fmt.Errorf("Model '%s' does not have a SageMakerModelName. %s", namespacedName, causedBy)
	}
	return &model.Status.SageMakerModelName, nil
}
func (r *endpointConfigReconciler) getActualEndpointConfigsForHostingDeployment(ctx context.Context, desiredDeployment *hostingv1.HostingDeployment) (map[string]*endpointconfigv1.EndpointConfig, error) {
	ownershipLabelSelector := client.MatchingLabels(GetResourceOwnershipLabelsForHostingDeployment(*desiredDeployment))
	endpointConfigs := &endpointconfigv1.EndpointConfigList{}
	if err := r.k8sClient.List(ctx, endpointConfigs, ownershipLabelSelector); err != nil {
		return nil, errors.Wrap(err, "Unable to get existing EndpointConfigs")
	}
	actualEndpointConfigs := map[string]*endpointconfigv1.EndpointConfig{}
	for i, endpointConfig := range endpointConfigs.Items {
		actualEndpointConfigs[endpointConfig.ObjectMeta.Name] = &endpointConfigs.Items[i]
	}
	return actualEndpointConfigs, nil
}