Skip to content

Commit

Permalink
Add iac providers endpoint (#1307)
Browse files Browse the repository at this point in the history
* Add GetProviderIacVersions function

* Add providers endpoint

* Fix set json resp Content-Type

* Update var name

* Add providers e2e test

* Add comments to iac providers

* Add IacProviders unit tests

Co-authored-by: Kyle Hasty <khasty@tenable.com>
Co-authored-by: Cesar Rodriguez <cesar@accurics.com>
Co-authored-by: Gaurav Gogia <16029099+gaurav-gogia@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 5, 2022
1 parent da754a7 commit 752d2a0
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 1 deletion.
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) {
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

0 comments on commit 752d2a0

Please sign in to comment.