github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formation/resolver.go (about)

     1  package formation
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/log"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/internal/domain/formationassignment"
    10  
    11  	webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client"
    12  
    13  	dataloader "github.com/kyma-incubator/compass/components/director/internal/dataloaders"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    16  	"github.com/kyma-incubator/compass/components/director/internal/model"
    17  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    18  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    19  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // Service missing godoc
    24  //
    25  //go:generate mockery --name=Service --output=automock --outpkg=automock --case=underscore --disable-version-string
    26  type Service interface {
    27  	Get(ctx context.Context, id string) (*model.Formation, error)
    28  	GetFormationByName(ctx context.Context, formationName, tnt string) (*model.Formation, error)
    29  	List(ctx context.Context, pageSize int, cursor string) (*model.FormationPage, error)
    30  	CreateFormation(ctx context.Context, tnt string, formation model.Formation, templateName string) (*model.Formation, error)
    31  	DeleteFormation(ctx context.Context, tnt string, formation model.Formation) (*model.Formation, error)
    32  	AssignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
    33  	UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error)
    34  	ResynchronizeFormationNotifications(ctx context.Context, formationID string) (*model.Formation, error)
    35  }
    36  
    37  // Converter missing godoc
    38  //
    39  //go:generate mockery --name=Converter --output=automock --outpkg=automock --case=underscore --disable-version-string
    40  type Converter interface {
    41  	FromGraphQL(i graphql.FormationInput) model.Formation
    42  	ToGraphQL(i *model.Formation) (*graphql.Formation, error)
    43  	MultipleToGraphQL(in []*model.Formation) ([]*graphql.Formation, error)
    44  }
    45  
    46  //go:generate mockery --exported --name=formationAssignmentService --output=automock --outpkg=automock --case=underscore --disable-version-string
    47  type formationAssignmentService interface {
    48  	Delete(ctx context.Context, id string) error
    49  	DeleteAssignmentsForObjectID(ctx context.Context, formationID, objectID string) error
    50  	ListByFormationIDs(ctx context.Context, formationIDs []string, pageSize int, cursor string) ([]*model.FormationAssignmentPage, error)
    51  	ListByFormationIDsNoPaging(ctx context.Context, formationIDs []string) ([][]*model.FormationAssignment, error)
    52  	GetForFormation(ctx context.Context, id, formationID string) (*model.FormationAssignment, error)
    53  	ListFormationAssignmentsForObjectID(ctx context.Context, formationID, objectID string) ([]*model.FormationAssignment, error)
    54  	ProcessFormationAssignments(ctx context.Context, formationAssignmentsForObject []*model.FormationAssignment, runtimeContextIDToRuntimeIDMapping map[string]string, applicationIDToApplicationTemplateIDMapping map[string]string, requests []*webhookclient.FormationAssignmentNotificationRequest, operation func(context.Context, *formationassignment.AssignmentMappingPairWithOperation) (bool, error), formationOperation model.FormationOperation) error
    55  	ProcessFormationAssignmentPair(ctx context.Context, mappingPair *formationassignment.AssignmentMappingPairWithOperation) (bool, error)
    56  	GenerateAssignments(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation *model.Formation) ([]*model.FormationAssignment, error)
    57  	CleanupFormationAssignment(ctx context.Context, mappingPair *formationassignment.AssignmentMappingPairWithOperation) (bool, error)
    58  	GetAssignmentsForFormationWithStates(ctx context.Context, tenantID, formationID string, states []string) ([]*model.FormationAssignment, error)
    59  	GetReverseBySourceAndTarget(ctx context.Context, formationID, sourceID, targetID string) (*model.FormationAssignment, error)
    60  }
    61  
    62  // FormationAssignmentConverter converts FormationAssignment between the model.FormationAssignment service-layer representation and graphql.FormationAssignment.
    63  //
    64  //go:generate mockery --name=FormationAssignmentConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    65  type FormationAssignmentConverter interface {
    66  	MultipleToGraphQL(in []*model.FormationAssignment) ([]*graphql.FormationAssignment, error)
    67  	ToGraphQL(in *model.FormationAssignment) (*graphql.FormationAssignment, error)
    68  }
    69  
    70  // TenantFetcher calls an API which fetches details for the given tenant from an external tenancy service, stores the tenant in the Compass DB and returns 200 OK if the tenant was successfully created.
    71  //
    72  //go:generate mockery --name=TenantFetcher --output=automock --outpkg=automock --case=underscore --disable-version-string
    73  type TenantFetcher interface {
    74  	FetchOnDemand(tenant, parentTenant string) error
    75  }
    76  
    77  // Resolver is the formation resolver
    78  type Resolver struct {
    79  	transact                persistence.Transactioner
    80  	service                 Service
    81  	conv                    Converter
    82  	formationAssignmentSvc  formationAssignmentService
    83  	formationAssignmentConv FormationAssignmentConverter
    84  	fetcher                 TenantFetcher
    85  }
    86  
    87  // NewResolver creates formation resolver
    88  func NewResolver(transact persistence.Transactioner, service Service, conv Converter, formationAssignmentSvc formationAssignmentService, formationAssignmentConv FormationAssignmentConverter, fetcher TenantFetcher) *Resolver {
    89  	return &Resolver{
    90  		transact:                transact,
    91  		service:                 service,
    92  		conv:                    conv,
    93  		formationAssignmentSvc:  formationAssignmentSvc,
    94  		formationAssignmentConv: formationAssignmentConv,
    95  		fetcher:                 fetcher,
    96  	}
    97  }
    98  
    99  func (r *Resolver) getFormation(ctx context.Context, get func(context.Context) (*model.Formation, error)) (*graphql.Formation, error) {
   100  	tx, err := r.transact.Begin()
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   105  
   106  	ctx = persistence.SaveToContext(ctx, tx)
   107  
   108  	formation, err := get(ctx)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	if err = tx.Commit(); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	return r.conv.ToGraphQL(formation)
   118  }
   119  
   120  // FormationByName returns a Formation by its name
   121  func (r *Resolver) FormationByName(ctx context.Context, name string) (*graphql.Formation, error) {
   122  	return r.getFormation(ctx, func(ctx context.Context) (*model.Formation, error) {
   123  		tnt, err := tenant.LoadFromContext(ctx)
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		return r.service.GetFormationByName(ctx, name, tnt)
   129  	})
   130  }
   131  
   132  // Formation returns a Formation by its id
   133  func (r *Resolver) Formation(ctx context.Context, id string) (*graphql.Formation, error) {
   134  	return r.getFormation(ctx, func(ctx context.Context) (*model.Formation, error) {
   135  		return r.service.Get(ctx, id)
   136  	})
   137  }
   138  
   139  // Formations returns paginated Formations based on first and after
   140  func (r *Resolver) Formations(ctx context.Context, first *int, after *graphql.PageCursor) (*graphql.FormationPage, error) {
   141  	var cursor string
   142  	if after != nil {
   143  		cursor = string(*after)
   144  	}
   145  	if first == nil {
   146  		return nil, apperrors.NewInvalidDataError("missing required parameter 'first'")
   147  	}
   148  
   149  	tx, err := r.transact.Begin()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   154  
   155  	ctx = persistence.SaveToContext(ctx, tx)
   156  
   157  	formationPage, err := r.service.List(ctx, *first, cursor)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	if err = tx.Commit(); err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	formations, err := r.conv.MultipleToGraphQL(formationPage.Data)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	return &graphql.FormationPage{
   171  		Data:       formations,
   172  		TotalCount: formationPage.TotalCount,
   173  		PageInfo: &graphql.PageInfo{
   174  			StartCursor: graphql.PageCursor(formationPage.PageInfo.StartCursor),
   175  			EndCursor:   graphql.PageCursor(formationPage.PageInfo.EndCursor),
   176  			HasNextPage: formationPage.PageInfo.HasNextPage,
   177  		},
   178  	}, nil
   179  }
   180  
   181  // CreateFormation creates new formation for the caller tenant
   182  func (r *Resolver) CreateFormation(ctx context.Context, formationInput graphql.FormationInput) (*graphql.Formation, error) {
   183  	tnt, err := tenant.LoadFromContext(ctx)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	tx, err := r.transact.Begin()
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   193  
   194  	ctx = persistence.SaveToContext(ctx, tx)
   195  
   196  	templateName := model.DefaultTemplateName
   197  	if formationInput.TemplateName != nil && *formationInput.TemplateName != "" {
   198  		templateName = *formationInput.TemplateName
   199  	}
   200  
   201  	newFormation, err := r.service.CreateFormation(ctx, tnt, r.conv.FromGraphQL(formationInput), templateName)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	if err = tx.Commit(); err != nil {
   207  		return nil, errors.Wrap(err, "while committing transaction")
   208  	}
   209  
   210  	return r.conv.ToGraphQL(newFormation)
   211  }
   212  
   213  // DeleteFormation deletes the formation from the caller tenant formations
   214  func (r *Resolver) DeleteFormation(ctx context.Context, formation graphql.FormationInput) (*graphql.Formation, error) {
   215  	tnt, err := tenant.LoadFromContext(ctx)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	tx, err := r.transact.Begin()
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   225  
   226  	ctx = persistence.SaveToContext(ctx, tx)
   227  
   228  	deletedFormation, err := r.service.DeleteFormation(ctx, tnt, r.conv.FromGraphQL(formation))
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	if err = tx.Commit(); err != nil {
   234  		return nil, errors.Wrap(err, "while committing transaction")
   235  	}
   236  
   237  	return r.conv.ToGraphQL(deletedFormation)
   238  }
   239  
   240  // AssignFormation assigns object to the provided formation
   241  func (r *Resolver) AssignFormation(ctx context.Context, objectID string, objectType graphql.FormationObjectType, formation graphql.FormationInput) (*graphql.Formation, error) {
   242  	tnt, err := tenant.LoadFromContext(ctx)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	if objectType == graphql.FormationObjectTypeTenant {
   248  		if err := r.fetcher.FetchOnDemand(objectID, tnt); err != nil {
   249  			return nil, errors.Wrapf(err, "while trying to create if not exists subaccount %s", objectID)
   250  		}
   251  	}
   252  
   253  	tx, err := r.transact.Begin()
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   258  
   259  	ctx = persistence.SaveToContext(ctx, tx)
   260  
   261  	newFormation, err := r.service.AssignFormation(ctx, tnt, objectID, objectType, r.conv.FromGraphQL(formation))
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	if err = tx.Commit(); err != nil {
   267  		return nil, errors.Wrap(err, "while committing transaction")
   268  	}
   269  
   270  	return r.conv.ToGraphQL(newFormation)
   271  }
   272  
   273  // UnassignFormation unassigns the object from the provided formation
   274  func (r *Resolver) UnassignFormation(ctx context.Context, objectID string, objectType graphql.FormationObjectType, formation graphql.FormationInput) (*graphql.Formation, error) {
   275  	tnt, err := tenant.LoadFromContext(ctx)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	if objectType != graphql.FormationObjectTypeTenant {
   281  		err = r.deleteSelfReferencedFormationAssignment(ctx, tnt, formation.Name, objectID)
   282  		if err != nil {
   283  			return nil, errors.Wrapf(err, "while deleting self referenced formation assignment for formation with name %q and object ID %q", formation.Name, objectID)
   284  		}
   285  	}
   286  
   287  	tx, err := r.transact.Begin()
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   292  
   293  	ctx = persistence.SaveToContext(ctx, tx)
   294  
   295  	newFormation, err := r.service.UnassignFormation(ctx, tnt, objectID, objectType, r.conv.FromGraphQL(formation))
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  
   300  	if err = tx.Commit(); err != nil {
   301  		return nil, errors.Wrap(err, "while committing transaction")
   302  	}
   303  
   304  	return r.conv.ToGraphQL(newFormation)
   305  }
   306  
   307  // FormationAssignments retrieves a page of FormationAssignments for the specified Formation
   308  func (r *Resolver) FormationAssignments(ctx context.Context, obj *graphql.Formation, first *int, after *graphql.PageCursor) (*graphql.FormationAssignmentPage, error) {
   309  	param := dataloader.ParamFormationAssignment{ID: obj.ID, Ctx: ctx, First: first, After: after}
   310  	return dataloader.FormationFor(ctx).FormationAssignmentByID.Load(param)
   311  }
   312  
   313  // FormationAssignmentsDataLoader retrieves a page of FormationAssignments for each Formation ID in the keys argument
   314  func (r *Resolver) FormationAssignmentsDataLoader(keys []dataloader.ParamFormationAssignment) ([]*graphql.FormationAssignmentPage, []error) {
   315  	if len(keys) == 0 {
   316  		return nil, []error{apperrors.NewInternalError("No Formations found")}
   317  	}
   318  
   319  	ctx := keys[0].Ctx
   320  	formationIDs := make([]string, 0, len(keys))
   321  	for _, key := range keys {
   322  		formationIDs = append(formationIDs, key.ID)
   323  	}
   324  
   325  	var cursor string
   326  	if keys[0].After != nil {
   327  		cursor = string(*keys[0].After)
   328  	}
   329  
   330  	if keys[0].First == nil {
   331  		return nil, []error{apperrors.NewInvalidDataError("missing required parameter 'first'")}
   332  	}
   333  
   334  	tx, err := r.transact.Begin()
   335  	if err != nil {
   336  		return nil, []error{err}
   337  	}
   338  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   339  
   340  	ctx = persistence.SaveToContext(ctx, tx)
   341  
   342  	formationAssignmentPages, err := r.formationAssignmentSvc.ListByFormationIDs(ctx, formationIDs, *keys[0].First, cursor)
   343  	if err != nil {
   344  		return nil, []error{err}
   345  	}
   346  
   347  	gqlFormationAssignments := make([]*graphql.FormationAssignmentPage, 0, len(formationAssignmentPages))
   348  	for _, page := range formationAssignmentPages {
   349  		fas, err := r.formationAssignmentConv.MultipleToGraphQL(page.Data)
   350  		if err != nil {
   351  			return nil, []error{err}
   352  		}
   353  
   354  		gqlFormationAssignments = append(gqlFormationAssignments, &graphql.FormationAssignmentPage{Data: fas, TotalCount: page.TotalCount, PageInfo: &graphql.PageInfo{
   355  			StartCursor: graphql.PageCursor(page.PageInfo.StartCursor),
   356  			EndCursor:   graphql.PageCursor(page.PageInfo.EndCursor),
   357  			HasNextPage: page.PageInfo.HasNextPage,
   358  		}})
   359  	}
   360  
   361  	if err = tx.Commit(); err != nil {
   362  		return nil, []error{err}
   363  	}
   364  
   365  	return gqlFormationAssignments, nil
   366  }
   367  
   368  // FormationAssignment missing godoc
   369  func (r *Resolver) FormationAssignment(ctx context.Context, obj *graphql.Formation, id string) (*graphql.FormationAssignment, error) {
   370  	if obj == nil {
   371  		return nil, apperrors.NewInternalError("Formation cannot be empty")
   372  	}
   373  
   374  	tx, err := r.transact.Begin()
   375  	if err != nil {
   376  		return nil, err
   377  	}
   378  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   379  
   380  	ctx = persistence.SaveToContext(ctx, tx)
   381  
   382  	formationAssignment, err := r.formationAssignmentSvc.GetForFormation(ctx, id, obj.ID)
   383  	if err != nil {
   384  		if apperrors.IsNotFoundError(err) {
   385  			return nil, tx.Commit()
   386  		}
   387  		return nil, err
   388  	}
   389  
   390  	if err = tx.Commit(); err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	return r.formationAssignmentConv.ToGraphQL(formationAssignment)
   395  }
   396  
   397  // Status retrieves a Status for the specified Formation
   398  func (r *Resolver) Status(ctx context.Context, obj *graphql.Formation) (*graphql.FormationStatus, error) {
   399  	param := dataloader.ParamFormationStatus{ID: obj.ID, State: obj.State, Message: obj.Error.Message, ErrorCode: obj.Error.ErrorCode, Ctx: ctx}
   400  	return dataloader.FormationStatusFor(ctx).FormationStatusByID.Load(param)
   401  }
   402  
   403  // StatusDataLoader retrieves a Status for each Formation ID in the keys argument
   404  func (r *Resolver) StatusDataLoader(keys []dataloader.ParamFormationStatus) ([]*graphql.FormationStatus, []error) {
   405  	if len(keys) == 0 {
   406  		return nil, []error{apperrors.NewInternalError("No Formations found")}
   407  	}
   408  
   409  	ctx := keys[0].Ctx
   410  	formationIDs := make([]string, 0, len(keys))
   411  	for _, key := range keys {
   412  		formationIDs = append(formationIDs, key.ID)
   413  	}
   414  
   415  	tx, err := r.transact.Begin()
   416  	if err != nil {
   417  		return nil, []error{err}
   418  	}
   419  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   420  
   421  	ctx = persistence.SaveToContext(ctx, tx)
   422  
   423  	formationAssignmentsPerFormation, err := r.formationAssignmentSvc.ListByFormationIDsNoPaging(ctx, formationIDs)
   424  	if err != nil {
   425  		return nil, []error{err}
   426  	}
   427  	gqlFormationStatuses := make([]*graphql.FormationStatus, 0, len(formationAssignmentsPerFormation))
   428  	for i := 0; i < len(keys); i++ {
   429  		formationAssignments := formationAssignmentsPerFormation[i]
   430  
   431  		var condition graphql.FormationStatusCondition
   432  		var formationStatusErrors []*graphql.FormationStatusError
   433  
   434  		switch formationState := keys[i].State; formationState {
   435  		case string(model.ReadyFormationState):
   436  			condition = graphql.FormationStatusConditionReady
   437  		case string(model.InitialFormationState), string(model.DeletingFormationState):
   438  			condition = graphql.FormationStatusConditionInProgress
   439  		case string(model.CreateErrorFormationState), string(model.DeleteErrorFormationState):
   440  			condition = graphql.FormationStatusConditionError
   441  			formationStatusErrors = append(formationStatusErrors, &graphql.FormationStatusError{Message: keys[i].Message, ErrorCode: keys[i].ErrorCode})
   442  		}
   443  
   444  		for _, fa := range formationAssignments {
   445  			if isInErrorState(fa.State) {
   446  				condition = graphql.FormationStatusConditionError
   447  
   448  				if fa.Value == nil {
   449  					formationStatusErrors = append(formationStatusErrors, &graphql.FormationStatusError{AssignmentID: &fa.ID})
   450  					continue
   451  				}
   452  				var assignmentError formationassignment.AssignmentErrorWrapper
   453  				if err = json.Unmarshal(fa.Value, &assignmentError); err != nil {
   454  					return nil, []error{errors.Wrapf(err, "while unmarshalling formation assignment error with assignment ID %q", fa.ID)}
   455  				}
   456  
   457  				formationStatusErrors = append(formationStatusErrors, &graphql.FormationStatusError{
   458  					AssignmentID: &fa.ID,
   459  					Message:      assignmentError.Error.Message,
   460  					ErrorCode:    int(assignmentError.Error.ErrorCode),
   461  				})
   462  			} else if condition != graphql.FormationStatusConditionError && isInProgressState(fa.State) {
   463  				condition = graphql.FormationStatusConditionInProgress
   464  			}
   465  		}
   466  
   467  		gqlFormationStatuses = append(gqlFormationStatuses, &graphql.FormationStatus{
   468  			Condition: condition,
   469  			Errors:    formationStatusErrors,
   470  		})
   471  	}
   472  
   473  	if err = tx.Commit(); err != nil {
   474  		return nil, []error{err}
   475  	}
   476  
   477  	return gqlFormationStatuses, nil
   478  }
   479  
   480  // ResynchronizeFormationNotifications sends all notifications that are in error or initial state
   481  func (r *Resolver) ResynchronizeFormationNotifications(ctx context.Context, formationID string) (*graphql.Formation, error) {
   482  	tx, err := r.transact.Begin()
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  	defer r.transact.RollbackUnlessCommitted(ctx, tx)
   487  
   488  	ctx = persistence.SaveToContext(ctx, tx)
   489  
   490  	updatedFormation, err := r.service.ResynchronizeFormationNotifications(ctx, formationID)
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  
   495  	if err = tx.Commit(); err != nil {
   496  		return nil, err
   497  	}
   498  
   499  	return r.conv.ToGraphQL(updatedFormation)
   500  }
   501  
   502  func (r *Resolver) deleteSelfReferencedFormationAssignment(ctx context.Context, tnt, formationName, objectID string) error {
   503  	selfFATx, err := r.transact.Begin()
   504  	if err != nil {
   505  		return err
   506  	}
   507  	selfFATransactionCtx := persistence.SaveToContext(ctx, selfFATx)
   508  	defer r.transact.RollbackUnlessCommitted(selfFATransactionCtx, selfFATx)
   509  
   510  	formationFromDB, err := r.service.GetFormationByName(selfFATransactionCtx, formationName, tnt)
   511  	if err != nil {
   512  		log.C(ctx).Errorf("An error occurred while getting formation by name: %q: %v", formationName, err)
   513  		return errors.Wrapf(err, "An error occurred while getting formation by name: %q", formationName)
   514  	}
   515  
   516  	fa, err := r.formationAssignmentSvc.GetReverseBySourceAndTarget(selfFATransactionCtx, formationFromDB.ID, objectID, objectID)
   517  	if err == nil {
   518  		_ = r.formationAssignmentSvc.Delete(selfFATransactionCtx, fa.ID)
   519  	}
   520  
   521  	err = selfFATx.Commit()
   522  	if err != nil {
   523  		return errors.Wrapf(err, "while committing transaction")
   524  	}
   525  	return nil
   526  }
   527  
   528  func isInErrorState(state string) bool {
   529  	return state == string(model.CreateErrorAssignmentState) || state == string(model.DeleteErrorAssignmentState)
   530  }
   531  
   532  func isInProgressState(state string) bool {
   533  	return state == string(model.InitialAssignmentState) ||
   534  		state == string(model.DeletingAssignmentState) ||
   535  		state == string(model.ConfigPendingAssignmentState)
   536  }