Skip to content

Commit

Permalink
feat(component): always check for vpn errors
Browse files Browse the repository at this point in the history
(cherry picked from commit 542d29e)

- Remove basic authentication requirement by the read operations test
  • Loading branch information
crhntr authored and pabloarodas committed Jun 5, 2023
1 parent 0e39cfb commit d1a1ac2
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 52 deletions.
53 changes: 32 additions & 21 deletions internal/component/artifactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

type ArtifactoryReleaseSource struct {
cargo.ReleaseSourceConfig
Client *http.Client
logger *log.Logger
ID string
}
Expand Down Expand Up @@ -69,18 +70,9 @@ func NewArtifactoryReleaseSource(c cargo.ReleaseSourceConfig) *ArtifactoryReleas
func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRelease Lock) (Local, error) {
downloadURL := ars.ArtifactoryHost + "/artifactory/" + ars.Repo + "/" + remoteRelease.RemotePath
ars.logger.Printf(logLineDownload, remoteRelease.Name, ReleaseSourceTypeArtifactory, ars.ID)
resp, err := http.Get(downloadURL)

unwrapped := err
for errors.Unwrap(unwrapped) != nil {
unwrapped = errors.Unwrap(unwrapped)
}
switch e := unwrapped.(type) {
case *net.DNSError:
return Local{}, fmt.Errorf("failed to download %s release from artifactory: %w. (hint: Are you connected to the corporate vpn?)", remoteRelease.Name, e)
case nil: // continue
default:
return Local{}, e
resp, err := ars.Client.Get(downloadURL)
if err != nil {
return Local{}, wrapVPNError(err)
}

if resp.StatusCode != http.StatusOK {
Expand Down Expand Up @@ -120,9 +112,9 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe
func (ars *ArtifactoryReleaseSource) getFileSHA1(release Lock) (string, error) {
fullURL := ars.ArtifactoryHost + "/api/storage/" + ars.Repo + "/" + release.RemotePath
ars.logger.Printf("Getting %s file info from artifactory", release.Name)
resp, err := http.Get(fullURL)
resp, err := ars.Client.Get(fullURL)
if err != nil {
return "", err
return "", wrapVPNError(err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get %s release info from artifactory with error code %d", release.Name, resp.StatusCode)
Expand Down Expand Up @@ -160,9 +152,9 @@ func (ars *ArtifactoryReleaseSource) GetMatchedRelease(spec Spec) (Lock, error)
return Lock{}, err
}

response, err := http.DefaultClient.Do(request)
response, err := ars.Client.Do(request)
if err != nil {
return Lock{}, err
return Lock{}, wrapVPNError(err)
}
defer func() {
_ = response.Body.Close()
Expand Down Expand Up @@ -199,9 +191,9 @@ func (ars *ArtifactoryReleaseSource) FindReleaseVersion(spec Spec, _ bool) (Lock
return Lock{}, err
}

response, err := http.DefaultClient.Do(request)
response, err := ars.Client.Do(request)
if err != nil {
return Lock{}, err
return Lock{}, wrapVPNError(err)
}
defer func() {
_ = response.Body.Close()
Expand Down Expand Up @@ -309,10 +301,9 @@ func (ars *ArtifactoryReleaseSource) UploadRelease(spec Spec, file io.Reader) (L
// TODO: check Sha1/2
// request.Header.Set("X-Checksum-Sha1", spec.??? )

response, err := http.DefaultClient.Do(request)
response, err := ars.Client.Do(request)
if err != nil {
fmt.Println(err)
return Lock{}, err
return Lock{}, wrapVPNError(err)
}

switch response.StatusCode {
Expand Down Expand Up @@ -346,3 +337,23 @@ func (ars *ArtifactoryReleaseSource) pathTemplate() *template.Template {
Funcs(template.FuncMap{"trimSuffix": strings.TrimSuffix}).
Parse(ars.ReleaseSourceConfig.PathTemplate))
}

type vpnError struct {
wrapped error
}

func (fe *vpnError) Unwrap() error {
return fe.wrapped
}

func (fe *vpnError) Error() string {
return fmt.Sprintf("failed to dial (hint: Are you connected to the corporate vpn?): %s", fe.wrapped)
}

func wrapVPNError(err error) error {
x := new(net.DNSError)
if errors.As(err, &x) {
return &vpnError{wrapped: err}
}
return err
}
29 changes: 29 additions & 0 deletions internal/component/artifactory_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package component

import (
"fmt"
"net"
"net/url"
"testing"

"github.com/stretchr/testify/require"
)

func Test_wrapFirewallError(t *testing.T) {
t.Run("dns error", func(t *testing.T) {
someErr := &url.Error{
Err: &net.DNSError{
Err: "some message",
},
}
err := wrapVPNError(someErr)
_, ok := err.(*vpnError)
require.True(t, ok)
})
t.Run("any other error", func(t *testing.T) {
someErr := fmt.Errorf("lemon")
err := wrapVPNError(someErr)
_, ok := err.(*vpnError)
require.False(t, ok)
})
}
117 changes: 86 additions & 31 deletions internal/component/artifactory_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package component_test

import (
"bytes"
"io"
"log"
"net"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -54,41 +56,37 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() {
server = httptest.NewServer(artifactoryRouter)
config.ArtifactoryHost = server.URL
source = component.NewArtifactoryReleaseSource(config)
source.Client = server.Client()
})
AfterEach(func() {
server.Close()
_ = os.RemoveAll(releasesDirectory)
})

Describe("read operations", func() {
BeforeEach(func() {
artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
// language=json
_, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`)
})))
artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
// language=json
_, _ = io.WriteString(res, `{"children": [{"uri": "/mango-2.3.4-smoothie-9.9.tgz", "folder": false}]}`)
})))
artifactoryRouter.Handler(http.MethodGet, "/artifactory/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
f, err := os.Open(filepath.Join("testdata", "some-release.tgz"))
if err != nil {
log.Fatal("failed to open some release test artifact")
}
defer closeAndIgnoreError(f)
_, _ = io.Copy(res, f)
}) /* put middleware here */))
})
When("the server has the a file at the expected path", func() {
BeforeEach(func() {
requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword)

artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
// language=json
_, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`)
})))
artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
// language=json
_, _ = io.WriteString(res, `{"children": [{"uri": "/mango-2.3.4-smoothie-9.9.tgz", "folder": false}]}`)
}), requireAuth))
artifactoryRouter.Handler(http.MethodGet, "/artifactory/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {
res.WriteHeader(http.StatusOK)
f, err := os.Open(filepath.Join("testdata", "some-release.tgz"))
if err != nil {
log.Fatal("failed to open some release test artifact")
}
defer closeAndIgnoreError(f)
_, _ = io.Copy(res, f)
}) /* put middleware here */))
})

It("resolves the lock from the spec", func() { // testing GetMatchedRelease
source.ID = "some-mango-tree"
source.ReleaseSourceConfig.ID = "why-are-we-not-using-this-field"
resultLock, resultErr := source.GetMatchedRelease(component.Spec{
Name: "mango",
Version: "2.3.4",
Expand Down Expand Up @@ -127,10 +125,7 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() {
}))
})

It("downloads the release", func() {
source.ID = "some-mango-tree"
source.ReleaseSourceConfig.ID = "why-are-we-not-using-this-field"

It("downloads the release", func() { // teesting DownloadRelease
By("calling FindReleaseVersion")
local, resultErr := source.DownloadRelease(releasesDirectory, component.Lock{
Name: "mango",
Expand Down Expand Up @@ -173,7 +168,6 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() {
Expect(err).NotTo(HaveOccurred())
defer closeAndIgnoreError(f)

By("calling UploadRelease")
resultLock, resultErr := source.UploadRelease(component.Spec{
Name: "mango",
Version: "2.3.4",
Expand All @@ -194,6 +188,60 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() {
Expect(filepath.Join(serverReleasesDirectory, "mango-2.3.4-smoothie-9.9.tgz")).To(BeAnExistingFile())
})
})

When("not behind the corporate firewall", func() {
JustBeforeEach(func() {
source.Client.Transport = dnsFailure{}
})
Describe("GetMatchedRelease", func() {
It("returns a helpful message", func() {
_, resultErr := source.GetMatchedRelease(component.Spec{
Name: "mango",
Version: "2.3.4",
StemcellOS: "smoothie",
StemcellVersion: "9.9",
})
Expect(resultErr).To(HaveOccurred())
Expect(resultErr.Error()).To(ContainSubstring("vpn"))
})
})
Describe("FindReleaseVersion", func() {
It("returns a helpful message", func() {
_, resultErr := source.FindReleaseVersion(component.Spec{
Name: "mango",
Version: "2.3.4",
StemcellOS: "smoothie",
StemcellVersion: "9.9",
}, false)
Expect(resultErr).To(HaveOccurred())
Expect(resultErr.Error()).To(ContainSubstring("vpn"))
})
})
Describe("DownloadRelease", func() {
It("returns a helpful message", func() {
_, resultErr := source.DownloadRelease(releasesDirectory, component.Lock{
Name: "mango",
Version: "2.3.4",
RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz",
RemoteSource: "some-mango-tree",
})
Expect(resultErr).To(HaveOccurred())
Expect(resultErr.Error()).To(ContainSubstring("vpn"))
})
})
Describe("UploadRelease", func() {
It("returns a helpful message", func() {
_, resultErr := source.UploadRelease(component.Spec{
Name: "mango",
Version: "2.3.4",
StemcellOS: "smoothie",
StemcellVersion: "9.9",
}, bytes.NewBuffer(nil))
Expect(resultErr).To(HaveOccurred())
Expect(resultErr.Error()).To(ContainSubstring("vpn"))
})
})
})
})

func closeAndIgnoreError(c io.Closer) {
Expand Down Expand Up @@ -229,8 +277,15 @@ func applyMiddleware(endpoint http.Handler, middleware ...func(http.Handler) htt
return h
}

type dnsFailure struct{}

func (dnsFailure) RoundTrip(*http.Request) (*http.Response, error) {
return nil, &net.DNSError{Err: "some error"}
}

func must[T any](value T, err error) T {
if err != nil {
log.Fatal(err)
}
return value
}

0 comments on commit d1a1ac2

Please sign in to comment.