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 }