cli/scorecard/inventory.go (130 lines of code) (raw):
// Copyright 2019 Google LLC
//
// 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 scorecard
import (
"context"
"fmt"
"time"
"github.com/briandowns/spinner"
"github.com/pkg/errors"
asset "cloud.google.com/go/asset/apiv1"
assetpb "cloud.google.com/go/asset/apiv1/assetpb"
)
// InventoryConfig manages a CAI inventory
type InventoryConfig struct {
projectID string
folderID string
organizationID string
bucketName string
dirPath string
readFromStdin bool
workers int
}
// Option for NewInventory
type Option func(*InventoryConfig)
// TargetProject sets the project for collecting inventory data
func TargetProject(projectID string) Option {
return func(inventory *InventoryConfig) {
inventory.projectID = projectID
}
}
// TargetFolder sets the folder for collecting inventory data
func TargetFolder(folderID string) Option {
return func(inventory *InventoryConfig) {
inventory.folderID = folderID
}
}
// TargetOrg sets the organzation for collecting inventory data
func TargetOrg(organizationID string) Option {
return func(inventory *InventoryConfig) {
inventory.organizationID = organizationID
}
}
// WorkerSize sets the number of workers for running violations review concurrently
func WorkerSize(workers int) Option {
return func(inventory *InventoryConfig) {
inventory.workers = workers
}
}
// NewInventory creates a new CAI inventory manager
func NewInventory(bucketName, dirPath string, readFromStdin bool, refresh bool, options ...Option) (*InventoryConfig, error) {
inventory := new(InventoryConfig)
inventory.bucketName = bucketName
inventory.dirPath = dirPath
inventory.readFromStdin = readFromStdin
for _, option := range options {
option(inventory)
}
Log.Debug("Initializing inventory", "target", inventory.getParent())
if refresh {
err := inventory.Export()
if err != nil {
return nil, err
}
}
return inventory, nil
}
func (inventory InventoryConfig) getParent() string {
if inventory.organizationID != "" {
return fmt.Sprintf("organizations/%v", inventory.organizationID)
} else if inventory.folderID != "" {
return fmt.Sprintf("folders/%v", inventory.folderID)
}
return fmt.Sprintf("projects/%v", inventory.projectID)
}
// destinationObjectNames maps the different export types to their expected file location
var destinationObjectNames = map[assetpb.ContentType]string{
assetpb.ContentType_RESOURCE: "resource_inventory.json",
assetpb.ContentType_IAM_POLICY: "iam_inventory.json",
assetpb.ContentType_ORG_POLICY: "org_policy_inventory.json",
assetpb.ContentType_ACCESS_POLICY: "access_policy_inventory.json",
}
func (inventory InventoryConfig) getGcsDestination(contentType assetpb.ContentType) *assetpb.GcsDestination_Uri {
objectName := destinationObjectNames[contentType]
return &assetpb.GcsDestination_Uri{
Uri: fmt.Sprintf("gs://%v/%v", inventory.bucketName, objectName),
}
}
// exportToGcs exports an inventory of the given resource type to GCS
func (inventory InventoryConfig) exportToGcs(contentType assetpb.ContentType) error {
ctx := context.Background()
c, err := asset.NewClient(ctx)
if err != nil {
return err
}
destination := inventory.getGcsDestination(contentType)
req := &assetpb.ExportAssetsRequest{
Parent: inventory.getParent(),
ContentType: contentType,
OutputConfig: &assetpb.OutputConfig{
Destination: &assetpb.OutputConfig_GcsDestination{
GcsDestination: &assetpb.GcsDestination{
ObjectUri: destination,
},
},
},
}
Log.Debug("Exporting Asset ", "contentType", contentType, "parent", inventory.getParent())
op, err := c.ExportAssets(ctx, req)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("destination = %v", destination))
}
_, err = op.Wait(ctx)
return err
}
// Export creates a new inventory export
func (inventory *InventoryConfig) Export() error {
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
s.Prefix = "Exporting Cloud Asset Inventory to GCS bucket... "
s.Start()
err := inventory.exportToGcs(assetpb.ContentType_RESOURCE)
if err != nil {
s.Stop()
return err
}
err = inventory.exportToGcs(assetpb.ContentType_IAM_POLICY)
if err != nil {
s.Stop()
return err
}
err = inventory.exportToGcs(assetpb.ContentType_ORG_POLICY)
if err != nil {
s.Stop()
return err
}
err = inventory.exportToGcs(assetpb.ContentType_ACCESS_POLICY)
if err != nil {
s.Stop()
return err
}
s.Stop()
return nil
}