github.com/koderover/helm@v2.17.0+incompatible/pkg/plugin/installer/http_installer_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package installer // import "k8s.io/helm/pkg/plugin/installer"
    17  
    18  import (
    19  	"archive/tar"
    20  	"bytes"
    21  	"compress/gzip"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"syscall"
    28  	"testing"
    29  
    30  	"k8s.io/helm/pkg/helm/helmpath"
    31  )
    32  
    33  var _ Installer = new(HTTPInstaller)
    34  
    35  // Fake http client
    36  type TestHTTPGetter struct {
    37  	MockResponse *bytes.Buffer
    38  	MockError    error
    39  }
    40  
    41  func (t *TestHTTPGetter) Get(href string) (*bytes.Buffer, error) { return t.MockResponse, t.MockError }
    42  
    43  // Fake plugin tarball data
    44  var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA="
    45  
    46  func TestStripName(t *testing.T) {
    47  	if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" {
    48  		t.Errorf("name does not match expected value")
    49  	}
    50  	if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" {
    51  		t.Errorf("name does not match expected value")
    52  	}
    53  	if stripPluginName("fake-plugin.tgz") != "fake-plugin" {
    54  		t.Errorf("name does not match expected value")
    55  	}
    56  	if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" {
    57  		t.Errorf("name does not match expected value")
    58  	}
    59  }
    60  
    61  func TestHTTPInstaller(t *testing.T) {
    62  	source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
    63  	hh, err := ioutil.TempDir("", "helm-home-")
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  	defer os.RemoveAll(hh)
    68  
    69  	home := helmpath.Home(hh)
    70  	if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
    71  		t.Fatalf("Could not create %s: %s", home.Plugins(), err)
    72  	}
    73  
    74  	i, err := NewForSource(source, "0.0.1", home)
    75  	if err != nil {
    76  		t.Errorf("unexpected error: %s", err)
    77  	}
    78  
    79  	// ensure a HTTPInstaller was returned
    80  	httpInstaller, ok := i.(*HTTPInstaller)
    81  	if !ok {
    82  		t.Error("expected a HTTPInstaller")
    83  	}
    84  
    85  	// inject fake http client responding with minimal plugin tarball
    86  	mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
    87  	if err != nil {
    88  		t.Fatalf("Could not decode fake tgz plugin: %s", err)
    89  	}
    90  
    91  	httpInstaller.getter = &TestHTTPGetter{
    92  		MockResponse: bytes.NewBuffer(mockTgz),
    93  	}
    94  
    95  	// install the plugin
    96  	if err := Install(i); err != nil {
    97  		t.Error(err)
    98  	}
    99  	if i.Path() != home.Path("plugins", "fake-plugin") {
   100  		t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path())
   101  	}
   102  
   103  	// Install again to test plugin exists error
   104  	if err := Install(i); err == nil {
   105  		t.Error("expected error for plugin exists, got none")
   106  	} else if err.Error() != "plugin already exists" {
   107  		t.Errorf("expected error for plugin exists, got (%v)", err)
   108  	}
   109  
   110  }
   111  
   112  func TestHTTPInstallerNonExistentVersion(t *testing.T) {
   113  	source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
   114  	hh, err := ioutil.TempDir("", "helm-home-")
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  	defer os.RemoveAll(hh)
   119  
   120  	home := helmpath.Home(hh)
   121  	if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
   122  		t.Fatalf("Could not create %s: %s", home.Plugins(), err)
   123  	}
   124  
   125  	i, err := NewForSource(source, "0.0.2", home)
   126  	if err != nil {
   127  		t.Errorf("unexpected error: %s", err)
   128  	}
   129  
   130  	// ensure a HTTPInstaller was returned
   131  	httpInstaller, ok := i.(*HTTPInstaller)
   132  	if !ok {
   133  		t.Error("expected a HTTPInstaller")
   134  	}
   135  
   136  	// inject fake http client responding with error
   137  	httpInstaller.getter = &TestHTTPGetter{
   138  		MockError: fmt.Errorf("failed to download plugin for some reason"),
   139  	}
   140  
   141  	// attempt to install the plugin
   142  	if err := Install(i); err == nil {
   143  		t.Error("expected error from http client")
   144  	}
   145  
   146  }
   147  
   148  func TestHTTPInstallerUpdate(t *testing.T) {
   149  	source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
   150  	hh, err := ioutil.TempDir("", "helm-home-")
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	defer os.RemoveAll(hh)
   155  
   156  	home := helmpath.Home(hh)
   157  	if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
   158  		t.Fatalf("Could not create %s: %s", home.Plugins(), err)
   159  	}
   160  
   161  	i, err := NewForSource(source, "0.0.1", home)
   162  	if err != nil {
   163  		t.Errorf("unexpected error: %s", err)
   164  	}
   165  
   166  	// ensure a HTTPInstaller was returned
   167  	httpInstaller, ok := i.(*HTTPInstaller)
   168  	if !ok {
   169  		t.Error("expected a HTTPInstaller")
   170  	}
   171  
   172  	// inject fake http client responding with minimal plugin tarball
   173  	mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
   174  	if err != nil {
   175  		t.Fatalf("Could not decode fake tgz plugin: %s", err)
   176  	}
   177  
   178  	httpInstaller.getter = &TestHTTPGetter{
   179  		MockResponse: bytes.NewBuffer(mockTgz),
   180  	}
   181  
   182  	// install the plugin before updating
   183  	if err := Install(i); err != nil {
   184  		t.Error(err)
   185  	}
   186  	if i.Path() != home.Path("plugins", "fake-plugin") {
   187  		t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path())
   188  	}
   189  
   190  	// Update plugin, should fail because it is not implemented
   191  	if err := Update(i); err == nil {
   192  		t.Error("update method not implemented for http installer")
   193  	}
   194  }
   195  
   196  func TestExtract(t *testing.T) {
   197  	//create a temp home
   198  	hh, err := ioutil.TempDir("", "helm-home-")
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	defer os.RemoveAll(hh)
   203  
   204  	home := helmpath.Home(hh)
   205  	if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
   206  		t.Fatalf("Could not create %s: %s", home.Plugins(), err)
   207  	}
   208  
   209  	cacheDir := filepath.Join(home.Cache(), "plugins", "plugin-key")
   210  	if err := os.MkdirAll(cacheDir, 0755); err != nil {
   211  		t.Fatalf("Could not create %s: %s", cacheDir, err)
   212  	}
   213  
   214  	//{"plugin.yaml", "plugin metadata up in here"},
   215  	//{"README.md", "so you know what's upp"},
   216  	//{"script.sh", "echo script"},
   217  
   218  	syscall.Umask(0000)
   219  
   220  	var tarbuf bytes.Buffer
   221  	tw := tar.NewWriter(&tarbuf)
   222  	var files = []struct {
   223  		Name, Body string
   224  		Mode       int64
   225  	}{
   226  		{"./plugin.yaml", "sneaky plugin metadata", 0600},
   227  		{"README.md", "some text", 0777},
   228  	}
   229  	for _, file := range files {
   230  		hdr := &tar.Header{
   231  			Name:     file.Name,
   232  			Typeflag: tar.TypeReg,
   233  			Mode:     file.Mode,
   234  			Size:     int64(len(file.Body)),
   235  		}
   236  		if err := tw.WriteHeader(hdr); err != nil {
   237  			t.Fatal(err)
   238  		}
   239  		if _, err := tw.Write([]byte(file.Body)); err != nil {
   240  			t.Fatal(err)
   241  		}
   242  	}
   243  	if err := tw.Close(); err != nil {
   244  		t.Fatal(err)
   245  	}
   246  
   247  	var buf bytes.Buffer
   248  	gz := gzip.NewWriter(&buf)
   249  	if _, err := gz.Write(tarbuf.Bytes()); err != nil {
   250  		t.Fatal(err)
   251  	}
   252  	gz.Close()
   253  
   254  	source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tgz"
   255  	extr, err := NewExtractor(source)
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  
   260  	if err = extr.Extract(&buf, cacheDir); err != nil {
   261  		t.Errorf("Did not expect error but got error: %v", err)
   262  	}
   263  
   264  	pluginYAMLFullPath := filepath.Join(cacheDir, "plugin.yaml")
   265  	if info, err := os.Stat(pluginYAMLFullPath); err != nil {
   266  		if os.IsNotExist(err) {
   267  			t.Errorf("Expected %s to exist but doesn't", pluginYAMLFullPath)
   268  		} else {
   269  			t.Error(err)
   270  		}
   271  	} else if info.Mode().Perm() != 0600 {
   272  		t.Errorf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm())
   273  	}
   274  
   275  	readmeFullPath := filepath.Join(cacheDir, "README.md")
   276  	if info, err := os.Stat(readmeFullPath); err != nil {
   277  		if os.IsNotExist(err) {
   278  			t.Errorf("Expected %s to exist but doesn't", readmeFullPath)
   279  		} else {
   280  			t.Error(err)
   281  		}
   282  	} else if info.Mode().Perm() != 0777 {
   283  		t.Errorf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm())
   284  	}
   285  
   286  }
   287  
   288  func TestExtractBadPlugin(t *testing.T) {
   289  	//create a temp home
   290  	hh, err := ioutil.TempDir("", "helm-home-")
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	defer os.RemoveAll(hh)
   295  
   296  	home := helmpath.Home(hh)
   297  	if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
   298  		t.Fatalf("Could not create %s: %s", home.Plugins(), err)
   299  	}
   300  
   301  	cacheDir := filepath.Join(home.Cache(), "plugins", "plugin-key")
   302  	if err := os.MkdirAll(cacheDir, 0755); err != nil {
   303  		t.Fatalf("Could not create %s: %s", cacheDir, err)
   304  	}
   305  
   306  	syscall.Umask(0000)
   307  
   308  	var tarbuf bytes.Buffer
   309  	tw := tar.NewWriter(&tarbuf)
   310  	var files = []struct {
   311  		Name, Body string
   312  		Mode       int64
   313  	}{
   314  		{"../../plugin.yaml", "sneaky plugin metadata", 0600},
   315  		{"README.md", "some text", 0777},
   316  	}
   317  	for _, file := range files {
   318  		hdr := &tar.Header{
   319  			Name:     file.Name,
   320  			Typeflag: tar.TypeReg,
   321  			Mode:     file.Mode,
   322  			Size:     int64(len(file.Body)),
   323  		}
   324  		if err := tw.WriteHeader(hdr); err != nil {
   325  			t.Fatal(err)
   326  		}
   327  		if _, err := tw.Write([]byte(file.Body)); err != nil {
   328  			t.Fatal(err)
   329  		}
   330  	}
   331  	if err := tw.Close(); err != nil {
   332  		t.Fatal(err)
   333  	}
   334  
   335  	var buf bytes.Buffer
   336  	gz := gzip.NewWriter(&buf)
   337  	if _, err := gz.Write(tarbuf.Bytes()); err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	gz.Close()
   341  
   342  	source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tgz"
   343  	extr, err := NewExtractor(source)
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  
   348  	if err = extr.Extract(&buf, cacheDir); err == nil {
   349  		t.Error("Should have failed because of bad plugin.yaml path")
   350  	}
   351  }
   352  
   353  func TestCleanJoin(t *testing.T) {
   354  	for i, fixture := range []struct {
   355  		path        string
   356  		expect      string
   357  		expectError bool
   358  	}{
   359  		{"foo/bar.txt", "/tmp/foo/bar.txt", false},
   360  		{"/foo/bar.txt", "", true},
   361  		{"./foo/bar.txt", "/tmp/foo/bar.txt", false},
   362  		{"./././././foo/bar.txt", "/tmp/foo/bar.txt", false},
   363  		{"../../../../foo/bar.txt", "", true},
   364  		{"foo/../../../../bar.txt", "", true},
   365  		{"c:/foo/bar.txt", "/tmp/c:/foo/bar.txt", true},
   366  		{"foo\\bar.txt", "/tmp/foo/bar.txt", false},
   367  		{"c:\\foo\\bar.txt", "", true},
   368  	} {
   369  		out, err := cleanJoin("/tmp", fixture.path)
   370  		if err != nil {
   371  			if !fixture.expectError {
   372  				t.Errorf("Test %d: Path was not cleaned: %s", i, err)
   373  			}
   374  			continue
   375  		}
   376  		if fixture.expect != out {
   377  			t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out)
   378  		}
   379  	}
   380  
   381  }