Skip to content

Commit 4925450

Browse files
committed
#12 Adding new import command and adjusting client
1 parent 3225494 commit 4925450

File tree

10 files changed

+563
-12
lines changed

10 files changed

+563
-12
lines changed

README.md

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# microcks-cli
2+
23
Simple CLI for interacting with Microcks server APIs.
3-
It allows to launch tests with minimal dependencies.
4+
It allows to launch tests or import API artifacts with minimal dependencies.
45

5-
[![Join the chat at https://gitter.im/microcks/microcks-cli](https://badges.gitter.im/microcks/microcks-cli.svg)](https://gitter.im/microcks/microcks-cli?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6+
[![Join the chat on Zulip](https://img.shields.io/badge/chat-on_zulip-pink.svg?color=ff69b4&style=for-the-badge&logo=zulip)](https://microcksio.zulipchat.com/)
67

78
## Build Status
89

@@ -16,26 +17,29 @@ where `[command]` can be one of the following:
1617
* `version` to check this CLI version,
1718
* `help` to display usage informations,
1819
* `test` to launch new test on Microcks server.
20+
* `import` to import API artifacts on Microcks server.
21+
22+
### Test command
1923

20-
The main `test` command has a bunch of arguments and flags so that you can use it that way:
24+
The `test` command has a bunch of arguments and flags so that you can use it that way:
2125
```
2226
microcks-cli test <apiName:apiVersion> <testEndpoint> <runner>
2327
--microcksURL=<> --waitFor=5sec
2428
--keycloakClientId=<> --keycloakClientSecret=<>
2529
```
2630

2731
The arguments:
28-
* `<apiName:apiVersion>` : Service to test reference. Exemple: `'Beer Catalog API:0.9'`
32+
* `<apiName:apiVersion>` : Service to test reference. Example: `'Beer Catalog API:0.9'`
2933
* `<testEndpoint>` : URL where is deployed implementation to test
30-
* `<runner>` : Test strategy (one of: `HTTP`, `SOAP`, `SOAP_UI`, `POSTMAN`, `OPEN_API_SCHEMA`, `ASYNC_API_SCHEMA`)")
34+
* `<runner>` : Test strategy (one of: `HTTP`, `SOAP`, `SOAP_UI`, `POSTMAN`, `OPEN_API_SCHEMA`, `ASYNC_API_SCHEMA`, `GRPC_PROTOBUF`)")
3135

3236
The flags:
3337
* `--microcksURL` for the Microcks API endpoint,
3438
* `--waitFor` for the time to wait for test to finish (int + one of: milli, sec, min),
3539
* `--keycloakClientId` for the Keycloak Realm Service Account ClientId,
3640
* `--keycloakClientSecret` for the Keycloak Realm Service Account ClientSecret.
3741

38-
Real life exemple command and execution:
42+
Real life example command and execution:
3943
```
4044
$ ./microcks-cli test 'Beer Catalog API:0.9' http://localhost:9090/api/ POSTMAN \
4145
--microcksURL=http://localhost:8080/api/ \
@@ -48,7 +52,7 @@ MicrocksTester waiting for 2 seconds before checking again.
4852
MicrocksClient got status for test "5c1781cf6310d94f8169384e" - success: true, inProgress: false
4953
```
5054

51-
### Advanced options
55+
#### Advanced options
5256

5357
The `test` command provides additional flags for advanced usages and options:
5458
* `--verbose` allows to dump on standard output all the HTTP requests and responses,
@@ -70,6 +74,41 @@ Here's below an example of using some of this flags:
7074
--operationsHeaders='{"globals": [{"name": "x-api-key", "values": "my-values"}], "GET /beer": [{"name": "x-trace-id", "values": "xcvbnsdfghjklm"}]}'
7175
```
7276

77+
### Import command
78+
79+
The `import` command has one argument and common flags with `test` command. You can use it that way:
80+
```
81+
microcks-cli import <specificationFile1[:primary],specificationFile2[:primary]>
82+
--microcksURL=<>
83+
--keycloakClientId=<> --keycloakClientSecret=<>
84+
```
85+
86+
The arguments:
87+
* `<specificationFile1[:primary],specificationFile2[:primary]>` : Comma separated list of API specs to import with flag telling if it's a primary artifact. Example: `'specs/my-openapi.yaml:true,specs/my-postmancollection.json:false'`
88+
89+
The flags:
90+
* `--microcksURL` for the Microcks API endpoint,
91+
* `--keycloakClientId` for the Keycloak Realm Service Account ClientId,
92+
* `--keycloakClientSecret` for the Keycloak Realm Service Account ClientSecret.
93+
94+
Real life example command and execution:
95+
```
96+
$ ./microcks-cli import 'samples/weather-forecast-openapi.yml:true,samples/weather-forecast-postman.json:false' \
97+
--microcksURL=http://localhost:8080/api/ \
98+
--keycloakClientId=microcks-serviceaccount \
99+
--keycloakClientSecret=7deb71e8-8c80-4376-95ad-00a399ee3ca1
100+
Microcks has discovered 'WeatherForecast API:1.1.0'
101+
Microcks has discovered 'WeatherForecast API:1.1.0'
102+
```
103+
104+
#### Advanced options
105+
106+
The `import` command provides additional flags for advanced usages and options:
107+
* `--verbose` allows to dump on standard output all the HTTP requests and responses,
108+
* `--insecure` allows to interact with Microcks and Keycloak instances through HTTPS without checking certificates issuer CA,
109+
* `--caCerts=<path1,path2>` allows to specify additional certificates CRT files to add to trusted roots ones,
110+
111+
73112
## Installation
74113

75114
### Binary
@@ -91,4 +130,4 @@ $ docker run -it quay.io/microcks/microcks-cli:latest microcks-cli test 'Beer Ca
91130

92131
## Tekton tasks
93132

94-
This repository also contains different [Tekton](https://tekton.dev/) tasks definition and sample pipelines. You'll find under the `/tekton` folder the resource for current `v1beta1` Tekton API version and the older `v1alpha1` under `tekton/v1alpha1`.
133+
This repository also contains different [Tekton](https://tekton.dev/) tasks definition and sample pipelines. You'll find under the `/tekton` folder the resource for current `v1beta1` Tekton API version and the older `v1alpha1` under `tekton/v1alpha1`.

cmd/help.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func NewHelpCommand() Command {
1414
func (c *helpCommand) Execute() {
1515
fmt.Println("")
1616
fmt.Println("microcks-cli is a CLI for interacting with Microcks server APIs.")
17-
fmt.Println("It allows to launch tests with minimal dependencies")
17+
fmt.Println("It allows to launch tests or import API artifacts with minimal dependencies")
1818
fmt.Println("")
1919
fmt.Println("")
2020
fmt.Println("Usage:")
@@ -24,6 +24,7 @@ func (c *helpCommand) Execute() {
2424
fmt.Println(" version check this CLI version")
2525
fmt.Println(" help display this help message")
2626
fmt.Println(" test launch new test on Microcks server")
27+
fmt.Println(" import import API artifacts on Microcks server")
2728
fmt.Println("")
2829
fmt.Println("Use: microcks-cli test <apiName:apiVersion> <testEndpoint> <runner> \\")
2930
fmt.Println(" --microcksURL=<> --waitFor=5sec \\")
@@ -40,4 +41,17 @@ func (c *helpCommand) Execute() {
4041
fmt.Println(" --keycloakClientId Keycloak Realm Service Account ClientId")
4142
fmt.Println(" --keycloakClientSecret Keycloak Realm Service Account ClientSecret")
4243
fmt.Println("")
44+
fmt.Println("")
45+
fmt.Println("Use: microcks-cli import <specificationFile1[:primary],specificationFile2[:primary]> \\")
46+
fmt.Println(" --microcksURL=<> \\")
47+
fmt.Println(" --keycloakClientId=<> --keycloakClientSecret=<>")
48+
fmt.Println("")
49+
fmt.Println("Args: ")
50+
fmt.Println(" <specificationFile1[:primary],specificationFile2[:primary]> Exemple: 'specs/my-openapi.yaml:true,specs/my-postmancollection.json:false'")
51+
fmt.Println("")
52+
fmt.Println("Flags: ")
53+
fmt.Println(" --microcksURL Microcks API endpoint")
54+
fmt.Println(" --keycloakClientId Keycloak Realm Service Account ClientId")
55+
fmt.Println(" --keycloakClientSecret Keycloak Realm Service Account ClientSecret")
56+
fmt.Println("")
4357
}

cmd/import.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package cmd
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/microcks/microcks-cli/pkg/config"
11+
"github.com/microcks/microcks-cli/pkg/connectors"
12+
)
13+
14+
type importComamnd struct {
15+
}
16+
17+
// NewImportCommand build a new ImportCommand implementation
18+
func NewImportCommand() Command {
19+
return new(importComamnd)
20+
}
21+
22+
// Execute implementation of importComamnd structure
23+
func (c *importComamnd) Execute() {
24+
25+
// Parse subcommand args first.
26+
if len(os.Args) < 2 {
27+
fmt.Println("import command require <specificationFile1[:primary],specificationFile2[:primary]> args")
28+
os.Exit(1)
29+
}
30+
31+
specificationFiles := os.Args[2]
32+
33+
// Then parse flags.
34+
importCmd := flag.NewFlagSet("import", flag.ExitOnError)
35+
36+
var microcksURL string
37+
var keycloakURL string
38+
var keycloakClientID string
39+
var keycloakClientSecret string
40+
var insecureTLS bool
41+
var caCertPaths string
42+
var verbose bool
43+
44+
importCmd.StringVar(&microcksURL, "microcksURL", "", "Microcks API URL")
45+
importCmd.StringVar(&keycloakClientID, "keycloakClientId", "", "Keycloak Realm Service Account ClientId")
46+
importCmd.StringVar(&keycloakClientSecret, "keycloakClientSecret", "", "Keycloak Realm Service Account ClientSecret")
47+
importCmd.BoolVar(&insecureTLS, "insecure", false, "Whether to accept insecure HTTPS connection")
48+
importCmd.StringVar(&caCertPaths, "caCerts", "", "Comma separated paths of CRT files to add to Root CAs")
49+
importCmd.BoolVar(&verbose, "verbose", false, "Produce dumps of HTTP exchanges")
50+
importCmd.Parse(os.Args[3:])
51+
52+
// Validate presence and values of flags.
53+
if len(microcksURL) == 0 {
54+
fmt.Println("--microcksURL flag is mandatory. Check Usage.")
55+
os.Exit(1)
56+
}
57+
if len(keycloakClientID) == 0 {
58+
fmt.Println("--keycloakClientId flag is mandatory. Check Usage.")
59+
os.Exit(1)
60+
}
61+
if len(keycloakClientSecret) == 0 {
62+
fmt.Println("--keycloakClientSecret flag is mandatory. Check Usage.")
63+
os.Exit(1)
64+
}
65+
66+
// Collect optional HTTPS transport flags.
67+
if insecureTLS {
68+
config.InsecureTLS = true
69+
}
70+
if len(caCertPaths) > 0 {
71+
config.CaCertPaths = caCertPaths
72+
}
73+
if verbose {
74+
config.Verbose = true
75+
}
76+
77+
//
78+
79+
// Now we seems to be good ...
80+
// First - retrieve the Keycloak URL from Microcks configuration.
81+
mc := connectors.NewMicrocksClient(microcksURL)
82+
keycloakURL, err := mc.GetKeycloakURL()
83+
if err != nil {
84+
fmt.Printf("Got error when invoking Microcks client retrieving config: %s", err)
85+
os.Exit(1)
86+
}
87+
88+
// Second - retrieve an OAuth token using Keycloak Client.
89+
kc := connectors.NewKeycloakClient(keycloakURL, keycloakClientID, keycloakClientSecret)
90+
91+
var oauthToken string
92+
oauthToken, err = kc.ConnectAndGetToken()
93+
if err != nil {
94+
fmt.Printf("Got error when invoking Keycloack client: %s", err)
95+
os.Exit(1)
96+
}
97+
98+
// Then - for each specificationFile, upload the artifact on Microcks Server.
99+
mc.SetOAuthToken(oauthToken)
100+
101+
sepSpecificationFiles := strings.Split(specificationFiles, ",")
102+
for _, f := range sepSpecificationFiles {
103+
mainArtifact := true
104+
105+
// Check if mainArtifact flag is provided.
106+
if strings.Contains(f, ":") {
107+
pathAndMainArtifact := strings.Split(f, ":")
108+
f = pathAndMainArtifact[0]
109+
mainArtifact, err = strconv.ParseBool(pathAndMainArtifact[1])
110+
if err != nil {
111+
fmt.Printf("Cannot parse '%s' as Bool, default to true\n", pathAndMainArtifact[1])
112+
}
113+
}
114+
115+
// Try uploading this artifact.
116+
msg, err := mc.UploadArtifact(f, mainArtifact)
117+
if err != nil {
118+
fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err)
119+
os.Exit(1)
120+
}
121+
fmt.Printf("Microcks has discovered '%s'\n", msg)
122+
}
123+
}

cmd/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ func NewVersionCommand() Command {
1212

1313
// Execute implementation on versionCommand structure
1414
func (c *versionCommand) Execute() {
15-
fmt.Println("0.3.0")
15+
fmt.Println("0.4.0")
1616
}

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ func main() {
2121
c = cmd.NewHelpCommand()
2222
case "test":
2323
c = cmd.NewTestCommand()
24+
case "import":
25+
c = cmd.NewImportCommand()
2426
default:
2527
cmd.NewHelpCommand().Execute()
2628
os.Exit(1)

pkg/connectors/microcks_client.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package connectors
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
7+
"io"
68
"io/ioutil"
9+
"mime/multipart"
710
"net/http"
811
"net/url"
12+
"os"
13+
"path/filepath"
914
"strconv"
1015
"strings"
1116

@@ -16,8 +21,9 @@ import (
1621
type MicrocksClient interface {
1722
GetKeycloakURL() (string, error)
1823
SetOAuthToken(oauthToken string)
19-
CreateTestResult(serviceID string, testEndpoint string, runnerType string, secretName string, timeout int64, soperationsHeaders string) (string, error)
24+
CreateTestResult(serviceID string, testEndpoint string, runnerType string, secretName string, timeout int64, operationsHeaders string) (string, error)
2025
GetTestResult(testResultID string) (*TestResultSummary, error)
26+
UploadArtifact(specificationFilePath string, mainArtifact bool) (string, error)
2127
}
2228

2329
// TestResultSummary represents a simple view on Microcks TestResult
@@ -199,6 +205,65 @@ func (c *microcksClient) GetTestResult(testResultID string) (*TestResultSummary,
199205
return &result, err
200206
}
201207

208+
func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifact bool) (string, error) {
209+
// Ensure file exists on fs.
210+
file, err := os.Open(specificationFilePath)
211+
if err != nil {
212+
return "", err
213+
}
214+
defer file.Close()
215+
216+
// Create a multipart request body, reading the file.
217+
body := &bytes.Buffer{}
218+
writer := multipart.NewWriter(body)
219+
part, err := writer.CreateFormFile("file", filepath.Base(specificationFilePath))
220+
if err != nil {
221+
return "", err
222+
}
223+
_, err = io.Copy(part, file)
224+
if err != nil {
225+
panic(err.Error())
226+
}
227+
228+
// Add the mainArtifact flag to request.
229+
_ = writer.WriteField("mainArtifact", strconv.FormatBool(mainArtifact))
230+
231+
err = writer.Close()
232+
if err != nil {
233+
return "", err
234+
}
235+
236+
// Ensure we have a correct URL.
237+
rel := &url.URL{Path: "artifact/upload"}
238+
u := c.APIURL.ResolveReference(rel)
239+
240+
req, err := http.NewRequest("POST", u.String(), body)
241+
if err != nil {
242+
return "", err
243+
}
244+
req.Header.Set("Content-Type", writer.FormDataContentType())
245+
req.Header.Set("Authorization", "Bearer "+c.OAuthToken)
246+
247+
// Dump request if verbose required.
248+
config.DumpRequestIfRequired("Microcks for uploading artifact", req, true)
249+
250+
resp, err := c.httpClient.Do(req)
251+
if err != nil {
252+
return "", err
253+
}
254+
defer resp.Body.Close()
255+
256+
// Dump response if verbose required.
257+
config.DumpResponseIfRequired("Microcks for uploading artifact", resp, true)
258+
259+
respBody, err := ioutil.ReadAll(resp.Body)
260+
if err != nil {
261+
panic(err.Error())
262+
}
263+
264+
return string(respBody), err
265+
}
266+
202267
func ensureValid(operationsHeaders string) bool {
203268
// Unmarshal using a generic interface
204269
var headers = map[string][]HeaderDTO{}

0 commit comments

Comments
 (0)