mirror of
https://github.com/helm/helm.git
synced 2026-04-22 14:47:41 -04:00
* Fixing issue with PAX headers in plugin archive PAX Headers can be added by some systems that create archives. Helm should ignore them when extracting. There are two PAX headers. One is global and the other is not. Both are ignored. The test adds only the PAX global header because the Go tar package is unable to write the header that is not global. Closes #8084 Signed-off-by: Matt Farina <matt@mattfarina.com> * Removing the PAX header test as it is not working The PAX header test was making a WriteHeader call and ignoring the error. When writing the type TypeXHeader it was causing an error that was being silently ignored. The Go tar package cannot write this type and produces an error when one tries to. The error reads "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers" Signed-off-by: Matt Farina <matt@mattfarina.com> * Adding check of returned error in test Adding a check for the returned error to make sure a non-nil value is not returned. Signed-off-by: Matt Farina <matt@mattfarina.com>
279 lines
8 KiB
Go
279 lines
8 KiB
Go
/*
|
|
Copyright The Helm Authors.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package installer // import "helm.sh/helm/v3/pkg/plugin/installer"
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"helm.sh/helm/v3/internal/test/ensure"
|
|
"helm.sh/helm/v3/pkg/getter"
|
|
"helm.sh/helm/v3/pkg/helmpath"
|
|
)
|
|
|
|
var _ Installer = new(HTTPInstaller)
|
|
|
|
// Fake http client
|
|
type TestHTTPGetter struct {
|
|
MockResponse *bytes.Buffer
|
|
MockError error
|
|
}
|
|
|
|
func (t *TestHTTPGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) {
|
|
return t.MockResponse, t.MockError
|
|
}
|
|
|
|
// Fake plugin tarball data
|
|
var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA="
|
|
|
|
func TestStripName(t *testing.T) {
|
|
if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" {
|
|
t.Errorf("name does not match expected value")
|
|
}
|
|
if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" {
|
|
t.Errorf("name does not match expected value")
|
|
}
|
|
if stripPluginName("fake-plugin.tgz") != "fake-plugin" {
|
|
t.Errorf("name does not match expected value")
|
|
}
|
|
if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" {
|
|
t.Errorf("name does not match expected value")
|
|
}
|
|
}
|
|
|
|
func TestHTTPInstaller(t *testing.T) {
|
|
defer ensure.HelmHome(t)()
|
|
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
|
|
|
|
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
|
|
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
|
|
}
|
|
|
|
i, err := NewForSource(source, "0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// ensure a HTTPInstaller was returned
|
|
httpInstaller, ok := i.(*HTTPInstaller)
|
|
if !ok {
|
|
t.Fatal("expected a HTTPInstaller")
|
|
}
|
|
|
|
// inject fake http client responding with minimal plugin tarball
|
|
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
|
|
if err != nil {
|
|
t.Fatalf("Could not decode fake tgz plugin: %s", err)
|
|
}
|
|
|
|
httpInstaller.getter = &TestHTTPGetter{
|
|
MockResponse: bytes.NewBuffer(mockTgz),
|
|
}
|
|
|
|
// install the plugin
|
|
if err := Install(i); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
|
|
t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
|
|
}
|
|
|
|
// Install again to test plugin exists error
|
|
if err := Install(i); err == nil {
|
|
t.Fatal("expected error for plugin exists, got none")
|
|
} else if err.Error() != "plugin already exists" {
|
|
t.Fatalf("expected error for plugin exists, got (%v)", err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestHTTPInstallerNonExistentVersion(t *testing.T) {
|
|
defer ensure.HelmHome(t)()
|
|
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
|
|
|
|
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
|
|
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
|
|
}
|
|
|
|
i, err := NewForSource(source, "0.0.2")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// ensure a HTTPInstaller was returned
|
|
httpInstaller, ok := i.(*HTTPInstaller)
|
|
if !ok {
|
|
t.Fatal("expected a HTTPInstaller")
|
|
}
|
|
|
|
// inject fake http client responding with error
|
|
httpInstaller.getter = &TestHTTPGetter{
|
|
MockError: errors.Errorf("failed to download plugin for some reason"),
|
|
}
|
|
|
|
// attempt to install the plugin
|
|
if err := Install(i); err == nil {
|
|
t.Fatal("expected error from http client")
|
|
}
|
|
|
|
}
|
|
|
|
func TestHTTPInstallerUpdate(t *testing.T) {
|
|
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
|
|
defer ensure.HelmHome(t)()
|
|
|
|
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
|
|
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
|
|
}
|
|
|
|
i, err := NewForSource(source, "0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
// ensure a HTTPInstaller was returned
|
|
httpInstaller, ok := i.(*HTTPInstaller)
|
|
if !ok {
|
|
t.Fatal("expected a HTTPInstaller")
|
|
}
|
|
|
|
// inject fake http client responding with minimal plugin tarball
|
|
mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
|
|
if err != nil {
|
|
t.Fatalf("Could not decode fake tgz plugin: %s", err)
|
|
}
|
|
|
|
httpInstaller.getter = &TestHTTPGetter{
|
|
MockResponse: bytes.NewBuffer(mockTgz),
|
|
}
|
|
|
|
// install the plugin before updating
|
|
if err := Install(i); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
|
|
t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
|
|
}
|
|
|
|
// Update plugin, should fail because it is not implemented
|
|
if err := Update(i); err == nil {
|
|
t.Fatal("update method not implemented for http installer")
|
|
}
|
|
}
|
|
|
|
func TestExtract(t *testing.T) {
|
|
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
|
|
|
|
tempDir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Set the umask to default open permissions so we can actually test
|
|
oldmask := syscall.Umask(0000)
|
|
defer func() {
|
|
syscall.Umask(oldmask)
|
|
}()
|
|
|
|
// Write a tarball to a buffer for us to extract
|
|
var tarbuf bytes.Buffer
|
|
tw := tar.NewWriter(&tarbuf)
|
|
var files = []struct {
|
|
Name, Body string
|
|
Mode int64
|
|
}{
|
|
{"plugin.yaml", "plugin metadata", 0600},
|
|
{"README.md", "some text", 0777},
|
|
}
|
|
for _, file := range files {
|
|
hdr := &tar.Header{
|
|
Name: file.Name,
|
|
Typeflag: tar.TypeReg,
|
|
Mode: file.Mode,
|
|
Size: int64(len(file.Body)),
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Add pax global headers. This should be ignored.
|
|
// Note the PAX header that isn't global cannot be written using WriteHeader.
|
|
// Details are in the internal Go function for the tar packaged named
|
|
// allowedFormats. For a TypeXHeader it will return a message stating
|
|
// "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers"
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "pax_global_header",
|
|
Typeflag: tar.TypeXGlobalHeader,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
gz := gzip.NewWriter(&buf)
|
|
if _, err := gz.Write(tarbuf.Bytes()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gz.Close()
|
|
// END tarball creation
|
|
|
|
extractor, err := NewExtractor(source)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = extractor.Extract(&buf, tempDir); err != nil {
|
|
t.Fatalf("Did not expect error but got error: %v", err)
|
|
}
|
|
|
|
pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml")
|
|
if info, err := os.Stat(pluginYAMLFullPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath)
|
|
}
|
|
t.Fatal(err)
|
|
} else if info.Mode().Perm() != 0600 {
|
|
t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm())
|
|
}
|
|
|
|
readmeFullPath := filepath.Join(tempDir, "README.md")
|
|
if info, err := os.Stat(readmeFullPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
t.Fatalf("Expected %s to exist but doesn't", readmeFullPath)
|
|
}
|
|
t.Fatal(err)
|
|
} else if info.Mode().Perm() != 0777 {
|
|
t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm())
|
|
}
|
|
|
|
}
|