Skip to content

Commit bffe907

Browse files
authored
fix: add support for calling any endpoint from control plane (#1497)
1 parent 47a5b7c commit bffe907

File tree

6 files changed

+212
-3
lines changed

6 files changed

+212
-3
lines changed

pkg/api/generic/api.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package generic
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"net/http"
8+
"strings"
9+
)
10+
11+
type GenericAPI interface {
12+
GET(ctx context.Context, path string) (interface{}, *http.Response, error)
13+
POST(ctx context.Context, path string, body io.Reader) (interface{}, *http.Response, error)
14+
}
15+
16+
// APIConfig defines the available configuration options
17+
// to customize the API client settings
18+
type Config struct {
19+
// HTTPClient is a custom HTTP client
20+
HTTPClient *http.Client
21+
// Debug enables debug-level logging
22+
Debug bool
23+
// BaseURL sets a custom API server base URL
24+
BaseURL string
25+
}
26+
27+
func NewGenericAPIClient(cfg *Config) GenericAPI {
28+
if cfg.HTTPClient == nil {
29+
cfg.HTTPClient = http.DefaultClient
30+
}
31+
32+
c := APIClient{
33+
baseURL: cfg.BaseURL,
34+
httpClient: cfg.HTTPClient,
35+
}
36+
37+
return &c
38+
}
39+
40+
type APIClient struct {
41+
httpClient *http.Client
42+
baseURL string
43+
}
44+
45+
func (c *APIClient) GET(ctx context.Context, path string) (interface{}, *http.Response, error) {
46+
url := c.baseURL + path
47+
48+
req, err := http.NewRequest("GET", url, nil)
49+
if err != nil {
50+
return err, nil, err
51+
}
52+
53+
req = req.WithContext(ctx)
54+
55+
req.Header.Set("Accept", "application/json")
56+
57+
resp, err := c.httpClient.Do(req)
58+
if err != nil {
59+
return nil, resp, err
60+
}
61+
if resp.StatusCode > http.StatusBadRequest {
62+
return nil, resp, errors.New(resp.Status)
63+
}
64+
defer resp.Body.Close()
65+
66+
b, err := io.ReadAll(resp.Body)
67+
if err != nil {
68+
return nil, resp, err
69+
}
70+
71+
return string(b), resp, err
72+
}
73+
74+
func (c *APIClient) POST(ctx context.Context, path string, body io.Reader) (interface{}, *http.Response, error) {
75+
url := c.baseURL + path
76+
bodyBinary, err := io.ReadAll(body)
77+
78+
if err != nil {
79+
return err, nil, err
80+
}
81+
requestBody := strings.NewReader(string(bodyBinary))
82+
req, err := http.NewRequest("POST", url, requestBody)
83+
if err != nil {
84+
return nil, nil, err
85+
}
86+
87+
req = req.WithContext(ctx)
88+
req.Header.Set("Content-Type", "application/json")
89+
req.Header.Set("Accept", "application/json")
90+
91+
resp, err := c.httpClient.Do(req)
92+
if err != nil {
93+
return nil, resp, err
94+
}
95+
if resp.StatusCode > http.StatusBadRequest {
96+
return nil, resp, errors.New(resp.Status)
97+
}
98+
defer resp.Body.Close()
99+
100+
b, err := io.ReadAll(resp.Body)
101+
if err != nil {
102+
return nil, resp, err
103+
}
104+
105+
return string(b), resp, err
106+
}

pkg/api/rbac/api.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ func NewPrincipalAPIClient(cfg *Config) PrincipalAPI {
6868
}
6969

7070
type APIClient struct {
71-
httpClient *http.Client
72-
baseURL *url.URL
73-
AccessToken string
71+
httpClient *http.Client
72+
baseURL *url.URL
7473
}
7574

7675
// GetPrincipals returns the list of user's in the current users organization/tenant

pkg/cmd/request/request.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package request
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/redhat-developer/app-services-cli/pkg/cmd/registry/artifact/util"
9+
"github.com/redhat-developer/app-services-cli/pkg/core/ioutil/iostreams"
10+
"github.com/redhat-developer/app-services-cli/pkg/core/localize"
11+
"github.com/redhat-developer/app-services-cli/pkg/core/logging"
12+
"github.com/redhat-developer/app-services-cli/pkg/shared/connection"
13+
"github.com/redhat-developer/app-services-cli/pkg/shared/factory"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
type options struct {
18+
IO *iostreams.IOStreams
19+
Logger logging.Logger
20+
localizer localize.Localizer
21+
Context context.Context
22+
Connection factory.ConnectionFunc
23+
24+
urlPath string
25+
method string
26+
}
27+
28+
func NewCallCmd(f *factory.Factory) *cobra.Command {
29+
opts := &options{
30+
IO: f.IOStreams,
31+
Logger: f.Logger,
32+
localizer: f.Localizer,
33+
Context: f.Context,
34+
Connection: f.Connection,
35+
}
36+
37+
cmd := &cobra.Command{
38+
Use: "request",
39+
Short: "Allows you to perform API requests against the API server",
40+
Example: `
41+
# Perform a GET request to the specified path
42+
rhoas request --path /api/kafkas_mgmt/v1/kafkas
43+
44+
# Perform a POST request to the specified path
45+
cat request.json | rhoas request --path "/api/kafkas_mgmt/v1/kafkas?async=true" --method post `,
46+
Hidden: true,
47+
Args: cobra.NoArgs,
48+
RunE: func(cmd *cobra.Command, args []string) error {
49+
return runCmd(opts)
50+
},
51+
}
52+
cmd.Flags().StringVar(&opts.urlPath, "path", "", "Path to send request. For example /api/kafkas_mgmt/v1/kafkas?async=true")
53+
cmd.Flags().StringVar(&opts.method, "method", "GET", "HTTP method to use. (get, post)")
54+
return cmd
55+
}
56+
57+
func runCmd(opts *options) (err error) {
58+
if opts.urlPath == "" {
59+
return errors.New("--path is required")
60+
}
61+
opts.Logger.Info("Performing request to", opts.urlPath)
62+
conn, err := opts.Connection(connection.DefaultConfigSkipMasAuth)
63+
64+
if err != nil {
65+
return err
66+
}
67+
68+
var data interface{}
69+
var response interface{}
70+
if opts.method == "post" {
71+
opts.Logger.Info("POST request. Reading file from standard input")
72+
specifiedFile, err1 := util.CreateFileFromStdin()
73+
if err1 != nil {
74+
return err
75+
}
76+
data, response, err = conn.API().GenericAPI().POST(opts.Context, opts.urlPath, specifiedFile)
77+
} else {
78+
data, response, err = conn.API().GenericAPI().GET(opts.Context, opts.urlPath)
79+
}
80+
81+
if err != nil || data == nil {
82+
opts.Logger.Info("Fetching data failed", err, response)
83+
return err
84+
}
85+
86+
fmt.Fprint(opts.IO.Out, data)
87+
return nil
88+
}

pkg/cmd/root/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/redhat-developer/app-services-cli/pkg/cmd/login"
99
"github.com/redhat-developer/app-services-cli/pkg/cmd/logout"
1010
"github.com/redhat-developer/app-services-cli/pkg/cmd/registry"
11+
"github.com/redhat-developer/app-services-cli/pkg/cmd/request"
1112
"github.com/redhat-developer/app-services-cli/pkg/cmd/serviceaccount"
1213
"github.com/redhat-developer/app-services-cli/pkg/cmd/status"
1314
cliversion "github.com/redhat-developer/app-services-cli/pkg/cmd/version"
@@ -53,6 +54,7 @@ func NewRootCommand(f *factory.Factory, version string) *cobra.Command {
5354
cmd.AddCommand(registry.NewServiceRegistryCommand(f))
5455

5556
cmd.AddCommand(docs.NewDocsCmd(f))
57+
cmd.AddCommand(request.NewCallCmd(f))
5658

5759
return cmd
5860
}

pkg/shared/connection/api/api.go

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

33
import (
4+
"github.com/redhat-developer/app-services-cli/pkg/api/generic"
45
"github.com/redhat-developer/app-services-cli/pkg/api/rbac"
56
amsclient "github.com/redhat-developer/app-services-sdk-go/accountmgmt/apiv1/client"
67
kafkainstanceclient "github.com/redhat-developer/app-services-sdk-go/kafkainstance/apiv1internal/client"
@@ -17,4 +18,5 @@ type API interface {
1718
ServiceRegistryInstance(instanceID string) (*registryinstanceclient.APIClient, *registrymgmtclient.Registry, error)
1819
AccountMgmt() amsclient.AppServicesApi
1920
RBAC() rbac.RbacAPI
21+
GenericAPI() generic.GenericAPI
2022
}

pkg/shared/connection/api/defaultapi/default_client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/redhat-developer/app-services-cli/pkg/shared/kafkautil"
1313

1414
"github.com/redhat-developer/app-services-cli/internal/build"
15+
"github.com/redhat-developer/app-services-cli/pkg/api/generic"
1516
"github.com/redhat-developer/app-services-cli/pkg/api/rbac"
1617
"github.com/redhat-developer/app-services-cli/pkg/core/logging"
1718
"github.com/redhat-developer/app-services-cli/pkg/shared/connection/api"
@@ -231,6 +232,17 @@ func (a *defaultAPI) ServiceRegistryInstance(instanceID string) (*registryinstan
231232
return client, &instance, nil
232233
}
233234

235+
func (a *defaultAPI) GenericAPI() generic.GenericAPI {
236+
tc := a.createOAuthTransport(a.AccessToken)
237+
client := generic.NewGenericAPIClient(&generic.Config{
238+
BaseURL: a.ApiURL.String(),
239+
Debug: a.Logger.DebugEnabled(),
240+
HTTPClient: tc,
241+
})
242+
243+
return client
244+
}
245+
234246
// AccountMgmt returns a new Account Management API client instance
235247
func (a *defaultAPI) AccountMgmt() amsclient.AppServicesApi {
236248
cfg := amsclient.NewConfiguration()

0 commit comments

Comments
 (0)