diff --git a/cmd/vmclarity-cli/asset/asset_create.go b/cmd/vmclarity-cli/asset/asset_create.go new file mode 100644 index 000000000..4f2c6d66b --- /dev/null +++ b/cmd/vmclarity-cli/asset/asset_create.go @@ -0,0 +1,151 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// 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 asset + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + + "github.com/openclarity/vmclarity/cmd/vmclarity-cli/root" + cliutils "github.com/openclarity/vmclarity/pkg/cli/utils" + + "github.com/openclarity/vmclarity/api/models" + "github.com/openclarity/vmclarity/pkg/shared/backendclient" +) + +// assetCreateCmd represents the standalone command. +var assetCreateCmd = &cobra.Command{ + Use: "asset-create", + Short: "Create asset", + Long: `It creates asset. It's useful in the CI/CD mode without VMClarity orchestration`, + Run: func(cmd *cobra.Command, args []string) { + root.Logger.Infof("Creating asset...") + filename, err := cmd.Flags().GetString("file") + if err != nil { + root.Logger.Fatalf("Unable to get asset json file name: %v", err) + } + server, err := cmd.Flags().GetString("server") + if err != nil { + root.Logger.Fatalf("Unable to get VMClarity server address: %v", err) + } + assetType, err := getAssetFromJSONFile(filename) + if err != nil { + root.Logger.Fatalf("Failed to get asset from json file: %v", err) + } + updateIfExists, err := cmd.Flags().GetBool("update-if-exists") + if err != nil { + root.Logger.Fatalf("Unable to get update-if-exists flag vaule: %v", err) + } + jsonPath, err := cmd.Flags().GetString("jsonpath") + if err != nil { + root.Logger.Fatalf("Unable to get jsonpath: %v", err) + } + + _, err = assetType.ValueByDiscriminator() + if err != nil { + root.Logger.Fatalf("Failed to determine asset type: %v", err) + } + + asset, err := createAsset(context.TODO(), assetType, server, updateIfExists) + if err != nil { + root.Logger.Fatalf("Failed to create asset: %v", err) + } + + if err := cliutils.PrintJSONData(asset, jsonPath); err != nil { + root.Logger.Fatalf("Failed to print jsonpath: %v", err) + } + }, +} + +func init() { + root.RootCmd.AddCommand(assetCreateCmd) + + assetCreateCmd.Flags().String("file", "", "asset json filename") + assetCreateCmd.Flags().String("server", "", "VMClarity server to create asset to, for example: http://localhost:9999/api") + assetCreateCmd.Flags().Bool("update-if-exists", false, "the asset will be updated the asset if it exists") + assetCreateCmd.Flags().String("jsonpath", "", "print selected value of asset") + if err := assetCreateCmd.MarkFlagRequired("file"); err != nil { + root.Logger.Fatalf("Failed to mark file flag as required: %v", err) + } + if err := assetCreateCmd.MarkFlagRequired("server"); err != nil { + root.Logger.Fatalf("Failed to mark server flag as required: %v", err) + } +} + +func getAssetFromJSONFile(filename string) (*models.AssetType, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %v", err) + } + defer file.Close() + + // get the file size + stat, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("failed to get file stat: %v", err) + } + + // read the file + bs := make([]byte, stat.Size()) + _, err = file.Read(bs) + if err != nil { + return nil, fmt.Errorf("failed to read file: %v", err) + } + + assetType := &models.AssetType{} + if err := assetType.UnmarshalJSON(bs); err != nil { + return nil, fmt.Errorf("failed to unmarshal asset into AssetType %v", err) + } + + return assetType, nil +} + +func createAsset(ctx context.Context, assetType *models.AssetType, server string, updateIfExists bool) (*models.Asset, error) { + client, err := backendclient.Create(server) + if err != nil { + return nil, fmt.Errorf("failed to create VMClarity API client: %w", err) + } + + creationTime := time.Now() + assetData := models.Asset{ + AssetInfo: assetType, + LastSeen: &creationTime, + FirstSeen: &creationTime, + } + asset, err := client.PostAsset(ctx, assetData) + if err == nil { + return asset, nil + } + var conflictError backendclient.AssetConflictError + // As we got a conflict it means there is an existing asset + // which matches the unique properties of this asset, in this + // case if the update-if-exists flag is set we'll patch the just AssetInfo and FirstSeen instead. + if !errors.As(err, &conflictError) || !updateIfExists { + return nil, fmt.Errorf("failed to post asset: %v", err) + } + assetData.FirstSeen = nil + err = client.PatchAsset(ctx, assetData, *conflictError.ConflictingAsset.Id) + if err != nil { + return nil, fmt.Errorf("failed to patch asset: %v", err) + } + + return conflictError.ConflictingAsset, nil +} diff --git a/cmd/vmclarity-cli/asset/asset_scan_create.go b/cmd/vmclarity-cli/asset/asset_scan_create.go new file mode 100644 index 000000000..0b026dc99 --- /dev/null +++ b/cmd/vmclarity-cli/asset/asset_scan_create.go @@ -0,0 +1,114 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// 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 asset + +import ( + "context" + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/openclarity/vmclarity/cmd/vmclarity-cli/root" + cliutils "github.com/openclarity/vmclarity/pkg/cli/utils" + + "github.com/openclarity/vmclarity/api/models" + "github.com/openclarity/vmclarity/pkg/shared/backendclient" + "github.com/openclarity/vmclarity/pkg/shared/utils" +) + +// assetScanCreateCmd represents the standalone command. +var assetScanCreateCmd = &cobra.Command{ + Use: "asset-scan-create", + Short: "Create asset scan", + Long: `It creates asset scan. It's useful in the CI/CD mode without VMClarity orchestration`, + Run: func(cmd *cobra.Command, args []string) { + root.Logger.Infof("asset-scan-create called") + assetID, err := cmd.Flags().GetString("asset-id") + if err != nil { + root.Logger.Fatalf("Unable to get asset id: %v", err) + } + server, err := cmd.Flags().GetString("server") + if err != nil { + root.Logger.Fatalf("Unable to get VMClarity server address: %v", err) + } + jsonPath, err := cmd.Flags().GetString("jsonpath") + if err != nil { + root.Logger.Fatalf("Unable to get jsonpath: %v", err) + } + + assetScan, err := createAssetScan(context.TODO(), server, assetID) + if err != nil { + root.Logger.Fatalf("Failed to create asset scan: %v", err) + } + + if err := cliutils.PrintJSONData(assetScan, jsonPath); err != nil { + root.Logger.Fatalf("Failed to print jsonpath: %v", err) + } + }, +} + +func init() { + root.RootCmd.AddCommand(assetScanCreateCmd) + assetScanCreateCmd.Flags().String("server", "", "VMClarity server to create asset to, for example: http://localhost:9999/api") + assetScanCreateCmd.Flags().String("asset-id", "", "Asset ID for asset scan") + assetScanCreateCmd.Flags().String("jsonpath", "", "print selected value of asset scan") + if err := assetScanCreateCmd.MarkFlagRequired("server"); err != nil { + root.Logger.Fatalf("Failed to mark server flag as required: %v", err) + } + if err := assetScanCreateCmd.MarkFlagRequired("asset-id"); err != nil { + root.Logger.Fatalf("Failed to mark asset-id flag as required: %v", err) + } +} + +func createAssetScan(ctx context.Context, server, assetID string) (*models.AssetScan, error) { + client, err := backendclient.Create(server) + if err != nil { + return nil, fmt.Errorf("failed to create VMClarity API client: %w", err) + } + + asset, err := client.GetAsset(ctx, assetID, models.GetAssetsAssetIDParams{}) + if err != nil { + return nil, fmt.Errorf("failed to get asset %s: %w", assetID, err) + } + assetScanData := createEmptyAssetScanForAsset(asset) + + assetScan, err := client.PostAssetScan(ctx, assetScanData) + if err != nil { + var conErr backendclient.AssetScanConflictError + if errors.As(err, &conErr) { + assetScanID := *conErr.ConflictingAssetScan.Id + root.Logger.WithField("AssetScanID", assetScanID).Debug("AssetScan already exist.") + return conErr.ConflictingAssetScan, nil + } + return nil, fmt.Errorf("failed to post AssetScan to backend API: %w", err) + } + + return assetScan, nil +} + +func createEmptyAssetScanForAsset(asset models.Asset) models.AssetScan { + return models.AssetScan{ + Asset: &models.AssetRelationship{ + Id: *asset.Id, + }, + Status: &models.AssetScanStatus{ + General: &models.AssetScanState{ + State: utils.PointerTo(models.AssetScanStateStateReadyToScan), + }, + }, + } +} diff --git a/cmd/vmclarity-cli/root/root.go b/cmd/vmclarity-cli/root/root.go index a14ae0959..76835840f 100644 --- a/cmd/vmclarity-cli/root/root.go +++ b/cmd/vmclarity-cli/root/root.go @@ -16,228 +16,40 @@ package root import ( - "context" - "errors" - "fmt" "os" - "time" - "github.com/ghodss/yaml" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/openclarity/vmclarity/pkg/cli" - "github.com/openclarity/vmclarity/pkg/cli/presenter" - "github.com/openclarity/vmclarity/pkg/cli/state" - "github.com/openclarity/vmclarity/pkg/shared/backendclient" - "github.com/openclarity/vmclarity/pkg/shared/families" "github.com/openclarity/vmclarity/pkg/shared/log" ) -const ( - DefaultWatcherInterval = 2 * time.Minute - DefaultMountTimeout = 10 * time.Minute -) - -var ( - cfgFile string - config *families.Config - logger *logrus.Entry - output string +var Logger *logrus.Entry - server string - assetScanID string - mountVolume bool -) - -// rootCmd represents the base command when called without any subcommands. -var rootCmd = &cobra.Command{ +// RootCmd represents the base command when called without any subcommands. +var RootCmd = &cobra.Command{ Use: "vmclarity", Short: "VMClarity", Long: `VMClarity`, Version: cli.GitRevision, SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - logger.Infof("Running...") - - // Main context which remains active even if the scan is aborted allowing post-processing operations - // like updating asset scan state - ctx := log.SetLoggerForContext(cmd.Context(), logger) - - cli, err := newCli() - if err != nil { - return fmt.Errorf("failed to initialize CLI: %w", err) - } - - // Create context used to signal to operations that the scan is aborted - abortCtx, cancel := context.WithCancel(ctx) - defer cancel() - - // Start watching for abort event - cli.WatchForAbort(ctx, cancel, DefaultWatcherInterval) - - if err := cli.WaitForReadyState(abortCtx); err != nil { - err = fmt.Errorf("failed to wait for AssetScan being ready to scan: %w", err) - if e := cli.MarkDone(ctx, []error{err}); e != nil { - logger.Errorf("Failed to update AssetScan status to completed with errors: %v", e) - } - return err - } - - if mountVolume { - // Set timeout for mounting volumes - mountCtx, mountCancel := context.WithTimeout(abortCtx, DefaultMountTimeout) - defer mountCancel() - - mountPoints, err := cli.MountVolumes(mountCtx) - if err != nil { - err = fmt.Errorf("failed to mount attached volume: %w", err) - if e := cli.MarkDone(ctx, []error{err}); e != nil { - logger.Errorf("Failed to update asset scan stat to completed with errors: %v", e) - } - return err - } - families.SetMountPointsForFamiliesInput(mountPoints, config) - } - - err = cli.MarkInProgress(ctx) - if err != nil { - return fmt.Errorf("failed to inform server %v scan has started: %w", server, err) - } - - logger.Infof("Running scanners...") - runErrors := families.New(config).Run(abortCtx, cli) - - err = cli.MarkDone(ctx, runErrors) - if err != nil { - return fmt.Errorf("failed to inform the server %v the scan was completed: %w", server, err) - } - - if len(runErrors) > 0 { - logger.Errorf("Errors when running families: %+v", runErrors) - } - - return nil - }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - cobra.CheckErr(rootCmd.Execute()) + cobra.CheckErr(RootCmd.Execute()) } // nolint: gochecknoinits func init() { cobra.OnInitialize( initLogger, - initConfig, ) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.vmclarity.yaml)") - rootCmd.PersistentFlags().StringVar(&output, "output", "", "set output directory path. Stdout is used if not set.") - rootCmd.PersistentFlags().StringVar(&server, "server", "", "VMClarity server to export asset scans to, for example: http://localhost:9999/api") - rootCmd.PersistentFlags().StringVar(&assetScanID, "asset-scan-id", "", "the AssetScan ID to monitor and report results to") - rootCmd.PersistentFlags().BoolVar(&mountVolume, "mount-attached-volume", false, "discover for an attached volume and mount it before the scan") - - // TODO(sambetts) we may have to change this to our own validation when - // we add the CI/CD scenario and there isn't an existing asset-scan-id - // in the backend to PATCH - rootCmd.MarkFlagsRequiredTogether("server", "asset-scan-id") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - logger.Infof("Initializing configuration...") - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory OR current directory with name ".families" (without extension). - viper.AddConfigPath(home) - viper.AddConfigPath(".") - viper.SetConfigType("yaml") - viper.SetConfigName(".families") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - err := viper.ReadInConfig() - cobra.CheckErr(err) - - // Load config - config = &families.Config{} - err = viper.Unmarshal(config) - cobra.CheckErr(err) - - if logrus.IsLevelEnabled(logrus.InfoLevel) { - configB, err := yaml.Marshal(config) - cobra.CheckErr(err) - logger.Infof("Using config file (%s):\n%s", viper.ConfigFileUsed(), string(configB)) - } } func initLogger() { log.InitLogger(logrus.InfoLevel.String(), os.Stderr) - logger = logrus.WithField("app", "vmclarity") -} - -func newCli() (*cli.CLI, error) { - var manager state.Manager - var presenters []presenter.Presenter - var err error - - if config == nil { - return nil, errors.New("families config must not be nil") - } - - if server != "" { - var client *backendclient.BackendClient - var p presenter.Presenter - - client, err = backendclient.Create(server) - if err != nil { - return nil, fmt.Errorf("failed to create VMClarity API client: %w", err) - } - - manager, err = state.NewVMClarityState(client, assetScanID) - if err != nil { - return nil, fmt.Errorf("failed to create VMClarity state manager: %w", err) - } - - p, err = presenter.NewVMClarityPresenter(client, assetScanID) - if err != nil { - return nil, fmt.Errorf("failed to create VMClarity presenter: %w", err) - } - presenters = append(presenters, p) - } else { - manager, err = state.NewLocalState() - if err != nil { - return nil, fmt.Errorf("failed to create local state: %w", err) - } - } - - if output != "" { - presenters = append(presenters, presenter.NewFilePresenter(output, config)) - } else { - presenters = append(presenters, presenter.NewConsolePresenter(os.Stdout, config)) - } - - var p presenter.Presenter - if len(presenters) == 1 { - p = presenters[0] - } else { - p = &presenter.MultiPresenter{Presenters: presenters} - } - - return &cli.CLI{Manager: manager, Presenter: p, FamiliesConfig: config}, nil + Logger = logrus.WithField("app", "vmclarity") } diff --git a/cmd/vmclarity-cli/scan/scan.go b/cmd/vmclarity-cli/scan/scan.go new file mode 100644 index 000000000..c1bbfd074 --- /dev/null +++ b/cmd/vmclarity-cli/scan/scan.go @@ -0,0 +1,244 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// 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 scan + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/ghodss/yaml" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/openclarity/vmclarity/cmd/vmclarity-cli/root" + "github.com/openclarity/vmclarity/pkg/cli" + + "github.com/openclarity/vmclarity/pkg/cli/state" + + "github.com/openclarity/vmclarity/pkg/cli/presenter" + + "github.com/openclarity/vmclarity/pkg/shared/backendclient" + "github.com/openclarity/vmclarity/pkg/shared/families" + "github.com/openclarity/vmclarity/pkg/shared/log" +) + +const ( + DefaultWatcherInterval = 2 * time.Minute + DefaultMountTimeout = 10 * time.Minute +) + +// scanCmd represents the scan command. +var scanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan", + Long: `Run scanner families`, + RunE: func(cmd *cobra.Command, args []string) error { + root.Logger.Infof("Running...") + + // Main context which remains active even if the scan is aborted allowing post-processing operations + // like updating asset scan state + ctx := log.SetLoggerForContext(cmd.Context(), root.Logger) + + cfgFile, err := cmd.Flags().GetString("config") + if err != nil { + root.Logger.Fatalf("Unable to get config file name: %v", err) + } + server, err := cmd.Flags().GetString("server") + if err != nil { + root.Logger.Fatalf("Unable to get VMClarity server address: %v", err) + } + output, err := cmd.Flags().GetString("output") + if err != nil { + root.Logger.Fatalf("Unable to get output file name: %v", err) + } + assetScanID, err := cmd.Flags().GetString("asset-scan-id") + if err != nil { + root.Logger.Fatalf("Unable to get asset scan ID: %v", err) + } + mountVolume, err := cmd.Flags().GetBool("mount-attached-volume") + if err != nil { + root.Logger.Fatalf("Unable to get mount attached volume flag: %v", err) + } + + config := loadConfig(cfgFile) + cli, err := newCli(config, server, assetScanID, output) + if err != nil { + return fmt.Errorf("failed to initialize CLI: %w", err) + } + + // Create context used to signal to operations that the scan is aborted + abortCtx, cancel := context.WithCancel(ctx) + defer cancel() + + // Start watching for abort event + cli.WatchForAbort(ctx, cancel, DefaultWatcherInterval) + + if err := cli.WaitForReadyState(abortCtx); err != nil { + err = fmt.Errorf("failed to wait for AssetScan being ready to scan: %w", err) + if e := cli.MarkDone(ctx, []error{err}); e != nil { + root.Logger.Errorf("Failed to update AssetScan status to completed with errors: %v", e) + } + return err + } + + if mountVolume { + // Set timeout for mounting volumes + mountCtx, mountCancel := context.WithTimeout(abortCtx, DefaultMountTimeout) + defer mountCancel() + + mountPoints, err := cli.MountVolumes(mountCtx) + if err != nil { + err = fmt.Errorf("failed to mount attached volume: %w", err) + if e := cli.MarkDone(ctx, []error{err}); e != nil { + root.Logger.Errorf("Failed to update asset scan stat to completed with errors: %v", e) + } + return err + } + families.SetMountPointsForFamiliesInput(mountPoints, config) + } + + err = cli.MarkInProgress(ctx) + if err != nil { + return fmt.Errorf("failed to inform server %v scan has started: %w", server, err) + } + + root.Logger.Infof("Running scanners...") + runErrors := families.New(config).Run(abortCtx, cli) + + err = cli.MarkDone(ctx, runErrors) + if err != nil { + return fmt.Errorf("failed to inform the server %v the scan was completed: %w", server, err) + } + + if len(runErrors) > 0 { + root.Logger.Errorf("Errors when running families: %+v", runErrors) + } + + return nil + }, +} + +// nolint: gochecknoinits +func init() { + root.RootCmd.AddCommand(scanCmd) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + scanCmd.Flags().String("config", "", "config file (default is $HOME/.vmclarity.yaml)") + scanCmd.Flags().String("output", "", "set output directory path. Stdout is used if not set.") + scanCmd.Flags().String("server", "", "VMClarity server to export asset scans to, for example: http://localhost:9999/api") + scanCmd.Flags().String("asset-scan-id", "", "the AssetScan ID to monitor and report results to") + scanCmd.Flags().Bool("mount-attached-volume", false, "discover for an attached volume and mount it before the scan") + + // TODO(sambetts) we may have to change this to our own validation when + // we add the CI/CD scenario and there isn't an existing asset-scan-id + // in the backend to PATCH + scanCmd.MarkFlagsRequiredTogether("server", "asset-scan-id") +} + +// loadConfig reads in config file and ENV variables if set. +func loadConfig(cfgFile string) *families.Config { + root.Logger.Infof("Initializing configuration...") + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory OR current directory with name ".families" (without extension). + viper.AddConfigPath(home) + viper.AddConfigPath(".") + viper.SetConfigType("yaml") + viper.SetConfigName(".families") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + err := viper.ReadInConfig() + cobra.CheckErr(err) + + // Load config + config := &families.Config{} + err = viper.Unmarshal(config) + cobra.CheckErr(err) + + if logrus.IsLevelEnabled(logrus.InfoLevel) { + configB, err := yaml.Marshal(config) + cobra.CheckErr(err) + root.Logger.Infof("Using config file (%s):\n%s", viper.ConfigFileUsed(), string(configB)) + } + + return config +} + +func newCli(config *families.Config, server, assetScanID, output string) (*cli.CLI, error) { + var manager state.Manager + var presenters []presenter.Presenter + var err error + + if config == nil { + return nil, errors.New("families config must not be nil") + } + + if server != "" { + var client *backendclient.BackendClient + var p presenter.Presenter + + client, err = backendclient.Create(server) + if err != nil { + return nil, fmt.Errorf("failed to create VMClarity API client: %w", err) + } + + manager, err = state.NewVMClarityState(client, assetScanID) + if err != nil { + return nil, fmt.Errorf("failed to create VMClarity state manager: %w", err) + } + + p, err = presenter.NewVMClarityPresenter(client, assetScanID) + if err != nil { + return nil, fmt.Errorf("failed to create VMClarity presenter: %w", err) + } + presenters = append(presenters, p) + } else { + manager, err = state.NewLocalState() + if err != nil { + return nil, fmt.Errorf("failed to create local state: %w", err) + } + } + + if output != "" { + presenters = append(presenters, presenter.NewFilePresenter(output, config)) + } else { + presenters = append(presenters, presenter.NewConsolePresenter(os.Stdout, config)) + } + + var p presenter.Presenter + if len(presenters) == 1 { + p = presenters[0] + } else { + p = &presenter.MultiPresenter{Presenters: presenters} + } + + return &cli.CLI{Manager: manager, Presenter: p, FamiliesConfig: config}, nil +} diff --git a/docs/assets/dir-asset.json b/docs/assets/dir-asset.json new file mode 100644 index 000000000..a741b5183 --- /dev/null +++ b/docs/assets/dir-asset.json @@ -0,0 +1,5 @@ +{ + "objectType": "DirInfo", + "dirName": "/test/path/test", + "location": "test-location" +} diff --git a/docs/assets/vm-asset.json b/docs/assets/vm-asset.json new file mode 100644 index 000000000..151161e11 --- /dev/null +++ b/docs/assets/vm-asset.json @@ -0,0 +1,20 @@ +{ + "objectType": "VMInfo", + "instanceID": "test-instance-id", + "instanceProvider": "AWS", + "location": "eu-west-2", + "tags": [ + { + "key": "test", + "value": "test" + } + ], + "securityGroups": [ + { + "id": "test-sg" + } + ], + "image": "test-image", + "instanceType": "t2-large", + "launchTime": "2023-07-26T10:33:09.080Z" +} diff --git a/docs/command_line.md b/docs/command_line.md new file mode 100644 index 000000000..22b6bd451 --- /dev/null +++ b/docs/command_line.md @@ -0,0 +1,21 @@ +# Initiate scan using the cli: + +## Reporting results into file: +``` +./cli/bin/vmclarity-cli scan --config ~/testConf.yaml -o outputfile +``` + +If we want to report results to the VMClarity backend, we need to create asset and asset scan object before scan because it requires asset-scan-id + +## Reporting results to VMClarity backend: + +``` +ASSET_ID=$(./cli/bin/vmclarity-cli asset-create --file assets/dir-asset.json --server http://localhost:8888/api) --jsonpath {.id} +ASSET_SCAN_ID=$(./cli/bin/vmclarity-cli asset-scan-create --asset-id $ASSET_ID --server http://localhost:8888/api) --jsonpath {.id} +./cli/bin/vmclarity-cli scan --config ~/testConf.yaml --server http://localhost:8888/api --asset-scan-id $ASSET_SCAN_ID +``` + +Using one-liner: +``` +./cli/bin/vmclarity-cli asset-create --file docs/assets/dir-asset.json --server http://localhost:8888/api --update-if-exists --jsonpath {.id} | xargs -I{} ./cli/bin/vmclarity-cli asset-scan-create --asset-id {} --server http://localhost:8888/api --jsonpath {.id} | xargs -I{} ./cli/bin/vmclarity-cli scan --config ~/testConf.yaml --server http://localhost:8888/api --asset-scan-id {} +``` diff --git a/pkg/backend/database/gorm/asset.go b/pkg/backend/database/gorm/asset.go index e2352c836..2d01ef90c 100644 --- a/pkg/backend/database/gorm/asset.go +++ b/pkg/backend/database/gorm/asset.go @@ -285,6 +285,7 @@ func (t *AssetsTableHandler) DeleteAsset(assetID models.AssetID) error { return nil } +// nolint: cyclop func (t *AssetsTableHandler) checkUniqueness(asset models.Asset) (*models.Asset, error) { discriminator, err := asset.AssetInfo.ValueByDiscriminator() if err != nil { @@ -298,7 +299,11 @@ func (t *AssetsTableHandler) checkUniqueness(asset models.Asset) (*models.Asset, "id ne '%s' and assetInfo/instanceID eq '%s' and assetInfo/location eq '%s'", *asset.Id, info.InstanceID, info.Location, ) - + case models.DirInfo: + filter = fmt.Sprintf( + "id ne '%s' and assetInfo/dirName eq '%s' and assetInfo/location eq '%s'", + *asset.Id, *info.DirName, *info.Location, + ) case models.ContainerInfo: filter = fmt.Sprintf("id ne '%s' and assetInfo/Id eq '%s'", *asset.Id, *info.Id) diff --git a/pkg/cli/utils/json_helper.go b/pkg/cli/utils/json_helper.go new file mode 100644 index 000000000..013337978 --- /dev/null +++ b/pkg/cli/utils/json_helper.go @@ -0,0 +1,45 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// 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 utils + +import ( + "encoding/json" + "fmt" + "os" + + "k8s.io/client-go/util/jsonpath" +) + +func PrintJSONData(data interface{}, fields string) error { + // If jsonpath is not set it will print the whole data as json format. + if fields == "" { + dataB, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal data: %v", err) + } + fmt.Println(string(dataB)) + return nil + } + j := jsonpath.New("parser") + if err := j.Parse(fields); err != nil { + return fmt.Errorf("failed to parse jsonpath: %v", err) + } + err := j.Execute(os.Stdout, data) + if err != nil { + return fmt.Errorf("failed to execute jsonpath: %v", err) + } + return nil +} diff --git a/pkg/orchestrator/provider/cloudinit/cloud-init.tmpl.yaml b/pkg/orchestrator/provider/cloudinit/cloud-init.tmpl.yaml index 89dfcb74c..e1d40a41d 100644 --- a/pkg/orchestrator/provider/cloudinit/cloud-init.tmpl.yaml +++ b/pkg/orchestrator/provider/cloudinit/cloud-init.tmpl.yaml @@ -27,6 +27,7 @@ write_files: -v /run:/run \ -v /var/opt/vmclarity:/var/opt/vmclarity \ {{ .ScannerImage }} \ + scan \ --config /opt/vmclarity/scanconfig.yaml \ --server {{ .VMClarityAddress }} \ --mount-attached-volume \ diff --git a/pkg/orchestrator/provider/cloudinit/testdata/cloud-init.yaml b/pkg/orchestrator/provider/cloudinit/testdata/cloud-init.yaml index a3400696a..95f521880 100644 --- a/pkg/orchestrator/provider/cloudinit/testdata/cloud-init.yaml +++ b/pkg/orchestrator/provider/cloudinit/testdata/cloud-init.yaml @@ -37,6 +37,7 @@ write_files: -v /run:/run \ -v /var/opt/vmclarity:/var/opt/vmclarity \ ghcr.io/openclarity/vmclarity-cli:latest \ + scan \ --config /opt/vmclarity/scanconfig.yaml \ --server 10.1.1.1:8888 \ --mount-attached-volume \