Skip to content

Commit e06c0f4

Browse files
adding delete command
Signed-off-by: DivyanshuVorrtex <divyanshuchandra9027@gmail.com>
1 parent c01f511 commit e06c0f4

5 files changed

Lines changed: 292 additions & 0 deletions

File tree

cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func NewCommad() *cobra.Command {
3939
}
4040

4141
command.AddCommand(NewImportCommand(&clientOpts))
42+
command.AddCommand(NewDeleteCommand(&clientOpts))
4243
command.AddCommand(NewImportDirCommand(&clientOpts))
4344
command.AddCommand(NewVersionCommand())
4445
command.AddCommand(NewTestCommand(&clientOpts))

cmd/delete.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/microcks/microcks-cli/pkg/config"
9+
"github.com/microcks/microcks-cli/pkg/connectors"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func NewDeleteCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
14+
15+
var deleteCmd = &cobra.Command{
16+
Use: "delete <serviceName:version>",
17+
Short: "Delete an API from Microcks server",
18+
Long: "Delete an API (service + version) from Microcks server",
19+
Args: cobra.ExactArgs(1),
20+
21+
Run: func(cmd *cobra.Command, args []string) {
22+
23+
input := args[0]
24+
25+
// Validate input format
26+
if !strings.Contains(input, ":") {
27+
fmt.Println("delete requires <serviceName:version>")
28+
os.Exit(1)
29+
}
30+
31+
parts := strings.SplitN(input, ":", 2)
32+
service := parts[0]
33+
version := parts[1]
34+
35+
if service == "" || version == "" {
36+
fmt.Println("delete requires both serviceName and version (neither can be empty)")
37+
os.Exit(1)
38+
}
39+
40+
// Load config (same as import)
41+
config.InsecureTLS = globalClientOpts.InsecureTLS
42+
config.CaCertPaths = globalClientOpts.CaCertPaths
43+
config.Verbose = globalClientOpts.Verbose
44+
45+
localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath)
46+
if err != nil {
47+
fmt.Println(err)
48+
return
49+
}
50+
51+
var mc connectors.MicrocksClient
52+
53+
// Same auth logic as import (DO NOT DUPLICATE BADLY → reuse later)
54+
if globalClientOpts.ServerAddr != "" &&
55+
globalClientOpts.ClientId != "" &&
56+
globalClientOpts.ClientSecret != "" {
57+
58+
mc = connectors.NewMicrocksClient(globalClientOpts.ServerAddr)
59+
60+
keycloakURL, err := mc.GetKeycloakURL()
61+
if err != nil {
62+
fmt.Printf("Error retrieving config: %s", err)
63+
os.Exit(1)
64+
}
65+
66+
token := "unauthenticated-token"
67+
68+
if keycloakURL != "null" {
69+
kc := connectors.NewKeycloakClient(
70+
keycloakURL,
71+
globalClientOpts.ClientId,
72+
globalClientOpts.ClientSecret,
73+
)
74+
75+
token, err = kc.ConnectAndGetToken()
76+
if err != nil {
77+
fmt.Printf("Auth error: %s", err)
78+
os.Exit(1)
79+
}
80+
}
81+
82+
mc.SetOAuthToken(token)
83+
84+
} else {
85+
if localConfig == nil {
86+
fmt.Println("Please login to perform operation...")
87+
return
88+
}
89+
90+
mc, err = connectors.NewClient(*globalClientOpts)
91+
if err != nil {
92+
fmt.Printf("error %v", err)
93+
return
94+
}
95+
}
96+
97+
err = mc.DeleteService(service, version)
98+
if err != nil {
99+
fmt.Printf("Delete failed: %s\n", err)
100+
os.Exit(1)
101+
}
102+
103+
fmt.Printf("Deleted service '%s:%s'\n", service, version)
104+
},
105+
}
106+
107+
return deleteCmd
108+
}

documentation/cmd/delete.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## `microcks delete` – Delete an API from Microcks
2+
Deletes a specific API (service + version) from the Microcks server.
3+
4+
### Usage
5+
```bash
6+
microcks delete <serviceName:version> [flags]
7+
```
8+
9+
### Example
10+
```bash
11+
# Delete the local 'Simple' API version '1.1'
12+
microcks delete "Simple:1.1"
13+
14+
# Delete without previously logining to microcks
15+
microcks delete "Simple:1.1" \
16+
--microcksURL <microcks-url> \
17+
--keycloakClientId <client-id> \
18+
--keycloakClientSecret <client-secret>
19+
```
20+
21+
### Options
22+
| Flag | Description |
23+
| ---------------------- | ----------------------------------------------------------------------------------- |
24+
| `-h, --help` | help for delete |
25+
26+
### Options Inherited from Parent Commands
27+
| Flag | Description |
28+
| ------------------------ | ------------------------------------------- |
29+
| `--config` | Path to Microcks config file |
30+
| `--microcks-context` | Name of the Microcks context to use |
31+
| `--verbose` | Produce dumps of HTTP exchanges |
32+
| `--insecure-tls` | Allow insecure HTTPS connections |
33+
| `--caCerts` | Comma-separated paths of CA cert files |
34+
| `--keycloakClientId` | Keycloak Realm Service Account ClientId |
35+
| `--keycloakClientSecret` | Keycloak Realm Service Account ClientSecret |
36+
| `--microcksURL` | Microcks API URL |

pkg/connectors/microcks_client.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type MicrocksClient interface {
5252
GetTestResult(testResultID string) (*TestResultSummary, error)
5353
UploadArtifact(specificationFilePath string, mainArtifact bool) (string, error)
5454
DownloadArtifact(artifactURL string, mainArtifact bool, secret string) (string, error)
55+
DeleteService(service string, version string) error
5556
}
5657

5758
// TestResultSummary represents a simple view on Microcks TestResult
@@ -170,6 +171,101 @@ func NewClient(opts ClientOptions) (MicrocksClient, error) {
170171
return &c, nil
171172
}
172173

174+
type serviceSummary struct {
175+
ID string `json:"id"`
176+
Name string `json:"name"`
177+
Version string `json:"version"`
178+
}
179+
180+
func (c *microcksClient) DeleteService(service string, version string) error {
181+
// First, search for the service ID
182+
searchRel := &url.URL{Path: "services/search"}
183+
searchU := c.APIURL.ResolveReference(searchRel)
184+
185+
q := searchU.Query()
186+
q.Set("name", service)
187+
q.Set("version", version)
188+
// We also set queryMap JSON string just in case Microcks uses a custom deserializer,
189+
// but standard Spring Boot uses direct query params.
190+
queryMap := map[string]string{
191+
"name": service,
192+
"version": version,
193+
}
194+
queryMapBytes, _ := json.Marshal(queryMap)
195+
q.Set("queryMap", string(queryMapBytes))
196+
searchU.RawQuery = q.Encode()
197+
198+
searchReq, err := http.NewRequest("GET", searchU.String(), nil)
199+
if err != nil {
200+
return err
201+
}
202+
searchReq.Header.Set("Authorization", "Bearer "+c.AuthToken)
203+
searchReq.Header.Set("Accept", "application/json")
204+
205+
config.DumpRequestIfRequired("Microcks search service", searchReq, true)
206+
207+
searchResp, err := c.httpClient.Do(searchReq)
208+
if err != nil {
209+
return err
210+
}
211+
defer searchResp.Body.Close()
212+
213+
config.DumpResponseIfRequired("Microcks search service", searchResp, true)
214+
215+
if searchResp.StatusCode != 200 {
216+
body, _ := io.ReadAll(searchResp.Body)
217+
return fmt.Errorf("failed to search service: %s", string(body))
218+
}
219+
220+
body, _ := io.ReadAll(searchResp.Body)
221+
var services []serviceSummary
222+
if err := json.Unmarshal(body, &services); err != nil {
223+
return fmt.Errorf("failed to parse search response: %v", err)
224+
}
225+
226+
var serviceID string
227+
for _, s := range services {
228+
if s.Name == service && s.Version == version {
229+
serviceID = s.ID
230+
break
231+
}
232+
}
233+
234+
if serviceID == "" {
235+
return fmt.Errorf("service '%s:%s' not found", service, version)
236+
}
237+
238+
// Now delete using the ID
239+
deleteRel := &url.URL{Path: "services/" + serviceID}
240+
deleteU := c.APIURL.ResolveReference(deleteRel)
241+
242+
req, err := http.NewRequest("DELETE", deleteU.String(), nil)
243+
if err != nil {
244+
return err
245+
}
246+
247+
req.Header.Set("Authorization", "Bearer "+c.AuthToken)
248+
req.Header.Set("Accept", "application/json")
249+
250+
config.DumpRequestIfRequired("Microcks delete service", req, true)
251+
252+
resp, err := c.httpClient.Do(req)
253+
if err != nil {
254+
return err
255+
}
256+
defer resp.Body.Close()
257+
258+
config.DumpResponseIfRequired("Microcks delete service", resp, true)
259+
260+
body, _ = io.ReadAll(resp.Body)
261+
262+
if resp.StatusCode != 200 && resp.StatusCode != 204 {
263+
return fmt.Errorf("delete failed: %s", string(body))
264+
}
265+
266+
return nil
267+
}
268+
173269
// NewMicrocksClient builds a new headless MicrocksClient without any authtoken and all for general purposes
174270
func NewMicrocksClient(apiURL string) MicrocksClient {
175271
mc := microcksClient{}

pkg/connectors/microcks_client_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,54 @@ func TestDownloadArtifactReturnsResponseBody(t *testing.T) {
4141
t.Fatalf("expected response body %q, got %q", expectedBody, msg)
4242
}
4343
}
44+
45+
func TestDeleteServiceReturnsNoErrorOnSuccess(t *testing.T) {
46+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47+
if r.Method == http.MethodGet && r.URL.Path == "/api/services/search" {
48+
if got := r.URL.Query().Get("name"); got != "Simple" {
49+
t.Fatalf("unexpected service name: %s", got)
50+
}
51+
if got := r.URL.Query().Get("version"); got != "1.1" {
52+
t.Fatalf("unexpected service version: %s", got)
53+
}
54+
w.WriteHeader(http.StatusOK)
55+
_, _ = w.Write([]byte(`[{"id": "test-id-123", "name": "Simple", "version": "1.1"}]`))
56+
return
57+
}
58+
if r.Method == http.MethodDelete && r.URL.Path == "/api/services/test-id-123" {
59+
w.WriteHeader(http.StatusNoContent)
60+
return
61+
}
62+
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
63+
}))
64+
defer server.Close()
65+
66+
client := NewMicrocksClient(server.URL)
67+
68+
err := client.DeleteService("Simple", "1.1")
69+
if err != nil {
70+
t.Fatalf("DeleteService returned error: %v", err)
71+
}
72+
}
73+
74+
func TestDeleteServiceReturnsErrorOnNotFound(t *testing.T) {
75+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76+
if r.Method == http.MethodGet && r.URL.Path == "/api/services/search" {
77+
w.WriteHeader(http.StatusOK)
78+
_, _ = w.Write([]byte(`[]`))
79+
return
80+
}
81+
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
82+
}))
83+
defer server.Close()
84+
85+
client := NewMicrocksClient(server.URL)
86+
87+
err := client.DeleteService("Simple", "1.1")
88+
if err == nil {
89+
t.Fatalf("expected DeleteService to return error on not found, got nil")
90+
}
91+
if !strings.Contains(err.Error(), "not found") {
92+
t.Fatalf("expected error to contain 'not found', got: %v", err)
93+
}
94+
}

0 commit comments

Comments
 (0)