Skip to content

Commit 7367b1d

Browse files
authored
feat: AG-59 improve code enablement flow (#6)
1 parent 8b09be6 commit 7367b1d

File tree

4 files changed

+781
-16
lines changed

4 files changed

+781
-16
lines changed

internal/mcp/api.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* © 2025 Snyk Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package mcp
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"encoding/json"
23+
"fmt"
24+
"io"
25+
"net/http"
26+
"net/url"
27+
"time"
28+
29+
"github.com/snyk/go-application-framework/pkg/configuration"
30+
"github.com/snyk/go-application-framework/pkg/workflow"
31+
)
32+
33+
const (
34+
apiRequestTimeout = 30 * time.Second
35+
)
36+
37+
// snykRestAPIRequest represents a request to the Snyk REST API
38+
type snykRestAPIRequest struct {
39+
URI string
40+
Method string
41+
Body io.Reader
42+
}
43+
44+
// doRequest executes the API request with proper headers and error handling
45+
func (r *snykRestAPIRequest) doRequest(ctx context.Context, baseURL string, httpClient *http.Client) (*http.Response, []byte, error) {
46+
baseURLParsed, err := url.Parse(baseURL)
47+
if err != nil {
48+
return nil, nil, fmt.Errorf("failed to parse API URL: %w", err)
49+
}
50+
51+
// Parse the URI to separate path and query
52+
uriParsed, err := url.Parse(r.URI)
53+
if err != nil {
54+
return nil, nil, fmt.Errorf("failed to parse URI: %w", err)
55+
}
56+
57+
// Use JoinPath to construct the full URL with the REST API path
58+
baseURLParsed.Path = ""
59+
baseURLParsed.RawQuery = ""
60+
fullURLParsed := baseURLParsed.JoinPath(uriParsed.Path)
61+
fullURLParsed.RawQuery = uriParsed.RawQuery
62+
fullURL := fullURLParsed.String()
63+
64+
method := r.Method
65+
if method == "" {
66+
method = http.MethodGet
67+
}
68+
69+
body := r.Body
70+
if body == nil {
71+
body = http.NoBody
72+
}
73+
74+
req, err := http.NewRequestWithContext(ctx, method, fullURL, body)
75+
if err != nil {
76+
return nil, nil, fmt.Errorf("failed to create request: %w", err)
77+
}
78+
79+
if r.Body != nil {
80+
req.Header.Set("Content-Type", "application/vnd.api+json")
81+
}
82+
83+
resp, err := httpClient.Do(req)
84+
if err != nil {
85+
return nil, nil, fmt.Errorf("failed to send request: %w", err)
86+
}
87+
defer func() {
88+
_ = resp.Body.Close()
89+
}()
90+
91+
respBody, err := io.ReadAll(resp.Body)
92+
if err != nil {
93+
return nil, nil, fmt.Errorf("failed to read response: %w", err)
94+
}
95+
96+
return resp, respBody, nil
97+
}
98+
99+
// enableSnykCodeForOrg attempts to enable Snyk Code (SAST) for the given organization via REST API
100+
func (m *McpLLMBinding) enableSnykCodeForOrg(ctx context.Context, invocationCtx workflow.InvocationContext, orgId string) error {
101+
logger := m.logger.With().Str("method", "enableSnykCodeForOrg").Logger()
102+
103+
config := invocationCtx.GetEngine().GetConfiguration()
104+
apiUrl := config.GetString(configuration.API_URL)
105+
106+
if orgId == "" {
107+
return fmt.Errorf("organization ID not found")
108+
}
109+
110+
// Get HTTP client from GAF (handles auth automatically)
111+
httpClient := invocationCtx.GetNetworkAccess().GetHttpClient()
112+
113+
requestBody := map[string]interface{}{
114+
"data": map[string]interface{}{
115+
"type": "sast_settings",
116+
"id": orgId,
117+
"attributes": map[string]interface{}{
118+
"sast_enabled": true,
119+
},
120+
},
121+
}
122+
123+
jsonBody, err := json.Marshal(requestBody)
124+
if err != nil {
125+
return fmt.Errorf("failed to marshal request body: %w", err)
126+
}
127+
128+
apiRequest := &snykRestAPIRequest{
129+
URI: fmt.Sprintf("/rest/orgs/%s/settings/sast?version=2024-10-15", orgId),
130+
Method: http.MethodPatch,
131+
Body: bytes.NewBuffer(jsonBody),
132+
}
133+
134+
logger.Debug().Str("uri", apiRequest.URI).Str("orgId", orgId).Msg("Enabling Snyk Code via API")
135+
136+
ctx, cancel := context.WithTimeout(ctx, apiRequestTimeout)
137+
defer cancel()
138+
139+
resp, body, err := apiRequest.doRequest(ctx, apiUrl, httpClient)
140+
if err != nil {
141+
return err
142+
}
143+
144+
logger.Debug().Int("statusCode", resp.StatusCode).Str("response", string(body)).Msg("Received API response")
145+
146+
if resp.StatusCode != http.StatusCreated {
147+
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
148+
}
149+
150+
logger.Info().Msg("Successfully enabled Snyk Code for organization")
151+
return nil
152+
}

0 commit comments

Comments
 (0)