func (r *ModelReconciler) updateStatusWithAdditional(ctx reconcileRequestContext, status, additional string) error {
	ctx.Model.Status.Status = status
	ctx.Model.Status.Additional = additional
	ctx.Model.Status.ModelArn = ""
	ctx.Model.Status.SageMakerModelName = ""
	if ctx.ModelDescription != nil {
		if ctx.ModelDescription.ModelName != nil {
			ctx.Model.Status.SageMakerModelName = *ctx.ModelDescription.ModelName
		}
		if ctx.ModelDescription.ModelArn != nil {
			ctx.Model.Status.ModelArn = *ctx.ModelDescription.ModelArn
		}
	}
	ctx.Model.Status.LastCheckTime = Now()
	ctx.Log.Info("Updating status", "status", status, "additional", additional, "statusObject", ctx.Model.Status)
	if err := r.Status().Update(ctx, ctx.Model); err != nil {
		return errors.Wrap(err, "Unable to update status")
	}
	return nil
}
Update the status and LastCheckTime. This also updates the status fields obtained from
the SageMaker description, if they are present.
package model
import (
	"context"
	"fmt"
	"time"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sagemaker"
	"github.com/aws/aws-sdk-go/service/sagemaker/sagemakeriface"
	"github.com/go-logr/logr"
	"github.com/pkg/errors"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	modelv1 "github.com/aws/amazon-sagemaker-operator-for-k8s/api/v1/model"
	. "github.com/aws/amazon-sagemaker-operator-for-k8s/controllers"
	"github.com/aws/amazon-sagemaker-operator-for-k8s/controllers/controllertest"
	"github.com/aws/amazon-sagemaker-operator-for-k8s/controllers/sdkutil"
	"github.com/aws/amazon-sagemaker-operator-for-k8s/controllers/sdkutil/clientwrapper"
)
const (
	CreatedStatus = "Created"
	DeletedStatus = "Deleted"
	MaxModelNameLength = 63
	DefaultContainerDefinitionMode = "SingleModel"
)
type ModelReconciler struct {
	client.Client
	Log          logr.Logger
	PollInterval time.Duration
	awsConfigLoader       AWSConfigLoader
	createSageMakerClient SageMakerClientProvider
}
func NewModelReconciler(client client.Client, log logr.Logger, pollInterval time.Duration) *ModelReconciler {
	return &ModelReconciler{
		Client:       client,
		Log:          log,
		PollInterval: pollInterval,
		createSageMakerClient: func(cfg aws.Config) sagemakeriface.SageMakerAPI {
			return sagemaker.New(CreateNewAWSSessionFromConfig(cfg))
		},
		awsConfigLoader: NewAWSConfigLoader(),
	}
}
func (r *ModelReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
	ctx := reconcileRequestContext{
		Context: context.Background(),
		Log:     r.Log.WithValues("model", req.NamespacedName),
		Model:   new(modelv1.Model),
	}
	if err := r.Get(ctx, req.NamespacedName, ctx.Model); err != nil {
		ctx.Log.Info("Unable to fetch Model", "reason", err)
		if apierrs.IsNotFound(err) {
			return NoRequeue()
		} else {
			return RequeueImmediately()
		}
	}
	if err := r.reconcileModel(ctx); err != nil {
		ctx.Log.Info("Got error while reconciling, will retry", "err", err)
		return RequeueImmediately()
	} else {
		return RequeueAfterInterval(r.PollInterval, nil)
	}
}
type reconcileRequestContext struct {
	context.Context
	Log             logr.Logger
	SageMakerClient clientwrapper.SageMakerClientWrapper
	ModelDescription *sagemaker.DescribeModelOutput
	Model            *modelv1.Model
}
func (r *ModelReconciler) reconcileModel(ctx reconcileRequestContext) error {
	var err error
	if ctx.Model.Status.Status == "" {
		if err = r.updateStatus(ctx, InitializingJobStatus); err != nil {
			return err
		}
	}
	if err = r.initializeContext(&ctx); err != nil {
		return r.updateStatusAndReturnError(ctx, ErrorStatus, errors.Wrap(err, "Unable to initialize operator"))
	}
	if !HasDeletionTimestamp(ctx.Model.ObjectMeta) {
		if !ContainsString(ctx.Model.ObjectMeta.GetFinalizers(), SageMakerResourceFinalizerName) {
			ctx.Model.ObjectMeta.Finalizers = append(ctx.Model.ObjectMeta.Finalizers, SageMakerResourceFinalizerName)
			if err := r.Update(ctx, ctx.Model); err != nil {
				return errors.Wrap(err, "Failed to add finalizer")
			}
			ctx.Log.Info("Finalizer has been added")
		}
	}
	if ctx.ModelDescription, err = ctx.SageMakerClient.DescribeModel(ctx, generateModelName(ctx.Model)); err != nil {
		return r.updateStatusAndReturnError(ctx, ErrorStatus, errors.Wrap(err, "Unable to get SageMaker Model description"))
	}
	var action ReconcileAction
	if action, err = r.determineActionForModel(ctx.Model, ctx.ModelDescription); err != nil {
		return r.updateStatusAndReturnError(ctx, ErrorStatus, errors.Wrap(err, "Unable to determine action for SageMaker Model."))
	}
	ctx.Log.Info("Determined action for model", "action", action)
	if action == NeedsDelete || action == NeedsUpdate {
		if err = r.reconcileDeletion(ctx, ctx.SageMakerClient, generateModelName(ctx.Model)); err != nil {
			return r.updateStatusAndReturnError(ctx, ErrorStatus, errors.Wrap(err, "Unable to delete SageMaker model"))
		}
		ctx.ModelDescription = nil
	}
	if action == NeedsCreate || action == NeedsUpdate {
		if ctx.ModelDescription, err = r.reconcileCreation(ctx, ctx.SageMakerClient, ctx.Model.Spec, generateModelName(ctx.Model)); err != nil {
			return r.updateStatusAndReturnError(ctx, ErrorStatus, errors.Wrap(err, "Unable to create SageMaker model"))
		}
	}
	status := CreatedStatus
	if ctx.ModelDescription == nil {
		status = DeletedStatus
	}
	if err = r.updateStatus(ctx, status); err != nil {
		return err
	}
	if HasDeletionTimestamp(ctx.Model.ObjectMeta) {
		ctx.Model.ObjectMeta.Finalizers = RemoveString(ctx.Model.ObjectMeta.Finalizers, SageMakerResourceFinalizerName)
		if err := r.Update(ctx, ctx.Model); err != nil {
			return errors.Wrap(err, "Failed to remove finalizer")
		}
		ctx.Log.Info("Finalizer has been removed")
	}
	return nil
}
func (r *ModelReconciler) initializeContext(ctx *reconcileRequestContext) error {
	awsConfig, err := r.awsConfigLoader.LoadAWSConfigWithOverrides(ctx.Model.Spec.Region, ctx.Model.Spec.SageMakerEndpoint)
	if err != nil {
		ctx.Log.Error(err, "Error loading AWS config")
		return err
	}
	ctx.SageMakerClient = clientwrapper.NewSageMakerClientWrapper(r.createSageMakerClient(awsConfig))
	ctx.Log.Info("Loaded AWS config")
	if ctx.Model.Spec.PrimaryContainer != nil && ctx.Model.Spec.PrimaryContainer.Mode == nil {
		ctx.Model.Spec.PrimaryContainer.Mode = controllertest.ToStringPtr(DefaultContainerDefinitionMode)
	}
	for _, container := range ctx.Model.Spec.Containers {
		if container.Mode == nil {
			container.Mode = controllertest.ToStringPtr(DefaultContainerDefinitionMode)
		}
	}
	return nil
}
func (r *ModelReconciler) determineActionForModel(desiredModel *modelv1.Model, actualModel *sagemaker.DescribeModelOutput) (ReconcileAction, error) {
	if HasDeletionTimestamp(desiredModel.ObjectMeta) {
		if actualModel != nil {
			return NeedsDelete, nil
		}
		return NeedsNoop, nil
	}
	if actualModel == nil {
		return NeedsCreate, nil
	}
	var comparison sdkutil.Comparison
	var err error
	if comparison, err = sdkutil.ModelSpecMatchesDescription(*actualModel, desiredModel.Spec); err != nil {
		return NeedsNoop, err
	}
	r.Log.Info("Compared existing model to actual model to determine if model needs to be updated.", "differences", comparison.Differences, "equal", comparison.Equal)
	if comparison.Equal {
		return NeedsNoop, nil
	}
	return NeedsUpdate, nil
}
func (r *ModelReconciler) reconcileDeletion(ctx context.Context, sageMakerClient clientwrapper.SageMakerClientWrapper, modelName string) error {
	var err error
	var input *sagemaker.DeleteModelInput
	if input, err = sdkutil.CreateDeleteModelInput(&modelName); err != nil {
		return errors.Wrap(err, "Unable to create SageMaker DeleteModel request")
	}
	if _, err := sageMakerClient.DeleteModel(ctx, input); err != nil && !clientwrapper.IsDeleteModel404Error(err) {
		return errors.Wrap(err, "Unable to delete SageMaker Model")
	}
	return nil
}
func (r *ModelReconciler) reconcileCreation(ctx context.Context, sageMakerClient clientwrapper.SageMakerClientWrapper, spec modelv1.ModelSpec, modelName string) (*sagemaker.DescribeModelOutput, error) {
	var err error
	var input *sagemaker.CreateModelInput
	if input, err = sdkutil.CreateCreateModelInputFromSpec(&spec, modelName); err != nil {
		return nil, errors.Wrap(err, "Unable to create SageMaker CreateModel request")
	}
	if _, err := sageMakerClient.CreateModel(ctx, input); err != nil {
		return nil, errors.Wrap(err, "Unable to create SageMaker Model")
	}
	var output *sagemaker.DescribeModelOutput
	if output, err = sageMakerClient.DescribeModel(ctx, modelName); err != nil {
		return nil, errors.Wrap(err, "Unable to get SageMaker model description")
	}
	if output == nil {
		return nil, fmt.Errorf("Creation failed, model does not exist after creation")
	}
	return output, err
}
func (r *ModelReconciler) updateStatusAndReturnError(ctx reconcileRequestContext, status string, reconcileErr error) error {
	if err := r.updateStatusWithAdditional(ctx, status, reconcileErr.Error()); err != nil {
		return errors.Wrapf(reconcileErr, "Unable to update status with error. Status failure was caused by: '%s'", err.Error())
	}
	return reconcileErr
}
func (r *ModelReconciler) updateStatus(ctx reconcileRequestContext, status string) error {
	return r.updateStatusWithAdditional(ctx, status, "")
}