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

Feature/remove lagoon api delete calls #57

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,6 @@ func main() {

determineLogLevel()

// simple check to ensure we have everything we need to write to the API if required.
if lagoon.PushProblemsToInsightRemote {
if lagoonApiBaseUrl == "" {
log.Fatal("lagoon api base url not provided")
}
if lagoonApiToken == "" {
log.Fatal("lagoon api token not provided")
}
}

for _, f := range checksFiles {
if !utils.StringIsUrl(f) {
if _, err := os.Stat(f); os.IsNotExist(err) {
Expand All @@ -111,9 +101,7 @@ func main() {
checkTypesToRun,
excludeDb,
remediate,
logLevel,
lagoonApiBaseUrl,
lagoonApiToken)
logLevel)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -186,7 +174,6 @@ func parseFlags() {
pflag.BoolVarP(&debug, "debug", "d", false, "Display debug information - equivalent to --log-level debug")
pflag.BoolVarP(&excludeDb, "exclude-db", "x", false, "Exclude checks requiring a database; overrides any db checks specified by '--types'")
pflag.BoolVarP(&remediate, "remediate", "r", false, "Run remediation for supported checks")
pflag.StringVar(&lagoonApiBaseUrl, "lagoon-api-base-url", "", "Base url for the Lagoon API when pushing problems to API (env: LAGOON_API_BASE_URL)")
pflag.StringVar(&lagoonApiToken, "lagoon-api-token", "", "Lagoon API token when pushing problems to API (env: LAGOON_API_TOKEN)")
pflag.BoolVar(&lagoon.PushProblemsToInsightRemote, "lagoon-push-problems-to-insights", false, "Push audit facts to Lagoon via Insights Remote")
pflag.StringVar(&lagoon.LagoonInsightsRemoteEndpoint, "lagoon-insights-remote-endpoint", "http://lagoon-remote-insights-remote.lagoon.svc/problems", "Insights Remote Problems endpoint")
Expand Down
2 changes: 2 additions & 0 deletions pkg/internal/testutils_lagoon.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func MockLagoonReset() {
}

type MockInsightsRemoteTestState struct {
LastCallMethod string
LastCallBody string
LastCallHeaders map[string]string
LastCallEndpoint string
Expand All @@ -44,6 +45,7 @@ type MockInsightsRemoteTestState struct {
func MockRemoteInsightsServer(state *MockInsightsRemoteTestState) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
state.LastCallEndpoint = r.RequestURI
state.LastCallMethod = r.Method
requestBody, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(500)
Expand Down
76 changes: 23 additions & 53 deletions pkg/lagoon/lagoon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package lagoon
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -13,9 +12,7 @@ import (
"github.com/salsadigitalauorg/shipshape/pkg/config"
"github.com/salsadigitalauorg/shipshape/pkg/result"

"github.com/hasura/go-graphql-client"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)

type Fact struct {
Expand Down Expand Up @@ -46,27 +43,12 @@ type Problem struct {
const SourceName string = "Shipshape"
const FactMaxValueLength int = 300

var ApiBaseUrl string
var ApiToken string
var PushProblemsToInsightRemote bool
var LagoonInsightsRemoteEndpoint string

var project string
var environment string

var Client *graphql.Client

func InitClient() {
if Client != nil {
return
}
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: ApiToken},
)
httpClient := oauth2.NewClient(context.Background(), src)
Client = graphql.NewClient(ApiBaseUrl+"/graphql", httpClient)
}

func MustHaveEnvVars() {
project = os.Getenv("LAGOON_PROJECT")
environment = os.Getenv("LAGOON_ENVIRONMENT")
Expand All @@ -76,26 +58,6 @@ func MustHaveEnvVars() {
}
}

// GetEnvironmentIdFromEnvVars derives the environment id from shell variables
// LAGOON_PROJECT & LAGOON_ENVIRONMENT.
func GetEnvironmentIdFromEnvVars() (int, error) {
MustHaveEnvVars()

ns := project + "-" + environment
log.WithField("namespace", ns).Info("fetching environment id")
var q struct {
EnvironmentByKubernetesNamespaceName struct {
Id int
} `graphql:"environmentByKubernetesNamespaceName(kubernetesNamespaceName: $ns)"`
}
variables := map[string]interface{}{"ns": ns}
err := Client.Query(context.Background(), &q, variables)
if err != nil {
return 0, err
}
return q.EnvironmentByKubernetesNamespaceName.Id, nil
}

const DefaultLagoonInsightsTokenLocation = "/var/run/secrets/lagoon/dynamic/insights-token/INSIGHTS_TOKEN"

func GetBearerTokenFromDisk(tokenLocation string) (string, error) {
Expand All @@ -115,13 +77,15 @@ func GetBearerTokenFromDisk(tokenLocation string) (string, error) {
func ProcessResultList(w *bufio.Writer, list result.ResultList) error {
problems := []Problem{}

// first, let's try doing this via in-cluster functionality
bearerToken, err := GetBearerTokenFromDisk(DefaultLagoonInsightsTokenLocation)

if list.TotalBreaches == 0 {
InitClient()
err := DeleteProblems()
err := DeleteProblems(LagoonInsightsRemoteEndpoint, bearerToken)
if err != nil {
return err
}
fmt.Fprint(w, "no breach to push to Lagoon; only deleted previous problems")
fmt.Fprintln(w, "no breach to push to Lagoon; only deleted previous problems")
w.Flush()
return nil
}
Expand Down Expand Up @@ -152,9 +116,6 @@ func ProcessResultList(w *bufio.Writer, list result.ResultList) error {
})
}

InitClient()
// first, let's try doing this via in-cluster functionality
bearerToken, err := GetBearerTokenFromDisk(DefaultLagoonInsightsTokenLocation)
if err == nil { // we have a token, and so we can proceed via the internal service call
err = ProblemsToInsightsRemote(problems, LagoonInsightsRemoteEndpoint, bearerToken)
if err != nil {
Expand Down Expand Up @@ -190,20 +151,29 @@ func ProblemsToInsightsRemote(problems []Problem, serviceEndpoint string, bearer
return nil
}

func DeleteProblems() error {
envId, err := GetEnvironmentIdFromEnvVars()
func DeleteProblems(serviceEndpoint string, bearerToken string) error {

deleteEndpoint := fmt.Sprintf("%v/%v", serviceEndpoint, SourceName)

bodyString, err := json.Marshal("{}")
if err != nil {
return err
}
var m struct {
DeleteFactsFromSource string `graphql:"deleteProblemsFromSource(input: {environment: $envId, source: $sourceName, service:$service})"`

req, _ := http.NewRequest(http.MethodDelete, deleteEndpoint, bytes.NewBuffer(bodyString))
req.Header.Set("Authorization", bearerToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(req)

if err != nil {
return err
}
variables := map[string]interface{}{
"envId": envId,
"sourceName": SourceName,
"service": "",

if response.StatusCode != 200 {
return fmt.Errorf("there was an error deleting the problems at '%s' : %s", deleteEndpoint, response.Body)
}
return Client.Mutate(context.Background(), &m, variables)
return nil
}

// SeverityTranslation will convert a ShipShape severity rating to a Lagoon rating
Expand Down
102 changes: 35 additions & 67 deletions pkg/lagoon/lagoon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,10 @@ import (
"github.com/salsadigitalauorg/shipshape/pkg/internal"
"github.com/salsadigitalauorg/shipshape/pkg/lagoon"

"github.com/hasura/go-graphql-client"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestInitClient(t *testing.T) {
assert := assert.New(t)

assert.Nil(lagoon.Client)
lagoon.InitClient()
assert.NotNil(lagoon.Client)
}

func TestMustHaveEnvVars(t *testing.T) {
assert := assert.New(t)

Expand Down Expand Up @@ -60,64 +51,6 @@ func TestMustHaveEnvVars(t *testing.T) {
})
}

func TestGetEnvironmentIdFromEnvVars(t *testing.T) {
assert := assert.New(t)

svr := internal.MockLagoonServer()
lagoon.Client = graphql.NewClient(svr.URL, http.DefaultClient)
origOutput := logrus.StandardLogger().Out
os.Setenv("LAGOON_PROJECT", "foo")
os.Setenv("LAGOON_ENVIRONMENT", "bar")
var buf bytes.Buffer
logrus.SetOutput(&buf)
defer func() {
svr.Close()
internal.MockLagoonReset()
lagoon.Client = nil
os.Unsetenv("LAGOON_PROJECT")
os.Unsetenv("LAGOON_ENVIRONMENT")
logrus.SetOutput(origOutput)
}()

_, err := lagoon.GetEnvironmentIdFromEnvVars()
assert.NoError(err)
assert.Equal(1, internal.MockLagoonNumCalls)
assert.Equal("{\"query\":\"query ($ns:String!){"+
"environmentByKubernetesNamespaceName(kubernetesNamespaceName: $ns)"+
"{id}}\",\"variables\":{\"ns\":\"foo-bar\"}}\n", internal.MockLagoonRequestBodies[0])
}

func TestDeleteProblems(t *testing.T) {
assert := assert.New(t)

svr := internal.MockLagoonServer()
lagoon.Client = graphql.NewClient(svr.URL, http.DefaultClient)
origOutput := logrus.StandardLogger().Out
os.Setenv("LAGOON_PROJECT", "foo")
os.Setenv("LAGOON_ENVIRONMENT", "bar")
var buf bytes.Buffer
logrus.SetOutput(&buf)
defer func() {
svr.Close()
internal.MockLagoonReset()
lagoon.Client = nil
os.Unsetenv("LAGOON_PROJECT")
os.Unsetenv("LAGOON_ENVIRONMENT")
logrus.SetOutput(origOutput)
}()

err := lagoon.DeleteProblems()
assert.NoError(err)
assert.Equal(2, internal.MockLagoonNumCalls)
assert.Equal("{\"query\":\"query ($ns:String!){"+
"environmentByKubernetesNamespaceName(kubernetesNamespaceName: $ns)"+
"{id}}\",\"variables\":{\"ns\":\"foo-bar\"}}\n", internal.MockLagoonRequestBodies[0])
assert.Equal("{\"query\":\"mutation ($envId:Int!$service:String!$sourceName:String!)"+
"{deleteProblemsFromSource(input: {environment: $envId, source: "+
"$sourceName, service:$service})}\",\"variables\":{\"envId\":50,\"service\":\"\",\"sourceName\":"+
"\"Shipshape\"}}\n", internal.MockLagoonRequestBodies[1])
}

func Test_GetBearerTokenFromDisk(t *testing.T) {
type args struct {
tokenLocation string
Expand Down Expand Up @@ -148,6 +81,41 @@ func Test_GetBearerTokenFromDisk(t *testing.T) {
}
}

func Test_DeleteProblemsInsightsRemote(t *testing.T) {
type args struct {
bearerToken string
}
tests := []struct {
name string
args args
wantErr assert.ErrorAssertionFunc
testBodyEqual bool
}{
{
name: "Successful post",
args: args{
bearerToken: "bearertoken",
},
wantErr: assert.NoError,
testBodyEqual: true,
},
}
for _, tt := range tests {

mockServerData := internal.MockInsightsRemoteTestState{}

serv := internal.MockRemoteInsightsServer(&mockServerData)

t.Run(tt.name, func(t *testing.T) {
tt.wantErr(t, lagoon.DeleteProblems(serv.URL, tt.args.bearerToken), fmt.Sprintf("DeleteProblems(%v)", tt.args.bearerToken))
if tt.testBodyEqual == true { //let's check the data we sent through seems correct
assert.Equal(t, http.MethodDelete, mockServerData.LastCallMethod)
assert.Contains(t, mockServerData.LastCallEndpoint, lagoon.SourceName)
}
})
}
}

func Test_ProblemsToInsightsRemote(t *testing.T) {
type args struct {
problems []lagoon.Problem
Expand Down
12 changes: 1 addition & 11 deletions pkg/shipshape/shipshape.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"sync"

"github.com/salsadigitalauorg/shipshape/pkg/config"
"github.com/salsadigitalauorg/shipshape/pkg/lagoon"
"github.com/salsadigitalauorg/shipshape/pkg/result"
"github.com/salsadigitalauorg/shipshape/pkg/utils"

Expand All @@ -21,7 +20,7 @@ var RunConfig config.Config
var RunResultList result.ResultList
var OutputFormats = []string{"json", "junit", "simple", "table"}

func Init(projectDir string, configFiles []string, checkTypesToRun []string, excludeDb bool, remediate bool, logLevel string, lagoonApiBaseUrl string, lagoonApiToken string) error {
func Init(projectDir string, configFiles []string, checkTypesToRun []string, excludeDb bool, remediate bool, logLevel string) error {
if logLevel == "" {
logLevel = "warn"
}
Expand All @@ -44,15 +43,6 @@ func Init(projectDir string, configFiles []string, checkTypesToRun []string, exc
// config parsing.
RunConfig.Remediate = remediate

// Base url can either be provided in the config file or in env var, the
// latter being final.
if lagoonApiBaseUrl != "" {
lagoon.ApiBaseUrl = lagoonApiBaseUrl
} else {
lagoon.ApiBaseUrl = RunConfig.LagoonApiBaseUrl
}
lagoon.ApiToken = lagoonApiToken

log.WithFields(log.Fields{
"ProjectDir": RunConfig.ProjectDir,
"FailSeverity": RunConfig.FailSeverity,
Expand Down
4 changes: 2 additions & 2 deletions pkg/shipshape/shipshape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestInit(t *testing.T) {

t.Run("defaultValues", func(t *testing.T) {
currDir, _ := os.Getwd()
err := Init("", []string{}, []string{}, false, false, "", "", "")
err := Init("", []string{}, []string{}, false, false, "")
assert.NoError(err)
assert.Equal(currDir, config.ProjectDir)
assert.Equal(config.Config{
Expand All @@ -32,7 +32,7 @@ func TestInit(t *testing.T) {
})

t.Run("projectDirIsSet", func(t *testing.T) {
err := Init("foo", []string{}, []string{}, false, false, "warn", "", "")
err := Init("foo", []string{}, []string{}, false, false, "warn")
assert.NoError(err)
assert.Equal("foo", config.ProjectDir)
})
Expand Down
Loading