Skip to content

Commit bcd4ca2

Browse files
Merge pull request #1762 from SaschaSchwarze0/sascha-test-connection
Test connectivity to the Git or Registry host before accessing the source
2 parents 4d626db + 48b2ed6 commit bcd4ca2

File tree

9 files changed

+249
-0
lines changed

9 files changed

+249
-0
lines changed

cmd/bundle/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ func Do(ctx context.Context) error {
7373
return fmt.Errorf("mandatory flag --image is not set")
7474
}
7575

76+
// check the endpoint, if hostname extraction fails, ignore that failure
77+
if hostname, port, err := image.ExtractHostnamePort(flagValues.image); err == nil {
78+
if !util.TestConnection(hostname, port, 9) {
79+
log.Printf("Warning: a connection test to %s:%d failed. The operation will likely fail.\n", hostname, port)
80+
}
81+
}
82+
7683
ref, err := name.ParseReference(flagValues.image)
7784
if err != nil {
7885
return err

cmd/git/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ func runGitClone(ctx context.Context) error {
164164
return &ExitError{Code: 101, Message: "the 'target' argument must not be empty"}
165165
}
166166

167+
// check the endpoint, if hostname extraction fails, ignore that failure
168+
if hostname, port, err := shpgit.ExtractHostnamePort(flagValues.url); err == nil {
169+
if !util.TestConnection(hostname, port, 9) {
170+
log.Printf("Warning: a connection test to %s:%d failed. The operation will likely fail.\n", hostname, port)
171+
}
172+
}
173+
167174
if err := clone(ctx); err != nil {
168175
return err
169176
}

pkg/git/git.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ const (
2424
gitProtocol = "ssh"
2525
)
2626

27+
// ExtractHostnamePort extracts the hostname and port of the provided Git URL
28+
func ExtractHostnamePort(url string) (string, int, error) {
29+
endpoint, err := transport.NewEndpoint(url)
30+
if err != nil {
31+
return "", 0, err
32+
}
33+
34+
port := endpoint.Port
35+
36+
if port == 0 {
37+
switch endpoint.Protocol {
38+
case httpProtocol:
39+
port = 80
40+
case httpsProtocol:
41+
port = 443
42+
case gitProtocol:
43+
port = 22
44+
45+
default:
46+
return "", 0, fmt.Errorf("unknown protocol: %s", endpoint.Protocol)
47+
}
48+
}
49+
50+
return endpoint.Host, port, nil
51+
}
52+
2753
// ValidateGitURLExists validate if a source URL exists or not
2854
// Note: We have an upcoming PR for the Build Status, where we
2955
// intend to define a single Status.Reason in the form of 'remoteRepositoryUnreachable',

pkg/git/git_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,27 @@ import (
1616

1717
var _ = Describe("Git", func() {
1818

19+
DescribeTable("the extraction of hostname and port",
20+
func(url string, expectedHost string, expectedPort int, expectError bool) {
21+
host, port, err := git.ExtractHostnamePort(url)
22+
if expectError {
23+
Expect(err).To(HaveOccurred())
24+
} else {
25+
Expect(err).ToNot(HaveOccurred())
26+
Expect(host).To(Equal(expectedHost), "for "+url)
27+
Expect(port).To(Equal(expectedPort), "for "+url)
28+
}
29+
},
30+
Entry("Check heritage SSH URL with default port", "ssh://github.com/shipwright-io/build.git", "github.com", 22, false),
31+
Entry("Check heritage SSH URL with custom port", "ssh://github.com:12134/shipwright-io/build.git", "github.com", 12134, false),
32+
Entry("Check SSH URL with default port", "[email protected]:shipwright-io/build.git", "github.com", 22, false),
33+
Entry("Check HTTP URL with default port", "http://github.com/shipwright-io/build.git", "github.com", 80, false),
34+
Entry("Check HTTPS URL with default port", "https://github.com/shipwright-io/build.git", "github.com", 443, false),
35+
Entry("Check HTTPS URL with custom port", "https://github.com:9443/shipwright-io/build.git", "github.com", 9443, false),
36+
Entry("Check HTTPS URL with credentials", "https://somebody:[email protected]/shipwright-io/build.git", "github.com", 443, false),
37+
Entry("Check invalid URL", "ftp://github.com/shipwright-io/build", "", 0, true),
38+
)
39+
1940
DescribeTable("the source url validation errors",
2041
func(url string, expected types.GomegaMatcher) {
2142
Expect(git.ValidateGitURLExists(context.TODO(), url)).To(expected)

pkg/image/endpoint.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package image
6+
7+
import (
8+
"fmt"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/google/go-containerregistry/pkg/name"
13+
)
14+
15+
// ExtractHostnamePort tries to extract the hostname and port of the provided image URL
16+
func ExtractHostnamePort(url string) (string, int, error) {
17+
ref, err := name.ParseReference(url)
18+
if err != nil {
19+
return "", 0, err
20+
}
21+
22+
registry := ref.Context().Registry
23+
host := registry.RegistryStr()
24+
hostname := host
25+
port := 0
26+
27+
parts := strings.SplitN(host, ":", 2)
28+
if len(parts) == 2 {
29+
hostname = parts[0]
30+
if port, err = strconv.Atoi(parts[1]); err != nil {
31+
return "", 0, err
32+
}
33+
} else {
34+
scheme := registry.Scheme()
35+
36+
switch scheme {
37+
case "http":
38+
port = 80
39+
case "https":
40+
port = 443
41+
42+
default:
43+
return "", 0, fmt.Errorf("unknown protocol: %s", scheme)
44+
}
45+
}
46+
47+
return hostname, port, nil
48+
}

pkg/image/endpoint_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package image_test
6+
7+
import (
8+
"github.com/shipwright-io/build/pkg/image"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
)
13+
14+
var _ = Describe("Endpoints", func() {
15+
16+
DescribeTable("the extraction of hostname and port",
17+
func(url string, expectedHost string, expectedPort int, expectError bool) {
18+
host, port, err := image.ExtractHostnamePort(url)
19+
if expectError {
20+
Expect(err).To(HaveOccurred())
21+
} else {
22+
Expect(err).ToNot(HaveOccurred())
23+
Expect(host).To(Equal(expectedHost), "for "+url)
24+
Expect(port).To(Equal(expectedPort), "for "+url)
25+
}
26+
},
27+
Entry("Check a URL with default port", "registry.access.redhat.com/ubi9/ubi-minimal", "registry.access.redhat.com", 443, false),
28+
Entry("Check a URL with custom port", "registry.access.redhat.com:9443/ubi9/ubi-minimal", "registry.access.redhat.com", 9443, false),
29+
Entry("Check a URL without host", "ubuntu", "index.docker.io", 443, false),
30+
Entry("Check invalid URL", "ftp://registry.access.redhat.com/ubi9/ubi-minimal", "", 0, true),
31+
)
32+
})

pkg/util/tcp.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package util
6+
7+
import (
8+
"fmt"
9+
"net"
10+
"time"
11+
)
12+
13+
// TestConnection tries to establish a connection to a provided host using a 5 seconds timeout.
14+
func TestConnection(hostname string, port int, retries int) bool {
15+
host := fmt.Sprintf("%s:%d", hostname, port)
16+
17+
dialer := &net.Dialer{
18+
Timeout: 5 * time.Second,
19+
}
20+
21+
for i := 0; i <= retries; i++ {
22+
conn, _ := dialer.Dial("tcp", host)
23+
if conn != nil {
24+
_ = conn.Close()
25+
return true
26+
}
27+
}
28+
29+
return false
30+
}

pkg/util/tcp_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package util_test
6+
7+
import (
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
"github.com/shipwright-io/build/pkg/util"
11+
)
12+
13+
var _ = Describe("TCP", func() {
14+
15+
Context("TestConnection", func() {
16+
17+
var result bool
18+
var hostname string
19+
var port int
20+
21+
JustBeforeEach(func() {
22+
result = util.TestConnection(hostname, port, 1)
23+
})
24+
25+
Context("For a broken endpoint", func() {
26+
27+
BeforeEach(func() {
28+
hostname = "shipwright.io"
29+
port = 33333
30+
})
31+
32+
It("returns false", func() {
33+
Expect(result).To(BeFalse())
34+
})
35+
})
36+
37+
Context("For an unknown host", func() {
38+
39+
BeforeEach(func() {
40+
hostname = "shipwright-dhasldglidgewidgwd.io"
41+
port = 33333
42+
})
43+
44+
It("returns false", func() {
45+
Expect(result).To(BeFalse())
46+
})
47+
})
48+
49+
Context("For a functional endpoint", func() {
50+
51+
BeforeEach(func() {
52+
hostname = "github.com"
53+
port = 443
54+
})
55+
56+
It("returns true", func() {
57+
Expect(result).To(BeTrue())
58+
})
59+
})
60+
})
61+
})

pkg/util/util_suite_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package util_test
6+
7+
import (
8+
"testing"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
)
13+
14+
func TestGit(t *testing.T) {
15+
RegisterFailHandler(Fail)
16+
RunSpecs(t, "Util Suite")
17+
}

0 commit comments

Comments
 (0)