diff --git a/CHANGELOG.md b/CHANGELOG.md index f85e1eb4b..73eb83d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ -## 0.16.5 (unreleased) +## 0.16.6 (Unreleased) ### Improvements - Fix an issue where running two copies of `pulumi plugin install` in parallel for the same plugin version could cause one to fail with an error about renaming a directory. +## 0.16.5 (Released Novemeber 16th, 2018) + +### Improvements + +- Fix an issue where `pulumi plugin install` would fail on Windows with an access deined message. + ## 0.16.4 (Released Novemeber 12th, 2018) ### Major Changes diff --git a/pkg/workspace/plugins.go b/pkg/workspace/plugins.go index dcc6d3703..32cb9a392 100644 --- a/pkg/workspace/plugins.go +++ b/pkg/workspace/plugins.go @@ -154,44 +154,52 @@ func (info PluginInfo) Install(tarball io.ReadCloser) error { contract.IgnoreError(os.RemoveAll(tempDir)) }() - // Unzip and untar the file as we go. - defer contract.IgnoreClose(tarball) - gzr, err := gzip.NewReader(tarball) - if err != nil { - return errors.Wrapf(err, "unzipping") - } - r := tar.NewReader(gzr) - for { - header, err := r.Next() - if err == io.EOF { - break - } else if err != nil { - return errors.Wrapf(err, "untarring") + // Unzip and untar the file as we go. We do this inside a function so that the `defer`'s to close files happen + // before we later try to rename the directory. Otherwise, the open file handles cause issues on Windows. + err = (func() error { + defer contract.IgnoreClose(tarball) + gzr, err := gzip.NewReader(tarball) + if err != nil { + return errors.Wrapf(err, "unzipping") } + r := tar.NewReader(gzr) + for { + header, err := r.Next() + if err == io.EOF { + break + } else if err != nil { + return errors.Wrapf(err, "untarring") + } - path := filepath.Join(tempDir, header.Name) + path := filepath.Join(tempDir, header.Name) - switch header.Typeflag { - case tar.TypeDir: - // Create any directories as needed. - if _, err := os.Stat(path); err != nil { - if err = os.MkdirAll(path, 0700); err != nil { - return errors.Wrapf(err, "untarring dir %s", path) + switch header.Typeflag { + case tar.TypeDir: + // Create any directories as needed. + if _, err := os.Stat(path); err != nil { + if err = os.MkdirAll(path, 0700); err != nil { + return errors.Wrapf(err, "untarring dir %s", path) + } } + case tar.TypeReg: + // Expand files into the target directory. + dst, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return errors.Wrapf(err, "opening file %s for untar", path) + } + defer contract.IgnoreClose(dst) + if _, err = io.Copy(dst, r); err != nil { + return errors.Wrapf(err, "untarring file %s", path) + } + default: + return errors.Errorf("unexpected plugin file type %s (%v)", header.Name, header.Typeflag) } - case tar.TypeReg: - // Expand files into the target directory. - dst, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) - if err != nil { - return errors.Wrapf(err, "opening file %s for untar", path) - } - defer contract.IgnoreClose(dst) - if _, err = io.Copy(dst, r); err != nil { - return errors.Wrapf(err, "untarring file %s", path) - } - default: - return errors.Errorf("unexpected plugin file type %s (%v)", header.Name, header.Typeflag) } + + return nil + })() + if err != nil { + return err } // If two calls to `plugin install` for the same plugin are racing, the second one will be unable to rename diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index c37ffd77d..e0713d07e 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -215,13 +215,12 @@ func TestStackOutputsJSON(t *testing.T) { e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL()) e.RunCommand("pulumi", "stack", "init", "stack-outs") e.RunCommand("pulumi", "up", "--non-interactive", "--skip-preview") - stdout, stderr := e.RunCommand("pulumi", "stack", "output", "--json") + stdout, _ := e.RunCommand("pulumi", "stack", "output", "--json") assert.Equal(t, `{ "foo": 42, "xyz": "ABC" } `, stdout) - assert.Equal(t, "", stderr) } // TestStackOutputsDisplayed ensures that outputs are printed at the end of an update