diff --git a/.gitignore b/.gitignore index b8e6ee0..7d8f8ea 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Ouput of the build-binaries script build/_output + +# .DS_Store file of MacOS +.DS_Store \ No newline at end of file diff --git a/cmd/help.go b/cmd/help.go index 69940fa..65888e0 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -40,6 +40,7 @@ func (c *helpCommand) Execute() { fmt.Println(" help display this help message") fmt.Println(" test launch new test on Microcks server") fmt.Println(" import import API artifacts on Microcks server") + fmt.Println(" import-url import API artifacts from URL on Microcks server") fmt.Println("") fmt.Println("Use: microcks-cli test \\") fmt.Println(" --microcksURL=<> --waitFor=5sec \\") diff --git a/cmd/importURL.go b/cmd/importURL.go new file mode 100644 index 0000000..7f41292 --- /dev/null +++ b/cmd/importURL.go @@ -0,0 +1,128 @@ +package cmd + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/microcks/microcks-cli/pkg/config" + "github.com/microcks/microcks-cli/pkg/connectors" +) + +type importURLCommand struct { +} + +func NewImportURLCommand() Command { + return new(importURLCommand) +} + +func (c *importURLCommand) Execute() { + // Parse subcommand args first. + if len(os.Args) < 2 { + fmt.Println("import-url command require args") + os.Exit(1) + } + + specificationFiles := os.Args[2] + + // Then parse flags. + importCmd := flag.NewFlagSet("import-url", flag.ExitOnError) + + var microcksURL string + var keycloakURL string + var keycloakClientID string + var keycloakClientSecret string + var insecureTLS bool + var caCertPaths string + var verbose bool + + importCmd.StringVar(µcksURL, "microcksURL", "", "Microcks API URL") + importCmd.StringVar(&keycloakClientID, "keycloakClientId", "", "Keycloak Realm Service Account ClientId") + importCmd.StringVar(&keycloakClientSecret, "keycloakClientSecret", "", "Keycloak Realm Service Account ClientSecret") + importCmd.BoolVar(&insecureTLS, "insecure", false, "Whether to accept insecure HTTPS connection") + importCmd.StringVar(&caCertPaths, "caCerts", "", "Comma separated paths of CRT files to add to Root CAs") + importCmd.BoolVar(&verbose, "verbose", false, "Produce dumps of HTTP exchanges") + importCmd.Parse(os.Args[3:]) + + // Validate presence and values of flags. + if len(microcksURL) == 0 { + fmt.Println("--microcksURL flag is mandatory. Check Usage.") + os.Exit(1) + } + if len(keycloakClientID) == 0 { + fmt.Println("--keycloakClientId flag is mandatory. Check Usage.") + os.Exit(1) + } + if len(keycloakClientSecret) == 0 { + fmt.Println("--keycloakClientSecret flag is mandatory. Check Usage.") + os.Exit(1) + } + + // Collect optional HTTPS transport flags. + if insecureTLS { + config.InsecureTLS = true + } + if len(caCertPaths) > 0 { + config.CaCertPaths = caCertPaths + } + if verbose { + config.Verbose = true + } + + // Now we seems to be good ... + // First - retrieve the Keycloak URL from Microcks configuration. + mc := connectors.NewMicrocksClient(microcksURL) + keycloakURL, err := mc.GetKeycloakURL() + if err != nil { + fmt.Printf("Got error when invoking Microcks client retrieving config: %s", err) + os.Exit(1) + } + + var oauthToken string = "unauthentifed-token" + if keycloakURL != "null" { + // If Keycloak is enabled, retrieve an OAuth token using Keycloak Client. + kc := connectors.NewKeycloakClient(keycloakURL, keycloakClientID, keycloakClientSecret) + + oauthToken, err = kc.ConnectAndGetToken() + if err != nil { + fmt.Printf("Got error when invoking Keycloack client: %s", err) + os.Exit(1) + } + } + + // Then - for each specificationFile, upload the artifact on Microcks Server. + mc.SetOAuthToken(oauthToken) + + sepSpecificationFiles := strings.Split(specificationFiles, ",") + for _, f := range sepSpecificationFiles { + mainArtifact := true + secret := "" + + // Check if URL starts with https or http + if strings.HasPrefix(f, "https://") || strings.HasPrefix(f, "http://") { + urlAndMainAtrifactAndSecretName := strings.Split(f, ":") + n := len(urlAndMainAtrifactAndSecretName) + f = urlAndMainAtrifactAndSecretName[0] + ":" + urlAndMainAtrifactAndSecretName[1] + if n > 2 { + val, err := strconv.ParseBool(urlAndMainAtrifactAndSecretName[2]) + if err != nil { + fmt.Println(err) + } + mainArtifact = val + } + if n > 3 { + secret = urlAndMainAtrifactAndSecretName[3] + } + } + + // Try downloading the artifcat + msg, err := mc.DownloadArtifact(f, mainArtifact, secret) + if err != nil { + fmt.Printf("Got error when invoking Microcks client importing Artifact: %s", err) + os.Exit(1) + } + fmt.Printf("Microcks has discovered '%s'\n", msg) + } +} diff --git a/main.go b/main.go index 92427da..557c030 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,8 @@ func main() { c = cmd.NewTestCommand() case "import": c = cmd.NewImportCommand() + case "import-url": + c = cmd.NewImportURLCommand() default: cmd.NewHelpCommand().Execute() os.Exit(1) diff --git a/pkg/connectors/microcks_client.go b/pkg/connectors/microcks_client.go index cc498af..1b0e861 100644 --- a/pkg/connectors/microcks_client.go +++ b/pkg/connectors/microcks_client.go @@ -44,6 +44,7 @@ type MicrocksClient interface { CreateTestResult(serviceID string, testEndpoint string, runnerType string, secretName string, timeout int64, filteredOperations string, operationsHeaders string, oAuth2Context string) (string, error) GetTestResult(testResultID string) (*TestResultSummary, error) UploadArtifact(specificationFilePath string, mainArtifact bool) (string, error) + DownloadArtifact(artifactURL string, mainArtifact bool, secret string) (string, error) } // TestResultSummary represents a simple view on Microcks TestResult @@ -308,7 +309,61 @@ func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifa // Dump response if verbose required. config.DumpResponseIfRequired("Microcks for uploading artifact", resp, true) - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + panic(err.Error()) + } + + // Raise exception if not created. + if resp.StatusCode != 201 { + return "", errors.New(string(respBody)) + } + + return string(respBody), err +} + +func (c *microcksClient) DownloadArtifact(artifactURL string, mainArtifact bool, secret string) (string, error) { + + // create Multipart Form to add fields + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // Add all the form fields + writer.WriteField("url", artifactURL) + writer.WriteField("mainArtifact", strconv.FormatBool(mainArtifact)) + if secret != "" { + writer.WriteField("secret", secret) + } + + err := writer.Close() + if err != nil { + return "", err + } + + // Ensure we have a correct URL. + rel := &url.URL{Path: "artifact/download"} + u := c.APIURL.ResolveReference(rel) + + req, err := http.NewRequest("POST", u.String(), body) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+c.OAuthToken) + + // Dump request if verbose required. + config.DumpRequestIfRequired("Microcks for uploading artifact", req, true) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Dump response if verbose required. + config.DumpResponseIfRequired("Microcks for uploading artifact", resp, true) + + respBody, err := io.ReadAll(req.Body) if err != nil { panic(err.Error()) }