zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/lint/lint.go (about)

     1  //go:build lint
     2  // +build lint
     3  
     4  package lint
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  
    10  	godigest "github.com/opencontainers/go-digest"
    11  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    12  
    13  	zerr "zotregistry.dev/zot/errors"
    14  	"zotregistry.dev/zot/pkg/extensions/config"
    15  	"zotregistry.dev/zot/pkg/log"
    16  	storageTypes "zotregistry.dev/zot/pkg/storage/types"
    17  )
    18  
    19  type Linter struct {
    20  	config *config.LintConfig
    21  	log    log.Logger
    22  }
    23  
    24  func NewLinter(config *config.LintConfig, log log.Logger) *Linter {
    25  	return &Linter{
    26  		config: config,
    27  		log:    log,
    28  	}
    29  }
    30  
    31  func (linter *Linter) CheckMandatoryAnnotations(repo string, manifestDigest godigest.Digest,
    32  	imgStore storageTypes.ImageStore,
    33  ) (bool, error) {
    34  	if linter.config == nil {
    35  		return true, nil
    36  	}
    37  
    38  	if (linter.config != nil && !*linter.config.Enable) || len(linter.config.MandatoryAnnotations) == 0 {
    39  		return true, nil
    40  	}
    41  
    42  	mandatoryAnnotationsList := linter.config.MandatoryAnnotations
    43  
    44  	content, err := imgStore.GetBlobContent(repo, manifestDigest)
    45  	if err != nil {
    46  		linter.log.Error().Err(err).Str("component", "linter").Msg("failed to get image manifest")
    47  
    48  		return false, err
    49  	}
    50  
    51  	var manifest ispec.Manifest
    52  
    53  	if err := json.Unmarshal(content, &manifest); err != nil {
    54  		linter.log.Error().Err(err).Str("component", "linter").Msg("failed to unmarshal manifest JSON")
    55  
    56  		return false, err
    57  	}
    58  
    59  	mandatoryAnnotationsMap := make(map[string]bool)
    60  	for _, annotation := range mandatoryAnnotationsList {
    61  		mandatoryAnnotationsMap[annotation] = false
    62  	}
    63  
    64  	manifestAnnotations := manifest.Annotations
    65  	for annotation := range manifestAnnotations {
    66  		if _, ok := mandatoryAnnotationsMap[annotation]; ok {
    67  			mandatoryAnnotationsMap[annotation] = true
    68  		}
    69  	}
    70  
    71  	missingAnnotations := getMissingAnnotations(mandatoryAnnotationsMap)
    72  	if len(missingAnnotations) == 0 {
    73  		return true, nil
    74  	}
    75  
    76  	// if there are mandatory annotations missing in the manifest, get config and check these annotations too
    77  	configDigest := manifest.Config.Digest
    78  
    79  	content, err = imgStore.GetBlobContent(repo, configDigest)
    80  	if err != nil {
    81  		linter.log.Error().Err(err).Str("component", "linter").Msg("failed to get config JSON " +
    82  			configDigest.String())
    83  
    84  		return false, err
    85  	}
    86  
    87  	var imageConfig ispec.Image
    88  	if err := json.Unmarshal(content, &imageConfig); err != nil {
    89  		linter.log.Error().Err(err).Str("component", "linter").Msg("failed to unmarshal config JSON " + configDigest.String())
    90  
    91  		return false, err
    92  	}
    93  
    94  	configAnnotations := imageConfig.Config.Labels
    95  
    96  	for annotation := range configAnnotations {
    97  		if _, ok := mandatoryAnnotationsMap[annotation]; ok {
    98  			mandatoryAnnotationsMap[annotation] = true
    99  		}
   100  	}
   101  
   102  	missingAnnotations = getMissingAnnotations(mandatoryAnnotationsMap)
   103  	if len(missingAnnotations) > 0 {
   104  		msg := fmt.Sprintf("\nlinter: manifest %s\nor config %s\nis missing the next annotations: %s",
   105  			string(manifestDigest), string(configDigest), missingAnnotations)
   106  		linter.log.Error().Msg(msg)
   107  
   108  		return false, zerr.NewError(zerr.ErrImageLintAnnotations).AddDetail("missingAnnotations", msg)
   109  	}
   110  
   111  	return true, nil
   112  }
   113  
   114  func (linter *Linter) Lint(repo string, manifestDigest godigest.Digest,
   115  	imageStore storageTypes.ImageStore,
   116  ) (bool, error) {
   117  	return linter.CheckMandatoryAnnotations(repo, manifestDigest, imageStore)
   118  }
   119  
   120  func getMissingAnnotations(mandatoryAnnotationsMap map[string]bool) []string {
   121  	var missingAnnotations []string
   122  
   123  	for annotation, flag := range mandatoryAnnotationsMap {
   124  		if !flag {
   125  			missingAnnotations = append(missingAnnotations, annotation)
   126  		}
   127  	}
   128  
   129  	return missingAnnotations
   130  }