Skip to content

Commit

Permalink
⭐ Framework cmd (#1355)
Browse files Browse the repository at this point in the history
* feat: list frameworks base

* feat: set framework state commands

* feat: handle download framework

* feat: handle framework upload and better errors handling

* feat: handle list from local bundle

* fix: flags -> env handling

* copyright

* feat: retrive active only and handle --all flag

* tidy

* fix test: update test data - help message

* Update apps/cnspec/cmd/framework.go

Co-authored-by: Tim Smith <[email protected]>

* Update test/cli/testdata/cnspec.ct

Co-authored-by: Tim Smith <[email protected]>

* format list error message

---------

Co-authored-by: Tim Smith <[email protected]>
  • Loading branch information
slntopp and tas50 authored Jul 8, 2024
1 parent 005f560 commit b419bbc
Show file tree
Hide file tree
Showing 5 changed files with 403 additions and 3 deletions.
300 changes: 300 additions & 0 deletions apps/cnspec/cmd/framework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package cmd

import (
"context"
"fmt"
"os"
"strings"

"github.com/muesli/termenv"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.mondoo.com/cnquery/v11/cli/config"
"go.mondoo.com/cnquery/v11/cli/theme"
"go.mondoo.com/cnspec/v11/policy"
cnspec_upstream "go.mondoo.com/cnspec/v11/upstream"
mondoogql "go.mondoo.com/mondoo-go"
"k8s.io/utils/ptr"
)

const (
FrameworkMrnPrefix = "//policy.api.mondoo.app/frameworks"
)

func init() {
rootCmd.AddCommand(frameworkCmd)

// list
frameworkListCmd.Flags().StringP("file", "f", "", "a local bundle file")
frameworkListCmd.Flags().BoolP("all", "a", false, "list all frameworks, not only the active ones (applicable only for upstream)")
frameworkCmd.AddCommand(frameworkListCmd)

// preview
frameworkCmd.AddCommand(frameworkPreviewCmd)
// active
frameworkCmd.AddCommand(frameworkActiveCmd)
// download
frameworkDownloadCmd.Flags().StringP("file", "f", "", "output file")
frameworkCmd.AddCommand(frameworkDownloadCmd)
// upload
frameworkUploadCmd.Flags().StringP("file", "f", "", "input file")
frameworkCmd.AddCommand(frameworkUploadCmd)
}

var frameworkCmd = &cobra.Command{
Use: "framework",
Short: "Manage local and Mondoo Platform hosted compliance frameworks",
}

var frameworkListCmd = &cobra.Command{
Use: "list",
Short: "List available compliance frameworks",
Args: cobra.MaximumNArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
return err
}
if err := viper.BindPFlag("all", cmd.Flags().Lookup("all")); err != nil {
return err
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
bundleFile := viper.GetString("file")
var frameworks []*cnspec_upstream.UpstreamFramework

if bundleFile != "" {
policyBundle, err := policy.DefaultBundleLoader().BundleFromPaths(bundleFile)
if err != nil {
return err
}
for _, f := range policyBundle.Frameworks {
frameworks = append(frameworks, &cnspec_upstream.UpstreamFramework{Framework: *f})
}
} else {
opts, err := config.Read()
if err != nil {
return err
}
config.DisplayUsedConfig()

mondooClient, err := getGqlClient(opts)
if err != nil {
return err
}

state := ptr.To(mondoogql.ComplianceFrameworkStateActive)
if viper.GetBool("all") {
state = nil
}

frameworks, err = cnspec_upstream.ListFrameworks(context.Background(), mondooClient, opts.GetParentMrn(), state)
if err != nil {
log.Error().Msgf("failed to list compliance frameworks: %s", err)
os.Exit(1)
return err
}
}

for _, framework := range frameworks {
extraInfo := []string{}
if framework.State == mondoogql.ComplianceFrameworkStateActive {
extraInfo = append(extraInfo, theme.DefaultTheme.Success("active"))
} else if framework.State == mondoogql.ComplianceFrameworkState("") {
extraInfo = append(extraInfo, theme.DefaultTheme.Disabled("local"))
}

extraInfoStr := ""
if len(extraInfo) > 0 {
extraInfoStr = " (" + strings.Join(extraInfo, ", ") + ")"
}
fmt.Println(framework.Name + " " + framework.Version + extraInfoStr)
id := framework.Uid
if framework.Mrn != "" {
id = framework.Mrn
}
fmt.Println(termenv.String(" " + id).Foreground(theme.DefaultTheme.Colors.Disabled))
}
return nil
},
}

var frameworkDownloadCmd = &cobra.Command{
Use: "download [mrn]",
Short: "Download a compliance framework",
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
return err
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
outputFile := viper.GetString("file")
if outputFile == "" {
log.Error().Msgf("output file is required")
os.Exit(1)
}

opts, err := config.Read()
if err != nil {
log.Error().Msgf("failed to get config: %s", err)
os.Exit(1)
}
config.DisplayUsedConfig()

mondooClient, err := getGqlClient(opts)
if err != nil {
return err
}

frameworkMrn := args[0]
if !strings.HasPrefix(frameworkMrn, PolicyMrnPrefix) {
frameworkMrn = FrameworkMrnPrefix + "/" + frameworkMrn
}

data, err := cnspec_upstream.DownloadFramework(context.Background(), mondooClient, frameworkMrn, opts.GetParentMrn())
if err != nil {
log.Error().Msgf("failed to download compliance framework: %s", err)
os.Exit(1)
}

if err := os.WriteFile(outputFile, []byte(data), 0o644); err != nil {
log.Error().Msgf("failed to store framework: %s", err)
os.Exit(1)
}
log.Info().Msg(theme.DefaultTheme.Success("successfully downloaded to ", outputFile))

return nil
},
}

var frameworkUploadCmd = &cobra.Command{
Use: "upload [file]",
Short: "Upload a compliance framework",
Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
return err
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
inputFile := viper.GetString("file")
if inputFile == "" {
log.Error().Msgf("output file is required")
os.Exit(1)
}

opts, err := config.Read()
if err != nil {
log.Error().Msgf("failed to get config: %s", err)
os.Exit(1)
}
config.DisplayUsedConfig()

mondooClient, err := getGqlClient(opts)
if err != nil {
return err
}

data, err := os.ReadFile(inputFile)
if err != nil {
log.Error().Msgf("failed to read file: %s", err)
os.Exit(1)
}

ok, err := cnspec_upstream.UploadFramework(context.Background(), mondooClient, data, opts.GetParentMrn())
if err != nil {
log.Error().Msgf("failed to upload compliance framework: %s", err)
os.Exit(1)
}
if !ok {
log.Error().Msgf("failed to upload compliance framework")
os.Exit(1)
}
log.Info().Msg(theme.DefaultTheme.Success("successfully uploaded compliance framework"))
return nil
},
}

var frameworkPreviewCmd = &cobra.Command{
Use: "preview [mrn]",
Short: "Change a framework status to preview",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts, err := config.Read()
if err != nil {
return err
}
config.DisplayUsedConfig()

mondooClient, err := getGqlClient(opts)
if err != nil {
return err
}

frameworkMrn := args[0]
if !strings.HasPrefix(frameworkMrn, PolicyMrnPrefix) {
frameworkMrn = FrameworkMrnPrefix + "/" + frameworkMrn
}
ok, err := cnspec_upstream.MutateFrameworkState(
context.Background(), mondooClient, frameworkMrn,
opts.GetParentMrn(), mondoogql.ComplianceFrameworkMutationActionPreview,
)
if err != nil {
log.Error().Msgf("failed to set compliance framework to preview state in space: %s", err)
os.Exit(1)
}
if !ok {
log.Error().Msgf("failed to set compliance framework to preview state in space")
os.Exit(1)
}
log.Info().Msg(theme.DefaultTheme.Success("successfully set compliance framework to preview state in space"))

return nil
},
}

var frameworkActiveCmd = &cobra.Command{
Use: "active [mrn]",
Short: "Change a framework status to active",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts, err := config.Read()
if err != nil {
return err
}
config.DisplayUsedConfig()

mondooClient, err := getGqlClient(opts)
if err != nil {
return err
}

frameworkMrn := args[0]
if !strings.HasPrefix(frameworkMrn, PolicyMrnPrefix) {
frameworkMrn = FrameworkMrnPrefix + "/" + frameworkMrn
}

ok, err := cnspec_upstream.MutateFrameworkState(
context.Background(), mondooClient, frameworkMrn,
opts.GetParentMrn(), mondoogql.ComplianceFrameworkMutationActionPreview,
)
if err != nil {
log.Error().Msgf("failed to set compliance framework to active state in space: %s", err)
os.Exit(1)
}
if !ok {
log.Error().Msgf("failed to set compliance framework to preview state in space")
os.Exit(1)
}
log.Info().Msg(theme.DefaultTheme.Success("successfully set compliance framework to active state in space"))

return nil
},
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.mondoo.com/cnquery/v11 v11.11.1-0.20240703150909-053a52bebd81
go.mondoo.com/mondoo-go v0.0.0-20240611114249-2c3b9b20e67a
go.mondoo.com/mondoo-go v0.0.0-20240704105318-097765f8523d
go.mondoo.com/ranger-rpc v0.6.1
go.opentelemetry.io/otel v1.28.0
gocloud.dev v0.37.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1233,8 +1233,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.mondoo.com/cnquery/v11 v11.11.1-0.20240703150909-053a52bebd81 h1:ksvbrg63acRH7UOcZEf+8x+PSYGpAQO3ptDdxzJE3CA=
go.mondoo.com/cnquery/v11 v11.11.1-0.20240703150909-053a52bebd81/go.mod h1:fwsl8ivZwHW/GDEevxir1cQF864/gJ0rmjVtAigQuS4=
go.mondoo.com/mondoo-go v0.0.0-20240611114249-2c3b9b20e67a h1:+EQW5uXRyUyeiyZnTy2Jc371PTynJm5OruUWt3SqiT4=
go.mondoo.com/mondoo-go v0.0.0-20240611114249-2c3b9b20e67a/go.mod h1:4032UBD0ph9LyhXq5OQmmxkJv37HdAGi34YLWbhnMDA=
go.mondoo.com/mondoo-go v0.0.0-20240704105318-097765f8523d h1:Jr55zA89Yf70egaA1wZXUUJGnUc+O5HkTGBBKjU9poI=
go.mondoo.com/mondoo-go v0.0.0-20240704105318-097765f8523d/go.mod h1:4032UBD0ph9LyhXq5OQmmxkJv37HdAGi34YLWbhnMDA=
go.mondoo.com/ranger-rpc v0.6.1 h1:aOMsKD7zwQBGmt998fdAkk/G+XWk5+sjsi/XPVUSCJw=
go.mondoo.com/ranger-rpc v0.6.1/go.mod h1:sbv789sxgfu1vpJzmD7j4/FgjFB41GDWsM0d6fNsu68=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
Expand Down
1 change: 1 addition & 0 deletions test/cli/testdata/cnspec.ct
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Usage:

Available Commands:
completion Generate the autocompletion script for the specified shell
framework Manage local and Mondoo Platform hosted compliance frameworks
help Help about any command
login Register with Mondoo Platform
logout Log out from Mondoo Platform
Expand Down
Loading

0 comments on commit b419bbc

Please sign in to comment.