diff --git a/build/images/BUILD.gn b/build/images/BUILD.gn index 7ef3758ba1c8ea5ad068872b14daa210031ae65c..997ccd86e004b4ae9ac3dc4f834e9a1ea126b709 100644 --- a/build/images/BUILD.gn +++ b/build/images/BUILD.gn @@ -193,6 +193,7 @@ group("package_lists") { ":available_packages.list", ":monolith_packages.list", ":preinstall_packages.list", + ":all_package_manifests.list", ] } @@ -1444,19 +1445,17 @@ action("update.sources.manifest") { ] } -# The amber index is the index of all requested packages, naming each meta.far -# file instead of its merkleroot. Additionally the amber_index has the system -# package itself, and the system update package. -amber_index = "$target_out_dir/amber_index" - -package_metadata_list("amber_index") { - visibility = [ ":amber_publish_index" ] +# This output is a manifest of manifests that is usable as an input to `pm +# publish -lp`, a tool for publishing a set of packages from a build produced +# list of package manifests. +all_package_manifests_list = root_build_dir + "/all_package_manifests.list" +package_metadata_list("all_package_manifests.list") { testonly = true - outputs = [ - amber_index, + all_package_manifests_list, ] - data_keys = [ "meta_far_index_entries" ] + data_keys = [ "package_output_manifests" ] + rebase = root_build_dir deps = [ ":packages", ":system_image.meta", @@ -1588,64 +1587,21 @@ compiled_action("system_snapshot") { ] } -# Available blob manifest is a manifest of merkleroot=source_path for all blobs -# in all packages produced by the build, including the system image and the -# update package. -available_blob_manifest = "$root_build_dir/available_blobs.manifest" - -collect_blob_manifest("available_blobs.manifest") { - visibility = [ ":*" ] - testonly = true - outputs = [ - available_blob_manifest, - ] - deps = [ - ":packages", - ":system_image.meta", - ":update.meta", - ] -} - -# Populate the repository directory with content ID-named copies. -action("amber_publish_blobs") { - testonly = true - outputs = [ - "$amber_repository_dir.stamp", - ] - deps = [ - ":available_blobs.manifest", - ] - inputs = [] - foreach(dep, deps) { - inputs += get_target_outputs(dep) - } - script = "manifest.py" - args = [ - "--copy-contentaddr", - "--output=" + rebase_path(amber_repository_blobs_dir), - "--stamp=" + rebase_path("$amber_repository_dir.stamp"), - ] - foreach(manifest, inputs) { - args += [ "--manifest=" + rebase_path(manifest, root_build_dir) ] - } -} - -# Sign and publish the package index. -pm_publish("amber_publish_index") { +# publish all packages to the package repository. +pm_publish("publish") { testonly = true deps = [ - ":amber_index", + ":all_package_manifests.list", ] inputs = [ - amber_index, + all_package_manifests_list, ] } group("updates") { testonly = true deps = [ - ":amber_publish_blobs", - ":amber_publish_index", + ":publish", ":ids.txt", ":package_lists", ":system_snapshot", diff --git a/build/package.gni b/build/package.gni index 05c39c952af9daca7f56b0b74955d9772cc76fd7..7a2f5d98156fe6f852651fe5fc9a20c0ffb62184 100644 --- a/build/package.gni +++ b/build/package.gni @@ -50,6 +50,9 @@ declare_args() { # The metafar merkle index entries are aggregated in image builds to # produce package server indices for monolith serving. # +# package_output_manifests +# The path of each output manifest for each package. +# # package_barrier # [list of labels] This is passed to walk_keys and can be used as a barrier to # control dependency propgatation below a package target. @@ -60,6 +63,7 @@ declare_args() { # output_conversion (optional) # outputs (optional) # public_deps (optional) +# rebase (optional) # testonly (optional) # visibility (optional) # Same as for any GN `generated_file()` target. @@ -74,6 +78,7 @@ template("package_metadata_list") { "output_conversion", "outputs", "public_deps", + "rebase", "testonly", "visibility", ]) @@ -186,6 +191,7 @@ template("pm_build_package") { depfile = "$pkg_out_dir/meta.far.d" + pkg_output_manifest = "$pkg_out_dir/package_manifest.json" outputs = [ # produced by seal, must be listed first because of depfile rules. "$pkg_out_dir/meta.far", @@ -205,6 +211,9 @@ template("pm_build_package") { # package blob manifest "$pkg_out_dir/blobs.manifest", + + # package output manifest + pkg_output_manifest ] blobs_json_path = rebase_path("${pkg_out_dir}/blobs.json", root_build_dir) @@ -237,6 +246,10 @@ template("pm_build_package") { meta_far_merkle_index_entries = [ "$package_name/$package_variant=" + rebase_path("$pkg_out_dir/meta.far.merkle", root_build_dir) ] + + package_output_manifests = [ + pkg_output_manifest, + ] } args = [ @@ -247,6 +260,8 @@ template("pm_build_package") { "-m", rebase_path(pkg_manifest_file, root_build_dir), "build", + "-output-package-manifest", + rebase_path(pkg_output_manifest, root_build_dir), "-depfile", "-blobsfile", "-blobs-manifest", diff --git a/build/packages/prebuilt_package.gni b/build/packages/prebuilt_package.gni index 6c654346a67ccaa6f7b222b65a66f5409b3baf43..821f590e01d6135efec001ddce81bd0f2a0929bf 100644 --- a/build/packages/prebuilt_package.gni +++ b/build/packages/prebuilt_package.gni @@ -26,6 +26,7 @@ template("prebuilt_package") { meta_dir = target_out_dir + "/" + pkg_name + ".meta" blobs_json = "$meta_dir/blobs.json" + package_manifest_json = "$meta_dir/package_manifest.json" pkg = { package_name = pkg_name @@ -66,6 +67,7 @@ template("prebuilt_package") { blobs_manifest, system_rsp, blobs_json, + package_manifest_json, meta_merkle, ] @@ -99,6 +101,10 @@ template("prebuilt_package") { meta_far_merkle_index_entries = [ "${pkg.package_name}/${pkg.package_version}=" + rebase_path("$meta_dir/meta.far.merkle", root_build_dir) ] + + package_output_manifests = [ + package_manifest_json, + ] } } diff --git a/garnet/go/src/amber/daemon/daemon_test.go b/garnet/go/src/amber/daemon/daemon_test.go index afc0ebaa48c5aa2044b303ec863be3a376bac188..dba8b163e0cc57913d0c0945b747183e4e16dda8 100644 --- a/garnet/go/src/amber/daemon/daemon_test.go +++ b/garnet/go/src/amber/daemon/daemon_test.go @@ -180,7 +180,7 @@ func TestDaemon(t *testing.T) { mf, err := os.Open(store + "/" + pkgBlob) panicerr(err) defer mf.Close() - panicerr(repo.AddPackage("foo/0", mf)) + panicerr(repo.AddPackage("foo/0", mf, "")) for _, blob := range []string{pkgBlob, root1} { b, err := os.Open(store + "/" + blob) @@ -292,7 +292,7 @@ func TestOpenRepository(t *testing.T) { mf, err := os.Open(store + "/" + pkgBlob) panicerr(err) defer mf.Close() - panicerr(repo.AddPackage("foo/0", mf)) + panicerr(repo.AddPackage("foo/0", mf, "")) for _, blob := range []string{pkgBlob, root1} { b, err := os.Open(store + "/" + blob) @@ -398,7 +398,7 @@ func TestDaemonWithEncryption(t *testing.T) { mf, err := os.Open(store + "/" + pkgBlob) panicerr(err) defer mf.Close() - panicerr(repo.AddPackage("foo/0", mf)) + panicerr(repo.AddPackage("foo/0", mf, "")) for _, blob := range []string{pkgBlob, root1} { b, err := os.Open(store + "/" + blob) diff --git a/garnet/go/src/pm/build/config.go b/garnet/go/src/pm/build/config.go index 81173f572c8b89613535fc1af0cb385557d5b879..7ce747ab00780b89167516e03995cc79c4fc6712 100644 --- a/garnet/go/src/pm/build/config.go +++ b/garnet/go/src/pm/build/config.go @@ -6,10 +6,12 @@ package build import ( "flag" + "fmt" "io/ioutil" "os" "path/filepath" + "fuchsia.googlesource.com/pm/pkg" "golang.org/x/crypto/ed25519" ) @@ -20,6 +22,7 @@ type Config struct { KeyPath string TempDir string PkgName string + PkgVersion string // the manifest is memoized lazily, on the first call to Manifest() manifest *Manifest @@ -33,6 +36,7 @@ func NewConfig() *Config { KeyPath: "", TempDir: os.TempDir(), PkgName: "", + PkgVersion: "0", } return cfg } @@ -51,6 +55,7 @@ func TestConfig() *Config { KeyPath: filepath.Join(d, "key"), TempDir: filepath.Join(d, "tmp"), PkgName: filepath.Join(d, "pkg"), + PkgVersion: "0", } for _, d := range []string{cfg.OutputDir, cfg.TempDir} { os.MkdirAll(d, os.ModePerm) @@ -65,6 +70,7 @@ func (c *Config) InitFlags(fs *flag.FlagSet) { fs.StringVar(&c.KeyPath, "k", c.KeyPath, "signing key") fs.StringVar(&c.TempDir, "t", c.TempDir, "temporary directory") fs.StringVar(&c.PkgName, "n", c.PkgName, "name of the packages") + fs.StringVar(&c.PkgVersion, "version", c.PkgVersion, "version of the packages") } // PrivateKey loads the configured private key @@ -109,3 +115,23 @@ func (c *Config) MetaFAR() string { func (c *Config) MetaFARMerkle() string { return filepath.Join(c.OutputDir, "meta.far.merkle") } + +func (c *Config) Package() (pkg.Package, error) { + p := pkg.Package{ + Name: c.PkgName, + Version: c.PkgVersion, + } + + if p.Name == "" { + p.Name = filepath.Base(c.OutputDir) + if p.Name == "." { + var err error + p.Name, err = filepath.Abs(p.Name) + if err != nil { + return p, fmt.Errorf("build: unable to compute package name from directory: %s", err) + } + p.Name = filepath.Base(p.Name) + } + } + return p, nil +} diff --git a/garnet/go/src/pm/build/package.go b/garnet/go/src/pm/build/package.go index a3f902e37764186d9f063826305e6613cf456cca..9210101a5316d4bc44ad53be3fb94af6c331f89a 100644 --- a/garnet/go/src/pm/build/package.go +++ b/garnet/go/src/pm/build/package.go @@ -23,21 +23,17 @@ import ( "fuchsia.googlesource.com/pm/pkg" ) +// PackageManifest is the json structure representation of a full package +// manifest. +type PackageManifest struct { + Version string `json:"version"` + Package pkg.Package `json:"package"` + Blobs []PackageBlobInfo `json:"blobs"` +} + // Init initializes package metadata in the output directory. A manifest // is generated with a name matching the output directory name. func Init(cfg *Config) error { - pkgName := cfg.PkgName - if pkgName == "" { - pkgName = filepath.Base(cfg.OutputDir) - if pkgName == "." { - var err error - pkgName, err = filepath.Abs(pkgName) - if err != nil { - return fmt.Errorf("build: unable to compute package name from directory: %s", err) - } - pkgName = filepath.Base(pkgName) - } - } metadir := filepath.Join(cfg.OutputDir, "meta") if err := os.MkdirAll(metadir, os.ModePerm); err != nil { return err @@ -50,9 +46,9 @@ func Init(cfg *Config) error { return err } - p := pkg.Package{ - Name: pkgName, - Version: "0", + p, err := cfg.Package() + if err != nil { + return err } err = json.NewEncoder(f).Encode(&p) diff --git a/garnet/go/src/pm/cmd/pm/build/build.go b/garnet/go/src/pm/cmd/pm/build/build.go index 1664e5149650941658dea54b470da803b7818949..6629fcebca694af2bc788dc61e84bf964948faea 100644 --- a/garnet/go/src/pm/cmd/pm/build/build.go +++ b/garnet/go/src/pm/cmd/pm/build/build.go @@ -29,6 +29,7 @@ func Run(cfg *build.Config, args []string) error { fs := flag.NewFlagSet("build", flag.ExitOnError) var depfile = fs.Bool("depfile", true, "Produce a depfile") + var pkgManifestPath = fs.String("output-package-manifest", "", "If set, produce a package manifest at the given path") var blobsfile = fs.Bool("blobsfile", false, "Produce blobs.json file") var blobsmani = fs.Bool("blobs-manifest", false, "Produce blobs.manifest file") @@ -68,34 +69,52 @@ func Run(cfg *build.Config, args []string) error { } } + if cfg.ManifestPath == "" { + return fmt.Errorf("the -blobsfile option requires the use of the -m manifest option") + } + + blobs, err := buildPackageBlobInfo(cfg) + if err != nil { + return err + } + if *blobsfile { - if cfg.ManifestPath == "" { - return fmt.Errorf("the -blobsfile option requires the use of the -m manifest option") + content, err := json.Marshal(blobs) + if err != nil { + return err + } + if err := ioutil.WriteFile(filepath.Join(cfg.OutputDir, "blobs.json"), content, 0644); err != nil { + return err } + } + + if *blobsmani { + var buf bytes.Buffer + for _, blob := range blobs { + fmt.Fprintf(&buf, "%s=%s\n", blob.Merkle.String(), blob.SourcePath) + } + if err := ioutil.WriteFile(filepath.Join(cfg.OutputDir, "blobs.manifest"), buf.Bytes(), 0644); err != nil { + return err + } + } - blobs, err := buildPackageBlobInfo(cfg) + if *pkgManifestPath != "" { + p, err := cfg.Package() if err != nil { return err } + pkgManifest := build.PackageManifest{ + Version: "1", + Package: p, + Blobs: blobs, + } - if *blobsfile { - content, err := json.Marshal(blobs) - if err != nil { - return err - } - if err := ioutil.WriteFile(filepath.Join(cfg.OutputDir, "blobs.json"), content, 0644); err != nil { - return err - } - } - - if *blobsmani { - var buf bytes.Buffer - for _, blob := range blobs { - fmt.Fprintf(&buf, "%s=%s\n", blob.Merkle.String(), blob.SourcePath) - } - if err := ioutil.WriteFile(filepath.Join(cfg.OutputDir, "blobs.manifest"), buf.Bytes(), 0644); err != nil { - return err - } + content, err := json.Marshal(pkgManifest) + if err != nil { + return err + } + if err := ioutil.WriteFile(*pkgManifestPath, content, 0644); err != nil { + return err } } diff --git a/garnet/go/src/pm/cmd/pm/expand/expand.go b/garnet/go/src/pm/cmd/pm/expand/expand.go index d23b4129fb789dc48f0a974a3fd6f3a98ec62357..a733ca4bea8e266c141bef3bf5bf199dbca591f3 100644 --- a/garnet/go/src/pm/cmd/pm/expand/expand.go +++ b/garnet/go/src/pm/cmd/pm/expand/expand.go @@ -88,10 +88,11 @@ func merkleFor(b []byte) (build.MerkleRoot, error) { return res, nil } -// Extract the meta.far to the `outputDir`, and write out a package manifest -// into `$outputDir/package.manifest`. for it. The format of the manifest is: -// +// Extract the meta.far to the `outputDir`, and write package manifests. +// `$outputDir/package.manifest` contains: // $PKG_NAME.$PKG_VERSION=$outputDir/meta.far +// `package_manifest.json` contains a package output manifest as built by `pm +// build -outut-package-manifest`. func writeMetadataAndManifest(pkgArchive *far.Reader, outputDir string) error { // First, extract the package info from the archive, or error out if // the meta.far is malformed. @@ -164,6 +165,20 @@ func writeMetadataAndManifest(pkgArchive *far.Reader, outputDir string) error { } f.Close() + // Write out package_manifest.json + pkgManifest := build.PackageManifest{ + Version: "1", + Package: *p, + Blobs: blobs, + } + content, err := json.Marshal(pkgManifest) + if err != nil { + return err + } + if err := ioutil.WriteFile(filepath.Join(outputDir, "package_manifest.json"), content, 0644); err != nil { + return err + } + cwd, err := os.Getwd() if err != nil { return err diff --git a/garnet/go/src/pm/cmd/pm/publish/publish.go b/garnet/go/src/pm/cmd/pm/publish/publish.go index 8c62a2e4fe8b7e6eb382bc25cc79a8d277024aa3..7b603bf6afcb84182b9230bbf1adc4def1cfff1b 100644 --- a/garnet/go/src/pm/cmd/pm/publish/publish.go +++ b/garnet/go/src/pm/cmd/pm/publish/publish.go @@ -50,10 +50,11 @@ type manifestEntry struct { func Run(cfg *build.Config, args []string) error { fs := flag.NewFlagSet("serve", flag.ExitOnError) + listOfPackageManifestsMode := fs.Bool("lp", false, "(mode) Publish a list of packages (and blobs) by package output manifest") archiveMode := fs.Bool("a", false, "(mode) Publish an archived package.") packageSetMode := fs.Bool("ps", false, "(mode) Publish a set of packages from a manifest.") blobSetMode := fs.Bool("bs", false, "(mode) Publish a set of blobs from a manifest.") - modeFlags := []*bool{archiveMode, packageSetMode, blobSetMode} + modeFlags := []*bool{listOfPackageManifestsMode, archiveMode, packageSetMode, blobSetMode} config := &repo.Config{} config.Vars(fs) @@ -150,6 +151,39 @@ func Run(cfg *build.Config, args []string) error { } switch { + case *listOfPackageManifestsMode: + if len(filePaths) != 1 { + return fmt.Errorf("too many file paths supplied") + } + deps = append(deps, filePaths[0]) + f, err := os.Open(filePaths[0]) + if err != nil { + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + pkgManifestPath := scanner.Text() + deps = append(deps, pkgManifestPath) + if *verbose { + fmt.Printf("publishing: %s\n", pkgManifestPath) + } + if err := repo.PublishManifest(pkgManifestPath); err != nil { + return err + } + } + if err := scanner.Err(); err != nil { + return err + } + + if *verbose { + fmt.Printf("committing updates\n") + } + if err := repo.CommitUpdates(config.TimeVersioned); err != nil { + log.Fatalf("error committing repository updates: %s", err) + } case *archiveMode: if len(filePaths) != 1 { return fmt.Errorf("too many file paths supplied") @@ -190,7 +224,7 @@ func Run(cfg *build.Config, args []string) error { if *verbose { fmt.Printf("adding package %s\n", name) } - if err := repo.AddPackage(name, bytes.NewReader(b)); err != nil { + if err := repo.AddPackage(name, bytes.NewReader(b), ""); err != nil { return err } @@ -223,7 +257,7 @@ func Run(cfg *build.Config, args []string) error { return err } defer f.Close() - if err := repo.AddPackage(name, f); err != nil { + if err := repo.AddPackage(name, f, ""); err != nil { return fmt.Errorf("failed to add package %q from %q: %s", name, src, err) } return nil diff --git a/garnet/go/src/pm/pm.gni b/garnet/go/src/pm/pm.gni index 0652032406bfa4f70868b79187e78707bcf0b8d4..7b9069d93f3da919bd645ab0723c547e8bf6a9cf 100644 --- a/garnet/go/src/pm/pm.gni +++ b/garnet/go/src/pm/pm.gni @@ -75,7 +75,7 @@ template("pm_publish") { "-C", "-r", rebase_path(amber_repository_dir, root_build_dir), - "-ps", + "-lp", "-f", rebase_path(inputs[0], root_build_dir), "-vt", diff --git a/garnet/go/src/pm/repo/repo.go b/garnet/go/src/pm/repo/repo.go index 5d8d180a75cf87eb40474f9d039a9d6b572bff06..dfa4938595ada6775b49dab4d8961139c2489c38 100644 --- a/garnet/go/src/pm/repo/repo.go +++ b/garnet/go/src/pm/repo/repo.go @@ -19,6 +19,7 @@ import ( "time" "fuchsia.googlesource.com/merkle" + "fuchsia.googlesource.com/pm/build" tuf "github.com/flynn/go-tuf" ) @@ -56,7 +57,20 @@ func New(path string) (*Repo, error) { } repo, err := tuf.NewRepo(tuf.FileSystemStore(path, passphrase), "sha512") - return &Repo{repo, path, nil}, err + if err != nil { + return nil, err + } + r := &Repo{repo, path, nil} + + blobDir := filepath.Join(r.path, "repository", "blobs") + if err := os.MkdirAll(blobDir, os.ModePerm); err != nil { + return nil, err + } + if err := os.MkdirAll(r.stagedFilesPath(), os.ModePerm); err != nil { + return nil, err + } + + return r, nil } func (r *Repo) EncryptWith(path string) error { @@ -98,9 +112,10 @@ func (r *Repo) GenKeys() error { } // AddPackage adds a package with the given name with the content from the given -// reader. The package blob is also added. -func (r *Repo) AddPackage(name string, rd io.Reader) error { - root, size, err := r.AddBlob("", rd) +// reader. The package blob is also added. If merkle is non-empty, it is used, +// otherwise the package merkleroot is computed on the fly. +func (r *Repo) AddPackage(name string, rd io.Reader, merkle string) error { + root, size, err := r.AddBlob(merkle, rd) if err != nil { return NewAddErr("adding package blob", err) } @@ -115,25 +130,12 @@ func (r *Repo) AddPackage(name string, rd io.Reader) error { return NewAddErr(fmt.Sprintf("serializing %v", metadata), err) } - // The staged package blob is copied from the path produced by AddBlob, as the - // AddBlob operation my have performed blob encryption. - dst, err := os.Create(stagingPath) - if err != nil { - return NewAddErr("creating file in staging directory", err) - } blobDir := filepath.Join(r.path, "repository", "blobs") - src, err := os.Open(filepath.Join(blobDir, root)) - if err != nil { - dst.Close() - return NewAddErr("reading blob", err) - } - if _, err := io.Copy(dst, src); err != nil { - dst.Close() - src.Close() - return NewAddErr("staging meta.far blob", err) + blobPath := filepath.Join(blobDir, root) + + if err := linkOrCopy(blobPath, stagingPath); err != nil { + return NewAddErr("creating file in staging directory", err) } - dst.Close() - src.Close() // add file with custom JSON to repository if err := r.AddTarget(name, json.RawMessage(jsonStr)); err != nil { @@ -159,13 +161,20 @@ func cryptingWriter(dst io.Writer, key []byte) (io.WriteCloser, error) { return cipher.StreamWriter{stream, dst, nil}, nil } +// HasBlob returns true if the given merkleroot is already in the repository +// blob store. +func (r *Repo) HasBlob(root string) bool { + blobPath := filepath.Join(r.path, "repository", "blobs", root) + fi, err := os.Stat(blobPath) + return err == nil && fi.Mode().IsRegular() +} + // AddBlob writes the content of the given reader to the blob identified by the // given merkleroot. If merkleroot is empty string, a merkleroot is computed. // Addblob always returns the plaintext size of the blob that is added, even if // blob encryption is used. func (r *Repo) AddBlob(root string, rd io.Reader) (string, int64, error) { blobDir := filepath.Join(r.path, "repository", "blobs") - os.MkdirAll(blobDir, os.ModePerm) if root != "" { dstPath := filepath.Join(blobDir, root) @@ -255,6 +264,47 @@ func (r *Repo) CommitUpdates(dateVersioning bool) error { return r.commitUpdates() } +func (r *Repo) PublishManifest(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + var packageManifest build.PackageManifest + if err := json.NewDecoder(f).Decode(&packageManifest); err != nil { + return err + } + if packageManifest.Version != "1" { + return fmt.Errorf("unknown version %q, can't publish", packageManifest.Version) + } + + for _, blob := range packageManifest.Blobs { + if blob.Path == "meta/" { + p := packageManifest.Package + name := p.Name + "/" + p.Version + f, err := os.Open(blob.SourcePath) + if err != nil { + return err + } + err = r.AddPackage(name, f, blob.Merkle.String()) + f.Close() + } else { + if !r.HasBlob(blob.Merkle.String()) { + f, err := os.Open(blob.SourcePath) + if err != nil { + return err + } + _, _, err = r.AddBlob(blob.Merkle.String(), f) + f.Close() + } + } + if err != nil { + return err + } + } + return nil +} + func (r *Repo) commitUpdates() error { if err := r.SnapshotWithExpires(tuf.CompressionTypeNone, time.Now().AddDate(0, 0, 30)); err != nil { return NewAddErr("problem snapshotting repository", err) @@ -289,3 +339,28 @@ func (r *Repo) fixupRootConsistentSnapshot() error { } return nil } + +func linkOrCopy(dstPath, srcPath string) error { + if err := os.Link(dstPath, srcPath); err != nil { + s, err := os.Open(srcPath) + if err != nil { + return err + } + defer s.Close() + + d, err := ioutil.TempFile(filepath.Dir(dstPath), filepath.Base(dstPath)) + if err != nil { + return err + } + if _, err := io.Copy(d, s); err != nil { + d.Close() + os.Remove(d.Name()) + return err + } + if err := d.Close(); err != nil { + return err + } + return os.Rename(d.Name(), dstPath) + } + return nil +} diff --git a/garnet/go/src/pm/repo/repo_test.go b/garnet/go/src/pm/repo/repo_test.go index 680e28f2b3c747e59f09561460162f8c22325e83..50caa329e617ab5bbba69331dfa699cc4872190f 100644 --- a/garnet/go/src/pm/repo/repo_test.go +++ b/garnet/go/src/pm/repo/repo_test.go @@ -92,7 +92,7 @@ func TestAddPackage(t *testing.T) { } targetName := "test-test" - err = r.AddPackage("test-test", io.LimitReader(rand.Reader, 8193)) + err = r.AddPackage("test-test", io.LimitReader(rand.Reader, 8193), "") if err != nil { t.Fatalf("Problem adding repo file %v", err)