Repository: sonatype-nexus-community/nancy
Pull request #68 (2020-01-02 21:14:38)

Nancy uses Nexus IQ Server, the journey
This is a quick lil ditty to get Nancy to work with Nexus IQ Server

This pull request makes the following changes:
* Adds `iq.go` that handles communication with Nexus IQ
* Adds `cyclonedx` package that will turn a `[]packageurl.PackageURL` of purls into a minimal SBOM string
* Adds command line config options that shouldn't interfere with current OSS Index usage such that you call `./nancy iq -a application`

Successful output looks like:

```
(base) 685 me:nancy (NancyWithIQ *)$ go list -m all | ./nancy iq -application testapp
2020/01/02 12:09:12 Nancy version: development
..........
Wonderbar! No policy violations reported for this audit!
Report URL:  http://localhost:8070/ui/links/application/testapp/report/7a9655ecf31c40cb89149378b64e439a
```

Draft PR for now so we people can laugh at my implementation with less shame. I'll add README results after we sort what I did technically.

cc @bhamail / @zendern / @ken-duck / @ajbrown  / @fitzoh 


zendern: I assume you saw the tests are broken....looks like you can't run it via passing go.sum or Gopkg.lock anymore ¯\_(ツ)_/¯ 

Also tests are a lacking in this PR but probably wouldn't hurt to add at least something around the cyclonedx stuff and maybe something around the api calls into IQ
DarthHater: @zendern yeah, I saw all that, gonna fix all that up tomorrow, just wanted to get some early feedback on my approach!
DarthHater: @zendern tests fixed (and ability to specify path to go.sum, etc...) in: 4de41cc
DarthHater: Errm maybe not! I'll take a gander.
DarthHater: @zendern @fitzoh ok, tests pass, tear this thing apart :)
fitzoh: >@zendern @fitzoh ok, tests pass, tear this thing apart :)

We were a little busy this past week :)
https://www.codemash.org/session-details/?id=145836

Might try to poke around a bit this weekend.
zendern: So overall what you have here is good....my biggest concern (and it may not be valid since this is still a draft) but we still have no real test coverage around any of the Nexus IQ server stuff. 

It'd be good to see at least some unit tests around it but would be awesome if we were able to also add an integration tests like we have for our other public interfaces into nancy (go.sum, go.lock, go list, etc) that run as part of a build.  

To do that though we are looking at something like this to spin up an IQ server before we run anything.  
https://docs.travis-ci.com/user/docker/#using-a-docker-image-from-a-repository-in-a-build

I still dont know enough about IQ server to know if that is actually likely or not. I see a docker file here https://github.com/sonatype/docker-nexus-iq-server/blob/master/Dockerfile so hopefully ¯\_(ツ)_/¯ 
DarthHater: I’m thinking I’ll likely setup a mock server that sends the responses back I expect, etc... for unit tests/integration tests. IQ Server needs a license etc... so doing a full fledged setup would be tricky. 

I’m on vacation till next week so I wouldn’t expect to see anything till then!
DarthHater: @zendern , ok so I did a few things:

- Nancy now calls OSS Index before it calls IQ, the reason for this is to report on Go mod stuff that has the timestamp version hash thing, OSS Index currently has that data.
- Changed some of the stuff around so I could use OSS Index results to create a cyclonedx_bom
- Wrote a test! I swear! It's a happy path for now but it mocks out OSS Index, and a theoretical IQ Server, and returns the responses that you'd get in a happy world

Probably some more stuff. Would love feedback!
zendern: > @zendern , ok so I did a few things:
> 
> * Nancy now calls OSS Index before it calls IQ, the reason for this is to report on Go mod stuff that has the timestamp version hash thing, OSS Index currently has that data.
> * Changed some of the stuff around so I could use OSS Index results to create a cyclonedx_bom
> * Wrote a test! I swear! It's a happy path for now but it mocks out OSS Index, and a theoretical IQ Server, and returns the responses that you'd get in a happy world
> 
> Probably some more stuff. Would love feedback!

![image](https://user-images.githubusercontent.com/1099906/73991881-7db24080-491b-11ea-9aa7-42d18b0832bf.png)
Does this mean i've officially been knighted or something 👑👑👑 🤣 🤣 🤣 🤣  
zendern: So first things first..... I know we have all been there in trying to fix stuff that works locally and doesn't on CI but can we squash these or whichever set makes sense to squash together?? 

![image](https://user-images.githubusercontent.com/1099906/73991997-c4079f80-491b-11ea-9834-7911db518f0f.png)

DarthHater: @zendern I was going to squash and merge to master once I get there, do you want me to squash first?
zendern: Re: squash into master. Nah that’s fine if you want to just do it that way. 👍
DarthHater: @fitzoh @zendern marking this as ready, I think it's more or less ready.
DarthHater: Bit of a follow up to, I'm noticing it's hard to test error cases in Nancy because we throw around `os.Exit` like it's going out of style (a mistake of mine). Does anyone have objection to using `panic()` more often like I've been doing here? It makes it easier to test in some cases (ok my function panic'd, etc...)
zendern: @DarthHater Latest changes look good to me. I do have a concern using panic and the fact that it now muddies user messaging when it is encountered. Basically it means two things....we lose control over the exit code coming back (maybe this is fine as long as it not 0 its fine) and we now give the user a stacktrace. Which sure nancy is a dev tool but feels like the wrong thing to show a user, most if any will not care about nancy internals. 

So few options come to mind....

1. Ignore me and just roll with it....dev can handle a stack trace
2. We leave the panic to make it easier to test but need to catch the panic at the top level and not present that stacktrace to the user. And maybe that would be nice b/c in the future we could add a flag to debug and it would spit out the stacktrace ¯\_(ツ)_/¯ 
3. Switch back to exit and change the test case to something like this. Where you spawn a subprocess test. 
https://stackoverflow.com/a/33404435
DarthHater: @zendern I'm with you on the stack trace stuff. 

```
The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.
```

I'm just trying to get more towards that, using panic in libs a bit more and allowing someone to recover if they want to, rather than explicitly force them to os.Exit
zendern: @darthhater so then you plan is to put that into place?? So somewhere probably on main.go we capture the panic and return the message/correct error code
DarthHater: Well, I'm kinda stuck on it personally. The middle ground is probably to panic, and then recover by throwing an error from our public APIs, that's consistent with the golang principle anyways. More so having an open conversation so I can figure out how to wrap this PR up, while heading some sort of direction :)
zendern: @darthhater sooooo middle ground maybe to get this pushed along. The panic stuff you are testing error states right?? What if we go back to exit, scrap the test you wrote around error path and write up an issue to move the project from exit -> panic and implement the public error handler code that would be needed. Eat the tech debt around error handling testing in this case and get this shipped ¯\_(ツ)_/¯ 
DarthHater: That sounds good, bud. I'll see what I can move forward today and then re-tag you in here!
DarthHater: @zendern , moved back to errs and exits for now, if you wanna give this ole PR the rubber stamp!

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index d89f1b1..110ea54 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -22,7 +22,7 @@ jobs:
       - run: go mod download
       - run: go mod tidy
       - run: go get -u github.com/jstemmer/go-junit-report
-      - run: go test ./... -v 2>&1 | go-junit-report > $TEST_RESULTS/gotest/report.xml
+      - run: go test ./... -v 2>&1 -p=1 | go-junit-report > $TEST_RESULTS/gotest/report.xml
       - run: go build 
       - run: cd testdata/dep && ../../nancy Gopkg.lock && cd -
       - run: ./nancy go.sum
diff --git a/README.md b/README.md
index 36a8abf..1a0f423 100644
--- a/README.md
+++ b/README.md
@@ -9,16 +9,16 @@
 
 # Nancy
 
-`nancy` is a tool to check for vulnerabilities in your Golang dependencies, powered by [Sonatype OSS Index](https://ossindex.sonatype.org/).
+`nancy` is a tool to check for vulnerabilities in your Golang dependencies, powered by [Sonatype OSS Index](https://ossindex.sonatype.org/), and as well, works with Nexus IQ Server, allowing you a smooth experience as a Golang developer, using the best tools in the market!
 
 ### Usage
 
 ```
  ~ > nancy
 Usage:
-go list -m all | nancy [options]
-nancy [options] </path/to/Gopkg.lock>
-nancy [options] </path/to/go.sum>
+        nancy [options] </path/to/Gopkg.lock>
+        nancy [options] </path/to/go.sum>
+        nancy iq [options]
 
 Options:
   -clean-cache
@@ -26,9 +26,9 @@ Options:
   -exclude-vulnerability value
     	Comma separated list of CVEs to exclude
   -exclude-vulnerability-file string
-    	Path to a file containing newline separated CVEs to be excluded (default "./.nancy-ignore")
+        Path to a file containing newline separated CVEs to be excluded (default "./.nancy-ignore")
   -help
-    	provides help text on how to use nancy
+        provides help text on how to use nancy
   -no-color
     	indicate output should not be colorized
   -output string
@@ -36,12 +36,30 @@ Options:
   -quiet
     	indicate output should contain only packages with vulnerabilities
   -version
-    	prints current nancy version
+        prints current nancy version
+
+$ > nancy iq
+Usage:
+        go list -m all | nancy iq [options]
+
+Options:
+  -application string
+        Specify application ID for request
+  -max-retries int
+        Specify maximum number of tries to poll Nexus IQ Server (default 300)
+  -server-url string
+        Specify Nexus IQ Server URL/port (default "http://localhost:8070")
+  -stage string
+        Specify stage for application (default "develop")
+  -token string
+        Specify token/password for request (default "admin123")
+  -user string
+        Specify username for request (default "admin")
 ```
 
 `nancy` currently works for projects that use `dep` or `go mod` for dependencies.
 
-### Options
+### OSS Index Options
 
 #### Quiet mode
 
@@ -79,6 +97,46 @@ CVN-123 # Mitigated the risk of this since we only use one method in this packag
 CVN-543
 ``` 
 
+### Nexus IQ Server Options
+
+By default, assuming you have an out of the box Nexus IQ Server running, you can run `nancy` like so:
+
+`go list -m all | ./nancy iq -application public-application-id`
+
+It is STRONGLY suggested that you do not do this, and we will warn you on output if you are.
+
+A more logical use of `nancy` against Nexus IQ Server will look like so:
+
+`go list -m all | ./nancy iq -application public-application-id -user nondefaultuser -token yourtoken -server-url http://adifferentserverurl:port -stage develop`
+
+Options for stage are as follows:
+
+`build, develop, stage-release, release`
+
+By default `-stage` will be `develop`.
+
+Successful submissions to Nexus IQ Server will result in either an OS exit of 0, meaning all is clear and a response akin to:
+
+```
+Wonderbar! No policy violations reported for this audit!
+Report URL:  http://reportURL
+```
+
+Failed submissions will either indicate failure because of an issue with processing the request, or a policy violation. Both will exit with a code of 1, allowing you to fail your build in CI. Policy Violation failures will include a report URL where you can learn more about why you encountered a failure.
+
+Policy violations will look like:
+
+```
+Hi, Nancy here, you have some policy violations to clean up!
+Report URL:  http://reportURL
+```
+
+Errors processing in Nexus IQ Server will look like:
+
+```
+Uh oh! There was an error with your request to Nexus IQ Server: <error>
+```
+
 #### Output
 
 We support multiple different output formats. Examples can be found below for each. [This intentionally vulnerable repo](https://github.com/sonatype-nexus-community/intentionally-vulnerable-golang-project) was used to generate the example output.
diff --git a/configuration/parse.go b/configuration/parse.go
index c0d8a6a..0d4e295 100644
--- a/configuration/parse.go
+++ b/configuration/parse.go
@@ -1,3 +1,16 @@
+// Copyright 2020 Sonatype 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 configuration
 
 import (
@@ -5,8 +18,8 @@ import (
 	"flag"
 	"fmt"
 	"os"
-	"regexp"
 	"reflect"
+	"regexp"
 	"strings"
 
 	"github.com/sirupsen/logrus"
@@ -15,19 +28,57 @@ import (
 )
 
 type Configuration struct {
-	UseStdIn bool
-	Help bool
-	NoColor bool
-	Quiet bool
-	Version bool
+	UseStdIn   bool
+	Help       bool
+	NoColor    bool
+	Quiet      bool
+	Version    bool
 	CleanCache bool
-	CveList types.CveListFlag
-	Path    string
-	Formatter logrus.Formatter
+	CveList    types.CveListFlag
+	Path       string
+	Formatter  logrus.Formatter
+}
+
+type IqConfiguration struct {
+	Help        bool
+	Version     bool
+	User        string
+	Token       string
+	Stage       string
+	Application string
+	Server      string
+	MaxRetries  int
 }
 
 var unixComments = regexp.MustCompile(`#.*$`)
 
+func ParseIQ(args []string) (config IqConfiguration, err error) {
+	iqCommand := flag.NewFlagSet("iq", flag.ExitOnError)
+	iqCommand.StringVar(&config.User, "user", "admin", "Specify username for request")
+	iqCommand.StringVar(&config.Token, "token", "admin123", "Specify token/password for request")
+	iqCommand.StringVar(&config.Server, "server-url", "http://localhost:8070", "Specify Nexus IQ Server URL/port")
+	iqCommand.StringVar(&config.Application, "application", "", "Specify application ID for request")
+	iqCommand.StringVar(&config.Stage, "stage", "develop", "Specify stage for application")
+	iqCommand.IntVar(&config.MaxRetries, "max-retries", 300, "Specify maximum number of tries to poll Nexus IQ Server")
+
+	flag.Usage = func() {
+		_, _ = fmt.Fprintf(os.Stderr, `Usage:
+	go list -m all | nancy iq [options]
+			
+Options:
+`)
+		iqCommand.PrintDefaults()
+		os.Exit(2)
+	}
+
+	err = iqCommand.Parse(args)
+	if err != nil {
+		return config, err
+	}
+
+	return config, nil
+}
+
 func Parse(args []string) (Configuration, error) {
 	config := Configuration{}
 	var excludeVulnerabilityFilePath string
@@ -50,21 +101,27 @@ func Parse(args []string) (Configuration, error) {
 	flag.StringVar(&outputFormat, "output", "text", "Styling for output format. "+fmt.Sprintf("%+q", reflect.ValueOf(outputFormats).MapKeys()))
 
 	flag.Usage = func() {
-		_, _ = fmt.Fprintf(os.Stderr, "Usage: \ngo list -m all | nancy [options]\nnancy [options] </path/to/Gopkg.lock>\nnancy [options] </path/to/go.sum>\n\nOptions:\n")
+		_, _ = fmt.Fprintf(os.Stderr, `Usage:
+	go list -m all | nancy [options]
+	go list -m all | nancy iq [options]
+	nancy [options] </path/to/Gopkg.lock>
+	nancy [options] </path/to/go.sum>
+			
+Options:
+`)
 		flag.PrintDefaults()
 		os.Exit(2)
 	}
 
-	// Parse config from the command line output
 	err := flag.CommandLine.Parse(args)
 	if err != nil {
 		return config, err
 	}
 
-	if len(flag.Args()) == 0 {
-		config.UseStdIn = true
-	} else {
+	if len(args) > 0 {
 		config.Path = args[len(args)-1]
+	} else {
+		config.UseStdIn = true
 	}
 
 	if outputFormats[outputFormat] != nil {
diff --git a/customerrors/error.go b/customerrors/error.go
index e31252e..a5c842c 100644
--- a/customerrors/error.go
+++ b/customerrors/error.go
@@ -15,8 +15,9 @@ package customerrors
 
 import (
 	"fmt"
-	"github.com/sonatype-nexus-community/nancy/buildversion"
 	"os"
+
+	"github.com/sonatype-nexus-community/nancy/buildversion"
 )
 
 type SwError struct {
diff --git a/cyclonedx/cyclonedx.go b/cyclonedx/cyclonedx.go
new file mode 100644
index 0000000..846b24e
--- /dev/null
+++ b/cyclonedx/cyclonedx.go
@@ -0,0 +1,82 @@
+// Copyright 2020 Sonatype 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 cyclonedx has definitions and functions for processing golang purls into a minimal CycloneDX 1.1 Sbom
+package cyclonedx
+
+import (
+	"encoding/xml"
+	"fmt"
+
+	"github.com/package-url/packageurl-go"
+	"github.com/sonatype-nexus-community/nancy/customerrors"
+	"github.com/sonatype-nexus-community/nancy/types"
+)
+
+const cycloneDXBomXmlns1_1 = "http://cyclonedx.org/schema/bom/1.1"
+const cycloneDXBomXmlns1_0V = "http://cyclonedx.org/schema/ext/vulnerability/1.0"
+
+const version = "1"
+
+// ProcessPurlsIntoSBOM will take a slice of packageurl.PackageURL and convert them
+// into a minimal 1.1 CycloneDX sbom
+func ProcessPurlsIntoSBOM(results []types.Coordinate) string {
+	return processPurlsIntoSBOMSchema1_1(results)
+}
+
+func processPurlsIntoSBOMSchema1_1(results []types.Coordinate) string {
+	sbom := types.Sbom{}
+	sbom.Xmlns = cycloneDXBomXmlns1_1
+	sbom.XMLNSV = cycloneDXBomXmlns1_0V
+	sbom.Version = version
+	for _, v := range results {
+		purl, err := packageurl.FromString(v.Coordinates)
+		customerrors.Check(err, "Error parsing purl from given coordinate")
+
+		component := types.Component{
+			Type:    "library",
+			BomRef:  purl.String(),
+			Purl:    purl.String(),
+			Name:    purl.Name,
+			Version: purl.Version,
+		}
+
+		if v.IsVulnerable() {
+			vulns := types.Vulnerabilities{}
+			for _, x := range v.Vulnerabilities {
+				rating := types.Rating{Score: types.Score{Base: x.CvssScore}}
+				rating.Vector = x.CvssVector
+				ratings := types.Ratings{}
+				ratings.Rating = rating
+				source := types.Source{Name: "ossindex"}
+				source.URL = x.Reference
+				vuln := types.SbomVulnerability{ID: x.Cve, Source: source, Description: x.Description, Ref: v.Coordinates}
+				vuln.Ratings = append(vuln.Ratings, ratings)
+				vulns.Vulnerability = append(vulns.Vulnerability, vuln)
+			}
+			component.Vulnerabilities = vulns
+		}
+
+		sbom.Components.Component = append(sbom.Components.Component, component)
+	}
+
+	output, err := xml.MarshalIndent(sbom, " ", "     ")
+	if err != nil {
+		fmt.Print(err)
+	}
+
+	output = []byte(xml.Header + string(output))
+
+	return string(output)
+}
diff --git a/go.mod b/go.mod
index bd91ee7..e09b562 100644
--- a/go.mod
+++ b/go.mod
@@ -6,16 +6,20 @@ require (
 	github.com/Masterminds/semver v0.0.0-20180403130225-3c92f33da7a8
 	github.com/Masterminds/vcs v1.13.1 // indirect
 	github.com/armon/go-radix v1.0.0 // indirect
+	github.com/beevik/etree v1.1.0
 	github.com/boltdb/bolt v1.3.1 // indirect
+	github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a
 	github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155
 	github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/golang/dep v0.5.4
 	github.com/golang/protobuf v1.2.0 // indirect
-	github.com/google/go-cmp v0.4.0 // indirect
+	github.com/google/go-cmp v0.3.1 // indirect
+	github.com/jarcoal/httpmock v1.0.4
 	github.com/jmank88/nuts v0.3.0 // indirect
 	github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
 	github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 // indirect
+	github.com/package-url/packageurl-go v0.1.0
 	github.com/pelletier/go-toml v1.4.0 // indirect
 	github.com/pkg/errors v0.8.0 // indirect
 	github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 // indirect
@@ -25,6 +29,8 @@ require (
 	github.com/stretchr/testify v1.3.0
 	golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
 	golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
+	golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
+	gopkg.in/go-playground/assert.v1 v1.2.1
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index 05ebcab..f539eaa 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,5 @@
 github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo=
 github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Flaque/filet v0.0.0-20190209224823-fc4d33cfcf93 h1:NnAUCP75PRm8yWE7+MZBIAR6PA9iwsBYEc6ZNYOy+AQ=
 github.com/Flaque/filet v0.0.0-20190209224823-fc4d33cfcf93/go.mod h1:TK+jB3mBs+8ZMWhU5BqZKnZWJ1MrLo8etNVg51ueTBo=
@@ -10,9 +9,12 @@ github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXN
 github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
 github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
+github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
 github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a h1:kTv7wPomOuRf17BKQKO5Y6GrKsYC52XHrjf26H6FdQU=
+github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -20,22 +22,23 @@ github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155 h1:T4boC9W0W8nX
 github.com/dgraph-io/badger v1.5.5-0.20181004181505-439fd464b155/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
 github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0=
 github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs=
 github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
+github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jmank88/nuts v0.3.0 h1:UZUboV1LXVkBUTHLRTEZrDfAL7QYgj9jEsBCiJHrxEM=
 github.com/jmank88/nuts v0.3.0/go.mod h1:kTf5cyoLibZUQg9Lns/gteKO1d/5XrhacD1QVKviAKk=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b h1:PMbSa9CgaiQR9NLlUTwKi+7aeLl3GG5JX5ERJxfQ3IE=
 github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 h1:+2OJrU8cmOstEoh0uQvYemRGVH1O6xtO2oANUWHFnP0=
 github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
+github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I=
+github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw=
 github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
 github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
@@ -59,13 +62,12 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/iq/iq.go b/iq/iq.go
new file mode 100644
index 0000000..fb257a7
--- /dev/null
+++ b/iq/iq.go
@@ -0,0 +1,211 @@
+// Copyright 2020 Sonatype 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 iq has definitions and functions for processing golang purls with Nexus IQ Server
+package iq
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"time"
+
+	"github.com/sonatype-nexus-community/nancy/buildversion"
+	"github.com/sonatype-nexus-community/nancy/configuration"
+	"github.com/sonatype-nexus-community/nancy/customerrors"
+	"github.com/sonatype-nexus-community/nancy/cyclonedx"
+	"github.com/sonatype-nexus-community/nancy/ossindex"
+	"github.com/sonatype-nexus-community/nancy/types"
+)
+
+const internalApplicationIDURL = "/api/v2/applications?publicId="
+
+const thirdPartyAPILeft = "/api/v2/scan/applications/"
+
+const thirdPartyAPIRight = "/sources/nancy?stageId="
+
+const (
+	pollInterval = 1 * time.Second
+)
+
+var (
+	localConfig configuration.IqConfiguration
+	tries       = 0
+)
+
+// Internal types for use by this package, don't need to expose them
+type applicationResponse struct {
+	Applications []application `json:"applications"`
+}
+
+type application struct {
+	ID string `json:"id"`
+}
+
+type thirdPartyAPIResult struct {
+	StatusURL string `json:"statusUrl"`
+}
+
+var statusURLResp types.StatusURLResult
+
+// AuditPackages accepts a slice of purls, public application ID, and configuration, and will submit these to
+// Nexus IQ Server for audit, and return a struct of StatusURLResult
+func AuditPackages(purls []string, applicationID string, config configuration.IqConfiguration) (types.StatusURLResult, error) {
+	localConfig = config
+
+	if localConfig.User == "admin" && localConfig.Token == "admin123" {
+		warnUserOfBadLifeChoices()
+	}
+
+	internalID := getInternalApplicationID(applicationID)
+	if internalID == "" {
+		return statusURLResp, fmt.Errorf("Internal ID for %s could not be found, or Nexus IQ Server is down", applicationID)
+	}
+
+	resultsFromOssIndex, err := ossindex.AuditPackages(purls)
+	customerrors.Check(err, "There was an issue auditing packages using OSS Index")
+
+	sbom := cyclonedx.ProcessPurlsIntoSBOM(resultsFromOssIndex)
+	statusURL := submitToThirdPartyAPI(sbom, internalID)
+	if statusURL == "" {
+		return statusURLResp, fmt.Errorf("There was an issue submitting your sbom to the Nexus IQ Third Party API, sbom: %s", sbom)
+	}
+
+	statusURLResp = types.StatusURLResult{}
+
+	finished := make(chan bool)
+
+	go func() {
+		for {
+			select {
+			case <-finished:
+				return
+			default:
+				pollIQServer(fmt.Sprintf("%s/%s", localConfig.Server, statusURL), finished, localConfig.MaxRetries)
+				time.Sleep(pollInterval)
+			}
+		}
+	}()
+
+	<-finished
+	return statusURLResp, nil
+}
+
+func getInternalApplicationID(applicationID string) (internalID string) {
+	client := &http.Client{}
+
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s%s%s", localConfig.Server, internalApplicationIDURL, applicationID),
+		nil,
+	)
+
+	req.SetBasicAuth(localConfig.User, localConfig.Token)
+
+	resp, err := client.Do(req)
+	customerrors.Check(err, "There was an error communicating with Nexus IQ Server to get your internal application ID")
+
+	defer resp.Body.Close()
+
+	if resp.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		customerrors.Check(err, "There was an error retrieving the bytes of the response for getting your internal application ID from Nexus IQ Server")
+
+		var response applicationResponse
+		json.Unmarshal(bodyBytes, &response)
+		return response.Applications[0].ID
+	}
+	return ""
+}
+
+func submitToThirdPartyAPI(sbom string, internalID string) string {
+	client := &http.Client{}
+
+	url := fmt.Sprintf("%s%s", localConfig.Server, fmt.Sprintf("%s%s%s%s", thirdPartyAPILeft, internalID, thirdPartyAPIRight, localConfig.Stage))
+
+	req, err := http.NewRequest(
+		"POST",
+		url,
+		bytes.NewBuffer([]byte(sbom)),
+	)
+
+	req.SetBasicAuth(localConfig.User, localConfig.Token)
+
+	req.Header.Set("Content-Type", "application/xml")
+	req.Header.Set("User-Agent", fmt.Sprintf("nancy-client/%s", buildversion.BuildVersion))
+
+	resp, err := client.Do(req)
+	customerrors.Check(err, "There was an issue communicating with the Nexus IQ Third Party API")
+
+	defer resp.Body.Close()
+
+	if resp.StatusCode == http.StatusAccepted {
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		customerrors.Check(err, "There was an issue submitting your sbom to the Nexus IQ Third Party API")
+
+		var response thirdPartyAPIResult
+		json.Unmarshal(bodyBytes, &response)
+		return response.StatusURL
+	}
+
+	return ""
+}
+
+func pollIQServer(statusURL string, finished chan bool, maxRetries int) {
+	if tries > maxRetries {
+		finished <- true
+	}
+
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", statusURL, nil)
+
+	req.SetBasicAuth(localConfig.User, localConfig.Token)
+
+	req.Header.Set("User-Agent", fmt.Sprintf("nancy-client/%s", buildversion.BuildVersion))
+
+	resp, err := client.Do(req)
+
+	if err != nil {
+		finished <- true
+		customerrors.Check(err, "There was an error polling Nexus IQ Server")
+	}
+
+	defer resp.Body.Close()
+
+	if resp.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		customerrors.Check(err, "There was an error with processing the response from polling Nexus IQ Server")
+
+		var response types.StatusURLResult
+		json.Unmarshal(bodyBytes, &response)
+		statusURLResp = response
+		if response.IsError {
+			finished <- true
+		}
+		finished <- true
+	}
+	tries++
+	fmt.Print(".")
+}
+
+func warnUserOfBadLifeChoices() {
+	fmt.Println()
+	fmt.Println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+	fmt.Println("!!!! WARNING : You are using the default username and password for Nexus IQ. !!!!")
+	fmt.Println("!!!! You are strongly encouraged to change these, and use a token.           !!!!")
+	fmt.Println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
+	fmt.Println()
+}
diff --git a/main.go b/main.go
index 22b1a8b..3f7ea21 100644
--- a/main.go
+++ b/main.go
@@ -18,33 +18,50 @@ import (
 	"errors"
 	"flag"
 	"fmt"
-	"github.com/sonatype-nexus-community/nancy/types"
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
 	"strings"
 
+	"github.com/sonatype-nexus-community/nancy/types"
+
+	figure "github.com/common-nighthawk/go-figure"
 	"github.com/golang/dep"
 	"github.com/sonatype-nexus-community/nancy/audit"
 	"github.com/sonatype-nexus-community/nancy/buildversion"
 	"github.com/sonatype-nexus-community/nancy/configuration"
 	"github.com/sonatype-nexus-community/nancy/customerrors"
+	"github.com/sonatype-nexus-community/nancy/iq"
 	"github.com/sonatype-nexus-community/nancy/ossindex"
 	"github.com/sonatype-nexus-community/nancy/packages"
 	"github.com/sonatype-nexus-community/nancy/parse"
 )
 
-var config configuration.Configuration
-
 func main() {
-	var err error
-	config, err = configuration.Parse(os.Args[1:])
-	if err != nil {
-		flag.Usage()
-		os.Exit(1)
+	if len(os.Args) > 1 && os.Args[1] == "iq" {
+		config, err := configuration.ParseIQ(os.Args[2:])
+		if err != nil {
+			flag.Usage()
+			os.Exit(1)
+		}
+		processIQConfig(config)
+	} else {
+		ossIndexConfig, err := configuration.Parse(os.Args[1:])
+		if err != nil {
+			flag.Usage()
+			os.Exit(1)
+		}
+		processConfig(ossIndexConfig)
 	}
+}
+
+func printHeader() {
+	figure.NewFigure("Nancy", "larry3d", true).Print()
+	figure.NewFigure("By Sonatype & Friends", "pepper", true).Print()
+}
 
+func processConfig(config configuration.Configuration) {
 	if config.Help {
 		flag.Usage()
 		os.Exit(0)
@@ -69,14 +86,43 @@ func main() {
 		log.SetOutput(ioutil.Discard)
 	}
 
-	if config.UseStdIn == true {
-		doStdInAndParse()
-	} else {
-		doCheckExistenceAndParse()
+	printHeader()
+
+	log.Println("Nancy version: " + buildversion.BuildVersion)
+
+	if config.UseStdIn {
+		doStdInAndParse(config)
+	}
+	if !config.UseStdIn {
+		doCheckExistenceAndParse(config)
 	}
 }
 
-func doStdInAndParse() {
+func processIQConfig(config configuration.IqConfiguration) {
+	if config.Help {
+		flag.Usage()
+		os.Exit(0)
+	}
+
+	if config.Version {
+		fmt.Println(buildversion.BuildVersion)
+		_, _ = fmt.Printf("build time: %s\n", buildversion.BuildTime)
+		_, _ = fmt.Printf("build commit: %s\n", buildversion.BuildCommit)
+		os.Exit(0)
+	}
+
+	if config.Application == "" {
+		flag.Usage()
+	}
+
+	printHeader()
+
+	log.Println("Nancy version: " + buildversion.BuildVersion)
+
+	doStdInAndParseForIQ(config)
+}
+
+func doStdInAndParse(config configuration.Configuration) {
 	fi, err := os.Stdin.Stat()
 	if err != nil {
 		panic(err)
@@ -89,11 +135,28 @@ func doStdInAndParse() {
 		scanner := bufio.NewScanner(os.Stdin)
 		mod.ProjectList, _ = parse.GoList(scanner)
 		var purls = mod.ExtractPurlsFromManifest()
-		checkOSSIndex(purls, []string{})
+		checkOSSIndex(purls, nil, config)
 	}
 }
 
-func doCheckExistenceAndParse() {
+func doStdInAndParseForIQ(config configuration.IqConfiguration) {
+	fi, err := os.Stdin.Stat()
+	if err != nil {
+		panic(err)
+	}
+	if (fi.Mode() & os.ModeNamedPipe) == 0 {
+		flag.Usage()
+		os.Exit(1)
+	} else {
+		mod := packages.Mod{}
+		scanner := bufio.NewScanner(os.Stdin)
+		mod.ProjectList, _ = parse.GoList(scanner)
+		var purls = mod.ExtractPurlsFromManifestForIQ()
+		auditWithIQServer(purls, config.Application, config)
+	}
+}
+
+func doCheckExistenceAndParse(config configuration.Configuration) {
 	switch {
 	case strings.Contains(config.Path, "Gopkg.lock"):
 		workingDir := filepath.Dir(config.Path)
@@ -115,7 +178,7 @@ func doCheckExistenceAndParse() {
 
 		purls, invalidPurls := packages.ExtractPurlsUsingDep(project)
 
-		checkOSSIndex(purls, invalidPurls)
+		checkOSSIndex(purls, invalidPurls, config)
 	case strings.Contains(config.Path, "go.sum"):
 		mod := packages.Mod{}
 		mod.GoSumPath = config.Path
@@ -123,14 +186,14 @@ func doCheckExistenceAndParse() {
 			mod.ProjectList, _ = parse.GoSum(config.Path)
 			var purls = mod.ExtractPurlsFromManifest()
 
-			checkOSSIndex(purls, nil)
+			checkOSSIndex(purls, nil, config)
 		}
 	default:
 		os.Exit(3)
 	}
 }
 
-func checkOSSIndex(purls []string, invalidpurls []string) {
+func checkOSSIndex(purls []string, invalidpurls []string, config configuration.Configuration) {
 	var packageCount = len(purls)
 	coordinates, err := ossindex.AuditPackages(purls)
 	customerrors.Check(err, "Error auditing packages")
@@ -144,3 +207,23 @@ func checkOSSIndex(purls []string, invalidpurls []string) {
 		os.Exit(count)
 	}
 }
+
+func auditWithIQServer(purls []string, applicationID string, config configuration.IqConfiguration) {
+	res, err := iq.AuditPackages(purls, applicationID, config)
+	customerrors.Check(err, "Uh oh! There was an error with your request to Nexus IQ Server")
+
+	fmt.Println()
+	if res.IsError {
+		customerrors.Check(errors.New(res.ErrorMessage), "Uh oh! There was an error with your request to Nexus IQ Server")
+	}
+
+	if res.PolicyAction != "Failure" {
+		fmt.Println("Wonderbar! No policy violations reported for this audit!")
+		fmt.Println("Report URL: ", res.ReportHTMLURL)
+		os.Exit(0)
+	} else {
+		fmt.Println("Hi, Nancy here, you have some policy violations to clean up!")
+		fmt.Println("Report URL: ", res.ReportHTMLURL)
+		os.Exit(1)
+	}
+}
diff --git a/ossindex/ossindex.go b/ossindex/ossindex.go
index af0c3a4..92ea932 100644
--- a/ossindex/ossindex.go
+++ b/ossindex/ossindex.go
@@ -18,6 +18,7 @@ package ossindex
 import (
 	"bytes"
 	"encoding/json"
+	"flag"
 	"fmt"
 	"io/ioutil"
 	"log"
@@ -64,8 +65,13 @@ func getOssIndexUrl() string {
 
 func openDb(dbDir string) (db *badger.DB, err error) {
 	opts := badger.DefaultOptions
-	opts.Dir = dbDir + "/" + dbValueDirName
-	opts.ValueDir = dbDir + "/" + dbValueDirName
+	if flag.Lookup("test") == nil {
+		opts.Dir = dbDir + "/" + dbValueDirName
+		opts.ValueDir = dbDir + "/" + dbValueDirName
+	} else {
+		opts.Dir = dbDir + "/" + "test-nancy"
+		opts.ValueDir = dbDir + "/" + "test-nancy"
+	}
 	db, err = badger.Open(opts)
 	return
 }
@@ -80,12 +86,7 @@ func AuditPackages(purls []string) ([]types.Coordinate, error) {
 	// Initialize the cache
 	db, err := openDb(dbDir)
 	customerrors.Check(err, "Error initializing cache")
-
-	defer func() {
-		if err := db.Close(); err != nil {
-			log.Printf("error closing db: %s\n", err)
-		}
-	}()
+	defer db.Close()
 
 	var newPurls []string
 	var results []types.Coordinate
diff --git a/packages/dep.go b/packages/dep.go
index bcbbaad..35e10da 100644
--- a/packages/dep.go
+++ b/packages/dep.go
@@ -14,9 +14,10 @@
 package packages
 
 import (
+	"strings"
+
 	"github.com/Masterminds/semver"
 	"github.com/golang/dep"
-	"strings"
 )
 
 func ExtractPurlsUsingDep(project *dep.Project) ([]string, []string) {
diff --git a/packages/mod.go b/packages/mod.go
index 56797dc..5c6d05e 100644
--- a/packages/mod.go
+++ b/packages/mod.go
@@ -37,6 +37,18 @@ func (m Mod) ExtractPurlsFromManifest() (purls []string) {
 	return purls
 }
 
+func (m Mod) ExtractPurlsFromManifestForIQ() (purls []string) {
+	for _, s := range m.ProjectList.Projects {
+		if len(s.Version) > 0 { // There must be a version we can use
+			var version = s.Version
+			version = strings.Replace(version, "+incompatible", "", -1)
+			var purl = "pkg:" + convertGopkgNameToPurl(s.Name) + "@" + version
+			purls = append(purls, purl)
+		}
+	}
+	return purls
+}
+
 func (m Mod) CheckExistenceOfManifest() bool {
 	if _, err := os.Stat(m.GoSumPath); os.IsNotExist(err) {
 		customerrors.Check(err, fmt.Sprint("No go.sum found at path: "+m.GoSumPath))
diff --git a/types/types.go b/types/types.go
index 3c84391..895f56d 100644
--- a/types/types.go
+++ b/types/types.go
@@ -14,6 +14,7 @@
 package types
 
 import (
+	"encoding/xml"
 	"fmt"
 	"strings"
 
@@ -91,3 +92,73 @@ func (cve *CveListFlag) Set(value string) error {
 
 	return nil
 }
+
+// IQ Types
+
+// StatusURLResult is a struct to let the consumer know what the response from Nexus IQ Server was
+type StatusURLResult struct {
+	PolicyAction  string `json:"policyAction"`
+	ReportHTMLURL string `json:"reportHtmlUrl"`
+	IsError       bool   `json:"isError"`
+	ErrorMessage  string `json:"errorMessage"`
+}
+
+// CycloneDX Types
+
+// Sbom is a struct to begin assembling a minimal SBOM
+type Sbom struct {
+	XMLName    xml.Name   `xml:"bom"`
+	Xmlns      string     `xml:"xmlns,attr"`
+	XMLNSV     string     `xml:"xmlns:v,attr"`
+	Version    string     `xml:"version,attr"`
+	Components Components `xml:"components"`
+}
+
+// Components is a struct to list the components in a SBOM
+type Components struct {
+	Component []Component `xml:"component"`
+}
+
+// Component is a struct to list the properties of a component in a SBOM
+type Component struct {
+	Type            string          `xml:"type,attr"`
+	BomRef          string          `xml:"bom-ref,attr"`
+	Name            string          `xml:"name"`
+	Version         string          `xml:"version"`
+	Purl            string          `xml:"purl"`
+	Vulnerabilities Vulnerabilities `xml:"v:vulnerabilities,omitempty"`
+}
+
+type Vulnerabilities struct {
+	Vulnerability []SbomVulnerability `xml:"v:vulnerability,omitempty"`
+}
+
+type SbomVulnerability struct {
+	Ref         string    `xml:"ref,attr,omitempty"`
+	ID          string    `xml:"v:id"`
+	Source      Source    `xml:"v:source"`
+	Ratings     []Ratings `xml:"v:ratings"`
+	Description string    `xml:"v:description"`
+}
+
+type Ratings struct {
+	Rating Rating `xml:"v:rating"`
+}
+
+type Rating struct {
+	Score    Score  `xml:"v:score,omitempty"`
+	Severity string `xml:"v:severity,omitempty"`
+	Method   string `xml:"v:method,omitempty"`
+	Vector   string `xml:"v:vector,omitempty"`
+}
+
+type Score struct {
+	Base           decimal.Decimal `xml:"v:base,omitempty"`
+	Impact         string          `xml:"v:impact,omitempty"`
+	Exploitability string          `xml:"v:exploitability,omitempty"`
+}
+
+type Source struct {
+	Name string `xml:"name,attr"`
+	URL  string `xml:"v:url"`
+}


Test patch:
diff --git a/configuration/parse_test.go b/configuration/parse_test.go
index af55e25..1727ad4 100644
--- a/configuration/parse_test.go
+++ b/configuration/parse_test.go
@@ -1,15 +1,29 @@
+// Copyright 2020 Sonatype 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 configuration
 
 import (
 	"bufio"
 	"errors"
 	"flag"
-	"github.com/sonatype-nexus-community/nancy/audit"
-	"github.com/sonatype-nexus-community/nancy/types"
-	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"os"
 	"testing"
+
+	"github.com/sonatype-nexus-community/nancy/audit"
+	"github.com/sonatype-nexus-community/nancy/types"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestConfigParse(t *testing.T) {
@@ -46,16 +60,16 @@ CVN-543`)
 		expectedConfig Configuration
 		expectedErr    error
 	}{
-		"defaults":                               {args: []string{"/tmp/go.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go.sum"}, expectedErr: nil},
-		"help":                                   {args: []string{"-help", "/tmp/go2.sum"}, expectedConfig: Configuration{Help: true, NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go2.sum"}, expectedErr: nil},
-		"no color":                               {args: []string{"-no-color", "/tmp/go2.sum"}, expectedConfig: Configuration{NoColor: true, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go2.sum"}, expectedErr: nil},
-		"quiet":                                  {args: []string{"-quiet", "/tmp/go3.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: true, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go3.sum"}, expectedErr: nil},
-		"version":                                {args: []string{"-version", "/tmp/go4.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: true, CveList: types.CveListFlag{}, Path: "/tmp/go4.sum"}, expectedErr: nil},
-		"exclude vulnerabilities":                {args: []string{"-exclude-vulnerability=CVE123,CVE988", "/tmp/go5.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{Cves: []string{"CVE123", "CVE988"}}, Path: "/tmp/go5.sum"}, expectedErr: nil},
-		"std in as input":                        {args: []string{}, expectedConfig: Configuration{UseStdIn: true}, expectedErr: nil},
-		"path but invalid arg":                   {args: []string{"-invalid", "/tmp/go6.sum"}, expectedConfig: Configuration{}, expectedErr: errors.New("flag provided but not defined: -invalid")},
+		"defaults":                {args: []string{"/tmp/go.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go.sum"}, expectedErr: nil},
+		"help":                    {args: []string{"-help", "/tmp/go2.sum"}, expectedConfig: Configuration{Help: true, NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go2.sum"}, expectedErr: nil},
+		"no color":                {args: []string{"-no-color", "/tmp/go2.sum"}, expectedConfig: Configuration{NoColor: true, Quiet: false, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go2.sum"}, expectedErr: nil},
+		"quiet":                   {args: []string{"-quiet", "/tmp/go3.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: true, Version: false, CveList: types.CveListFlag{}, Path: "/tmp/go3.sum"}, expectedErr: nil},
+		"version":                 {args: []string{"-version", "/tmp/go4.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: true, CveList: types.CveListFlag{}, Path: "/tmp/go4.sum"}, expectedErr: nil},
+		"exclude vulnerabilities": {args: []string{"-exclude-vulnerability=CVE123,CVE988", "/tmp/go5.sum"}, expectedConfig: Configuration{NoColor: false, Quiet: false, Version: false, CveList: types.CveListFlag{Cves: []string{"CVE123", "CVE988"}}, Path: "/tmp/go5.sum"}, expectedErr: nil},
+		"std in as input":         {args: []string{}, expectedConfig: Configuration{UseStdIn: true}, expectedErr: nil},
+		"path but invalid arg":    {args: []string{"-invalid", "/tmp/go6.sum"}, expectedConfig: Configuration{}, expectedErr: errors.New("flag provided but not defined: -invalid")},
 		"exclude vulnerabilities when has comments":                                  {args: []string{"-exclude-vulnerability-file=" + commentedFile.Name(), "/tmp/go14.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{Cves: []string{"CVN-111", "CVN-123", "CVN-543"}}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go14.sum"}, expectedErr: nil},
-		"exclude vulnerabilities with sane file": {args: []string{"-exclude-vulnerability-file=" + file.Name(), "/tmp/go7.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{Cves: []string{"CVF-000", "CVF-123", "CVF-9999"}}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go7.sum"}, expectedErr: nil},
+		"exclude vulnerabilities with sane file":                                     {args: []string{"-exclude-vulnerability-file=" + file.Name(), "/tmp/go7.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{Cves: []string{"CVF-000", "CVF-123", "CVF-9999"}}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go7.sum"}, expectedErr: nil},
 		"exclude vulnerabilities when file empty":                                    {args: []string{"-exclude-vulnerability-file=" + emptyFile.Name(), "/tmp/go8.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go8.sum"}, expectedErr: nil},
 		"exclude vulnerabilities when file has tons of newlines":                     {args: []string{"-exclude-vulnerability-file=" + lotsOfRandomNewlinesFile.Name(), "/tmp/go9.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{Cves: []string{"CVN-111", "CVN-123", "CVN-543"}}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go9.sum"}, expectedErr: nil},
 		"exclude vulnerabilities are combined with file and args values":             {args: []string{"-exclude-vulnerability=CVE123,CVE988", "-exclude-vulnerability-file=" + lotsOfRandomNewlinesFile.Name(), "/tmp/go10.sum"}, expectedConfig: Configuration{CveList: types.CveListFlag{Cves: []string{"CVE123", "CVE988", "CVN-111", "CVN-123", "CVN-543"}}, Formatter: &audit.AuditLogTextFormatter{}, Path: "/tmp/go10.sum"}, expectedErr: nil},
@@ -89,6 +103,31 @@ CVN-543`)
 	}
 }
 
+func TestConfigParseIQ(t *testing.T) {
+	tests := map[string]struct {
+		args           []string
+		expectedConfig IqConfiguration
+		expectedErr    error
+	}{
+		"defaults":                {args: []string{"iq"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "admin", Token: "admin123", Stage: "develop", Server: "http://localhost:8070", MaxRetries: 300}, expectedErr: nil},
+		"user token non defaults": {args: []string{"iq", "-user", "nonadmin", "-token", "admin1234"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "nonadmin", Token: "admin1234", Stage: "develop", Server: "http://localhost:8070", MaxRetries: 300}, expectedErr: nil},
+		"server-url non default":  {args: []string{"iq", "-server-url", "http://localhost:8090"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "admin", Token: "admin123", Stage: "develop", Server: "http://localhost:8090", MaxRetries: 300}, expectedErr: nil},
+		"max-retries non default": {args: []string{"iq", "-max-retries", "200"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "admin", Token: "admin123", Stage: "develop", Server: "http://localhost:8070", MaxRetries: 200}, expectedErr: nil},
+		"stage non default":       {args: []string{"iq", "-stage", "build"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "admin", Token: "admin123", Stage: "build", Server: "http://localhost:8070", MaxRetries: 300}, expectedErr: nil},
+		"specify application":     {args: []string{"iq", "-application", "testapp"}, expectedConfig: IqConfiguration{Help: false, Version: false, User: "admin", Token: "admin123", Stage: "develop", Server: "http://localhost:8070", MaxRetries: 300, Application: "testapp"}, expectedErr: nil},
+	}
+
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			setup()
+
+			actualConfig, actualErr := ParseIQ(test.args[1:])
+			assert.Equal(t, test.expectedErr, actualErr)
+			assert.Equal(t, test.expectedConfig, actualConfig)
+		})
+	}
+}
+
 func setupCVEExcludeFile(t *testing.T, fileContents string) (file *os.File) {
 	file, err := ioutil.TempFile("", "prefix")
 	if err != nil {
diff --git a/cyclonedx/cyclonedx_test.go b/cyclonedx/cyclonedx_test.go
new file mode 100644
index 0000000..d6e7784
--- /dev/null
+++ b/cyclonedx/cyclonedx_test.go
@@ -0,0 +1,129 @@
+// Copyright 2020 Sonatype 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 cyclonedx has definitions and functions for processing golang purls into a minimal CycloneDX 1.1 Sbom
+package cyclonedx
+
+import (
+	"testing"
+
+	"github.com/beevik/etree"
+	"github.com/package-url/packageurl-go"
+	"github.com/shopspring/decimal"
+	"github.com/sonatype-nexus-community/nancy/types"
+	assert "gopkg.in/go-playground/assert.v1"
+)
+
+func TestProcessPurlsIntoSBOM(t *testing.T) {
+	results := []types.Coordinate{}
+	crypto := types.Coordinate{
+		Coordinates:     "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+		Reference:       "https://ossindex.sonatype.org/component/pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+		Vulnerabilities: []types.Vulnerability{},
+	}
+	dec, _ := decimal.NewFromString("5.8")
+	crypto.Vulnerabilities = append(crypto.Vulnerabilities,
+		types.Vulnerability{
+			Id:          "CVE-123",
+			Title:       "CVE-123",
+			Description: "Hello I am a CVE",
+			CvssScore:   dec,
+			CvssVector:  "WhatsYourVectorVictor",
+			Cve:         "CVE-123",
+			Reference:   "http://www.google.com",
+		})
+	results = append(results, crypto)
+
+	results = append(results, types.Coordinate{
+		Coordinates:     "pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+		Reference:       "https://ossindex.sonatype.org/component/pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+		Vulnerabilities: []types.Vulnerability{},
+	})
+	result := ProcessPurlsIntoSBOM(results)
+
+	doc := etree.NewDocument()
+
+	if err := doc.ReadFromString(result); err != nil {
+		t.Error("Uh Oh")
+	}
+
+	root := doc.SelectElement("bom")
+	assert.Equal(t, root.Tag, "bom")
+	assert.Equal(t, root.Attr[0].Key, "xmlns")
+	assert.Equal(t, root.Attr[0].Value, "http://cyclonedx.org/schema/bom/1.1")
+	assert.Equal(t, root.Attr[1].Space, "xmlns")
+	assert.Equal(t, root.Attr[1].Key, "v")
+	assert.Equal(t, root.Attr[1].Value, "http://cyclonedx.org/schema/ext/vulnerability/1.0")
+	assert.Equal(t, root.Attr[2].Key, "version")
+	assert.Equal(t, root.Attr[2].Value, "1")
+	components := root.SelectElement("components")
+	for i, component := range components.SelectElements("component") {
+		coordinate, _ := packageurl.FromString(results[i].Coordinates)
+		assert.Equal(t, component.Tag, "component")
+		assert.Equal(t, component.Attr[0].Key, "type")
+		assert.Equal(t, component.Attr[0].Value, "library")
+		assert.Equal(t, component.Attr[1].Key, "bom-ref")
+		assert.Equal(t, component.Attr[1].Value, results[i].Coordinates)
+		name := component.SelectElement("name")
+		assert.Equal(t, name.Tag, "name")
+		assert.Equal(t, name.Text(), coordinate.Name)
+		version := component.SelectElement("version")
+		assert.Equal(t, version.Tag, "version")
+		assert.Equal(t, version.Text(), coordinate.Version)
+		purl := component.SelectElement("purl")
+		assert.Equal(t, purl.Tag, "purl")
+		assert.Equal(t, purl.Text(), coordinate.ToString())
+		if purl.Text() == "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2" {
+			vulnerabilities := component.SelectElement("vulnerabilities")
+			assert.Equal(t, vulnerabilities.Space, "v")
+			assert.Equal(t, vulnerabilities.Tag, "vulnerabilities")
+			for x, vulnerability := range vulnerabilities.SelectElements("vulnerability") {
+				assert.Equal(t, vulnerability.Tag, "vulnerability")
+				assert.Equal(t, vulnerability.Space, "v")
+				assert.Equal(t, vulnerability.Attr[0].Key, "ref")
+				assert.Equal(t, vulnerability.Attr[0].Value, coordinate.ToString())
+				id := vulnerability.SelectElement("id")
+				assert.Equal(t, id.Tag, "id")
+				assert.Equal(t, id.Space, "v")
+				assert.Equal(t, id.Text(), results[0].Vulnerabilities[x].Title)
+				source := vulnerability.SelectElement("source")
+				assert.Equal(t, source.Tag, "source")
+				assert.Equal(t, source.Space, "v")
+				assert.Equal(t, source.Attr[0].Key, "name")
+				assert.Equal(t, source.Attr[0].Value, "ossindex")
+				url := source.SelectElement("url")
+				assert.Equal(t, url.Tag, "url")
+				assert.Equal(t, url.Space, "v")
+				assert.Equal(t, url.Text(), results[0].Vulnerabilities[x].Reference)
+				ratings := vulnerability.SelectElement("ratings")
+				assert.Equal(t, ratings.Tag, "ratings")
+				assert.Equal(t, ratings.Space, "v")
+				rating := ratings.SelectElement("rating")
+				assert.Equal(t, rating.Tag, "rating")
+				assert.Equal(t, rating.Space, "v")
+				score := rating.SelectElement("score")
+				assert.Equal(t, score.Tag, "score")
+				assert.Equal(t, score.Space, "v")
+				base := score.SelectElement("base")
+				assert.Equal(t, base.Tag, "base")
+				assert.Equal(t, base.Space, "v")
+				assert.Equal(t, base.Text(), results[0].Vulnerabilities[x].CvssScore.String())
+				vector := rating.SelectElement("vector")
+				assert.Equal(t, vector.Tag, "vector")
+				assert.Equal(t, vector.Space, "v")
+				assert.Equal(t, vector.Text(), results[0].Vulnerabilities[x].CvssVector)
+			}
+		}
+	}
+}
diff --git a/iq/iq_test.go b/iq/iq_test.go
new file mode 100644
index 0000000..85101bf
--- /dev/null
+++ b/iq/iq_test.go
@@ -0,0 +1,173 @@
+// Copyright 2020 Sonatype 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 iq has definitions and functions for processing golang purls with Nexus IQ Server
+package iq
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/jarcoal/httpmock"
+	"github.com/sonatype-nexus-community/nancy/configuration"
+	"github.com/sonatype-nexus-community/nancy/types"
+	"github.com/stretchr/testify/assert"
+)
+
+const applicationsResponse = `{
+	"applications": [
+		{
+			"id": "4bb67dcfc86344e3a483832f8c496419",
+			"publicId": "testapp",
+			"name": "TestApp",
+			"organizationId": "bb41817bd3e2403a8a52fe8bcd8fe25a",
+			"contactUserName": "NewAppContact",
+			"applicationTags": [
+				{
+					"id": "9beee80c6fc148dfa51e8b0359ee4d4e",
+					"tagId": "cfea8fa79df64283bd64e5b6b624ba48",
+					"applicationId": "4bb67dcfc86344e3a483832f8c496419"
+				}
+			]
+		}
+	]
+}`
+
+const thirdPartyAPIResultJSON = `{
+		"statusUrl": "api/v2/scan/applications/4bb67dcfc86344e3a483832f8c496419/status/9cee2b6366fc4d328edc318eae46b2cb"
+}`
+
+const pollingResult = `{
+	"policyAction": "None",
+	"reportHtmlUrl": "http://sillyplace.com:8090/ui/links/application/test-app/report/95c4c14e",
+	"isError": false
+}`
+
+func setupIqConfiguration() (config configuration.IqConfiguration) {
+	config.Application = "testapp"
+	config.Server = "http://sillyplace.com:8090"
+	config.Stage = "develop"
+	config.User = "admin"
+	config.Token = "admin123"
+	return
+}
+
+func TestAuditPackages(t *testing.T) {
+	httpmock.Activate()
+	defer httpmock.DeactivateAndReset()
+
+	jsonCoordinates, _ := json.Marshal([]types.Coordinate{
+		{
+			Coordinates:     "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+		{
+			Coordinates:     "pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+	})
+
+	httpmock.RegisterResponder("POST", "https://ossindex.sonatype.org/api/v3/component-report",
+		httpmock.NewStringResponder(200, string(jsonCoordinates)))
+
+	httpmock.RegisterResponder("GET", "http://sillyplace.com:8090/api/v2/applications?publicId=testapp",
+		httpmock.NewStringResponder(200, applicationsResponse))
+
+	httpmock.RegisterResponder("POST", "http://sillyplace.com:8090/api/v2/scan/applications/4bb67dcfc86344e3a483832f8c496419/sources/nancy?stageId=develop",
+		httpmock.NewStringResponder(202, thirdPartyAPIResultJSON))
+
+	httpmock.RegisterResponder("GET", "http://sillyplace.com:8090/api/v2/scan/applications/4bb67dcfc86344e3a483832f8c496419/status/9cee2b6366fc4d328edc318eae46b2cb",
+		httpmock.NewStringResponder(200, pollingResult))
+
+	var purls []string
+	purls = append(purls, "pkg:golang/github.com/go-yaml/yaml@v2.2.2")
+	purls = append(purls, "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2")
+
+	result, _ := AuditPackages(purls, "testapp", setupIqConfiguration())
+
+	statusExpected := types.StatusURLResult{PolicyAction: "None", ReportHTMLURL: "http://sillyplace.com:8090/ui/links/application/test-app/report/95c4c14e", IsError: false}
+
+	assert.Equal(t, result, statusExpected)
+}
+
+func TestAuditPackagesIqDownOrUnreachable(t *testing.T) {
+	httpmock.Activate()
+	defer httpmock.DeactivateAndReset()
+
+	jsonCoordinates, _ := json.Marshal([]types.Coordinate{
+		{
+			Coordinates:     "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+		{
+			Coordinates:     "pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+	})
+
+	httpmock.RegisterResponder("POST", "https://ossindex.sonatype.org/api/v3/component-report",
+		httpmock.NewStringResponder(200, string(jsonCoordinates)))
+
+	httpmock.RegisterResponder("GET", "http://sillyplace.com:8090/api/v2/applications?publicId=testapp",
+		httpmock.NewBytesResponder(404, []byte("")))
+
+	var purls []string
+	purls = append(purls, "pkg:golang/github.com/go-yaml/yaml@v2.2.2")
+	purls = append(purls, "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2")
+
+	_, err := AuditPackages(purls, "testapp", setupIqConfiguration())
+	if err == nil {
+		t.Error("There is an error")
+	}
+}
+
+func TestAuditPackagesIqUpButBadThirdPartyAPIResponse(t *testing.T) {
+	httpmock.Activate()
+	defer httpmock.DeactivateAndReset()
+
+	jsonCoordinates, _ := json.Marshal([]types.Coordinate{
+		{
+			Coordinates:     "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+		{
+			Coordinates:     "pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Reference:       "https://ossindex.sonatype.org/component/pkg:golang/github.com/go-yaml/yaml@v2.2.2",
+			Vulnerabilities: []types.Vulnerability{},
+		},
+	})
+
+	httpmock.RegisterResponder("POST", "https://ossindex.sonatype.org/api/v3/component-report",
+		httpmock.NewStringResponder(200, string(jsonCoordinates)))
+
+	httpmock.RegisterResponder("GET", "http://sillyplace.com:8090/api/v2/applications?publicId=testapp",
+		httpmock.NewStringResponder(200, applicationsResponse))
+
+	httpmock.RegisterResponder("POST", "http://sillyplace.com:8090/api/v2/scan/applications/4bb67dcfc86344e3a483832f8c496419/sources/nancy?stageId=develop",
+		httpmock.NewBytesResponder(500, []byte("")))
+
+	var purls []string
+	purls = append(purls, "pkg:golang/github.com/go-yaml/yaml@v2.2.2")
+	purls = append(purls, "pkg:golang/golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2")
+
+	_, err := AuditPackages(purls, "testapp", setupIqConfiguration())
+	if err == nil {
+		t.Error("There is an error")
+	}
+}
diff --git a/ossindex/ossindex_test.go b/ossindex/ossindex_test.go
index d68f10e..ea1a1e8 100644
--- a/ossindex/ossindex_test.go
+++ b/ossindex/ossindex_test.go
@@ -17,9 +17,6 @@ package ossindex
 
 import (
 	"encoding/json"
-	"github.com/dgraph-io/badger"
-	"github.com/sonatype-nexus-community/nancy/types"
-	"github.com/stretchr/testify/assert"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -27,6 +24,10 @@ import (
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/dgraph-io/badger"
+	"github.com/sonatype-nexus-community/nancy/types"
+	"github.com/stretchr/testify/assert"
 )
 
 const purl = "pkg:github/BurntSushi/toml@0.3.1"