mirror of
https://github.com/turbot/steampipe.git
synced 2026-02-21 11:00:21 -05:00
Add deprecation warnings for deprecated hcl properties. Closes #2973
This commit is contained in:
@@ -239,13 +239,13 @@ func initDashboard(ctx context.Context) *initialisation.InitData {
|
||||
}
|
||||
|
||||
func getInitData(ctx context.Context) *initialisation.InitData {
|
||||
w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if err != nil {
|
||||
return initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error()))
|
||||
w, errAndWarnings := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
return initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", errAndWarnings.GetError().Error()))
|
||||
}
|
||||
|
||||
i := initialisation.NewInitData(w).
|
||||
Init(ctx, constants.InvokerDashboard)
|
||||
i := initialisation.NewInitData(w).Init(ctx, constants.InvokerDashboard)
|
||||
i.Result.AddWarnings(errAndWarnings.Warnings...)
|
||||
|
||||
if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
|
||||
i.RegisterExporters(dashboardExporters()...)
|
||||
|
||||
@@ -45,8 +45,8 @@ func getRunListSubCmd(opts listSubCmdOptions) func(cmd *cobra.Command, args []st
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
workspacePath := viper.GetString(constants.ArgModLocation)
|
||||
|
||||
w, err := workspace.Load(cmd.Context(), workspacePath)
|
||||
error_helpers.FailOnError(err)
|
||||
w, errAndWarnings := workspace.Load(cmd.Context(), workspacePath)
|
||||
error_helpers.FailOnError(errAndWarnings.GetError())
|
||||
|
||||
modResources, depResources, err := listResourcesInMod(cmd.Context(), w.Mod, cmd)
|
||||
error_helpers.FailOnErrorWithMessage(err, "could not list resources")
|
||||
|
||||
@@ -30,10 +30,10 @@ func NewInitData(ctx context.Context) *InitData {
|
||||
defer statushooks.Done(ctx)
|
||||
|
||||
// load the workspace
|
||||
w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if err != nil {
|
||||
w, errAndWarnings := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
return &InitData{
|
||||
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error())),
|
||||
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", errAndWarnings.GetError().Error())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ func NewInitData(ctx context.Context) *InitData {
|
||||
i := &InitData{
|
||||
InitData: *initialisation.NewInitData(w),
|
||||
}
|
||||
i.Result.AddWarnings(errAndWarnings.Warnings...)
|
||||
|
||||
if !w.ModfileExists() {
|
||||
i.Result.Error = workspace.ErrorNoModDefinition
|
||||
@@ -51,7 +52,7 @@ func NewInitData(ctx context.Context) *InitData {
|
||||
viper.Set(constants.ArgProgress, false)
|
||||
}
|
||||
// set color schema
|
||||
err = initialiseCheckColorScheme()
|
||||
err := initialiseCheckColorScheme()
|
||||
if err != nil {
|
||||
i.Result.Error = err
|
||||
return i
|
||||
|
||||
@@ -24,10 +24,10 @@ type InitData struct {
|
||||
// InitData.Done closes after asynchronous initialization completes
|
||||
func NewInitData(ctx context.Context, args []string) *InitData {
|
||||
// load the workspace
|
||||
w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if err != nil {
|
||||
w, errAndWarnings := workspace.LoadWorkspacePromptingForVariables(ctx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
return &InitData{
|
||||
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error())),
|
||||
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", errAndWarnings.GetError().Error())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ func NewInitData(ctx context.Context, args []string) *InitData {
|
||||
InitData: *initialisation.NewInitData(w),
|
||||
Loaded: make(chan struct{}),
|
||||
}
|
||||
// add any warnings
|
||||
i.Result.AddWarnings(errAndWarnings.Warnings...)
|
||||
|
||||
if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
|
||||
i.RegisterExporters(queryExporters()...)
|
||||
|
||||
@@ -21,27 +21,29 @@ import (
|
||||
// if CreatePseudoResources flag is set, construct hcl resources for files with specific extensions
|
||||
// NOTE: it is an error if there is more than 1 mod defined, however zero mods is acceptable
|
||||
// - a default mod will be created assuming there are any resource files
|
||||
func LoadMod(modPath string, parseCtx *parse.ModParseContext) (mod *modconfig.Mod, err error) {
|
||||
func LoadMod(modPath string, parseCtx *parse.ModParseContext) (mod *modconfig.Mod, errAndWarnings *modconfig.ErrorAndWarnings) {
|
||||
var err error
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = helpers.ToError(r)
|
||||
errAndWarnings = modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mod, err = loadModDefinition(modPath, parseCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if errAndWarnings != nil {
|
||||
return nil, errAndWarnings
|
||||
}
|
||||
// load the mod dependencies
|
||||
if err := loadModDependencies(mod, parseCtx); err != nil {
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
// now we have loaded dependencies, set the current mod on the run context
|
||||
parseCtx.CurrentMod = mod
|
||||
// populate the resource maps of the current mod using the dependency mods
|
||||
mod.ResourceMaps = parseCtx.GetResourceMaps()
|
||||
// now load the mod resource hcl
|
||||
return loadModResources(modPath, parseCtx, mod)
|
||||
return loadModResources(modPath, parseCtx)
|
||||
}
|
||||
|
||||
func loadModDefinition(modPath string, parseCtx *parse.ModParseContext) (*modconfig.Mod, error) {
|
||||
@@ -148,9 +150,9 @@ func loadModDependency(modDependency *modconfig.ModVersionConstraint, parseCtx *
|
||||
childRunCtx.BlockTypes = parseCtx.BlockTypes
|
||||
childRunCtx.ParentParseCtx = parseCtx
|
||||
|
||||
mod, err := LoadMod(dependencyPath, childRunCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
mod, errAndWarnings := LoadMod(dependencyPath, childRunCtx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
return errAndWarnings.GetError()
|
||||
}
|
||||
|
||||
// set the version and dependency path of the mod
|
||||
@@ -163,11 +165,11 @@ func loadModDependency(modDependency *modconfig.ModVersionConstraint, parseCtx *
|
||||
parseCtx.ParentParseCtx.LoadedDependencyMods[modDependency.Name] = mod
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func loadModResources(modPath string, parseCtx *parse.ModParseContext, mod *modconfig.Mod) (*modconfig.Mod, error) {
|
||||
func loadModResources(modPath string, parseCtx *parse.ModParseContext) (*modconfig.Mod, *modconfig.ErrorAndWarnings) {
|
||||
// if flag is set, create pseudo resources by mapping files
|
||||
var pseudoResources []modconfig.MappableResource
|
||||
var err error
|
||||
@@ -175,7 +177,7 @@ func loadModResources(modPath string, parseCtx *parse.ModParseContext, mod *modc
|
||||
// now execute any pseudo-resource creations based on file mappings
|
||||
pseudoResources, err = createPseudoResources(modPath, parseCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,28 +185,26 @@ func loadModResources(modPath string, parseCtx *parse.ModParseContext, mod *modc
|
||||
sourcePaths, err := getSourcePaths(modPath, parseCtx.ListOptions)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] LoadMod: failed to get mod file paths: %v\n", err)
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
|
||||
// load the raw file data
|
||||
fileData, diags := parse.LoadFileData(sourcePaths...)
|
||||
if diags.HasErrors() {
|
||||
return nil, plugin.DiagsToError("Failed to load all mod files", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to load all mod files", diags))
|
||||
}
|
||||
|
||||
// parse all hcl files.
|
||||
mod, err = parse.ParseMod(modPath, fileData, pseudoResources, parseCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
mod, errAndWarnings := parse.ParseMod(fileData, pseudoResources, parseCtx)
|
||||
if errAndWarnings.GetError() == nil {
|
||||
// now add fully populated mod to the parent run context
|
||||
if parseCtx.ParentParseCtx != nil {
|
||||
parseCtx.ParentParseCtx.CurrentMod = mod
|
||||
parseCtx.ParentParseCtx.AddMod(mod)
|
||||
}
|
||||
}
|
||||
|
||||
// now add fully populated mod to the parent run context
|
||||
if parseCtx.ParentParseCtx != nil {
|
||||
parseCtx.ParentParseCtx.CurrentMod = mod
|
||||
parseCtx.ParentParseCtx.AddMod(mod)
|
||||
}
|
||||
|
||||
return mod, err
|
||||
return mod, errAndWarnings
|
||||
}
|
||||
|
||||
// search the parent folder for a mod installatio which satisfied the given mod dependency
|
||||
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
func LoadVariableDefinitions(variablePath string, parseCtx *parse.ModParseContext) (*modconfig.ModVariableMap, error) {
|
||||
// only load mod and variables blocks
|
||||
parseCtx.BlockTypes = []string{modconfig.BlockTypeVariable}
|
||||
mod, err := LoadMod(variablePath, parseCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
mod, errAndWarnings := LoadMod(variablePath, parseCtx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
return nil, errAndWarnings.GetError()
|
||||
}
|
||||
|
||||
variableMap := modconfig.NewModVariableMap(mod, parseCtx.LoadedDependencyMods)
|
||||
|
||||
31
pkg/steampipeconfig/modconfig/error_and_warnings.go
Normal file
31
pkg/steampipeconfig/modconfig/error_and_warnings.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package modconfig
|
||||
|
||||
import "github.com/turbot/steampipe/pkg/error_helpers"
|
||||
|
||||
type ErrorAndWarnings struct {
|
||||
Error error
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
func NewErrorsAndWarning(err error, warnings ...string) *ErrorAndWarnings {
|
||||
return &ErrorAndWarnings{
|
||||
Error: err, Warnings: warnings,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ErrorAndWarnings) AddWarning(warnings ...string) {
|
||||
r.Warnings = append(r.Warnings, warnings...)
|
||||
}
|
||||
|
||||
func (r *ErrorAndWarnings) ShowWarnings() {
|
||||
for _, w := range r.Warnings {
|
||||
error_helpers.ShowWarning(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ErrorAndWarnings) GetError() error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r.Error
|
||||
}
|
||||
@@ -50,11 +50,8 @@ func decode(parseCtx *ModParseContext) hcl.Diagnostics {
|
||||
}
|
||||
} else {
|
||||
resource, res := decodeBlock(block, parseCtx)
|
||||
if !res.Success() {
|
||||
diags = append(diags, res.Diags...)
|
||||
continue
|
||||
}
|
||||
if resource == nil {
|
||||
diags = append(diags, res.Diags...)
|
||||
if !res.Success() || resource == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -725,7 +722,7 @@ func validateName(block *hcl.Block) hcl.Diagnostics {
|
||||
// We use partial decoding so that we can automatically decode as many properties as possible
|
||||
// and only manually decode properties requiring special logic.
|
||||
// The problem is the partial decode does not return errors for invalid attributes/blocks, so we must implement our own
|
||||
func validateHcl(body *hclsyntax.Body, schema *hcl.BodySchema) hcl.Diagnostics {
|
||||
func validateHcl(blockType string, body *hclsyntax.Body, schema *hcl.BodySchema) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// identify any blocks specified by hcl tags
|
||||
@@ -743,21 +740,39 @@ func validateHcl(body *hclsyntax.Body, schema *hcl.BodySchema) hcl.Diagnostics {
|
||||
if _, ok := supportedBlocks[block.Type]; !ok {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf(`Unsupported block type: Blocks of type "%s" are not expected here.`, block.Type),
|
||||
Summary: fmt.Sprintf(`Unsupported block type: Blocks of type '%s' are not expected here.`, block.Type),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, attribute := range body.Attributes {
|
||||
if _, ok := supportedAttributes[attribute.Name]; !ok {
|
||||
// special case code for deprecated properties
|
||||
subject := attribute.Range()
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf(`Unsupported attribute: Attribute "%s" not expected here.`, attribute.Name),
|
||||
Subject: &subject,
|
||||
})
|
||||
if isDeprecated(attribute, blockType) {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: fmt.Sprintf(`Deprecated attribute: '%s' is deprecated for '%s' blocks and will be ignored.`, attribute.Name, blockType),
|
||||
Subject: &subject,
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf(`Unsupported attribute: '%s' not expected here.`, attribute.Name),
|
||||
Subject: &subject,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func isDeprecated(attribute *hclsyntax.Attribute, blockType string) bool {
|
||||
switch attribute.Name {
|
||||
case "search_path", "search_path_prefix":
|
||||
return blockType == modconfig.BlockTypeQuery || blockType == modconfig.BlockTypeControl
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func decodeHclBody(body hcl.Body, evalCtx *hcl.EvalContext, resourceProvider mod
|
||||
// get the schema for this resource
|
||||
schema := getResourceSchema(resource, nestedStructs)
|
||||
// handle invalid block types
|
||||
moreDiags = validateHcl(body.(*hclsyntax.Body), schema)
|
||||
moreDiags = validateHcl(resource.BlockType(), body.(*hclsyntax.Body), schema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
moreDiags = decodeHclBodyIntoStruct(body, evalCtx, resourceProvider, resource)
|
||||
|
||||
@@ -40,7 +40,7 @@ func (p *decodeResult) handleDecodeDiags(diags hcl.Diagnostics) {
|
||||
}
|
||||
}
|
||||
// only register errors if there are NOT any missing variables
|
||||
if diags.HasErrors() && len(p.Depends) == 0 {
|
||||
if len(p.Depends) == 0 {
|
||||
p.addDiags(diags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,32 +155,32 @@ func ParseModDefinition(modPath string) (*modconfig.Mod, error) {
|
||||
|
||||
// ParseMod parses all source hcl files for the mod path and associated resources, and returns the mod object
|
||||
// NOTE: the mod definition has already been parsed (or a default created) and is in opts.RunCtx.RootMod
|
||||
func ParseMod(modPath string, fileData map[string][]byte, pseudoResources []modconfig.MappableResource, parseCtx *ModParseContext) (*modconfig.Mod, error) {
|
||||
func ParseMod(fileData map[string][]byte, pseudoResources []modconfig.MappableResource, parseCtx *ModParseContext) (*modconfig.Mod, *modconfig.ErrorAndWarnings) {
|
||||
body, diags := ParseHclFiles(fileData)
|
||||
if diags.HasErrors() {
|
||||
return nil, plugin.DiagsToError("Failed to load all mod source files", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to load all mod source files", diags))
|
||||
}
|
||||
|
||||
content, moreDiags := body.Content(WorkspaceBlockSchema)
|
||||
if moreDiags.HasErrors() {
|
||||
diags = append(diags, moreDiags...)
|
||||
return nil, plugin.DiagsToError("Failed to load mod", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to load mod", diags))
|
||||
}
|
||||
|
||||
mod := parseCtx.CurrentMod
|
||||
if mod == nil {
|
||||
return nil, fmt.Errorf("ParseMod called with no Current Mod set in ModParseContext")
|
||||
return nil, modconfig.NewErrorsAndWarning(fmt.Errorf("ParseMod called with no Current Mod set in ModParseContext"))
|
||||
}
|
||||
// get names of all resources defined in hcl which may also be created as pseudo resources
|
||||
hclResources, err := loadMappableResourceNames(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
|
||||
// if variables were passed in runcontext, add to the mod
|
||||
for _, v := range parseCtx.Variables {
|
||||
if diags = mod.AddResource(v); diags.HasErrors() {
|
||||
return nil, plugin.DiagsToError("Failed to add resource to mod", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to add resource to mod", diags))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,17 +193,22 @@ func ParseMod(modPath string, fileData map[string][]byte, pseudoResources []modc
|
||||
// add the mod to the run context
|
||||
// - this it to ensure all pseudo resources get added and build the eval context with the variables we just added
|
||||
if diags = parseCtx.AddMod(mod); diags.HasErrors() {
|
||||
return nil, plugin.DiagsToError("Failed to add mod to run context", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to add mod to run context", diags))
|
||||
}
|
||||
|
||||
// collect warnings as we parse
|
||||
var res = &modconfig.ErrorAndWarnings{}
|
||||
|
||||
// we may need to decode more than once as we gather dependencies as we go
|
||||
// continue decoding as long as the number of unresolved blocks decreases
|
||||
prevUnresolvedBlocks := 0
|
||||
for attempts := 0; ; attempts++ {
|
||||
diags = decode(parseCtx)
|
||||
if diags.HasErrors() {
|
||||
return nil, plugin.DiagsToError("Failed to decode all mod hcl files", diags)
|
||||
return nil, modconfig.NewErrorsAndWarning(plugin.DiagsToError("Failed to decode all mod hcl files", diags))
|
||||
}
|
||||
// now retrieve the warning strings
|
||||
res.AddWarning(plugin.DiagsToWarnings(diags)...)
|
||||
|
||||
// if there are no unresolved blocks, we are done
|
||||
unresolvedBlocks := len(parseCtx.UnresolvedBlocks)
|
||||
@@ -214,18 +219,16 @@ func ParseMod(modPath string, fileData map[string][]byte, pseudoResources []modc
|
||||
// if the number of unresolved blocks has NOT reduced, fail
|
||||
if prevUnresolvedBlocks != 0 && unresolvedBlocks >= prevUnresolvedBlocks {
|
||||
str := parseCtx.FormatDependencies()
|
||||
return nil, fmt.Errorf("failed to resolve mod dependencies after %d attempts\nDependencies:\n%s", attempts+1, str)
|
||||
return nil, modconfig.NewErrorsAndWarning(fmt.Errorf("failed to resolve mod dependencies after %d attempts\nDependencies:\n%s", attempts+1, str))
|
||||
}
|
||||
// update prevUnresolvedBlocks
|
||||
prevUnresolvedBlocks = unresolvedBlocks
|
||||
}
|
||||
|
||||
// now tell mod to build tree of controls.
|
||||
if err := mod.BuildResourceTree(parseCtx.LoadedDependencyMods); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Error = mod.BuildResourceTree(parseCtx.LoadedDependencyMods)
|
||||
|
||||
return mod, nil
|
||||
return mod, res
|
||||
}
|
||||
|
||||
// parse a yaml file into a hcl.File object
|
||||
|
||||
@@ -99,8 +99,8 @@ func validateParamAndQueryNotBothSet(resource modconfig.QueryProvider) hcl.Diagn
|
||||
}
|
||||
if !resource.IsTopLevel() && !resource.ParamsInheritedFromBase() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Only top level resources can have 'param' blocks",
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Deprecated usage: Only top level resources can have 'param' blocks",
|
||||
Detail: fmt.Sprintf("%s contains 'param' blocks but is not a top level resource.", resource.Name()),
|
||||
Subject: resource.GetDeclRange(),
|
||||
})
|
||||
|
||||
@@ -2,30 +2,19 @@ package steampipeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
|
||||
"strings"
|
||||
|
||||
"github.com/turbot/steampipe/pkg/error_helpers"
|
||||
"github.com/turbot/steampipe/pkg/utils"
|
||||
)
|
||||
|
||||
// RefreshConnectionResult is a structure used to contain the result of either a RefreshConnections or a NewLocalClient operation
|
||||
type RefreshConnectionResult struct {
|
||||
modconfig.ErrorAndWarnings
|
||||
UpdatedConnections bool
|
||||
Warnings []string
|
||||
Error error
|
||||
Updates *ConnectionUpdates
|
||||
}
|
||||
|
||||
func (r *RefreshConnectionResult) AddWarning(warning string) {
|
||||
r.Warnings = append(r.Warnings, warning)
|
||||
}
|
||||
|
||||
func (r *RefreshConnectionResult) ShowWarnings() {
|
||||
for _, w := range r.Warnings {
|
||||
error_helpers.ShowWarning(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RefreshConnectionResult) Merge(other *RefreshConnectionResult) {
|
||||
if other == nil {
|
||||
return
|
||||
|
||||
@@ -14,31 +14,31 @@ import (
|
||||
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
|
||||
)
|
||||
|
||||
func LoadWorkspacePromptingForVariables(ctx context.Context) (*Workspace, error) {
|
||||
func LoadWorkspacePromptingForVariables(ctx context.Context) (*Workspace, *modconfig.ErrorAndWarnings) {
|
||||
workspacePath := viper.GetString(constants.ArgModLocation)
|
||||
t := time.Now()
|
||||
defer func() {
|
||||
log.Printf("[TRANCE] Workspace load took %dms\n", time.Since(t).Milliseconds())
|
||||
}()
|
||||
w, err := Load(ctx, workspacePath)
|
||||
if err == nil {
|
||||
return w, nil
|
||||
w, errAndWarnings := Load(ctx, workspacePath)
|
||||
if errAndWarnings.GetError() == nil {
|
||||
return w, errAndWarnings
|
||||
}
|
||||
missingVariablesError, ok := err.(modconfig.MissingVariableError)
|
||||
missingVariablesError, ok := errAndWarnings.GetError().(modconfig.MissingVariableError)
|
||||
// if there was an error which is NOT a MissingVariableError, return it
|
||||
if !ok {
|
||||
return nil, err
|
||||
return nil, errAndWarnings
|
||||
}
|
||||
// if interactive input is disabled, return the missing variables error
|
||||
if !viper.GetBool(constants.ArgInput) {
|
||||
return nil, missingVariablesError
|
||||
return nil, modconfig.NewErrorsAndWarning(missingVariablesError)
|
||||
}
|
||||
// so we have missing variables - prompt for them
|
||||
// first hide spinner if it is there
|
||||
statushooks.Done(ctx)
|
||||
if err := promptForMissingVariables(ctx, missingVariablesError.MissingVariables, workspacePath); err != nil {
|
||||
log.Printf("[TRACE] Interactive variables prompting returned error %v", err)
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
// ok we should have all variables now - reload workspace
|
||||
return Load(ctx, workspacePath)
|
||||
|
||||
@@ -60,23 +60,18 @@ type Workspace struct {
|
||||
}
|
||||
|
||||
// Load creates a Workspace and loads the workspace mod
|
||||
func Load(ctx context.Context, workspacePath string) (*Workspace, error) {
|
||||
func Load(ctx context.Context, workspacePath string) (*Workspace, *modconfig.ErrorAndWarnings) {
|
||||
utils.LogTime("workspace.Load start")
|
||||
defer utils.LogTime("workspace.Load end")
|
||||
|
||||
workspace, err := createShellWorkspace(workspacePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
|
||||
// load the workspace mod
|
||||
if err := workspace.loadWorkspaceMod(ctx); err != nil {
|
||||
log.Printf("[TRACE] loadWorkspaceMod failed: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return context error so calling code can handle cancellations
|
||||
return workspace, nil
|
||||
errAndWarnings := workspace.loadWorkspaceMod(ctx)
|
||||
return workspace, errAndWarnings
|
||||
}
|
||||
|
||||
// LoadVariables creates a Workspace and uses it to load all variables, ignoring any value resolution errors
|
||||
@@ -245,13 +240,13 @@ func (w *Workspace) findModFilePath(folder string) (string, error) {
|
||||
return modFilePath, nil
|
||||
}
|
||||
|
||||
func (w *Workspace) loadWorkspaceMod(ctx context.Context) error {
|
||||
func (w *Workspace) loadWorkspaceMod(ctx context.Context) *modconfig.ErrorAndWarnings {
|
||||
// resolve values of all input variables
|
||||
// we WILL validate missing variables when loading
|
||||
validateMissing := true
|
||||
inputVariables, err := w.getInputVariables(ctx, validateMissing)
|
||||
if err != nil {
|
||||
return err
|
||||
return modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
// populate the parsed variable values
|
||||
w.VariableValues = inputVariables.VariableValues
|
||||
@@ -259,7 +254,7 @@ func (w *Workspace) loadWorkspaceMod(ctx context.Context) error {
|
||||
// build run context which we use to load the workspace
|
||||
parseCtx, err := w.getParseContext()
|
||||
if err != nil {
|
||||
return err
|
||||
return modconfig.NewErrorsAndWarning(err)
|
||||
}
|
||||
// add variables to runContext
|
||||
parseCtx.AddInputVariables(inputVariables)
|
||||
@@ -267,9 +262,9 @@ func (w *Workspace) loadWorkspaceMod(ctx context.Context) error {
|
||||
parseCtx.BlockTypeExclusions = []string{modconfig.BlockTypeVariable}
|
||||
|
||||
// load the workspace mod
|
||||
m, err := steampipeconfig.LoadMod(w.Path, parseCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
m, errorAndWarning := steampipeconfig.LoadMod(w.Path, parseCtx)
|
||||
if errorAndWarning.Error != nil {
|
||||
return errorAndWarning
|
||||
}
|
||||
|
||||
// now set workspace properties
|
||||
@@ -282,7 +277,9 @@ func (w *Workspace) loadWorkspaceMod(ctx context.Context) error {
|
||||
w.Mods[w.Mod.Name()] = w.Mod
|
||||
|
||||
// verify all runtime dependencies can be resolved
|
||||
return w.verifyResourceRuntimeDependencies()
|
||||
errorAndWarning.Error = w.verifyResourceRuntimeDependencies()
|
||||
|
||||
return errorAndWarning
|
||||
}
|
||||
|
||||
func (w *Workspace) getInputVariables(ctx context.Context, validateMissing bool) (*modconfig.ModVariableMap, error) {
|
||||
|
||||
@@ -78,12 +78,12 @@ func (w *Workspace) handleDashboardEvent() {
|
||||
|
||||
func (w *Workspace) handleFileWatcherEvent(ctx context.Context, client db_common.Client, ev []fsnotify.Event) {
|
||||
log.Printf("[TRACE] handleFileWatcherEvent")
|
||||
prevResourceMaps, resourceMaps, err := w.reloadResourceMaps(ctx)
|
||||
prevResourceMaps, resourceMaps, errAndWarnings := w.reloadResourceMaps(ctx)
|
||||
|
||||
if err != nil {
|
||||
if errAndWarnings.GetError() != nil {
|
||||
log.Printf("[TRACE] handleFileWatcherEvent reloadResourceMaps returned error - call PublishDashboardEvent")
|
||||
// publish error event
|
||||
w.PublishDashboardEvent(ctx, &dashboardevents.WorkspaceError{Error: err})
|
||||
w.PublishDashboardEvent(ctx, &dashboardevents.WorkspaceError{Error: errAndWarnings.GetError()})
|
||||
log.Printf("[TRACE] back from PublishDashboardEvent")
|
||||
return
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (w *Workspace) handleFileWatcherEvent(ctx context.Context, client db_common
|
||||
w.raiseDashboardChangedEvents(ctx, resourceMaps, prevResourceMaps)
|
||||
}
|
||||
|
||||
func (w *Workspace) reloadResourceMaps(ctx context.Context) (*modconfig.ResourceMaps, *modconfig.ResourceMaps, error) {
|
||||
func (w *Workspace) reloadResourceMaps(ctx context.Context) (*modconfig.ResourceMaps, *modconfig.ResourceMaps, *modconfig.ErrorAndWarnings) {
|
||||
w.loadLock.Lock()
|
||||
defer w.loadLock.Unlock()
|
||||
|
||||
@@ -115,15 +115,15 @@ func (w *Workspace) reloadResourceMaps(ctx context.Context) (*modconfig.Resource
|
||||
}
|
||||
|
||||
// now reload the workspace
|
||||
err := w.loadWorkspaceMod(ctx)
|
||||
if err != nil {
|
||||
errAndWarnings := w.loadWorkspaceMod(ctx)
|
||||
if errAndWarnings.GetError() != nil {
|
||||
// check the existing watcher error - if we are already in an error state, do not show error
|
||||
if w.watcherError == nil {
|
||||
w.fileWatcherErrorHandler(ctx, error_helpers.PrefixError(err, "failed to reload workspace"))
|
||||
w.fileWatcherErrorHandler(ctx, error_helpers.PrefixError(errAndWarnings.GetError(), "failed to reload workspace"))
|
||||
}
|
||||
// now set watcher error to new error
|
||||
w.watcherError = err
|
||||
return nil, nil, err
|
||||
w.watcherError = errAndWarnings.GetError()
|
||||
return nil, nil, errAndWarnings
|
||||
}
|
||||
// clear watcher error
|
||||
w.watcherError = nil
|
||||
@@ -131,7 +131,7 @@ func (w *Workspace) reloadResourceMaps(ctx context.Context) (*modconfig.Resource
|
||||
// reload the resource maps
|
||||
resourceMaps := w.Mod.ResourceMaps
|
||||
|
||||
return prevResourceMaps, resourceMaps, nil
|
||||
return prevResourceMaps, resourceMaps, errAndWarnings
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
dashboard "bug5_multiple_inputs" {
|
||||
title = "bug5: multiple inputs"
|
||||
input "bucket_arn" {
|
||||
title = "Select a bucket:"
|
||||
sql = query.bug5_s3_bucket_input.sql
|
||||
width = 4
|
||||
}
|
||||
graph {
|
||||
title = "Relationships 1"
|
||||
type = "graph"
|
||||
//direction = "left_right"
|
||||
with "policy_std" {
|
||||
sql = <<-EOQ
|
||||
select
|
||||
policy_std
|
||||
from
|
||||
aws_s3_bucket
|
||||
where
|
||||
arn = $1
|
||||
EOQ
|
||||
args = [self.input.bucket_arn.value]
|
||||
}
|
||||
nodes = [
|
||||
node.bug5_me_node,
|
||||
node.bug5_iam_policy_statement_nodes,
|
||||
]
|
||||
edges = [
|
||||
edge.bug5_bucket_policy_statement_edges,
|
||||
]
|
||||
args = {
|
||||
arn = self.input.bucket_arn.value
|
||||
policy_std = with.policy_std.rows[0].policy_std
|
||||
bucket_arns = [self.input.bucket_arn.value]
|
||||
}
|
||||
}
|
||||
table {
|
||||
# sql = <<-EOQ
|
||||
sql = <<-EOQ
|
||||
select
|
||||
concat('statement:', i) as id,
|
||||
coalesce (
|
||||
t.stmt ->> 'Sid',
|
||||
concat('[', i::text, ']')
|
||||
) as title
|
||||
from
|
||||
aws_s3_bucket,
|
||||
jsonb_array_elements(policy_std -> 'Statement') with ordinality as t(stmt,i)
|
||||
where
|
||||
arn = $1
|
||||
EOQ
|
||||
args = [self.input.bucket_arn.value]
|
||||
}
|
||||
//***************
|
||||
input "lambda_arn" {
|
||||
title = "Select a lambda function:"
|
||||
sql = query.bug5_lambda_function_input.sql
|
||||
width = 4
|
||||
}
|
||||
graph {
|
||||
title = "Relationships 2"
|
||||
type = "graph"
|
||||
//direction = "left_right"
|
||||
with "policy_std" {
|
||||
sql = <<-EOQ
|
||||
select
|
||||
policy_std
|
||||
from
|
||||
aws_lambda_function
|
||||
where
|
||||
arn = $1
|
||||
EOQ
|
||||
args = [self.input.lambda_arn.value]
|
||||
}
|
||||
nodes = [
|
||||
node.bug5_me_node,
|
||||
node.bug5_iam_policy_statement_nodes,
|
||||
]
|
||||
edges = [
|
||||
edge.bug5_lambda_function_policy_statement_edges,
|
||||
]
|
||||
args = {
|
||||
arn = self.input.lambda_arn.value
|
||||
policy_std = with.policy_std.rows[0].policy_std
|
||||
lambda_arns = [self.input.lambda_arn.value]
|
||||
}
|
||||
}
|
||||
table {
|
||||
sql = <<-EOQ
|
||||
select
|
||||
concat('statement:', i) as id,
|
||||
coalesce (
|
||||
t.stmt ->> 'Sid',
|
||||
concat('[', i::text, ']')
|
||||
) as title
|
||||
from
|
||||
aws_lambda_function,
|
||||
jsonb_array_elements(policy_std -> 'Statement') with ordinality as t(stmt,i)
|
||||
where
|
||||
arn = $1
|
||||
EOQ
|
||||
|
||||
args = [self.input.lambda_arn.value]
|
||||
}
|
||||
|
||||
}
|
||||
query "bug5_s3_bucket_input" {
|
||||
sql = <<-EOQ
|
||||
select
|
||||
title as label,
|
||||
arn as value,
|
||||
json_build_object(
|
||||
'account_id', account_id,
|
||||
'region', region
|
||||
) as tags
|
||||
from
|
||||
aws_s3_bucket
|
||||
order by
|
||||
title;
|
||||
EOQ
|
||||
}
|
||||
query "bug5_lambda_function_input" {
|
||||
sql = <<-EOQ
|
||||
select
|
||||
title as label,
|
||||
arn as value,
|
||||
json_build_object(
|
||||
'account_id', account_id,
|
||||
'region', region
|
||||
) as tags
|
||||
from
|
||||
aws_lambda_function
|
||||
order by
|
||||
title;
|
||||
EOQ
|
||||
}
|
||||
node "bug5_me_node" {
|
||||
//category = category.aws_iam_policy
|
||||
sql = <<-EOQ
|
||||
select
|
||||
$1 as id,
|
||||
$1 as title
|
||||
EOQ
|
||||
param "arn" {}
|
||||
}
|
||||
node "bug5_iam_policy_statement_nodes" {
|
||||
//category = category.aws_iam_policy_statement
|
||||
sql = <<-EOQ
|
||||
select
|
||||
concat('statement:', i) as id,
|
||||
coalesce (
|
||||
t.stmt ->> 'Sid',
|
||||
concat('[', i::text, ']')
|
||||
) as title
|
||||
from
|
||||
jsonb_array_elements(($1 :: jsonb) -> 'Statement') with ordinality as t(stmt,i)
|
||||
EOQ
|
||||
param "policy_std" {}
|
||||
}
|
||||
edge "bug5_bucket_policy_statement_edges" {
|
||||
title = "statement"
|
||||
sql = <<-EOQ
|
||||
|
||||
select
|
||||
distinct on (arn,i)
|
||||
arn as from_id,
|
||||
concat('statement:', i) as to_id
|
||||
from
|
||||
aws_s3_bucket,
|
||||
jsonb_array_elements(policy_std -> 'Statement') with ordinality as t(stmt,i)
|
||||
where
|
||||
arn = any($1)
|
||||
EOQ
|
||||
param "bucket_arns" {}
|
||||
}
|
||||
edge "bug5_lambda_function_policy_statement_edges" {
|
||||
title = "statement"
|
||||
sql = <<-EOQ
|
||||
|
||||
select
|
||||
distinct on (arn,i)
|
||||
arn as from_id,
|
||||
concat('statement:', i) as to_id
|
||||
from
|
||||
aws_lambda_function,
|
||||
jsonb_array_elements(policy_std -> 'Statement') with ordinality as t(stmt,i)
|
||||
where
|
||||
arn = any($1)
|
||||
EOQ
|
||||
param "lambda_arns" {}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
mod reports_poc {
|
||||
title = "Reports POC"
|
||||
}
|
||||
@@ -13,7 +13,8 @@ dashboard "inputs" {
|
||||
}
|
||||
|
||||
table {
|
||||
query = query.q1
|
||||
param "foo" {}
|
||||
sql = "select $1"
|
||||
args = {
|
||||
arn = self.input.i1.value
|
||||
}
|
||||
@@ -30,4 +31,5 @@ dashboard "inputs" {
|
||||
query "q1"{
|
||||
sql = "select arn from aws_account where arn = $1"
|
||||
param "arn" { }
|
||||
search_path="test"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user