Skip to content

Commit befd626

Browse files
authored
Merge pull request #1178 from CircleCI-Public/WEBXP-28/add-error-msg-to-circleci-setup
[WEBXP-28] Clear error message on invalid url
2 parents 43ed728 + d8c2543 commit befd626

File tree

10 files changed

+80
-34
lines changed

10 files changed

+80
-34
lines changed

api/rest/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ func NewFromConfig(host string, config *settings.Config) *Client {
3737
endpoint += "/"
3838
}
3939

40-
baseURL, _ := url.Parse(host)
40+
baseURL, err := url.Parse(host)
41+
if err != nil || baseURL.Host == "" {
42+
panic("Error: invalid CircleCI URL")
43+
}
44+
4145
timeout := header.GetDefaultTimeout()
4246
if timeoutEnv, ok := os.LookupEnv("CIRCLECI_CLI_TIMEOUT"); ok {
4347
if parsedTimeout, err := time.ParseDuration(timeoutEnv); err == nil {

api/rest/client_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,20 @@ func TestNewFromConfigTimeout(t *testing.T) {
2727
HTTPClient: http.DefaultClient,
2828
}
2929
t.Run("create new client without custom timeout", func(t *testing.T) {
30-
api := NewFromConfig("host", cfg)
30+
api := NewFromConfig("https://circleci.example.com", cfg)
3131
assert.Equal(t, api.client.Timeout, header.GetDefaultTimeout())
3232
})
3333
t.Run("create new client with custom timeout", func(t *testing.T) {
3434
customTimeout := 20 * time.Second
3535
os.Setenv("CIRCLECI_CLI_TIMEOUT", customTimeout.String())
36-
api := NewFromConfig("host", cfg)
36+
api := NewFromConfig("https://circleci.example.com", cfg)
3737
assert.Equal(t, api.client.Timeout, customTimeout)
3838
})
39+
t.Run("panic on invalid host URL", func(t *testing.T) {
40+
assert.Panics(t, func() {
41+
NewFromConfig("not-a-valid-url", cfg)
42+
})
43+
})
3944
}
4045
func TestClient_DoRequest(t *testing.T) {
4146
t.Run("PUT with req and resp", func(t *testing.T) {

cmd/orb_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ var _ = Describe("Orb integration tests", func() {
9999
"orb", "--help",
100100
)
101101

102-
tempSettings.Config.Write([]byte(`host: foo.bar`))
102+
tempSettings.Config.Write([]byte(`host: https://foo.bar`))
103103
})
104104

105105
AfterEach(func() {
@@ -2867,7 +2867,7 @@ https://circleci.com/account/api`))
28672867
"orb", "create", "bar-ns/foo-orb",
28682868
"--skip-update-check",
28692869
"--token", "",
2870-
"--host", "foo.bar",
2870+
"--host", "https://foo.bar",
28712871
)
28722872

28732873
By("running the command")
@@ -2876,7 +2876,7 @@ https://circleci.com/account/api`))
28762876
Expect(err).ShouldNot(HaveOccurred())
28772877
Eventually(session.Err).Should(gbytes.Say(`Error: please set a token with 'circleci setup'
28782878
You can create a new personal API token here:
2879-
foo.bar/account/api`))
2879+
https://foo.bar/account/api`))
28802880
Eventually(session).Should(clitest.ShouldFail())
28812881
})
28822882
})

cmd/project/project_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ func TestDLCPurge(t *testing.T) {
425425
// (this test doesn't use httptest because it's testing a
426426
// misconfiguration and doesn't get as far as making a http request)
427427
cmd := project.NewProjectCommand(&settings.Config{
428-
Host: "some custom value but dlhost is not set",
428+
Host: "https://example.com",
429429
HTTPClient: http.DefaultClient,
430430
}, noValidator)
431431
outbuf := new(bytes.Buffer)

cmd/setup.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ func setup(opts setupOptions) error {
179179
fmt.Println("API token has been set.")
180180
}
181181
opts.cfg.Host = opts.tty.readHostFromUser("CircleCI Host", defaultHost)
182+
183+
if !opts.integrationTesting {
184+
if err := settings.ValidateHost(opts.cfg.Host); err != nil {
185+
return errors.New("invalid CircleCI URL")
186+
}
187+
}
182188
fmt.Println("CircleCI host has been set.")
183189

184190
// Reset endpoint to default when running setup
@@ -265,6 +271,11 @@ func setupNoPrompt(opts setupOptions) error {
265271
config.Token = opts.cfg.Token
266272
}
267273

274+
// Validate the host URL before saving
275+
if err := settings.ValidateHost(config.Host); err != nil {
276+
return errors.New("invalid CircleCI URL")
277+
}
278+
268279
// Then save the new config to disk
269280
if err := config.WriteToDisk(); err != nil {
270281
return errors.Wrap(err, "Failed to save config file")

cmd/setup_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ token: fooBarBaz
185185
It("should change if provided one of flags", func() {
186186
command = commandWithHome(pathCLI, tempSettings.Home,
187187
"setup",
188-
"--host", "asdf",
188+
"--host", "https://asdf.example.com",
189189
"--no-prompt",
190190
"--skip-update-check",
191191
)
@@ -201,7 +201,7 @@ Your configuration has been saved to %s.
201201
Eventually(session).Should(gexec.Exit(0))
202202

203203
Context("re-open the config to check the contents", func() {
204-
tempSettings.AssertConfigRereadMatches(`host: asdf
204+
tempSettings.AssertConfigRereadMatches(`host: https://asdf.example.com
205205
endpoint: graphql-unstable
206206
token: fooBarBaz
207207
`)
@@ -233,6 +233,22 @@ token: asdf
233233
`)
234234
})
235235
})
236+
237+
It("should reject an invalid host URL", func() {
238+
command = commandWithHome(pathCLI, tempSettings.Home,
239+
"setup",
240+
"--host", "not-a-valid-url",
241+
"--no-prompt",
242+
"--skip-update-check",
243+
)
244+
245+
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
246+
Expect(err).ShouldNot(HaveOccurred())
247+
Eventually(session).Should(clitest.ShouldFail())
248+
249+
stderr := session.Wait().Err.Contents()
250+
Expect(string(stderr)).To(Equal("Error: invalid CircleCI URL\n"))
251+
})
236252
})
237253

238254
})

go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ require (
3939
github.com/hexops/gotextdiff v1.0.3
4040
github.com/segmentio/analytics-go v3.1.0+incompatible
4141
github.com/spf13/afero v1.12.0
42-
github.com/stretchr/testify v1.10.0
42+
github.com/stretchr/testify v1.11.1
4343
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
4444
golang.org/x/term v0.37.0
4545
)
@@ -65,7 +65,7 @@ require (
6565
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
6666
github.com/go-git/go-billy/v5 v5.6.2 // indirect
6767
github.com/go-ini/ini v1.67.0 // indirect
68-
github.com/go-logr/logr v1.4.2 // indirect
68+
github.com/go-logr/logr v1.4.3 // indirect
6969
github.com/go-logr/stdr v1.2.2 // indirect
7070
github.com/gobwas/glob v0.2.3 // indirect
7171
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
@@ -109,15 +109,15 @@ require (
109109
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
110110
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
111111
github.com/yashtewari/glob-intersection v0.2.0 // indirect
112-
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
113-
go.opentelemetry.io/otel v1.35.0 // indirect
114-
go.opentelemetry.io/otel/metric v1.35.0 // indirect
115-
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
116-
go.opentelemetry.io/otel/trace v1.35.0 // indirect
112+
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
113+
go.opentelemetry.io/otel v1.40.0 // indirect
114+
go.opentelemetry.io/otel/metric v1.40.0 // indirect
115+
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
116+
go.opentelemetry.io/otel/trace v1.40.0 // indirect
117117
golang.org/x/crypto v0.44.0 // indirect
118118
golang.org/x/net v0.46.0 // indirect
119119
golang.org/x/sync v0.18.0 // indirect
120-
golang.org/x/sys v0.38.0 // indirect
120+
golang.org/x/sys v0.40.0 // indirect
121121
golang.org/x/text v0.31.0 // indirect
122122
google.golang.org/protobuf v1.36.6 // indirect
123123
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect

go.sum

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lo
103103
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
104104
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
105105
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
106-
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
107-
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
106+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
107+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
108108
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
109109
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
110110
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
@@ -278,8 +278,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
278278
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
279279
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
280280
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
281-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
282-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
281+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
282+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
283283
github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
284284
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
285285
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
@@ -299,24 +299,26 @@ github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBe
299299
github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
300300
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
301301
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
302-
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
303-
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
302+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
303+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
304304
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
305305
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
306-
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
307-
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
306+
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
307+
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
308308
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
309309
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
310310
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
311311
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
312312
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
313313
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
314-
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
315-
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
316-
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
317-
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
318-
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
319-
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
314+
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
315+
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
316+
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
317+
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
318+
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
319+
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
320+
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
321+
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
320322
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
321323
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
322324
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -378,8 +380,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
378380
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
379381
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
380382
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
381-
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
382-
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
383+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
384+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
383385
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
384386
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
385387
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

integration_tests/features/root_commands.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Feature: Root Commands
1313

1414
@mocked_home_directory
1515
Scenario: Help test with a custom host
16-
Given a file named ".circleci/cli.yml" with "host: foo.bar"
16+
Given a file named ".circleci/cli.yml" with "host: https://foo.bar"
1717
When I run `circleci help`
1818
Then the output should not contain:
1919
"""

settings/settings.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,14 @@ func isWorldWritable(info os.FileInfo) bool {
341341
return strings.Contains(sysPerms, "w")
342342
}
343343

344+
func ValidateHost(host string) error {
345+
parsedURL, err := url.Parse(host)
346+
if err != nil || parsedURL.Host == "" {
347+
return errors.New("invalid URL")
348+
}
349+
return nil
350+
}
351+
344352
// ServerURL retrieves and formats a ServerURL from our restEndpoint and host.
345353
func (cfg *Config) ServerURL() (*url.URL, error) {
346354
var URL string

0 commit comments

Comments
 (0)