mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
feat(compliance): Add JSON output option
This adds a new --json flag to the compliance subcommand, which will output the compliance test results as minified JSON to stdout. Also a few other small additions: - Exit 1 if compliance tests fail - Use random port for test server using freeport library (added) Signed-off-by: Josh Dolitsky <393494+jdolitsky@users.noreply.github.com>
This commit is contained in:
parent
7de21820d7
commit
271b916a26
9 changed files with 133 additions and 27 deletions
|
@ -56,7 +56,7 @@ Examples of config files are available in [examples/](examples/) dir.
|
||||||
# Compliance checks
|
# Compliance checks
|
||||||
|
|
||||||
```
|
```
|
||||||
bin/zot compliance -H hostIP -P port [-V "all"]
|
bin/zot compliance -H hostIP -P port [-V "all"] [--json]
|
||||||
```
|
```
|
||||||
|
|
||||||
# Ecosystem
|
# Ecosystem
|
||||||
|
|
|
@ -1072,6 +1072,13 @@ go_repository(
|
||||||
version = "v0.0.0-20160315200505-970db520ece7",
|
version = "v0.0.0-20160315200505-970db520ece7",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_phayes_freeport",
|
||||||
|
importpath = "github.com/phayes/freeport",
|
||||||
|
sum = "h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=",
|
||||||
|
version = "v0.0.0-20180830031419-95f893ade6f2",
|
||||||
|
)
|
||||||
|
|
||||||
go_repository(
|
go_repository(
|
||||||
name = "com_github_pquerna_otp",
|
name = "com_github_pquerna_otp",
|
||||||
importpath = "github.com/pquerna/otp",
|
importpath = "github.com/pquerna/otp",
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
||||||
github.com/opencontainers/distribution-spec v1.0.0-rc0
|
github.com/opencontainers/distribution-spec v1.0.0-rc0
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||||
github.com/opencontainers/image-spec v1.0.1
|
github.com/opencontainers/image-spec v1.0.1
|
||||||
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||||
github.com/rs/zerolog v1.17.2
|
github.com/rs/zerolog v1.17.2
|
||||||
github.com/smartystreets/goconvey v1.6.4
|
github.com/smartystreets/goconvey v1.6.4
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -134,6 +134,8 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anuvu/zot/errors"
|
"github.com/anuvu/zot/errors"
|
||||||
|
@ -102,6 +103,9 @@ func NewRootCmd() *cobra.Command {
|
||||||
default:
|
default:
|
||||||
v1_0_0.CheckWorkflows(t, complianceConfig)
|
v1_0_0.CheckWorkflows(t, complianceConfig)
|
||||||
}
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +125,8 @@ func NewRootCmd() *cobra.Command {
|
||||||
|
|
||||||
complianceCmd.Flags().StringVarP(&complianceConfig.Version, "version", "V", "all",
|
complianceCmd.Flags().StringVarP(&complianceConfig.Version, "version", "V", "all",
|
||||||
"OCI dist-spec version to check")
|
"OCI dist-spec version to check")
|
||||||
|
complianceCmd.Flags().BoolVarP(&complianceConfig.OutputJSON, "json", "j", false,
|
||||||
|
"output test results as JSON")
|
||||||
|
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "zot",
|
Use: "zot",
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package compliance
|
package compliance
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address string
|
Address string
|
||||||
Port string
|
Port string
|
||||||
Version string
|
Version string
|
||||||
|
OutputJSON bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
|
|
|
@ -11,6 +11,7 @@ go_library(
|
||||||
"@com_github_opencontainers_go_digest//:go_default_library",
|
"@com_github_opencontainers_go_digest//:go_default_library",
|
||||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||||
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
||||||
|
"@com_github_smartystreets_goconvey//convey/reporting:go_default_library",
|
||||||
"@in_gopkg_resty_v1//:go_default_library",
|
"@in_gopkg_resty_v1//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -23,6 +24,7 @@ go_test(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/compliance:go_default_library",
|
"//pkg/compliance:go_default_library",
|
||||||
|
"@com_github_phayes_freeport//:go_default_library",
|
||||||
"@in_gopkg_resty_v1//:go_default_library",
|
"@in_gopkg_resty_v1//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anuvu/zot/pkg/api"
|
"github.com/anuvu/zot/pkg/api"
|
||||||
|
@ -12,6 +15,7 @@ import (
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/smartystreets/goconvey/convey/reporting"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,6 +23,12 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
||||||
if config == nil || config.Address == "" || config.Port == "" {
|
if config == nil || config.Address == "" || config.Port == "" {
|
||||||
panic("insufficient config")
|
panic("insufficient config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.OutputJSON {
|
||||||
|
outputJSONEnter()
|
||||||
|
defer outputJSONExit()
|
||||||
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port)
|
baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port)
|
||||||
|
|
||||||
fmt.Println("------------------------------")
|
fmt.Println("------------------------------")
|
||||||
|
@ -451,3 +461,60 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
old *os.File
|
||||||
|
r *os.File
|
||||||
|
w *os.File
|
||||||
|
outC chan string
|
||||||
|
)
|
||||||
|
|
||||||
|
func outputJSONEnter() {
|
||||||
|
// this env var instructs goconvey to output results to JSON (stdout)
|
||||||
|
os.Setenv("GOCONVEY_REPORTER", "json")
|
||||||
|
|
||||||
|
// stdout capture copied from: https://stackoverflow.com/a/29339052
|
||||||
|
old = os.Stdout
|
||||||
|
// keep backup of the real stdout
|
||||||
|
r, w, _ = os.Pipe()
|
||||||
|
outC = make(chan string)
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
// copy the output in a separate goroutine so printing can't block indefinitely
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, r)
|
||||||
|
outC <- buf.String()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputJSONExit() {
|
||||||
|
// back to normal state
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = old // restoring the real stdout
|
||||||
|
out := <-outC
|
||||||
|
|
||||||
|
// The output of JSON is combined with regular output, so we look for the
|
||||||
|
// first occurrence of the "{" character and take everything after that
|
||||||
|
rawJSON := "[{" + strings.Join(strings.Split(out, "{")[1:], "{")
|
||||||
|
rawJSON = strings.Replace(rawJSON, reporting.OpenJson, "", 1)
|
||||||
|
rawJSON = strings.Replace(rawJSON, reporting.CloseJson, "", 1)
|
||||||
|
tmp := strings.Split(rawJSON, ",")
|
||||||
|
rawJSON = strings.Join(tmp[0:len(tmp)-1], ",") + "]"
|
||||||
|
|
||||||
|
rawJSONMinified := validateMinifyRawJSON(rawJSON)
|
||||||
|
fmt.Println(rawJSONMinified)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMinifyRawJSON(rawJSON string) string {
|
||||||
|
var j interface{}
|
||||||
|
err := json.Unmarshal([]byte(rawJSON), &j)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rawJSONBytesMinified, err := json.Marshal(j)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(rawJSONBytesMinified)
|
||||||
|
}
|
||||||
|
|
|
@ -1,63 +1,83 @@
|
||||||
|
//nolint (dupl)
|
||||||
package v1_0_0_test
|
package v1_0_0_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anuvu/zot/pkg/api"
|
"github.com/anuvu/zot/pkg/api"
|
||||||
"github.com/anuvu/zot/pkg/compliance"
|
"github.com/anuvu/zot/pkg/compliance"
|
||||||
"github.com/anuvu/zot/pkg/compliance/v1_0_0"
|
"github.com/anuvu/zot/pkg/compliance/v1_0_0"
|
||||||
|
"github.com/phayes/freeport"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
Address = "127.0.0.1"
|
listenAddress = "127.0.0.1"
|
||||||
Port = "8080"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWorkflows(t *testing.T) {
|
func TestWorkflows(t *testing.T) {
|
||||||
v1_0_0.CheckWorkflows(t, &compliance.Config{Address: Address, Port: Port})
|
ctrl, randomPort := startServer()
|
||||||
|
defer stopServer(ctrl)
|
||||||
|
v1_0_0.CheckWorkflows(t, &compliance.Config{
|
||||||
|
Address: listenAddress,
|
||||||
|
Port: randomPort,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestWorkflowsOutputJSON(t *testing.T) {
|
||||||
config := api.NewConfig()
|
ctrl, randomPort := startServer()
|
||||||
config.HTTP.Address = Address
|
defer stopServer(ctrl)
|
||||||
config.HTTP.Port = Port
|
v1_0_0.CheckWorkflows(t, &compliance.Config{
|
||||||
c := api.NewController(config)
|
Address: listenAddress,
|
||||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
Port: randomPort,
|
||||||
|
OutputJSON: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// start local server on random open port
|
||||||
|
func startServer() (*api.Controller, string) {
|
||||||
|
portInt, err := freeport.GetFreePort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
//defer os.RemoveAll(dir)
|
randomPort := fmt.Sprintf("%d", portInt)
|
||||||
c.Config.Storage.RootDirectory = dir
|
fmt.Println(randomPort)
|
||||||
|
|
||||||
|
config := api.NewConfig()
|
||||||
|
config.HTTP.Address = listenAddress
|
||||||
|
config.HTTP.Port = randomPort
|
||||||
|
ctrl := api.NewController(config)
|
||||||
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//defer os.RemoveAll(dir)
|
||||||
|
ctrl.Config.Storage.RootDirectory = dir
|
||||||
go func() {
|
go func() {
|
||||||
// this blocks
|
// this blocks
|
||||||
if err := c.Run(); err != nil {
|
if err := ctrl.Run(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
BaseURL := fmt.Sprintf("http://%s:%s", Address, Port)
|
baseURL := fmt.Sprintf("http://%s:%s", listenAddress, randomPort)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// poll until ready
|
// poll until ready
|
||||||
resp, _ := resty.R().Get(BaseURL)
|
resp, _ := resty.R().Get(baseURL)
|
||||||
if resp.StatusCode() == 404 {
|
if resp.StatusCode() == 404 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
status := m.Run()
|
return ctrl, randomPort
|
||||||
ctx := context.Background()
|
}
|
||||||
_ = c.Server.Shutdown(ctx)
|
|
||||||
|
func stopServer(ctrl *api.Controller) {
|
||||||
os.Exit(status)
|
ctrl.Server.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue