Skip to content

Commit 4aeba5b

Browse files
mergify[bot]pchila
andauthored
[main](backport #4330) Fix GPG test hash mismatch (#4343)
* Fix GPG test hash mismatch (#4330) * add dumb http fetcher * use host as fetcher name * add http fetcher tests --------- Co-authored-by: Paolo Chilà <paolo.chila@elastic.co>
1 parent 53a4f92 commit 4aeba5b

File tree

3 files changed

+318
-5
lines changed

3 files changed

+318
-5
lines changed

pkg/testing/fetcher_http.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package testing
6+
7+
import (
8+
"context"
9+
"errors"
10+
"fmt"
11+
"net/http"
12+
"path/filepath"
13+
"regexp"
14+
"strings"
15+
)
16+
17+
const defaultAgentBaseURL = "https://artifacts.elastic.co/downloads/beats/elastic-agent/"
18+
19+
type HttpFetcher struct {
20+
baseURL string
21+
}
22+
23+
type HttpFetcherOpt func(hf *HttpFetcher)
24+
25+
func WithBaseURL(baseURL string) HttpFetcherOpt {
26+
return func(hf *HttpFetcher) {
27+
hf.baseURL = baseURL
28+
}
29+
}
30+
31+
func NewHttpFetcher(opts ...HttpFetcherOpt) *HttpFetcher {
32+
33+
f := &HttpFetcher{
34+
baseURL: defaultAgentBaseURL,
35+
}
36+
37+
for _, o := range opts {
38+
o(f)
39+
}
40+
41+
return f
42+
}
43+
44+
func (h HttpFetcher) Name() string {
45+
return fmt.Sprintf("httpFetcher-%s", sanitizeFetcherName(h.baseURL))
46+
}
47+
48+
func (h HttpFetcher) Fetch(ctx context.Context, operatingSystem string, architecture string, version string) (FetcherResult, error) {
49+
suffix, err := GetPackageSuffix(operatingSystem, architecture)
50+
if err != nil {
51+
return nil, err
52+
}
53+
// https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.12.1-linux-arm64.tar.gz
54+
packageName := fmt.Sprintf("elastic-agent-%s-%s", version, suffix)
55+
return &httpFetcherResult{
56+
packageName: packageName,
57+
baseURL: h.baseURL,
58+
}, nil
59+
60+
}
61+
62+
type httpFetcherResult struct {
63+
baseURL string
64+
packageName string
65+
}
66+
67+
func (h httpFetcherResult) Name() string {
68+
return h.packageName
69+
}
70+
71+
func (h httpFetcherResult) Fetch(ctx context.Context, l Logger, dir string) error {
72+
var err error
73+
baseURL := h.baseURL
74+
if !strings.HasSuffix(baseURL, "/") {
75+
baseURL += "/"
76+
}
77+
packageURL := baseURL + h.packageName
78+
packageSHAURL := baseURL + h.packageName + extHash
79+
packageASCURL := baseURL + h.packageName + extAsc
80+
err = DownloadPackage(ctx, l, http.DefaultClient, packageURL, filepath.Join(dir, h.packageName))
81+
err = errors.Join(err, DownloadPackage(ctx, l, http.DefaultClient, packageSHAURL, filepath.Join(dir, h.packageName+extHash)))
82+
err = errors.Join(err, DownloadPackage(ctx, l, http.DefaultClient, packageASCURL, filepath.Join(dir, h.packageName+extAsc)))
83+
return err
84+
}
85+
86+
var hostRegexString = `^http(?:s?)://([a-z,A-z,0-9,\.]+)(?::[0-9]+)?(?:/.*)*$`
87+
var hostRegex = regexp.MustCompile(hostRegexString)
88+
89+
const hostRegexGroup = 1
90+
91+
func sanitizeFetcherName(name string) string {
92+
match := hostRegex.FindStringSubmatch(name)
93+
if len(match) > 1 {
94+
host := match[hostRegexGroup]
95+
return host
96+
}
97+
// falllback in case we don't match the url regex
98+
sanitized := strings.ReplaceAll(name, ":", "-")
99+
sanitized = strings.ReplaceAll(sanitized, "/", "-")
100+
return sanitized
101+
}

pkg/testing/fetcher_http_test.go

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package testing
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"net/http"
11+
"net/http/httptest"
12+
"testing"
13+
"time"
14+
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestHttpFetcher_Fetch(t *testing.T) {
20+
type fields struct {
21+
baseURL string
22+
}
23+
type args struct {
24+
operatingSystem string
25+
architecture string
26+
version string
27+
}
28+
tests := []struct {
29+
name string
30+
fields fields
31+
args args
32+
want FetcherResult
33+
wantErr assert.ErrorAssertionFunc
34+
}{
35+
{
36+
name: "default elastic artifacts http fetcher",
37+
args: args{
38+
operatingSystem: "linux",
39+
architecture: "arm64",
40+
version: "1.2.3",
41+
},
42+
want: &httpFetcherResult{
43+
baseURL: "https://artifacts.elastic.co/downloads/beats/elastic-agent/",
44+
packageName: "elastic-agent-1.2.3-linux-arm64.tar.gz",
45+
},
46+
wantErr: assert.NoError,
47+
},
48+
{
49+
name: "custom baseURL http fetcher",
50+
fields: fields{baseURL: "http://somehost.somedomain/some/path/here"},
51+
args: args{
52+
operatingSystem: "windows",
53+
architecture: "amd64",
54+
version: "1.2.3",
55+
},
56+
want: &httpFetcherResult{
57+
baseURL: "http://somehost.somedomain/some/path/here",
58+
packageName: "elastic-agent-1.2.3-windows-x86_64.zip",
59+
},
60+
wantErr: assert.NoError,
61+
},
62+
}
63+
for _, tt := range tests {
64+
t.Run(tt.name, func(t *testing.T) {
65+
var opts []HttpFetcherOpt
66+
if tt.fields.baseURL != "" {
67+
opts = append(opts, WithBaseURL(tt.fields.baseURL))
68+
}
69+
h := NewHttpFetcher(opts...)
70+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
71+
defer cancel()
72+
got, err := h.Fetch(ctx, tt.args.operatingSystem, tt.args.architecture, tt.args.version)
73+
if !tt.wantErr(t, err, fmt.Sprintf("Fetch(%v, %v, %v, %v)", ctx, tt.args.operatingSystem, tt.args.architecture, tt.args.version)) {
74+
return
75+
}
76+
assert.Equalf(t, tt.want, got, "Fetch(%v, %v, %v, %v)", ctx, tt.args.operatingSystem, tt.args.architecture, tt.args.version)
77+
})
78+
}
79+
}
80+
81+
func TestHttpFetcher_Name(t *testing.T) {
82+
type fields struct {
83+
baseURL string
84+
}
85+
tests := []struct {
86+
name string
87+
fields fields
88+
want string
89+
}{
90+
{
91+
name: "default elastic artifacts http fetcher",
92+
want: "httpFetcher-artifacts.elastic.co",
93+
},
94+
{
95+
name: "http fetcher with custom http url",
96+
fields: fields{baseURL: "http://somehost.somedomain:8888"},
97+
want: "httpFetcher-somehost.somedomain",
98+
},
99+
{
100+
name: "http fetcher with base url not mantching regex",
101+
fields: fields{baseURL: "foo.bar-baz"},
102+
want: "httpFetcher-foo.bar-baz",
103+
},
104+
}
105+
for _, tt := range tests {
106+
t.Run(tt.name, func(t *testing.T) {
107+
var opts []HttpFetcherOpt
108+
if tt.fields.baseURL != "" {
109+
opts = append(opts, WithBaseURL(tt.fields.baseURL))
110+
}
111+
h := NewHttpFetcher(opts...)
112+
assert.Equalf(t, tt.want, h.Name(), "Name()")
113+
})
114+
}
115+
}
116+
117+
func Test_httpFetcherResult_Fetch(t *testing.T) {
118+
type fields struct {
119+
packageName string
120+
}
121+
type args struct {
122+
availableFiles map[string]string
123+
}
124+
tests := []struct {
125+
name string
126+
fields fields
127+
args args
128+
wantErr assert.ErrorAssertionFunc
129+
}{
130+
131+
{
132+
name: "happy path linux package",
133+
fields: fields{packageName: "elastic-agent-1.2.3-linux-arm64.tar.gz"},
134+
args: args{availableFiles: map[string]string{
135+
"/elastic-agent-1.2.3-linux-arm64.tar.gz": "elastic-agent-package-placeholder",
136+
"/elastic-agent-1.2.3-linux-arm64.tar.gz.sha512": "elastic-agent-package hash",
137+
"/elastic-agent-1.2.3-linux-arm64.tar.gz.asc": "elastic-agent-package signature",
138+
}},
139+
wantErr: assert.NoError,
140+
},
141+
{
142+
name: "linux package missing hash",
143+
fields: fields{packageName: "elastic-agent-1.2.3-linux-arm64.tar.gz"},
144+
args: args{availableFiles: map[string]string{
145+
"/elastic-agent-1.2.3-linux-arm64.tar.gz": "elastic-agent-package-placeholder",
146+
"/elastic-agent-1.2.3-linux-arm64.tar.gz.asc": "elastic-agent-package signature",
147+
}},
148+
wantErr: assert.Error,
149+
},
150+
{
151+
name: "windows package missing signature",
152+
fields: fields{packageName: "elastic-agent-1.2.3-windows-x86_64.zip"},
153+
args: args{availableFiles: map[string]string{
154+
"/elastic-agent-1.2.3-windows-x86_64.zip": "elastic-agent-package-placeholder",
155+
"/elastic-agent-1.2.3-windows-x86_64.zip.sha512": "elastic-agent-package hash",
156+
}},
157+
wantErr: assert.Error,
158+
},
159+
{
160+
name: "linux package missing completely",
161+
fields: fields{packageName: "elastic-agent-1.2.3-linux-arm64.tar.gz"},
162+
args: args{availableFiles: map[string]string{}},
163+
wantErr: assert.Error,
164+
},
165+
}
166+
for _, tt := range tests {
167+
t.Run(tt.name, func(t *testing.T) {
168+
hf := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
169+
path := request.URL.Path
170+
content, ok := tt.args.availableFiles[path]
171+
if !ok {
172+
writer.WriteHeader(http.StatusNotFound)
173+
return
174+
}
175+
176+
_, err := writer.Write([]byte(content))
177+
require.NoError(t, err, "error writing file content")
178+
})
179+
server := httptest.NewServer(hf)
180+
defer server.Close()
181+
h := httpFetcherResult{
182+
baseURL: server.URL,
183+
packageName: tt.fields.packageName,
184+
}
185+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
186+
defer cancel()
187+
188+
outdir := t.TempDir()
189+
tt.wantErr(t, h.Fetch(ctx, t, outdir), fmt.Sprintf("Fetch(%v, %v)", tt.fields.packageName, outdir))
190+
})
191+
}
192+
}

testing/integration/upgrade_gpg_test.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ func TestStandaloneUpgradeWithGPGFallback(t *testing.T) {
2929
Sudo: true, // requires Agent installation
3030
})
3131

32-
t.Skip("Skipped until https://github.com/elastic/elastic-agent/issues/4317 is solved")
33-
3432
minVersion := upgradetest.Version_8_10_0_SNAPSHOT
3533
currentVersion, err := version.ParseVersion(define.Version())
3634
require.NoError(t, err)
@@ -82,6 +80,11 @@ func TestStandaloneUpgradeWithGPGFallback(t *testing.T) {
8280

8381
err = upgradetest.PerformUpgrade(
8482
ctx, startFixture, endFixture, t,
83+
// passing "" as source URI is a hack to disable the --source-uri argument pointing at the endFixture srcPackage location
84+
// this test needs the agent to download the real thing from artifacts.elastic.co so empty string.
85+
// We need to download the same file from the same url and use that as end fixture
86+
// or we need a way to disable the commit hash check (in this case the upgrade can be verified just with the
87+
// version string)
8588
upgradetest.WithSourceURI(""),
8689
upgradetest.WithCustomPGP(customPGP),
8790
upgradetest.WithSkipVerify(false))
@@ -95,8 +98,6 @@ func TestStandaloneUpgradeWithGPGFallbackOneRemoteFailing(t *testing.T) {
9598
Sudo: true, // requires Agent installation
9699
})
97100

98-
t.Skip("Skipped until https://github.com/elastic/elastic-agent/issues/4317 is solved")
99-
100101
minVersion := upgradetest.Version_8_10_0_SNAPSHOT
101102
currentVersion, err := version.ParseVersion(define.Version())
102103
require.NoError(t, err)
@@ -114,12 +115,26 @@ func TestStandaloneUpgradeWithGPGFallbackOneRemoteFailing(t *testing.T) {
114115
require.NoError(t, err)
115116

116117
// Upgrade to an old build.
118+
// This is probably a way of getting a signed package
117119
upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
118120
require.NoError(t, err)
121+
var fetcher atesting.Fetcher
122+
123+
// FIXME: this is a hack, PreviousMinor() uses a version.ParsedSemVer internally and that's what we should use for the snapshot check
124+
// We need to distinguish between snapshots and released versions and use the appropriate fetcher
125+
if strings.HasSuffix(upgradeToVersion, "-SNAPSHOT") {
126+
// it's a snapshot, use the artifact fetcher
127+
fetcher = atesting.ArtifactFetcher()
128+
} else {
129+
// it's a released version, use httpFetcher
130+
// this fetcher will literally pull the package from the default elastic agent download URL
131+
fetcher = atesting.NewHttpFetcher()
132+
}
133+
119134
endFixture, err := atesting.NewFixture(
120135
t,
121136
upgradeToVersion,
122-
atesting.WithFetcher(atesting.ArtifactFetcher()),
137+
atesting.WithFetcher(fetcher),
123138
)
124139
require.NoError(t, err)
125140

@@ -141,6 +156,11 @@ func TestStandaloneUpgradeWithGPGFallbackOneRemoteFailing(t *testing.T) {
141156

142157
err = upgradetest.PerformUpgrade(
143158
ctx, startFixture, endFixture, t,
159+
// passing "" as source URI is a hack to disable the --source-uri argument pointing at the endFixture srcPackage location
160+
// this test needs the agent to download the real thing from artifacts.elastic.co so empty string.
161+
// We need to download the same file from the same url and use that as end fixture
162+
// or we need a way to disable the commit hash check (in this case the upgrade can be verified just with the
163+
// version string)
144164
upgradetest.WithSourceURI(""),
145165
upgradetest.WithCustomPGP(customPGP),
146166
upgradetest.WithSkipVerify(false))

0 commit comments

Comments
 (0)