0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/storage/scrub.go
Alex Stan d325c8b5f4 Fix problems signaled by new linter version v1.45.2
PR (linter: upgrade linter version #405) triggered lint job which failed
with many errors generated by various linters. Configurations were added to
golangcilint.yaml and several refactorings were made in order to improve the
results of the linter.

maintidx linter disabled

Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
2022-04-27 09:55:44 -07:00

275 lines
6 KiB
Go

package storage
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
"time"
"github.com/olekukonko/tablewriter"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/umoci"
"github.com/opencontainers/umoci/oci/casext"
"zotregistry.io/zot/errors"
)
const (
colImageNameIndex = iota
colTagIndex
colStatusIndex
colErrorIndex
imageNameWidth = 32
tagWidth = 24
statusWidth = 8
errorWidth = 8
)
type ScrubImageResult struct {
ImageName string `json:"imageName"`
Tag string `json:"tag"`
Status string `json:"status"`
Error string `json:"error"`
}
type ScrubResults struct {
ScrubResults []ScrubImageResult `json:"scrubResults"`
}
func (sc StoreController) CheckAllBlobsIntegrity() (ScrubResults, error) {
results := ScrubResults{}
imageStoreList := make(map[string]ImageStore)
if sc.SubStore != nil {
imageStoreList = sc.SubStore
}
imageStoreList[""] = sc.DefaultStore
for _, imgStore := range imageStoreList {
imgStoreResults, err := CheckImageStoreBlobsIntegrity(imgStore)
if err != nil {
return results, err
}
results.ScrubResults = append(results.ScrubResults, imgStoreResults...)
}
return results, nil
}
func CheckImageStoreBlobsIntegrity(imgStore ImageStore) ([]ScrubImageResult, error) {
results := []ScrubImageResult{}
repos, err := imgStore.GetRepositories()
if err != nil {
return results, err
}
for _, repo := range repos {
imageResults, err := checkRepo(repo, imgStore)
if err != nil {
return results, err
}
results = append(results, imageResults...)
}
return results, nil
}
func checkRepo(imageName string, imgStore ImageStore) ([]ScrubImageResult, error) {
results := []ScrubImageResult{}
dir := path.Join(imgStore.RootDir(), imageName)
if !imgStore.DirExists(dir) {
return results, errors.ErrRepoNotFound
}
ctxUmoci := context.Background()
oci, err := umoci.OpenLayout(dir)
if err != nil {
return results, err
}
defer oci.Close()
var lockLatency time.Time
imgStore.RLock(&lockLatency)
defer imgStore.RUnlock(&lockLatency)
buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
if err != nil {
return results, err
}
var index ispec.Index
if err := json.Unmarshal(buf, &index); err != nil {
return results, errors.ErrRepoNotFound
}
for _, m := range index.Manifests {
tag, ok := m.Annotations[ispec.AnnotationRefName]
if ok {
imageResult := checkIntegrity(ctxUmoci, imageName, tag, oci, m, dir)
results = append(results, imageResult)
}
}
return results, nil
}
func checkIntegrity(ctx context.Context, imageName, tagName string, oci casext.Engine, manifest ispec.Descriptor, dir string) ScrubImageResult { // nolint: lll
// check manifest and config
stat, err := umoci.Stat(ctx, oci, manifest)
imageRes := ScrubImageResult{}
if err != nil {
imageRes = getResult(imageName, tagName, err)
} else {
// check layers
for _, s := range stat.History {
layer := s.Layer
if layer == nil {
continue
}
// check layer
layerPath := path.Join(dir, "blobs", layer.Digest.Algorithm().String(), layer.Digest.Hex())
_, err = os.Stat(layerPath)
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBlobNotFound)
break
}
layerFh, err := os.Open(layerPath)
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBlobNotFound)
break
}
computedDigest, err := godigest.FromReader(layerFh)
layerFh.Close()
if err != nil {
imageRes = getResult(imageName, tagName, errors.ErrBadBlobDigest)
break
}
if computedDigest != layer.Digest {
imageRes = getResult(imageName, tagName, errors.ErrBadBlobDigest)
break
}
imageRes = getResult(imageName, tagName, nil)
}
}
return imageRes
}
func getResult(imageName, tag string, err error) ScrubImageResult {
var status string
var errField string
if err != nil {
status = "affected"
errField = err.Error()
} else {
status = "ok"
errField = ""
}
return ScrubImageResult{
ImageName: imageName,
Tag: tag,
Status: status,
Error: errField,
}
}
func getScrubTableWriter(writer io.Writer) *tablewriter.Table {
table := tablewriter.NewWriter(writer)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding(" ")
table.SetNoWhiteSpace(true)
table.SetColMinWidth(colImageNameIndex, imageNameWidth)
table.SetColMinWidth(colTagIndex, tagWidth)
table.SetColMinWidth(colStatusIndex, statusWidth)
table.SetColMinWidth(colErrorIndex, errorWidth)
return table
}
const tableCols = 4
func printScrubTableHeader(writer io.Writer) {
table := getScrubTableWriter(writer)
row := make([]string, tableCols)
row[colImageNameIndex] = "IMAGE NAME"
row[colTagIndex] = "TAG"
row[colStatusIndex] = "STATUS"
row[colErrorIndex] = "ERROR"
table.Append(row)
table.Render()
}
func printImageResult(imageResult ScrubImageResult) string {
var builder strings.Builder
table := getScrubTableWriter(&builder)
table.SetColMinWidth(colImageNameIndex, imageNameWidth)
table.SetColMinWidth(colTagIndex, tagWidth)
table.SetColMinWidth(colStatusIndex, statusWidth)
table.SetColMinWidth(colErrorIndex, errorWidth)
row := make([]string, tableCols)
row[colImageNameIndex] = imageResult.ImageName
row[colTagIndex] = imageResult.Tag
row[colStatusIndex] = imageResult.Status
row[colErrorIndex] = imageResult.Error
table.Append(row)
table.Render()
return builder.String()
}
func (results ScrubResults) PrintScrubResults(resultWriter io.Writer) {
var builder strings.Builder
printScrubTableHeader(&builder)
fmt.Fprint(resultWriter, builder.String())
for _, res := range results.ScrubResults {
imageResult := printImageResult(res)
fmt.Fprint(resultWriter, imageResult)
}
}