Skip to content

Commit 04d4ed4

Browse files
committed
fix: parse import-url suffixes from right
1 parent c01f511 commit 04d4ed4

6 files changed

Lines changed: 203 additions & 39 deletions

File tree

cmd/artifact_specifier.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright The Microcks Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"strconv"
21+
"strings"
22+
)
23+
24+
// parseImportURLSpecifier parses an import-url argument of the form:
25+
// <url>[:<mainArtifactBool>[:<secret>]]
26+
//
27+
// It is intentionally parsed from the right side so that normal URLs containing
28+
// additional ':' characters (scheme, ports, etc.) are preserved unchanged unless
29+
// they end with the supported suffixes.
30+
func parseImportURLSpecifier(spec string) (url string, mainArtifact bool, secret string) {
31+
mainArtifact = true
32+
33+
lastColon := strings.LastIndex(spec, ":")
34+
if lastColon == -1 {
35+
return spec, mainArtifact, ""
36+
}
37+
38+
tail := spec[lastColon+1:]
39+
if b, err := strconv.ParseBool(tail); err == nil {
40+
return spec[:lastColon], b, ""
41+
}
42+
43+
// Might be <url>:<bool>:<secret> — only treat it as such if the second-to-last
44+
// segment parses as bool.
45+
secretCandidate := tail
46+
rest := spec[:lastColon]
47+
secondColon := strings.LastIndex(rest, ":")
48+
if secondColon == -1 {
49+
return spec, mainArtifact, ""
50+
}
51+
boolCandidate := rest[secondColon+1:]
52+
if b, err := strconv.ParseBool(boolCandidate); err == nil {
53+
return rest[:secondColon], b, secretCandidate
54+
}
55+
56+
return spec, mainArtifact, ""
57+
}
58+
59+
// parseImportFileSpecifier parses an import argument of the form:
60+
// <path>[:<mainArtifactBool>]
61+
//
62+
// Like parseImportURLSpecifier, it parses from the right to avoid breaking
63+
// paths that may contain ':' characters.
64+
func parseImportFileSpecifier(spec string) (path string, mainArtifact bool) {
65+
mainArtifact = true
66+
67+
lastColon := strings.LastIndex(spec, ":")
68+
if lastColon == -1 {
69+
return spec, mainArtifact
70+
}
71+
72+
tail := spec[lastColon+1:]
73+
if b, err := strconv.ParseBool(tail); err == nil {
74+
return spec[:lastColon], b
75+
}
76+
77+
return spec, mainArtifact
78+
}
79+

cmd/artifact_specifier_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package cmd
2+
3+
import "testing"
4+
5+
func TestParseImportURLSpecifier_PreservesPortAndPathWithMainArtifactSuffix(t *testing.T) {
6+
in := "http://localhost:8080/spec.yaml:true"
7+
url, main, secret := parseImportURLSpecifier(in)
8+
9+
if url != "http://localhost:8080/spec.yaml" {
10+
t.Fatalf("url mismatch: got %q", url)
11+
}
12+
if main != true {
13+
t.Fatalf("mainArtifact mismatch: got %v", main)
14+
}
15+
if secret != "" {
16+
t.Fatalf("secret mismatch: got %q", secret)
17+
}
18+
}
19+
20+
func TestParseImportURLSpecifier_PreservesQueryAndFragment(t *testing.T) {
21+
in := "https://example.com:8443/spec.yaml?x=1#frag:false"
22+
url, main, secret := parseImportURLSpecifier(in)
23+
24+
if url != "https://example.com:8443/spec.yaml?x=1#frag" {
25+
t.Fatalf("url mismatch: got %q", url)
26+
}
27+
if main != false {
28+
t.Fatalf("mainArtifact mismatch: got %v", main)
29+
}
30+
if secret != "" {
31+
t.Fatalf("secret mismatch: got %q", secret)
32+
}
33+
}
34+
35+
func TestParseImportURLSpecifier_WithSecretSuffix(t *testing.T) {
36+
in := "http://localhost:8080/spec.yaml:true:mySecret"
37+
url, main, secret := parseImportURLSpecifier(in)
38+
39+
if url != "http://localhost:8080/spec.yaml" {
40+
t.Fatalf("url mismatch: got %q", url)
41+
}
42+
if main != true {
43+
t.Fatalf("mainArtifact mismatch: got %v", main)
44+
}
45+
if secret != "mySecret" {
46+
t.Fatalf("secret mismatch: got %q", secret)
47+
}
48+
}
49+
50+
func TestParseImportURLSpecifier_NoSuffixes_Unchanged(t *testing.T) {
51+
in := "http://localhost:8080/spec.yaml"
52+
url, main, secret := parseImportURLSpecifier(in)
53+
54+
if url != in {
55+
t.Fatalf("url mismatch: got %q", url)
56+
}
57+
if main != true {
58+
t.Fatalf("mainArtifact mismatch: got %v", main)
59+
}
60+
if secret != "" {
61+
t.Fatalf("secret mismatch: got %q", secret)
62+
}
63+
}
64+
65+
func TestParseImportURLSpecifier_PortOnly_NoSuffixes_Unchanged(t *testing.T) {
66+
in := "http://localhost:8080/spec.yaml:1234"
67+
url, main, secret := parseImportURLSpecifier(in)
68+
69+
if url != in {
70+
t.Fatalf("url mismatch: got %q", url)
71+
}
72+
if main != true {
73+
t.Fatalf("mainArtifact mismatch: got %v", main)
74+
}
75+
if secret != "" {
76+
t.Fatalf("secret mismatch: got %q", secret)
77+
}
78+
}
79+
80+
func TestParseImportFileSpecifier_SuffixBool(t *testing.T) {
81+
in := "./specs/openapi.yaml:false"
82+
path, main := parseImportFileSpecifier(in)
83+
if path != "./specs/openapi.yaml" {
84+
t.Fatalf("path mismatch: got %q", path)
85+
}
86+
if main != false {
87+
t.Fatalf("mainArtifact mismatch: got %v", main)
88+
}
89+
}
90+
91+
func TestParseImportFileSpecifier_NoSuffix_Unchanged(t *testing.T) {
92+
in := "./specs/openapi.yaml"
93+
path, main := parseImportFileSpecifier(in)
94+
if path != in {
95+
t.Fatalf("path mismatch: got %q", path)
96+
}
97+
if main != true {
98+
t.Fatalf("mainArtifact mismatch: got %v", main)
99+
}
100+
}
101+

cmd/context_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"os"
5+
"path/filepath"
56
"testing"
67

78
"github.com/microcks/microcks-cli/pkg/config"
@@ -39,6 +40,8 @@ users:
3940
const testConfigFilePath = "./testdata/local.config"
4041

4142
func TestDeleteContext(t *testing.T) {
43+
require.NoError(t, os.MkdirAll(filepath.Dir(testConfigFilePath), 0o755))
44+
4245
//write the test config file
4346
err := os.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
4447
require.NoError(t, err)

cmd/import.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package cmd
1818
import (
1919
"fmt"
2020
"os"
21-
"strconv"
2221
"strings"
2322

2423
"github.com/microcks/microcks-cli/pkg/config"
@@ -118,21 +117,10 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command
118117
// Handle multiple specification files separated by comma.
119118
sepSpecificationFiles := strings.Split(specificationFiles, ",")
120119
for _, f := range sepSpecificationFiles {
121-
mainArtifact := true
122-
var err error
123-
124-
// Check if mainArtifact flag is provided.
125-
if strings.Contains(f, ":") {
126-
pathAndMainArtifact := strings.Split(f, ":")
127-
f = pathAndMainArtifact[0]
128-
mainArtifact, err = strconv.ParseBool(pathAndMainArtifact[1])
129-
if err != nil {
130-
fmt.Printf("Cannot parse '%s' as Bool, default to true\n", pathAndMainArtifact[1])
131-
}
132-
}
120+
path, mainArtifact := parseImportFileSpecifier(f)
133121

134122
// Try uploading this artifact.
135-
msg, err := mc.UploadArtifact(f, mainArtifact)
123+
msg, err := mc.UploadArtifact(path, mainArtifact)
136124
if err != nil {
137125
fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err)
138126
os.Exit(1)
@@ -155,13 +143,11 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command
155143
}
156144

157145
// Normalize file path to match the watcher fsnotify events format.
158-
if strings.HasPrefix(f, "./") {
159-
f = strings.TrimPrefix(f, "./")
160-
}
146+
path = strings.TrimPrefix(path, "./")
161147

162148
// Upsert entry.
163149
watchCfg.UpsertEntry(config.WatchEntry{
164-
FilePath: f,
150+
FilePath: path,
165151
Context: []string{globalClientOpts.Context},
166152
MainArtifact: mainArtifact,
167153
})

cmd/importURL.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package cmd
1919
import (
2020
"fmt"
2121
"os"
22-
"strconv"
2322
"strings"
2423

2524
"github.com/microcks/microcks-cli/pkg/config"
@@ -97,28 +96,10 @@ func NewImportURLCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm
9796
}
9897
sepSpecificationFiles := strings.Split(specificationFiles, ",")
9998
for _, f := range sepSpecificationFiles {
100-
mainArtifact := true
101-
secret := ""
102-
103-
// Check if URL starts with https or http
104-
if strings.HasPrefix(f, "https://") || strings.HasPrefix(f, "http://") {
105-
urlAndMainAtrifactAndSecretName := strings.Split(f, ":")
106-
n := len(urlAndMainAtrifactAndSecretName)
107-
f = urlAndMainAtrifactAndSecretName[0] + ":" + urlAndMainAtrifactAndSecretName[1]
108-
if n > 2 {
109-
val, err := strconv.ParseBool(urlAndMainAtrifactAndSecretName[2])
110-
if err != nil {
111-
fmt.Println(err)
112-
}
113-
mainArtifact = val
114-
}
115-
if n > 3 {
116-
secret = urlAndMainAtrifactAndSecretName[3]
117-
}
118-
}
99+
artifactURL, mainArtifact, secret := parseImportURLSpecifier(f)
119100

120101
// Try downloading the artifcat
121-
msg, err := mc.DownloadArtifact(f, mainArtifact, secret)
102+
msg, err := mc.DownloadArtifact(artifactURL, mainArtifact, secret)
122103
if err != nil {
123104
fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err)
124105
os.Exit(1)

documentation/cmd/importURL.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ Imports API specification files (OpenAPI, AsyncAPI, etc.) hosted at a remote URL
66
microcks import-url <specURL1[:main][:secret]>,<specURL2[:main][:secret]> [flags]
77
```
88

9+
### URL suffix parsing
10+
You can optionally append metadata suffixes to each URL:
11+
12+
- `:<main>` where `<main>` is `true` or `false`
13+
- `:<main>:<secret>` to additionally specify a secret name
14+
15+
The CLI parses these suffixes from the **rightmost** `:` characters only, so normal URLs containing `:` (scheme, ports, etc.) are preserved.
16+
917
### Example
1018
```bash
1119
# Import a single artifact (marked as main)
@@ -14,6 +22,12 @@ microcks import-url https://example.com/openapi.yaml
1422
# Specify mainArtifact flag for each file
1523
microcks import-url https://example.com/spec1.yaml:true,https://example.com/spec2.yaml:false
1624

25+
# URL with port + :main suffix (port/path are preserved)
26+
microcks import-url http://localhost:8080/spec.yaml:true
27+
28+
# URL with port + :main + :secret
29+
microcks import-url http://localhost:8080/spec.yaml:true:mySecret
30+
1731
# Import specification to microcks without logining to microcks
1832
microcks import-url https://example.com/openapi.yaml \
1933
--microcksURL <microcks-url> \

0 commit comments

Comments
 (0)