improve the HTTP detection for tar archives

Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>
pull/8752/head
Matt Butcher 4 years ago
parent 459dcd7f72
commit e2da16f514
No known key found for this signature in database
GPG Key ID: DCD5F5E5EF32C345

@ -59,6 +59,18 @@ var Extractors = map[string]Extractor{
".tgz": &TarGzExtractor{}, ".tgz": &TarGzExtractor{},
} }
// Convert a media type to an extractor extension.
//
// This should be refactored in Helm 4, combined with the extension-based mechanism.
func mediaTypeToExtension(mt string) (string, bool) {
switch strings.ToLower(mt) {
case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar":
return ".tgz", true
default:
return "", false
}
}
// NewExtractor creates a new extractor matching the source file name // NewExtractor creates a new extractor matching the source file name
func NewExtractor(source string) (Extractor, error) { func NewExtractor(source string) (Extractor, error) {
for suffix, extractor := range Extractors { for suffix, extractor := range Extractors {

@ -20,9 +20,13 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"encoding/base64" "encoding/base64"
"fmt"
"io/ioutil" "io/ioutil"
"net/http"
"net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"testing" "testing"
@ -63,9 +67,24 @@ func TestStripName(t *testing.T) {
} }
} }
func mockArchiveServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, ".tar.gz") {
w.Header().Add("Content-Type", "text/html")
fmt.Fprintln(w, "broken")
return
}
w.Header().Add("Content-Type", "application/gzip")
fmt.Fprintln(w, "test")
}))
}
func TestHTTPInstaller(t *testing.T) { func TestHTTPInstaller(t *testing.T) {
defer ensure.HelmHome(t)() defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
@ -111,7 +130,9 @@ func TestHTTPInstaller(t *testing.T) {
func TestHTTPInstallerNonExistentVersion(t *testing.T) { func TestHTTPInstallerNonExistentVersion(t *testing.T) {
defer ensure.HelmHome(t)() defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
@ -141,7 +162,9 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
} }
func TestHTTPInstallerUpdate(t *testing.T) { func TestHTTPInstallerUpdate(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
defer ensure.HelmHome(t)() defer ensure.HelmHome(t)()
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
@ -307,3 +330,26 @@ func TestCleanJoin(t *testing.T) {
} }
} }
func TestMediaTypeToExtension(t *testing.T) {
for mt, shouldPass := range map[string]bool{
"": false,
"application/gzip": true,
"application/x-gzip": true,
"application/x-tgz": true,
"application/x-gtar": true,
"application/json": false,
} {
ext, ok := mediaTypeToExtension(mt)
if ok != shouldPass {
t.Errorf("Media type %q failed test", mt)
}
if shouldPass && ext == "" {
t.Errorf("Expected an extension but got empty string")
}
if !shouldPass && len(ext) != 0 {
t.Error("Expected extension to be empty for unrecognized type")
}
}
}

@ -18,6 +18,7 @@ package installer
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -89,10 +90,29 @@ func isLocalReference(source string) bool {
} }
// isRemoteHTTPArchive checks if the source is a http/https url and is an archive // isRemoteHTTPArchive checks if the source is a http/https url and is an archive
//
// It works by checking whether the source looks like a URL and, if it does, running a
// HEAD operation to see if the remote resource is a file that we understand.
func isRemoteHTTPArchive(source string) bool { func isRemoteHTTPArchive(source string) bool {
if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") {
res, err := http.Head(source)
if err != nil {
// If we get an error at the network layer, we can't install it. So
// we return false.
return false
}
// Next, we look for the content type or content disposition headers to see
// if they have matching extractors.
contentType := res.Header.Get("content-type")
foundSuffix, ok := mediaTypeToExtension(contentType)
if !ok {
// Media type not recognized
return false
}
for suffix := range Extractors { for suffix := range Extractors {
if strings.HasSuffix(source, suffix) { if strings.HasSuffix(foundSuffix, suffix) {
return true return true
} }
} }

@ -0,0 +1,40 @@
/*
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 "testing"
func TestIsRemoteHTTPArchive(t *testing.T) {
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if isRemoteHTTPArchive("/not/a/URL") {
t.Errorf("Expected non-URL to return false")
}
if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") {
t.Errorf("Bad URL should not have succeeded.")
}
if !isRemoteHTTPArchive(source) {
t.Errorf("Expected %q to be a valid archive URL", source)
}
if isRemoteHTTPArchive(source + "-not-an-extension") {
t.Error("Expected media type match to fail")
}
}
Loading…
Cancel
Save