diff --git a/.circleci/config.yml b/.circleci/config.yml index 8816f3092..315c0f170 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,6 +59,8 @@ jobs: - attach_workspace: at: bin - run: make integration-only + - store_artifacts: + path: circleci-artifacts - slack/status: fail_only: true mentions: << pipeline.parameters.slack-mentions >> @@ -73,6 +75,10 @@ jobs: command: | $env:LW_CLI_BIN = Join-Path (Get-Location).Path "bin\\lacework-cli-windows-amd64.exe" go test -v github.com/lacework/go-sdk/integration + - slack/status: + fail_only: true + mentions: << pipeline.parameters.slack-mentions >> + only_for_branches: <> environment: GOFLAGS: -mod=vendor verify-release: diff --git a/.gitignore b/.gitignore index 68f7d4ad9..b5ba26a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Go test coverage coverage.out +circleci-artifacts/ # for building binary files bin/ diff --git a/cli/cmd/vuln_container.go b/cli/cmd/vuln_container.go index 4b7e9c58a..4830f90b7 100644 --- a/cli/cmd/vuln_container.go +++ b/cli/cmd/vuln_container.go @@ -308,6 +308,9 @@ func checkOnDemandContainerVulnerabilityStatus(reqID string) error { } cli.OutputHuman(buildVulnerabilityReport(results)) + if vulCmdState.Html { + return generateVulnAssessmentHTML(results) + } return nil } diff --git a/cli/cmd/vulnerability.go b/cli/cmd/vulnerability.go index f3fd0a83d..74b5e2e0c 100644 --- a/cli/cmd/vulnerability.go +++ b/cli/cmd/vulnerability.go @@ -323,6 +323,9 @@ func pollScanStatus(requestID string) error { cli.StopProgress() cli.OutputHuman(buildVulnerabilityReport(assessment)) + if vulCmdState.Html { + return generateVulnAssessmentHTML(assessment) + } return nil } } diff --git a/integration/container_vulnerability_test.go b/integration/container_vulnerability_test.go index 0a7695e56..6279d85a1 100644 --- a/integration/container_vulnerability_test.go +++ b/integration/container_vulnerability_test.go @@ -22,6 +22,8 @@ import ( "bytes" "encoding/json" "fmt" + "os" + "path" "regexp" "testing" @@ -31,7 +33,8 @@ import ( const ( registry = "index.docker.io" repository = "lacework/lacework-cli" - tag = "ubuntu-1804" + tag1 = "ubuntu-1804" + tag2 = "amazonlinux-2" ) func TestContainerVulnerabilityCommandAliases(t *testing.T) { @@ -84,19 +87,34 @@ func TestContainerVulnerabilityCommandListAssessments(t *testing.T) { }) } -func TestContainerVulnerabilityCommandScanHumanReadable(t *testing.T) { - out, err, exitcode := LaceworkCLIWithTOMLConfig( - "vulnerability", "container", "scan", registry, repository, tag) +func TestContainerVulnerabilityCommandScanHumanReadablePollGenerateHtml(t *testing.T) { + // create a temporal directory to check that the HTML file is deployed + home := createTOMLConfigFromCIvars() + defer os.RemoveAll(home) + out, err, exitcode := LaceworkCLIWithHome(home, + "vulnerability", "container", "scan", registry, repository, tag2, "--poll", "--html") assert.Contains(t, out.String(), "A new vulnerability scan has been requested. (request_id:", "STDOUT changed, please check") - assert.Contains(t, out.String(), "lacework vulnerability container scan-status", + assert.Contains(t, out.String(), "The container vulnerability assessment was stored at 'lacework-lacework-cli-sha256", "STDOUT changed, please check") assert.Empty(t, err.String(), "STDERR should be empty") assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one") + + t.Run("assert that HTML file was generated", func(t *testing.T) { + var ( + m = regexp.MustCompile(`sha256:([0-9a-z])+`) + shas = m.FindAllString(out.String(), -1) + imageDigest = shas[len(shas)-1] + ) + assert.NotEmpty(t, imageDigest, "unable to extract image digest") + htmlFile := path.Join(home, fmt.Sprintf("lacework-lacework-cli-%s.html", imageDigest)) + assert.FileExists(t, htmlFile, "the HTML file was not generated") + storeFileInCircleCI(htmlFile) + }) } type containerVulnerabilityScan struct { @@ -116,9 +134,9 @@ func TestContainerVulnerabilityCommandsEndToEnd(t *testing.T) { // "requestId": "e94f2774-5662-4510-8ebf-2d5e3cd317f6", // "status": "Scanning" // } - t.Run(fmt.Sprintf("run scan for %s/%s:%s", registry, repository, tag), func(t *testing.T) { + t.Run(fmt.Sprintf("run scan for %s/%s:%s", registry, repository, tag1), func(t *testing.T) { out, err, exitcode = LaceworkCLIWithTOMLConfig( - "vulnerability", "container", "scan", registry, repository, tag, "--json") + "vulnerability", "container", "scan", registry, repository, tag1, "--json") assert.Empty(t, err.String(), "STDERR should be empty") @@ -172,7 +190,7 @@ func TestContainerVulnerabilityCommandsEndToEnd(t *testing.T) { // fields "Registry " + registry, "Repository " + repository, - "Tags " + tag, + "Tags " + tag1, "Size", "ID", "Digest", @@ -210,4 +228,35 @@ func TestContainerVulnerabilityCommandsEndToEnd(t *testing.T) { assert.Contains(t, scanStatusOutput, showAssessmentOutput, "STDOUT from scan-status and show-assessment are not the same") }) + + // render an HTML file using the show-assessment command + t.Run("render HTML file using show-assessment command", func(t *testing.T) { + // create a temporal directory to check that the HTML file is deployed + home := createTOMLConfigFromCIvars() + defer os.RemoveAll(home) + out, err, exitcode = LaceworkCLIWithHome(home, + "vulnerability", "container", "show-assessment", imageID, "--image_id", "--html") + assert.Empty(t, + err.String(), + "STDERR should be empty") + assert.Equal(t, 0, exitcode, + "EXITCODE is not the expected one") + assert.Contains(t, out.String(), "The container vulnerability assessment was stored at 'lacework-lacework-cli-sha256", + "STDOUT changed, please check") + + assert.NotContains(t, out.String(), "Try adding '--details' to increase details shown about the vulnerability assessment.", + "STDOUT breadcrumbs should not be displayed") + + t.Run("assert that HTML file was generated", func(t *testing.T) { + var ( + m = regexp.MustCompile(`sha256:([0-9a-z])+`) + shas = m.FindAllString(out.String(), -1) + imageDigest = shas[len(shas)-1] + ) + assert.NotEmpty(t, imageDigest, "unable to extract image digest") + htmlFile := path.Join(home, fmt.Sprintf("lacework-lacework-cli-%s.html", imageDigest)) + assert.FileExists(t, htmlFile, "the HTML file was not generated") + storeFileInCircleCI(htmlFile) + }) + }) } diff --git a/integration/framework_test.go b/integration/framework_test.go index 44122ebc0..feec4c8ab 100644 --- a/integration/framework_test.go +++ b/integration/framework_test.go @@ -25,6 +25,7 @@ import ( "log" "os" "os/exec" + "path" "path/filepath" "runtime" ) @@ -194,3 +195,22 @@ api_secret = '_11111111111111111111111111111111' } return dir } + +// store a file in Circle CI Working directory, only if we are running on CircleCI +func storeFileInCircleCI(f string) { + if jobDir := os.Getenv("CIRCLE_WORKING_DIRECTORY"); jobDir != "" { + var ( + file = filepath.Base(f) + artifacts = path.Join(jobDir, "circleci-artifacts") + err = os.Mkdir(artifacts, 0755) + ) + if err != nil { + fmt.Println(err) + } + + err = os.Rename(f, path.Join(artifacts, file)) + if err != nil { + fmt.Println(err) + } + } +} diff --git a/scripts/release.sh b/scripts/release.sh index fb974afe4..0c2937eba 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -15,6 +15,8 @@ readonly org_name=lacework readonly package_name=lacework-cli readonly binary_name=lacework readonly docker_org=lacework +readonly git_user="Salim Afiune Maya" +readonly git_email="afiune@lacework.net" readonly docker_tags=( latest scratch @@ -80,25 +82,16 @@ main() { } trigger_release() { - if [[ "$VERSION" =~ "-release" ]]; then - log "VERSION has 'x.y.z-release' tag. Triggering a release!" - log "" - log "removing release tag from version '${VERSION}'" - remove_tag_version - log "commiting and pushing the version bump to github" - git config --global user.email "afiune@lacework.net" - git config --global user.name "Salim Afiune Maya" - git add VERSION - git add api/version.go # file genereted by scripts/version_updater.sh - git commit -m "trigger release v$VERSION" - git push origin master - tag_release - bump_version - else + if [[ "$VERSION" =~ "-dev" ]]; then log "No release needed. (VERSION=${VERSION})" log "" log "Read more about the release process at:" log " - https://github.com/${org_name}/${project_name}/wiki/Release-Process" + else + log "VERSION ready to be released to 'x.y.z' tag. Triggering a release!" + log "" + tag_release + bump_version fi } @@ -123,14 +116,14 @@ verify_release() { fi done - if [[ "$VERSION" =~ "-release" ]]; then - log "(required) VERSION has 'x.y.z-release' tag. Great!" - else - warn "the 'VERSION' needs to be updated to have the 'x.y.z-release' tag" + if [[ "$VERSION" =~ "-dev" ]]; then + warn "the 'VERSION' needs to be cleaned up to be only 'x.y.z' tag" warn "" warn "Read more about the release process at:" warn " - https://github.com/${org_name}/${project_name}/wiki/Release-Process" exit 123 + else + log "(required) VERSION has been cleaned up to 'x.y.z' tag. Great!" fi } @@ -141,7 +134,6 @@ prepare_release() { cli_generate_files generate_release_notes update_changelog - add_tag_version "release" push_release } @@ -174,23 +166,56 @@ update_changelog() { load_list_of_changes() { latest_version=$(find_latest_version) local _list_of_changes=$(git log --no-merges --pretty="* %s (%an)([%h](https://github.com/${org_name}/${project_name}/commit/%H))" ${latest_version}..master) - echo "## Features" > CHANGES.md - echo "$_list_of_changes" | grep "\* feat[:(]" >> CHANGES.md - echo "## Refactor" >> CHANGES.md - echo "$_list_of_changes" | grep "\* refactor[:(]" >> CHANGES.md - echo "## Performance Improvements" >> CHANGES.md - echo "$_list_of_changes" | grep "\* perf[:(]" >> CHANGES.md - echo "## Bug Fixes" >> CHANGES.md - echo "$_list_of_changes" | grep "\* fix[:(]" >> CHANGES.md - echo "## Documentation Updates" >> CHANGES.md - echo "$_list_of_changes" | grep "\* doc[:(]" >> CHANGES.md - echo "$_list_of_changes" | grep "\* docs[:(]" >> CHANGES.md - echo "## Other Changes" >> CHANGES.md - echo "$_list_of_changes" | grep "\* style[:(]" >> CHANGES.md - echo "$_list_of_changes" | grep "\* chore[:(]" >> CHANGES.md - echo "$_list_of_changes" | grep "\* build[:(]" >> CHANGES.md - echo "$_list_of_changes" | grep "\* ci[:(]" >> CHANGES.md - echo "$_list_of_changes" | grep "\* test[:(]" >> CHANGES.md + + # init changes file + true > CHANGES.md + + _feat=$(echo "$_list_of_changes" | grep "\* feat[:(]") + _refactor=$(echo "$_list_of_changes" | grep "\* refactor[:(]") + _perf=$(echo "$_list_of_changes" | grep "\* perf[:(]") + _fix=$(echo "$_list_of_changes" | grep "\* fix[:(]") + _doc=$(echo "$_list_of_changes" | grep "\* doc[:(]") + _docs=$(echo "$_list_of_changes" | grep "\* docs[:(]") + _style=$(echo "$_list_of_changes" | grep "\* style[:(]") + _chore=$(echo "$_list_of_changes" | grep "\* chore[:(]") + _build=$(echo "$_list_of_changes" | grep "\* build[:(]") + _ci=$(echo "$_list_of_changes" | grep "\* ci[:(]") + _test=$(echo "$_list_of_changes" | grep "\* test[:(]") + + if [ "$_feat" != "" ]; then + echo "## Features" >> CHANGES.md + echo "$_feat" >> CHANGES.md + fi + + if [ "$_refactor" != "" ]; then + echo "## Refactor" >> CHANGES.md + echo "$_refactor" >> CHANGES.md + fi + + if [ "$_perf" != "" ]; then + echo "## Performance Improvements" >> CHANGES.md + echo "$_perf" >> CHANGES.md + fi + + if [ "$_fix" != "" ]; then + echo "## Bug Fixes" >> CHANGES.md + echo "$_fix" >> CHANGES.md + fi + + if [ "${_docs}${_doc}" != "" ]; then + echo "## Documentation Updates" >> CHANGES.md + if [ "$_doc" != "" ]; then echo "$_doc" >> CHANGES.md; fi + if [ "$_docs" != "" ]; then echo "$_docs" >> CHANGES.md; fi + fi + + if [ "${_style}${_chore}${_build}${_ci}${_test}" != "" ]; then + echo "## Other Changes" >> CHANGES.md + if [ "$_style" != "" ]; then echo "$_style" >> CHANGES.md; fi + if [ "$_chore" != "" ]; then echo "$_chore" >> CHANGES.md; fi + if [ "$_build" != "" ]; then echo "$_build" >> CHANGES.md; fi + if [ "$_ci" != "" ]; then echo "$_ci" >> CHANGES.md; fi + if [ "$_test" != "" ]; then echo "$_test" >> CHANGES.md; fi + fi } generate_release_notes() { @@ -297,6 +322,8 @@ bump_version() { fi log "commiting and pushing the vertion bump to github" + git config --global user.email $git_email + git config --global user.name $git_user git add VERSION git add api/version.go # file genereted by scripts/version_updater.sh git commit -m "version bump to v$VERSION"