github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/steampipeconfig/workspace_profile_loader.go (about)

     1  package steampipeconfig
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/spf13/viper"
    11  	"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
    12  	"github.com/turbot/steampipe/pkg/constants"
    13  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    14  	"github.com/turbot/steampipe/pkg/steampipeconfig/parse"
    15  	"github.com/turbot/steampipe/pkg/utils"
    16  )
    17  
    18  var GlobalWorkspaceProfile *modconfig.WorkspaceProfile
    19  var defaultWorkspaceSampleFileName = "workspaces.spc.sample"
    20  
    21  type WorkspaceProfileLoader struct {
    22  	workspaceProfiles    map[string]*modconfig.WorkspaceProfile
    23  	workspaceProfilePath string
    24  	DefaultProfile       *modconfig.WorkspaceProfile
    25  	ConfiguredProfile    *modconfig.WorkspaceProfile
    26  }
    27  
    28  func ensureDefaultWorkspaceFile(configFolder string) error {
    29  	// always write the workspaces.spc.sample file
    30  	err := os.MkdirAll(configFolder, 0755)
    31  	if err != nil {
    32  		return err
    33  	}
    34  	defaultWorkspaceSampleFile := filepath.Join(configFolder, defaultWorkspaceSampleFileName)
    35  	err = os.WriteFile(defaultWorkspaceSampleFile, []byte(constants.DefaultWorkspaceContent), 0755)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	return nil
    40  }
    41  
    42  func NewWorkspaceProfileLoader(ctx context.Context, workspaceProfilePath string) (*WorkspaceProfileLoader, error) {
    43  	// write the workspaces.spc.sample file
    44  	if err := ensureDefaultWorkspaceFile(workspaceProfilePath); err != nil {
    45  		return nil,
    46  			sperr.WrapWithMessage(
    47  				err,
    48  				"could not create sample workspace",
    49  			)
    50  	}
    51  	loader := &WorkspaceProfileLoader{workspaceProfilePath: workspaceProfilePath}
    52  	workspaceProfiles, err := loader.load(ctx)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	loader.workspaceProfiles = workspaceProfiles
    57  
    58  	defaultProfile, err := loader.get("default")
    59  	if err != nil {
    60  		// there must always be a default - this should have been added by parse.LoadWorkspaceProfiles
    61  		return nil, err
    62  	}
    63  	loader.DefaultProfile = defaultProfile
    64  
    65  	if viper.IsSet(constants.ArgWorkspaceProfile) {
    66  		configuredProfile, err := loader.get(viper.GetString(constants.ArgWorkspaceProfile))
    67  		if err != nil {
    68  			// could not find configured profile
    69  			return nil, err
    70  		}
    71  		loader.ConfiguredProfile = configuredProfile
    72  	}
    73  
    74  	return loader, nil
    75  }
    76  
    77  func (l *WorkspaceProfileLoader) GetActiveWorkspaceProfile() *modconfig.WorkspaceProfile {
    78  	if l.ConfiguredProfile != nil {
    79  		return l.ConfiguredProfile
    80  	}
    81  	return l.DefaultProfile
    82  }
    83  
    84  func (l *WorkspaceProfileLoader) get(name string) (*modconfig.WorkspaceProfile, error) {
    85  	if workspaceProfile, ok := l.workspaceProfiles[name]; ok {
    86  		return workspaceProfile, nil
    87  	}
    88  
    89  	if implicitWorkspace := l.getImplicitWorkspace(name); implicitWorkspace != nil {
    90  		return implicitWorkspace, nil
    91  	}
    92  
    93  	return nil, fmt.Errorf("workspace profile %s does not exist", name)
    94  }
    95  
    96  func (l *WorkspaceProfileLoader) load(ctx context.Context) (map[string]*modconfig.WorkspaceProfile, error) {
    97  	// get all the config files in the directory
    98  	return parse.LoadWorkspaceProfiles(ctx, l.workspaceProfilePath)
    99  }
   100  
   101  /*
   102  Named workspaces follow normal standards for hcl identities, thus they cannot contain the slash (/) character.
   103  
   104  If you pass a value to --workspace or STEAMPIPE_WORKSPACE in the form of {identity_handle}/{workspace_handle},
   105  it will be interpreted as an implicit workspace.
   106  
   107  Implicit workspaces, as the name suggests, do not need to be specified in the workspaces.spc file.
   108  
   109  Instead they will be assumed to refer to a Turbot Pipes workspace,
   110  which will be used as both the database and snapshot location.
   111  
   112  Essentially, --workspace acme/dev is equivalent to:
   113  
   114  	workspace "acme/dev" {
   115  	  workspace_database = "acme/dev"
   116  	  snapshot_location  = "acme/dev"
   117  	}
   118  */
   119  func (l *WorkspaceProfileLoader) getImplicitWorkspace(name string) *modconfig.WorkspaceProfile {
   120  	if IsCloudWorkspaceIdentifier(name) {
   121  		log.Printf("[TRACE] getImplicitWorkspace - %s is implicit workspace: SnapshotLocation=%s, WorkspaceDatabase=%s", name, name, name)
   122  		return &modconfig.WorkspaceProfile{
   123  			SnapshotLocation:  utils.ToStringPointer(name),
   124  			WorkspaceDatabase: utils.ToStringPointer(name),
   125  		}
   126  	}
   127  	return nil
   128  }