Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iac providers endpoint #1307

Merged
2 changes: 1 addition & 1 deletion pkg/http-server/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (

// apiResponse creates an API response
func apiResponse(w http.ResponseWriter, msg string, statusCode int) {
w.WriteHeader(statusCode)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
fmt.Fprint(w, msg)
}

Expand Down
55 changes: 55 additions & 0 deletions pkg/http-server/iac-providers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Copyright (C) 2022 Tenable, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package httpserver

import (
"encoding/json"
"fmt"
"net/http"

iacProvider "github.com/tenable/terrascan/pkg/iac-providers"
"go.uber.org/zap"
)

// IacProvider contains response body for iac providers
type IacProvider struct {
Type string `json:"type"`
Versions []string `json:"versions"`
DefaultVersion string `json:"defaultVersion"`
}

// iacProviders returns list of iac providers
func (g *APIHandler) iacProviders(w http.ResponseWriter, r *http.Request) {
khasty720 marked this conversation as resolved.
Show resolved Hide resolved
var providers = []IacProvider{}
for _, provider := range iacProvider.SupportedIacProviders() {
providers = append(providers, IacProvider{
Type: string(provider),
Versions: iacProvider.GetProviderIacVersions(provider),
DefaultVersion: iacProvider.GetDefaultIacVersion(provider),
})
}

response, err := json.MarshalIndent(providers, "", " ")
if err != nil {
errMsg := fmt.Sprintf("failed to create JSON. error: '%v'", err)
zap.S().Error(errMsg)
apiErrorResponse(w, errMsg, http.StatusInternalServerError)
return
}

apiResponse(w, string(response), http.StatusOK)
}
41 changes: 41 additions & 0 deletions pkg/http-server/iac-providers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package httpserver

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)

func TestIacProviders(t *testing.T) {

handler := NewAPIHandler()

t.Run("test providers api status", func(t *testing.T) {
var (
req, _ = http.NewRequest(http.MethodGet, "/v1/providers", nil)
resp = httptest.NewRecorder()
want = http.StatusOK
)
handler.iacProviders(resp, req)
got := resp.Result().StatusCode

if got != want {
t.Errorf("incorrect providers status code, got: '%v', want: '%v'", got, want)
}
})

t.Run("test providers api response structure", func(t *testing.T) {
var (
req, _ = http.NewRequest(http.MethodGet, "/v1/providers", nil)
resp = httptest.NewRecorder()
)
handler.iacProviders(resp, req)

var data []IacProvider
err := json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
t.Errorf("error parsing response body, error: '%v'", err.Error())
}
})
}
1 change: 1 addition & 0 deletions pkg/http-server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (g *APIServer) Routes() []*Route {
h := NewAPIHandler()
routes := []*Route{
{verb: "GET", path: "/health", fn: h.Health},
{verb: "GET", path: versionedPath("/providers"), fn: h.iacProviders},
{verb: "POST", path: versionedPath("/{iac}/{iacVersion}/{cloud}/local/file/scan"), fn: h.scanFile},
{verb: "POST", path: versionedPath("/{iac}/{iacVersion}/{cloud}/remote/dir/scan"), fn: h.scanRemoteRepo},

Expand Down
11 changes: 11 additions & 0 deletions pkg/iac-providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ func SupportedIacVersions() []string {
return iacVersions
}

// GetProviderIacVersions returns list of Iac Provider versions for the given IaC type
func GetProviderIacVersions(iacType string) []string {
var versions []string

for version := range supportedIacProviders[supportedIacType(iacType)] {
versions = append(versions, string(version))
}
sort.Strings(versions)
return versions
}

// GetDefaultIacVersion returns the default IaC version for the given IaC type
func GetDefaultIacVersion(iacType string) string {
return string(defaultIacVersions[supportedIacType(iacType)])
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,15 @@ var _ = Describe("Server", func() {
It("server should be accepting requests", func() {
// logs are written in StdErr
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("Route GET - /health"))
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("Route GET - /v1/providers"))
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("Route POST - /v1/{iac}/{iacVersion}/{cloud}/local/file/scan"))
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("Route POST - /v1/{iac}/{iacVersion}/{cloud}/remote/dir/scan"))
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("http server listening at port 9010"))
})

Context("request with no body on all handlers", func() {
healthCheckURL := fmt.Sprintf("%s:%d/health", host, defaultPort)
providersURL := fmt.Sprintf("%s:%d/v1/providers", host, defaultPort)
terraformV12LocalScanURL := fmt.Sprintf("%s:%d/v1/terraform/v12/all/local/file/scan", host, defaultPort)
terrformV12RemoteScanURL := fmt.Sprintf("%s:%d/v1/terraform/v12/aws/remote/dir/scan", host, defaultPort)

Expand All @@ -127,6 +129,16 @@ var _ = Describe("Server", func() {
})
})

When("GET request on providers is made", func() {
It("should get 200 OK response", func() {
r, err := serverUtils.MakeHTTPRequest(http.MethodGet, providersURL)
Expect(err).NotTo(HaveOccurred())
defer r.Body.Close()
Expect(r).NotTo(BeNil())
Expect(r.StatusCode).To(BeIdenticalTo(http.StatusOK))
})
})

When("GET request on file scan handler is made", func() {
It("should receive method not allowed response", func() {
r, err := serverUtils.MakeHTTPRequest(http.MethodGet, terraformV12LocalScanURL)
Expand Down Expand Up @@ -177,6 +189,16 @@ var _ = Describe("Server", func() {
})
})

When("POST request on providers", func() {
It("should receive method not allowed response", func() {
r, err := serverUtils.MakeHTTPRequest(http.MethodPost, providersURL)
Expect(err).NotTo(HaveOccurred())
defer r.Body.Close()
Expect(r).NotTo(BeNil())
Expect(r.StatusCode).To(BeIdenticalTo(http.StatusMethodNotAllowed))
})
})

Context("server is stopped", func() {
It("should gracefully exit", func() {
if utils.IsWindowsPlatform() {
Expand Down