Skip to content

Commit 8b59e19

Browse files
newthifansimeoer
authored andcommitted
nydusify chunkdict generate --sources
Add the 'nydus-image chunkdict save' command with the "--sources" followed by the nydus image of registry (e.g.,'registry.com/busybox:nydus-v1,registry.com/busybox:nydus-v2') Signed-off-by: Zhao Yuan <1627990440@qq.com>
1 parent 8a93024 commit 8b59e19

File tree

4 files changed

+253
-1
lines changed

4 files changed

+253
-1
lines changed

contrib/nydusify/cmd/nydusify.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/checker"
2626
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/checker/rule"
27+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/chunkdict/generator"
2728
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/converter"
2829
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/packer"
2930
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/provider"
@@ -639,6 +640,68 @@ func main() {
639640
return checker.Check(context.Background())
640641
},
641642
},
643+
{
644+
Name: "chunkdict",
645+
Usage: "Deduplicate chunk for Nydus image (experimental)",
646+
Subcommands: []*cli.Command{
647+
{
648+
Name: "generate",
649+
Usage: "Save chunk and blob information of Multi-image into the database (experimental)",
650+
Flags: []cli.Flag{
651+
&cli.StringSliceFlag{
652+
Name: "sources",
653+
Required: true,
654+
Usage: "One or more Nydus image reference(Multiple images should be split by commas)",
655+
EnvVars: []string{"SOURCES"},
656+
},
657+
&cli.BoolFlag{
658+
Name: "source-insecure",
659+
Required: false,
660+
Usage: "Skip verifying server certs for HTTPS source registry",
661+
EnvVars: []string{"SOURCE_INSECURE"},
662+
},
663+
&cli.StringFlag{
664+
Name: "work-dir",
665+
Value: "./output",
666+
Usage: "Working directory for generating chunkdict image",
667+
EnvVars: []string{"WORK_DIR"},
668+
},
669+
&cli.StringFlag{
670+
Name: "nydus-image",
671+
Value: "nydus-image",
672+
Usage: "Path to the nydus-image binary, default to search in PATH",
673+
EnvVars: []string{"NYDUS_IMAGE"},
674+
},
675+
&cli.StringFlag{
676+
Name: "platform",
677+
Value: "linux/" + runtime.GOARCH,
678+
Usage: "Specify platform identifier to choose image manifest, possible values: 'linux/amd64' and 'linux/arm64'",
679+
},
680+
},
681+
Action: func(c *cli.Context) error {
682+
setupLogLevel(c)
683+
684+
_, arch, err := provider.ExtractOsArch(c.String("platform"))
685+
if err != nil {
686+
return err
687+
}
688+
689+
generator, err := generator.New(generator.Opt{
690+
WorkDir: c.String("work-dir"),
691+
Sources: c.StringSlice("sources"),
692+
SourceInsecure: c.Bool("source-insecure"),
693+
NydusImagePath: c.String("nydus-image"),
694+
ExpectedArch: arch,
695+
})
696+
if err != nil {
697+
return err
698+
}
699+
700+
return generator.Generate(context.Background())
701+
},
702+
},
703+
},
704+
},
642705
{
643706
Name: "mount",
644707
Aliases: []string{"view"},

contrib/nydusify/pkg/build/builder.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 Ant Group. All rights reserved.
1+
// Copyright 2023 Nydus Developers. All rights reserved.
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

@@ -41,6 +41,10 @@ type CompactOption struct {
4141
CompactConfigPath string
4242
}
4343

44+
type SaveOption struct {
45+
BootstrapPath string
46+
}
47+
4448
type Builder struct {
4549
binaryPath string
4650
stdout io.Writer
@@ -143,3 +147,16 @@ func (builder *Builder) Run(option BuilderOption) error {
143147

144148
return builder.run(args, option.PrefetchPatterns)
145149
}
150+
151+
// Save calls `nydus-image chunkdict save` to parse Nydus bootstrap
152+
func (builder *Builder) Save(option SaveOption) error {
153+
args := []string{
154+
"chunkdict",
155+
"save",
156+
"--log-level",
157+
"warn",
158+
"--bootstrap",
159+
option.BootstrapPath,
160+
}
161+
return builder.run(args, "")
162+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package generator
2+
3+
import (
4+
"context"
5+
"io/fs"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/pkg/errors"
11+
"github.com/sirupsen/logrus"
12+
13+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/build"
14+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/parser"
15+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/provider"
16+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/utils"
17+
)
18+
19+
// Opt defines Chunkdict generate options.
20+
// Note: sources is one or more Nydus image references.
21+
type Opt struct {
22+
WorkDir string
23+
Sources []string
24+
SourceInsecure bool
25+
NydusImagePath string
26+
ExpectedArch string
27+
}
28+
29+
// Generator generates chunkdict by deduplicating multiple nydus images
30+
// invoking "nydus-image chunkdict save" to save image information into database.
31+
type Generator struct {
32+
Opt
33+
sourcesParser []*parser.Parser
34+
}
35+
36+
// New creates Generator instance.
37+
func New(opt Opt) (*Generator, error) {
38+
// TODO: support sources image resolver
39+
var sourcesParser []*parser.Parser
40+
for _, source := range opt.Sources {
41+
sourcesRemote, err := provider.DefaultRemote(source, opt.SourceInsecure)
42+
if err != nil {
43+
return nil, errors.Wrap(err, "Init source image parser")
44+
}
45+
sourceParser, err := parser.New(sourcesRemote, opt.ExpectedArch)
46+
sourcesParser = append(sourcesParser, sourceParser)
47+
if err != nil {
48+
return nil, errors.Wrap(err, "Failed to create parser")
49+
}
50+
}
51+
52+
generator := &Generator{
53+
Opt: opt,
54+
sourcesParser: sourcesParser,
55+
}
56+
57+
return generator, nil
58+
}
59+
60+
// Generate saves multiple Nydus bootstraps into the database one by one.
61+
func (generator *Generator) Generate(ctx context.Context) error {
62+
for index := range generator.Sources {
63+
if err := generator.save(ctx, index); err != nil {
64+
if utils.RetryWithHTTP(err) {
65+
generator.sourcesParser[index].Remote.MaybeWithHTTP(err)
66+
}
67+
if err := generator.save(ctx, index); err != nil {
68+
return err
69+
}
70+
}
71+
}
72+
return nil
73+
}
74+
75+
// "save" stores information of chunk and blob of a Nydus Image in the database
76+
func (generator *Generator) save(ctx context.Context, index int) error {
77+
sourceParsed, err := generator.sourcesParser[index].Parse(ctx)
78+
if err != nil {
79+
return errors.Wrap(err, "parse Nydus image")
80+
}
81+
82+
// Create a directory to store the image bootstrap
83+
nydusImageName := strings.Replace(generator.Sources[index], "/", ":", -1)
84+
folderPath := filepath.Join(generator.WorkDir, nydusImageName)
85+
if err := os.MkdirAll(folderPath, fs.ModePerm); err != nil {
86+
return errors.Wrap(err, "creat work directory")
87+
}
88+
if err := generator.Output(ctx, sourceParsed, folderPath, index); err != nil {
89+
return errors.Wrap(err, "output image information")
90+
}
91+
92+
// Invoke "nydus-image save" command
93+
builder := build.NewBuilder(generator.NydusImagePath)
94+
if err := builder.Save(build.SaveOption{
95+
BootstrapPath: filepath.Join(folderPath, "nydus_bootstrap"),
96+
}); err != nil {
97+
return errors.Wrap(err, "invalid nydus bootstrap format")
98+
}
99+
100+
logrus.Infof("Save chunk information from image %s", generator.sourcesParser[index].Remote.Ref)
101+
102+
if err := os.RemoveAll(folderPath); err != nil {
103+
return errors.Wrap(err, "remove work directory")
104+
}
105+
return nil
106+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package generator
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/pkg/errors"
11+
"github.com/sirupsen/logrus"
12+
13+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/parser"
14+
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/utils"
15+
)
16+
17+
func prettyDump(obj interface{}, name string) error {
18+
bytes, err := json.MarshalIndent(obj, "", " ")
19+
if err != nil {
20+
return err
21+
}
22+
return os.WriteFile(name, bytes, 0644)
23+
}
24+
25+
// Output outputs Nydus image nydus_bootstrap file and manifest, config to JSON file.
26+
func (generator *Generator) Output(
27+
ctx context.Context, sourceParsed *parser.Parsed, outputPath string, index int,
28+
) error {
29+
if sourceParsed.Index != nil {
30+
if err := prettyDump(
31+
sourceParsed.Index,
32+
filepath.Join(outputPath, "nydus_index.json"),
33+
); err != nil {
34+
return errors.Wrap(err, "output nydus index file")
35+
}
36+
}
37+
if sourceParsed.NydusImage != nil {
38+
if err := prettyDump(
39+
sourceParsed.NydusImage.Manifest,
40+
filepath.Join(outputPath, "nydus_manifest.json"),
41+
); err != nil {
42+
return errors.Wrap(err, "output Nydus manifest file")
43+
}
44+
if err := prettyDump(
45+
sourceParsed.NydusImage.Config,
46+
filepath.Join(outputPath, "nydus_config.json"),
47+
); err != nil {
48+
return errors.Wrap(err, "output Nydus config file")
49+
}
50+
source := filepath.Join(outputPath, "nydus_bootstrap")
51+
logrus.Infof("Pulling Nydus bootstrap to %s", source)
52+
bootstrapReader, err := generator.sourcesParser[index].PullNydusBootstrap(ctx, sourceParsed.NydusImage)
53+
if err != nil {
54+
return errors.Wrap(err, "pull Nydus bootstrap layer")
55+
}
56+
defer bootstrapReader.Close()
57+
58+
if err := utils.UnpackFile(bootstrapReader, utils.BootstrapFileNameInLayer, source); err != nil {
59+
return errors.Wrap(err, "unpack Nydus bootstrap layer")
60+
}
61+
} else {
62+
err := fmt.Errorf("the %s is not a Nydus image", generator.sourcesParser[index].Remote.Ref)
63+
return err
64+
}
65+
return nil
66+
}

0 commit comments

Comments
 (0)