Skip to content

Commit

Permalink
nydusify: enhance check subcommand
Browse files Browse the repository at this point in the history
TODO

Signed-off-by: Yan Song <[email protected]>
  • Loading branch information
imeoer committed Dec 4, 2024
1 parent 375f55f commit 7918b55
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 293 deletions.
55 changes: 42 additions & 13 deletions contrib/nydusify/cmd/nydusify.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,19 +559,39 @@ func main() {
},

&cli.StringFlag{
Name: "backend-type",
Name: "source-backend-type",
Value: "",
Usage: "Type of storage backend, enable verification of file data in Nydus image if specified, possible values: 'oss', 's3'",
Usage: "Type of storage backend, possible values: 'oss', 's3'",
EnvVars: []string{"BACKEND_TYPE"},
},
&cli.StringFlag{
Name: "backend-config",
Name: "source-backend-config",
Value: "",
Usage: "Json string for storage backend configuration",
Usage: "Json configuration string for storage backend",
EnvVars: []string{"BACKEND_CONFIG"},
},
&cli.PathFlag{
Name: "backend-config-file",
Name: "source-backend-config-file",
Value: "",
TakesFile: true,
Usage: "Json configuration file for storage backend",
EnvVars: []string{"BACKEND_CONFIG_FILE"},
},

&cli.StringFlag{
Name: "target-backend-type",
Value: "",
Usage: "Type of storage backend, possible values: 'oss', 's3'",
EnvVars: []string{"BACKEND_TYPE"},
},
&cli.StringFlag{
Name: "target-backend-config",
Value: "",
Usage: "Json configuration string for storage backend",
EnvVars: []string{"BACKEND_CONFIG"},
},
&cli.PathFlag{
Name: "target-backend-config-file",
Value: "",
TakesFile: true,
Usage: "Json configuration file for storage backend",
Expand Down Expand Up @@ -612,7 +632,12 @@ func main() {
Action: func(c *cli.Context) error {
setupLogLevel(c)

backendType, backendConfig, err := getBackendConfig(c, "", false)
sourceBackendType, sourceBackendConfig, err := getBackendConfig(c, "source-", false)
if err != nil {
return err
}

targetBackendType, targetBackendConfig, err := getBackendConfig(c, "target-", false)
if err != nil {
return err
}
Expand All @@ -623,16 +648,20 @@ func main() {
}

checker, err := checker.New(checker.Opt{
WorkDir: c.String("work-dir"),
Source: c.String("source"),
Target: c.String("target"),
WorkDir: c.String("work-dir"),

Source: c.String("source"),
Target: c.String("target"),
SourceInsecure: c.Bool("source-insecure"),
TargetInsecure: c.Bool("target-insecure"),
SourceBackendType: sourceBackendType,
SourceBackendConfig: sourceBackendConfig,
TargetBackendType: targetBackendType,
TargetBackendConfig: targetBackendConfig,

MultiPlatform: c.Bool("multi-platform"),
SourceInsecure: c.Bool("source-insecure"),
TargetInsecure: c.Bool("target-insecure"),
NydusImagePath: c.String("nydus-image"),
NydusdPath: c.String("nydusd"),
BackendType: backendType,
BackendConfig: backendConfig,
ExpectedArch: arch,
})
if err != nil {
Expand Down
112 changes: 47 additions & 65 deletions contrib/nydusify/pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,33 @@ import (
"github.com/sirupsen/logrus"

"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/checker/rule"
"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/checker/tool"
"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/parser"
"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/provider"
"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/remote"
"github.com/dragonflyoss/nydus/contrib/nydusify/pkg/utils"
)

// Opt defines Checker options.
// Note: target is the Nydus image reference.
type Opt struct {
WorkDir string
Source string
Target string
SourceInsecure bool
TargetInsecure bool
WorkDir string

Source string
Target string
SourceInsecure bool
TargetInsecure bool
SourceBackendType string
SourceBackendConfig string
TargetBackendType string
TargetBackendConfig string

MultiPlatform bool
NydusImagePath string
NydusdPath string
BackendType string
BackendConfig string
ExpectedArch string
}

// Checker validates Nydus image manifest, bootstrap and mounts filesystem
// by Nydusd to compare file metadata and data with OCI image.
// by Nydusd to compare file metadata and data between OCI / nydus image.
type Checker struct {
Opt
sourceParser *parser.Parser
Expand All @@ -46,10 +48,9 @@ type Checker struct {

// New creates Checker instance, target is the Nydus image reference.
func New(opt Opt) (*Checker, error) {
// TODO: support source and target resolver
targetRemote, err := provider.DefaultRemote(opt.Target, opt.TargetInsecure)
if err != nil {
return nil, errors.Wrap(err, "Init target image parser")
return nil, errors.Wrap(err, "init target image parser")
}
targetParser, err := parser.New(targetRemote, opt.ExpectedArch)
if err != nil {
Expand All @@ -63,7 +64,7 @@ func New(opt Opt) (*Checker, error) {
return nil, errors.Wrap(err, "Init source image parser")
}
sourceParser, err = parser.New(sourceRemote, opt.ExpectedArch)
if sourceParser == nil {
if err != nil {
return nil, errors.Wrap(err, "failed to create parser")
}
}
Expand Down Expand Up @@ -107,8 +108,6 @@ func (checker *Checker) check(ctx context.Context) error {
if err != nil {
return errors.Wrap(err, "parse source image")
}
} else {
sourceParsed = targetParsed
}

if err := os.RemoveAll(checker.WorkDir); err != nil {
Expand All @@ -119,67 +118,50 @@ func (checker *Checker) check(ctx context.Context) error {
return errors.Wrap(err, "create work directory")
}

if err := checker.Output(ctx, sourceParsed, targetParsed, checker.WorkDir); err != nil {
return errors.Wrap(err, "output image information")
}

mode := "direct"
digestValidate := false
if targetParsed.NydusImage != nil {
nydusManifest := parser.FindNydusBootstrapDesc(&targetParsed.NydusImage.Manifest)
if nydusManifest != nil {
v := utils.GetNydusFsVersionOrDefault(nydusManifest.Annotations, utils.V5)
if v == utils.V5 {
// Digest validate is not currently supported for v6,
// but v5 supports it. In order to make the check more sufficient,
// this validate needs to be turned on for v5.
digestValidate = true
}
if sourceParsed != nil {
if err := checker.Output(ctx, sourceParsed, filepath.Join(checker.WorkDir, "source")); err != nil {
return errors.Wrapf(err, "output image information: %s", sourceParsed.Remote.Ref)
}
}

var sourceRemote *remote.Remote
if checker.sourceParser != nil {
sourceRemote = checker.sourceParser.Remote
if targetParsed != nil {
if err := checker.Output(ctx, targetParsed, filepath.Join(checker.WorkDir, "target")); err != nil {
return errors.Wrapf(err, "output image information: %s", targetParsed.Remote.Ref)
}
}

rules := []rule.Rule{
&rule.ManifestRule{
SourceParsed: sourceParsed,
TargetParsed: targetParsed,
MultiPlatform: checker.MultiPlatform,
BackendType: checker.BackendType,
ExpectedArch: checker.ExpectedArch,
SourceParsed: sourceParsed,
TargetParsed: targetParsed,
},
&rule.BootstrapRule{
Parsed: targetParsed,
NydusImagePath: checker.NydusImagePath,
BackendType: checker.BackendType,
BootstrapPath: filepath.Join(checker.WorkDir, "nydus_bootstrap"),
DebugOutputPath: filepath.Join(checker.WorkDir, "nydus_bootstrap_debug.json"),
WorkDir: checker.WorkDir,
NydusImagePath: checker.NydusImagePath,

SourceParsed: sourceParsed,
TargetParsed: targetParsed,
SourceBackendType: checker.SourceBackendType,
SourceBackendConfig: checker.SourceBackendConfig,
TargetBackendType: checker.TargetBackendType,
TargetBackendConfig: checker.TargetBackendConfig,
},
&rule.FilesystemRule{
Source: checker.Source,
SourceMountPath: filepath.Join(checker.WorkDir, "fs/source_mounted"),
SourceParsed: sourceParsed,
SourcePath: filepath.Join(checker.WorkDir, "fs/source"),
SourceRemote: sourceRemote,
Target: checker.Target,
TargetInsecure: checker.TargetInsecure,
PlainHTTP: checker.targetParser.Remote.IsWithHTTP(),
NydusdConfig: tool.NydusdConfig{
EnablePrefetch: true,
NydusdPath: checker.NydusdPath,
BackendType: checker.BackendType,
BackendConfig: checker.BackendConfig,
BootstrapPath: filepath.Join(checker.WorkDir, "nydus_bootstrap"),
ConfigPath: filepath.Join(checker.WorkDir, "fs/nydusd_config.json"),
BlobCacheDir: filepath.Join(checker.WorkDir, "fs/nydus_blobs"),
MountPath: filepath.Join(checker.WorkDir, "fs/nydus_mounted"),
APISockPath: filepath.Join(checker.WorkDir, "fs/nydus_api.sock"),
Mode: mode,
DigestValidate: digestValidate,
WorkDir: checker.WorkDir,
NydusdPath: checker.NydusdPath,

SourceImage: &rule.Image{
Parsed: sourceParsed,
Insecure: checker.SourceInsecure,
},
TargetImage: &rule.Image{
Parsed: targetParsed,
Insecure: checker.TargetInsecure,
},
SourceBackendType: checker.SourceBackendType,
SourceBackendConfig: checker.SourceBackendConfig,
TargetBackendType: checker.TargetBackendType,
TargetBackendConfig: checker.TargetBackendConfig,
},
}

Expand All @@ -189,7 +171,7 @@ func (checker *Checker) check(ctx context.Context) error {
}
}

logrus.Infof("Verified Nydus image %s", checker.targetParser.Remote.Ref)
logrus.Infof("verified nydus image %s", checker.targetParser.Remote.Ref)

return nil
}
73 changes: 51 additions & 22 deletions contrib/nydusify/pkg/checker/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ package checker
import (
"context"
"encoding/json"
"io"
"os"
"path/filepath"

"github.com/containerd/containerd/archive/compression"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

Expand All @@ -28,68 +31,94 @@ func prettyDump(obj interface{}, name string) error {
// Output outputs OCI and Nydus image manifest, index, config to JSON file.
// Prefer to use source image to output OCI image information.
func (checker *Checker) Output(
ctx context.Context, sourceParsed, targetParsed *parser.Parsed, outputPath string,
ctx context.Context, parsed *parser.Parsed, dir string,
) error {
logrus.Infof("Dumping OCI and Nydus manifests to %s", outputPath)
logrus.Infof("dumping image manifest to: %s", dir)

if sourceParsed.Index != nil {
if err := os.MkdirAll(dir, 0755); err != nil {
return errors.Wrap(err, "create output directory")
}

if parsed.Index != nil && parsed.OCIImage != nil {
if err := prettyDump(
sourceParsed.Index,
filepath.Join(outputPath, "oci_index.json"),
parsed.Index,
filepath.Join(dir, "oci_index.json"),
); err != nil {
return errors.Wrap(err, "output oci index file")
}
}

if targetParsed.Index != nil {
if parsed.Index != nil && parsed.NydusImage != nil {
if err := prettyDump(
targetParsed.Index,
filepath.Join(outputPath, "nydus_index.json"),
parsed.Index,
filepath.Join(dir, "nydus_index.json"),
); err != nil {
return errors.Wrap(err, "output nydus index file")
}
}

if sourceParsed.OCIImage != nil {
if parsed.OCIImage != nil {
if err := prettyDump(
sourceParsed.OCIImage.Manifest,
filepath.Join(outputPath, "oci_manifest.json"),
parsed.OCIImage.Manifest,
filepath.Join(dir, "oci_manifest.json"),
); err != nil {
return errors.Wrap(err, "output OCI manifest file")
}
if err := prettyDump(
sourceParsed.OCIImage.Config,
filepath.Join(outputPath, "oci_config.json"),
parsed.OCIImage.Config,
filepath.Join(dir, "oci_config.json"),
); err != nil {
return errors.Wrap(err, "output OCI config file")
}
}

if targetParsed.NydusImage != nil {
if parsed.NydusImage != nil {
if err := prettyDump(
targetParsed.NydusImage.Manifest,
filepath.Join(outputPath, "nydus_manifest.json"),
parsed.NydusImage.Manifest,
filepath.Join(dir, "nydus_manifest.json"),
); err != nil {
return errors.Wrap(err, "output Nydus manifest file")
}
if err := prettyDump(
targetParsed.NydusImage.Config,
filepath.Join(outputPath, "nydus_config.json"),
parsed.NydusImage.Config,
filepath.Join(dir, "nydus_config.json"),
); err != nil {
return errors.Wrap(err, "output Nydus config file")
}

target := filepath.Join(outputPath, "nydus_bootstrap")
logrus.Infof("Pulling Nydus bootstrap to %s", target)
bootstrapReader, err := checker.targetParser.PullNydusBootstrap(ctx, targetParsed.NydusImage)
bootstrapDir := filepath.Join(dir, "nydus_bootstrap")
logrus.Infof("pulling nydus bootstrap to %s", bootstrapDir)
var parser *parser.Parser
if dir == "source" {
parser = checker.sourceParser
} else {
parser = checker.targetParser
}
bootstrapReader, err := parser.PullNydusBootstrap(ctx, parsed.NydusImage)
if err != nil {
return errors.Wrap(err, "pull Nydus bootstrap layer")
}
defer bootstrapReader.Close()

if err := utils.UnpackFile(bootstrapReader, utils.BootstrapFileNameInLayer, target); err != nil {
tarRc, err := compression.DecompressStream(bootstrapReader)
if err != nil {
return err
}
defer tarRc.Close()

diffID := digest.SHA256.Digester()
if err := utils.UnpackFromTar(io.TeeReader(tarRc, diffID.Hash()), bootstrapDir); err != nil {
return errors.Wrap(err, "unpack Nydus bootstrap layer")
}

diffIDs := parsed.NydusImage.Config.RootFS.DiffIDs
if digest.Digest(diffIDs[len(diffIDs)-1]) != diffID.Digest() {
return errors.Errorf(
"invalid bootstrap layer diff id: %s (calculated) != %s (in image config)",
diffID.Digest().String(),
diffIDs[len(diffIDs)-1].String(),
)
}
}

return nil
Expand Down
Loading

0 comments on commit 7918b55

Please sign in to comment.