Skip to content

Commit fec59bd

Browse files
authored
fix(ibm): add ibm client (#271)
* build(deps): bump golang.org/x/net * feat(ibm): add IBM client
1 parent 0e4534b commit fec59bd

File tree

8 files changed

+485
-56
lines changed

8 files changed

+485
-56
lines changed

sysdig/internal/client/v2/client.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,17 @@ import (
1717
)
1818

1919
const (
20-
AuthorizationHeader = "Authorization"
21-
ContentTypeHeader = "Content-Type"
22-
ContentTypeJSON = "application/json"
20+
SysdigTeamIDHeader = "SysdigTeamID"
21+
AuthorizationHeader = "Authorization"
22+
ContentTypeHeader = "Content-Type"
23+
ContentTypeJSON = "application/json"
24+
ContentTypeFormURLEncoded = "x-www-form-urlencoded"
2325
)
2426

2527
type Common interface {
2628
TeamInterface
2729
}
2830

29-
type Monitor interface {
30-
Common
31-
}
32-
33-
type Secure interface {
34-
Common
35-
}
36-
3731
type Requester interface {
3832
Request(ctx context.Context, method string, url string, payload io.Reader) (*http.Response, error)
3933
}

sysdig/internal/client/v2/config.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package v2
22

33
type config struct {
4-
url string
5-
token string
6-
insecure bool
7-
extraHeaders map[string]string
4+
url string
5+
token string
6+
insecure bool
7+
extraHeaders map[string]string
8+
teamID string
9+
ibmInstanceID string
10+
ibmAPIKey string
11+
ibmIamURL string
812
}
913

1014
type ClientOption func(c *config)
@@ -33,6 +37,30 @@ func WithExtraHeaders(headers map[string]string) ClientOption {
3337
}
3438
}
3539

40+
func WithTeamID(teamID string) ClientOption {
41+
return func(c *config) {
42+
c.teamID = teamID
43+
}
44+
}
45+
46+
func WithIBMInstanceID(instanceID string) ClientOption {
47+
return func(c *config) {
48+
c.ibmInstanceID = instanceID
49+
}
50+
}
51+
52+
func WithIBMAPIKey(key string) ClientOption {
53+
return func(c *config) {
54+
c.ibmAPIKey = key
55+
}
56+
}
57+
58+
func WithIBMIamURL(url string) ClientOption {
59+
return func(c *config) {
60+
c.ibmIamURL = url
61+
}
62+
}
63+
3664
func configure(opts ...ClientOption) *config {
3765
cfg := &config{}
3866
for _, opt := range opts {

sysdig/internal/client/v2/ibm.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
"time"
11+
)
12+
13+
const (
14+
IBMInstanceIDHeader = "IBMInstanceID"
15+
IBMIAMPath = "/identity/token"
16+
IBMGrantTypeFormValue = "grant_type"
17+
IBMApiKeyFormValue = "apikey"
18+
IBMAPIKeyGrantType = "urn:ibm:params:oauth:grant-type:apikey"
19+
)
20+
21+
type IBMCommon interface {
22+
Common
23+
}
24+
25+
type IBMMonitor interface {
26+
IBMCommon
27+
}
28+
29+
type IBMAccessToken string
30+
type UnixTimestamp int64
31+
32+
type IBMRequest struct {
33+
config *config
34+
httpClient *http.Client
35+
tokenExpiration UnixTimestamp
36+
token IBMAccessToken
37+
}
38+
39+
type IAMTokenResponse struct {
40+
AccessToken string `json:"access_token"`
41+
Expiration int64 `json:"expiration"`
42+
}
43+
44+
func (ir *IBMRequest) getIBMIAMToken() (IBMAccessToken, error) {
45+
if UnixTimestamp(time.Now().Unix()) < ir.tokenExpiration {
46+
return ir.token, nil
47+
}
48+
49+
data := url.Values{}
50+
data.Set(IBMGrantTypeFormValue, IBMAPIKeyGrantType)
51+
data.Set(IBMApiKeyFormValue, ir.config.ibmAPIKey)
52+
identityURL := fmt.Sprintf("%s%s", ir.config.ibmIamURL, IBMIAMPath)
53+
54+
r, err := http.NewRequest(http.MethodPost, identityURL, strings.NewReader(data.Encode()))
55+
if err != nil {
56+
return "", err
57+
}
58+
59+
r.Header.Set(ContentTypeHeader, ContentTypeFormURLEncoded)
60+
61+
resp, err := request(ir.httpClient, ir.config, r)
62+
if err != nil {
63+
return "", err
64+
}
65+
defer resp.Body.Close()
66+
67+
iamToken, err := Unmarshal[IAMTokenResponse](resp.Body)
68+
if err != nil {
69+
return "", err
70+
}
71+
72+
ir.token = IBMAccessToken(iamToken.AccessToken)
73+
ir.tokenExpiration = UnixTimestamp(iamToken.Expiration)
74+
75+
return ir.token, nil
76+
}
77+
78+
func (ir *IBMRequest) Request(ctx context.Context, method string, url string, payload io.Reader) (*http.Response, error) {
79+
r, err := http.NewRequest(method, url, payload)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
token, err := ir.getIBMIAMToken()
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
r = r.WithContext(ctx)
90+
if ir.config.teamID != "" {
91+
r.Header.Set(SysdigTeamIDHeader, ir.config.teamID)
92+
}
93+
r.Header.Set(IBMInstanceIDHeader, ir.config.ibmInstanceID)
94+
r.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", token))
95+
r.Header.Set(ContentTypeHeader, ContentTypeJSON)
96+
97+
return request(ir.httpClient, ir.config, r)
98+
}
99+
100+
func newIBMClient(opts ...ClientOption) *Client {
101+
cfg := configure(opts...)
102+
return &Client{
103+
config: cfg,
104+
requester: &IBMRequest{
105+
config: cfg,
106+
httpClient: newHTTPClient(cfg),
107+
},
108+
}
109+
}
110+
111+
func NewIBMMonitor(opts ...ClientOption) IBMMonitor {
112+
return newIBMClient(opts...)
113+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
"time"
11+
)
12+
13+
func TestIBMClient_DoIBMRequest(t *testing.T) {
14+
testTable := []struct {
15+
TokenExpiration int64
16+
Iterations int
17+
ExpectedIAMCalls int
18+
}{
19+
{
20+
TokenExpiration: time.Now().Add(-time.Hour).Unix(),
21+
Iterations: 3,
22+
ExpectedIAMCalls: 3,
23+
},
24+
{
25+
TokenExpiration: time.Now().Add(time.Hour).Unix(),
26+
Iterations: 3,
27+
ExpectedIAMCalls: 1,
28+
},
29+
}
30+
for _, testCase := range testTable {
31+
instanceID := "instance ID"
32+
apiKey := "api key"
33+
token := "token"
34+
teamID := "team ID"
35+
iamEndpointCalled := 0
36+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37+
if r.URL.Path == IBMIAMPath {
38+
iamEndpointCalled++
39+
data, err := json.Marshal(IAMTokenResponse{
40+
AccessToken: token,
41+
Expiration: testCase.TokenExpiration,
42+
})
43+
if err != nil {
44+
t.Errorf("failed to create IAM response, err: %v", err)
45+
}
46+
47+
_, err = w.Write(data)
48+
if err != nil {
49+
t.Errorf("failed to create IAM response, err: %v", err)
50+
}
51+
return
52+
}
53+
54+
if value := r.Header.Get(AuthorizationHeader); value != fmt.Sprintf("Bearer %s", token) {
55+
t.Errorf("invalid authorization header, %v", value)
56+
}
57+
if value := r.Header.Get(IBMInstanceIDHeader); value != instanceID {
58+
t.Errorf("expected instance id %v, got %v", instanceID, value)
59+
}
60+
if value := r.Header.Get(SysdigTeamIDHeader); value != teamID {
61+
t.Errorf("expected team id %v, got %v", teamID, value)
62+
}
63+
}))
64+
65+
c := newIBMClient(
66+
WithIBMInstanceID(instanceID),
67+
WithIBMAPIKey(apiKey),
68+
WithIBMIamURL(server.URL),
69+
WithURL(server.URL),
70+
WithTeamID(teamID),
71+
)
72+
73+
url := fmt.Sprintf("%s/foo/bar", server.URL)
74+
for i := 0; i < testCase.Iterations; i++ {
75+
_, err := c.requester.Request(context.Background(), http.MethodGet, url, nil)
76+
if err != nil {
77+
t.Errorf("got error while sending request, err: %v", err)
78+
}
79+
}
80+
81+
if iamEndpointCalled != testCase.ExpectedIAMCalls {
82+
t.Errorf("expected IAM calls %d, got %d", testCase.ExpectedIAMCalls, iamEndpointCalled)
83+
}
84+
85+
server.Close()
86+
}
87+
}

sysdig/internal/client/v2/sysdig.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ type SysdigRequest struct {
1212
httpClient *http.Client
1313
}
1414

15+
type SysdigCommon interface {
16+
Common
17+
}
18+
19+
type SysdigMonitor interface {
20+
SysdigCommon
21+
}
22+
23+
type SysdigSecure interface {
24+
SysdigCommon
25+
}
26+
1527
func (sr *SysdigRequest) Request(ctx context.Context, method string, url string, payload io.Reader) (*http.Response, error) {
1628
r, err := http.NewRequest(method, url, payload)
1729
if err != nil {
@@ -25,11 +37,11 @@ func (sr *SysdigRequest) Request(ctx context.Context, method string, url string,
2537
return request(sr.httpClient, sr.config, r)
2638
}
2739

28-
func NewMonitor(opts ...ClientOption) Monitor {
40+
func NewSysdigMonitor(opts ...ClientOption) SysdigMonitor {
2941
return newSysdigClient(opts...)
3042
}
3143

32-
func NewSecure(opts ...ClientOption) Secure {
44+
func NewSysdigSecure(opts ...ClientOption) SysdigSecure {
3345
return newSysdigClient(opts...)
3446
}
3547

sysdig/provider.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
)
99

1010
func Provider() *schema.Provider {
11+
undocumentedCodeMsg := "You are using undocumented provider argument which can change in future releases. Please do not use it."
1112
return &schema.Provider{
1213
Schema: map[string]*schema.Schema{
1314
"sysdig_secure_api_token": {
@@ -47,6 +48,42 @@ func Provider() *schema.Provider {
4748
Optional: true,
4849
Elem: &schema.Schema{Type: schema.TypeString},
4950
},
51+
"ibm_monitor_url": {
52+
Type: schema.TypeString,
53+
Optional: true,
54+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_URL", nil),
55+
Deprecated: undocumentedCodeMsg,
56+
},
57+
"ibm_monitor_iam_url": {
58+
Type: schema.TypeString,
59+
Optional: true,
60+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_IAM_URL", nil),
61+
Deprecated: undocumentedCodeMsg,
62+
},
63+
"ibm_monitor_instance_id": {
64+
Type: schema.TypeString,
65+
Optional: true,
66+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_INSTANCE_ID", nil),
67+
Deprecated: undocumentedCodeMsg,
68+
},
69+
"ibm_monitor_api_key": {
70+
Type: schema.TypeString,
71+
Optional: true,
72+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_API_KEY", nil),
73+
Deprecated: undocumentedCodeMsg,
74+
},
75+
"ibm_monitor_insecure_tls": {
76+
Type: schema.TypeBool,
77+
Optional: true,
78+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_INSECURE_TLS", nil),
79+
Deprecated: undocumentedCodeMsg,
80+
},
81+
"ibm_monitor_team_id": {
82+
Type: schema.TypeString,
83+
Optional: true,
84+
DefaultFunc: schema.EnvDefaultFunc("SYSDIG_IBM_MONITOR_TEAM_ID", nil),
85+
Deprecated: undocumentedCodeMsg,
86+
},
5087
},
5188
ResourcesMap: map[string]*schema.Resource{
5289
"sysdig_user": resourceSysdigUser(),

0 commit comments

Comments
 (0)