Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow loading file from http #2167

Merged
merged 2 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions api/internal/target/kusttarget.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"bytes"
"encoding/json"
"fmt"
"log"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -302,18 +301,15 @@ func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, err
func (kt *KustTarget) accumulateResources(
ra *accumulator.ResAccumulator, paths []string) error {
for _, path := range paths {
ldr, err := kt.ldr.New(path)
if err == nil {
err = kt.accumulateDirectory(ra, ldr)
if err != nil {
return err
// try loading resource as file then as base (directory or git repository)
if errF := kt.accumulateFile(ra, path); errF != nil {
ldr, errL := kt.ldr.New(path)
if errL != nil {
return fmt.Errorf("accumulateFile %q, loader.New %q", errF, errL)
}
} else {
err2 := kt.accumulateFile(ra, path)
if err2 != nil {
// Log ldr.New() error to highlight git failures.
log.Print(err.Error())
return err2
errD := kt.accumulateDirectory(ra, ldr)
if errD != nil {
return fmt.Errorf("accumulateFile %q, accumulateDirector: %q", errF, errD)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions api/krusty/accumulation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ spec:
if err == nil {
t.Fatalf("expected an error")
}
if !IsMissingKustomizationFileError(err) {
if !strings.Contains(err.Error(), "accumulating resources") {
t.Fatalf("unexpected error: %q", err)
}
}
Expand All @@ -89,7 +89,7 @@ resources:
if err == nil {
t.Fatalf("expected an error")
}
if !strings.Contains(err.Error(), "'/app/deployment.yaml' doesn't exist") {
if !strings.Contains(err.Error(), "accumulating resources") {
t.Fatalf("unexpected error: %q", err)
}
}
25 changes: 25 additions & 0 deletions api/loader/fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ package loader

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"path/filepath"
"strings"

Expand Down Expand Up @@ -86,6 +89,9 @@ type fileLoader struct {
// File system utilities.
fSys filesys.FileSystem

// Used to load from HTTP
http *http.Client

// Used to clone repositories.
cloner git.Cloner

Expand Down Expand Up @@ -293,6 +299,25 @@ func (fl *fileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
// else an error. Relative paths are taken relative
// to the root.
func (fl *fileLoader) Load(path string) ([]byte, error) {
if u, err := url.Parse(path); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
var hc *http.Client
if fl.http != nil {
hc = fl.http
} else {
hc = &http.Client{}
}
resp, err := hc.Get(path)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}

if !filepath.IsAbs(path) {
path = fl.root.Join(path)
}
Expand Down
93 changes: 93 additions & 0 deletions api/loader/fileloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package loader

import (
"bytes"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -54,6 +56,7 @@ func makeLoader() *fileLoader {
return NewFileLoaderAtRoot(MakeFakeFs(testCases))

}

func TestLoaderLoad(t *testing.T) {
l1 := makeLoader()
if "/" != l1.Root() {
Expand Down Expand Up @@ -579,3 +582,93 @@ func TestRepoIndirectCycleDetection(t *testing.T) {
t.Fatalf("unexpected err: %v", err)
}
}

// Inspired by https://hassansin.github.io/Unit-Testing-http-client-in-Go
type fakeRoundTripper func(req *http.Request) *http.Response

func (f fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}

func makeFakeHTTPClient(fn fakeRoundTripper) *http.Client {
return &http.Client{
Transport: fn,
}
}

// TestLoaderHTTP test http file loader
func TestLoaderHTTP(t *testing.T) {
var testCasesFile = []testData{
{
path: "http/file.yaml",
expectedContent: "file content",
},
}

l1 := NewFileLoaderAtRoot(MakeFakeFs(testCasesFile))
if "/" != l1.Root() {
t.Fatalf("incorrect root: '%s'\n", l1.Root())
}
for _, x := range testCasesFile {
b, err := l1.Load(x.path)
if err != nil {
t.Fatalf("unexpected load error: %v", err)
}
if !reflect.DeepEqual([]byte(x.expectedContent), b) {
t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
}
}

var testCasesHTTP = []testData{
{
path: "http://example.com/resource.yaml",
expectedContent: "http content",
},
{
path: "https://example.com/resource.yaml",
expectedContent: "https content",
},
}

for _, x := range testCasesHTTP {
hc := makeFakeHTTPClient(func(req *http.Request) *http.Response {
u := req.URL.String()
if x.path != u {
t.Fatalf("expected URL %s, but got %s", x.path, u)
}
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(x.expectedContent)),
Header: make(http.Header),
}
})
l2 := l1
l2.http = hc
b, err := l2.Load(x.path)
if err != nil {
t.Fatalf("unexpected load error: %v", err)
}
if !reflect.DeepEqual([]byte(x.expectedContent), b) {
t.Fatalf("in load expected %s, but got %s", x.expectedContent, b)
}
}

var testCaseUnsupported = []testData{
{
path: "httpsnotreal://example.com/resource.yaml",
expectedContent: "invalid",
},
}
for _, x := range testCaseUnsupported {
hc := makeFakeHTTPClient(func(req *http.Request) *http.Response {
t.Fatalf("unexpected request to URL %s", req.URL.String())
return nil
})
l2 := l1
l2.http = hc
_, err := l2.Load(x.path)
if err == nil {
t.Fatalf("expect error but get %v", err)
}
}
}
25 changes: 25 additions & 0 deletions examples/loadHttp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# load file from http

Resource and patch files could be loaded from http

<!-- @loadHttp -->
```sh
DEMO_HOME=$(mktemp -d)

cat <<EOF >$DEMO_HOME/kustomization.yaml
resources:
- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/helloWorld/configMap.yaml
EOF
```

<!-- @loadHttp -->
```sh
test 1 == \
$(kustomize build $DEMO_HOME | grep "Good Morning!" | wc -l); \
echo $?
```

Kustomize will try loading resource as a file either from local or http. If it
fails, try to load it as a directory or git repository.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "load it as a directory or git repository" mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example of load as a directory or git repository:

resources:
- ../base
- github.com/kustless/kustomize-examples

It is not possible to tell if it is a file before trying to load.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that example is now valid? The documentation only mentions loading via http[s]:// urls.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example should be valid at 300dd10 but might be broken on 0b1ad03 , see #2264 .

github.com/kustless/kustomize-examples is supported by the original git cloner. http loader is for single yaml file only as given in the example. It does not apply for git repository or http archive. When failed to load file, it will fallback to original directory loader and git cloner.

#2169 try to solve the remote get problem with go-getter but it was merged by accident before completely tested...


Http load applies to patches as well. See full example in [loadHttp](loadHttp/).
15 changes: 15 additions & 0 deletions examples/loadHttp/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/wordpress/wordpress/deployment.yaml
- https://github.com/knative/serving/releases/download/v0.12.0/serving.yaml # redirects to s3
patches:
- https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/examples/wordpress/patch.yaml
patchesStrategicMerge:
- |-
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: custom-metrics-auth-reader
namespace: kube-system
$patch: delete