...

Source file src/edge-infra.dev/pkg/edge/api/services/compatibility_service.go

Documentation: edge-infra.dev/pkg/edge/api/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"slices"
     9  	"strconv"
    10  	"strings"
    11  
    12  	sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
    13  	"edge-infra.dev/pkg/edge/api/graph/model"
    14  	sqlquery "edge-infra.dev/pkg/edge/api/sql"
    15  )
    16  
    17  var (
    18  	defaultBWC = 1
    19  )
    20  
    21  //go:generate mockgen -destination=../mocks/mock_compatibility_service.go -package=mocks edge-infra.dev/pkg/edge/api/services CompatibilityService
    22  type CompatibilityService interface {
    23  	GetArtifactVersionCompatibility(ctx context.Context, artifact model.ArtifactVersion, compatibleArtifactName *string) (*model.ArtifactCompatibility, error)
    24  	IsCompatible(ctx context.Context, primaryArtifact, secondaryArtifact model.ArtifactVersion) (bool, error)
    25  	AddArtifactCompatibility(ctx context.Context, artifactCompatibility model.ArtifactCompatibilityPayload) (added bool, err error)
    26  	DeleteArtifactCompatibility(ctx context.Context, artifactCompatibility model.ArtifactCompatibilityPayload) (deleted bool, err error)
    27  }
    28  
    29  type compatibilityService struct {
    30  	SQLDB *sql.DB
    31  }
    32  
    33  func NewCompatibilityService(sqlDB *sql.DB) *compatibilityService { // nolint can't return the CompatibilityService interface type or mocks will break
    34  	return &compatibilityService{
    35  		SQLDB: sqlDB,
    36  	}
    37  }
    38  
    39  // GetArtifactVersionCompatibility returns the compatible artifacts and their versions for a particular artifact and version
    40  func (s *compatibilityService) GetArtifactVersionCompatibility(ctx context.Context, artifact model.ArtifactVersion, compatibleArtifactName *string) (*model.ArtifactCompatibility, error) {
    41  	var rows *sql.Rows
    42  	var err error
    43  	artifactCompatibility := model.ArtifactCompatibility{Artifact: &artifact}
    44  	if compatibleArtifactName != nil {
    45  		rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetArtifactVersionsAndCompatibilityForArtifactName, artifact.Name, artifact.Version, compatibleArtifactName)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  	} else {
    50  		rows, err = s.SQLDB.QueryContext(ctx, sqlquery.GetArtifactVersionsAndCompatibility, artifact.Name, artifact.Version)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  	}
    55  	for rows.Next() {
    56  		var aName, aVersion, compatibleArtifactName, compatibleArtifactVersion string
    57  		var artifactNthIndex int
    58  		if err := rows.Scan(&aName, &aVersion, &artifactNthIndex, &compatibleArtifactName, &compatibleArtifactVersion); err != nil {
    59  			return nil, err
    60  		}
    61  		artifactCompatibility.CompatibleArtifacts = append(artifactCompatibility.CompatibleArtifacts, &model.ArtifactVersion{Name: compatibleArtifactName, Version: compatibleArtifactVersion})
    62  		if artifactCompatibility.NthIndex == 0 {
    63  			artifactCompatibility.NthIndex = artifactNthIndex
    64  		}
    65  	}
    66  	if err := rows.Err(); err != nil {
    67  		return nil, sqlerr.Wrap(err)
    68  	}
    69  	return &artifactCompatibility, nil
    70  }
    71  
    72  // IsCompatible checks if an artifact is compatible with another or bwc
    73  func (s *compatibilityService) IsCompatible(ctx context.Context, primaryArtifact, secondaryArtifact model.ArtifactVersion) (bool, error) {
    74  	compatibleArtifacts, err := s.GetArtifactVersionCompatibility(ctx, primaryArtifact, nil)
    75  	if err != nil {
    76  		return false, err
    77  	}
    78  	if compatibleArtifacts == nil {
    79  		return false, nil
    80  	}
    81  	if primaryArtifact.Name == secondaryArtifact.Name {
    82  		primaryArtifactParts := strings.Split(primaryArtifact.Version, ".")
    83  		primaryArtifactMinorVersion, err := strconv.Atoi(primaryArtifactParts[1])
    84  		if err != nil {
    85  			return false, err
    86  		}
    87  		secondaryArtifactParts := strings.Split(secondaryArtifact.Version, ".")
    88  		secondaryArtifactMinorVersion, err := strconv.Atoi(secondaryArtifactParts[1])
    89  		if err != nil {
    90  			return false, err
    91  		}
    92  		return primaryArtifactMinorVersion-secondaryArtifactMinorVersion <= compatibleArtifacts.NthIndex, nil
    93  	}
    94  	compatible := slices.ContainsFunc(compatibleArtifacts.CompatibleArtifacts, func(artifact *model.ArtifactVersion) bool {
    95  		return artifact.Name == secondaryArtifact.Name && artifact.Version == secondaryArtifact.Version
    96  	})
    97  	return compatible, nil
    98  }
    99  
   100  // AddArtifactCompatibility adds a compatible artifact and version and self backwards compabitibility for a given artifact and version
   101  func (s *compatibilityService) AddArtifactCompatibility(ctx context.Context, artifactCompatibility model.ArtifactCompatibilityPayload) (added bool, err error) {
   102  	// first checks if artifact version exists
   103  	exists, err := s.validateArtifactExists(ctx, artifactCompatibility.Artifact.Name, artifactCompatibility.Artifact.Version)
   104  	if err != nil {
   105  		return false, err
   106  	}
   107  	if !exists {
   108  		return false, fmt.Errorf("artifact name or version does not exist")
   109  	}
   110  
   111  	//  backwards compatibility assigned to default to if not explicitly set
   112  	if artifactCompatibility.NthIndex == nil {
   113  		artifactCompatibility.NthIndex = &defaultBWC
   114  	}
   115  	tx, err := s.SQLDB.BeginTx(ctx, nil)
   116  	if err != nil {
   117  		return false, err
   118  	}
   119  
   120  	defer func() {
   121  		if err != nil {
   122  			err = errors.Join(err, tx.Rollback())
   123  		}
   124  	}()
   125  
   126  	for i := range artifactCompatibility.CompatibleArtifacts {
   127  		if _, err := tx.ExecContext(ctx, sqlquery.AddArtifactCompatibility, artifactCompatibility.Artifact.Name, artifactCompatibility.Artifact.Version, artifactCompatibility.NthIndex, artifactCompatibility.CompatibleArtifacts[i].Name, artifactCompatibility.CompatibleArtifacts[i].Version); err != nil {
   128  			return false, err
   129  		}
   130  	}
   131  	if err := tx.Commit(); err != nil {
   132  		return false, fmt.Errorf("failed to commit transaction. err: %v", err)
   133  	}
   134  	return true, nil
   135  }
   136  
   137  // DeleteArtifactCompatibility deletes a compatible artifact and version from a given artifact
   138  func (s *compatibilityService) DeleteArtifactCompatibility(ctx context.Context, artifactCompatibility model.ArtifactCompatibilityPayload) (deleted bool, err error) {
   139  	// first checks if artifact version exists
   140  	exists, err := s.validateArtifactExists(ctx, artifactCompatibility.Artifact.Name, artifactCompatibility.Artifact.Version)
   141  	if err != nil {
   142  		return false, err
   143  	}
   144  	if !exists {
   145  		return false, fmt.Errorf("artifact name or version does not exist")
   146  	}
   147  
   148  	tx, err := s.SQLDB.BeginTx(ctx, nil)
   149  	if err != nil {
   150  		return false, err
   151  	}
   152  
   153  	defer func() {
   154  		if err != nil {
   155  			err = errors.Join(err, tx.Rollback())
   156  		}
   157  	}()
   158  
   159  	for i := range artifactCompatibility.CompatibleArtifacts {
   160  		if _, err := tx.ExecContext(ctx, sqlquery.DeleteArtifactCompatibility, artifactCompatibility.Artifact.Name, artifactCompatibility.Artifact.Version, artifactCompatibility.CompatibleArtifacts[i].Name, artifactCompatibility.CompatibleArtifacts[i].Version); err != nil {
   161  			return false, err
   162  		}
   163  	}
   164  	if err := tx.Commit(); err != nil {
   165  		return false, fmt.Errorf("failed to commit transaction. err: %v", err)
   166  	}
   167  	return true, nil
   168  }
   169  
   170  func (s *compatibilityService) validateArtifactExists(ctx context.Context, artifactName, artifactVersion string) (bool, error) {
   171  	// first checks if artifact version exists
   172  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.CheckArtifactVersionExists, artifactName, artifactVersion)
   173  	var exists bool
   174  	err := row.Scan(&exists)
   175  	if err != nil {
   176  		return false, err
   177  	}
   178  	if !exists {
   179  		return false, nil
   180  	}
   181  	return true, nil
   182  }
   183  

View as plain text