diff --git a/cli/cmd/component.go b/cli/cmd/component.go index 18692aa59..e4bcbb040 100644 --- a/cli/cmd/component.go +++ b/cli/cmd/component.go @@ -34,6 +34,7 @@ import ( const ( componentTypeAnnotation string = "component" + componentsCacheKey string = "components" ) var ( @@ -163,14 +164,12 @@ func (c *cliState) IsComponentInstalled(name string) bool { // Load v1 components func (c *cliState) LoadComponents() { - components, err := lwcomponent.LocalComponents() + components, err := lwcomponent.LoadLocalComponents() if err != nil { c.Log.Debugw("unable to load components", "error", err) return } - // @jon-stewart: TODO: load from cached API info - for _, component := range components { exists := false @@ -187,18 +186,8 @@ func (c *cliState) LoadComponents() { } version := component.InstalledVersion() - componentDir, err := component.Dir() - if err != nil { - c.Log.Debugw("unable to find component directory", "error", err) - return - } - if devInfo, _ := lwcomponent.NewDevInfo(componentDir); devInfo != nil { - if devVersion, _ := semver.NewVersion(devInfo.Version); devVersion != nil { - version = devVersion - } - } - if version != nil && component.Exec.Executable() { + if version != nil { componentCmd := &cobra.Command{ Use: component.Name, Short: component.Description, @@ -208,7 +197,7 @@ func (c *cliState) LoadComponents() { DisableFlagParsing: true, DisableFlagsInUseLine: true, RunE: func(cmd *cobra.Command, args []string) error { - return v1ComponentCommand(c, cmd, args) + return v1ComponentCommand(c, cmd) }, } @@ -224,7 +213,7 @@ func startGrpcServer(c *cliState) { } } -func v1ComponentCommand(c *cliState, cmd *cobra.Command, args []string) error { +func v1ComponentCommand(c *cliState, cmd *cobra.Command) error { // Parse component -v/--version flag versionVal, _ := cmd.Flags().GetBool("version") if versionVal { @@ -234,11 +223,7 @@ func v1ComponentCommand(c *cliState, cmd *cobra.Command, args []string) error { go startGrpcServer(c) - c.Log.Debugw("running component", "component", cmd.Use, - "args", c.componentParser.componentArgs, - "cli_flags", c.componentParser.cliArgs) - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) + catalog, err := LoadCatalog() if err != nil { return errors.Wrap(err, "unable to load component Catalog") } @@ -248,6 +233,14 @@ func v1ComponentCommand(c *cliState, cmd *cobra.Command, args []string) error { return err } + if !component.Exec.Executable() { + return errors.New("component is not executable") + } + + c.Log.Debugw("running component", "component", cmd.Use, + "args", c.componentParser.componentArgs, + "cli_flags", c.componentParser.cliArgs) + // @jon-stewart: TODO: v1 dailyComponentUpdateAvailable envs := []string{ @@ -355,12 +348,7 @@ func runComponentsList(_ *cobra.Command, _ []string) (err error) { } func listComponents() error { - cli.StartProgress("Loading component Catalog...") - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, true) - defer catalog.Persist() - - cli.StopProgress() + catalog, err := LoadCatalog() if err != nil { return errors.Wrap(err, "unable to load component Catalog") } @@ -379,8 +367,6 @@ func listComponents() error { printComponents(catalog.PrintComponents()) - cli.OutputHuman("\nComponents version: %s\n", cli.LwComponents.Version) - return nil } @@ -423,12 +409,7 @@ func installComponent(cmd *cobra.Command, args []string) (err error) { cli.Event.Feature = "install_component" defer cli.SendHoneyvent() - cli.StartProgress("Loading component Catalog...") - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) - defer catalog.Persist() - - cli.StopProgress() + catalog, err := LoadCatalog() if err != nil { err = errors.Wrap(err, "unable to load component Catalog") return @@ -524,12 +505,7 @@ func showComponent(args []string) error { componentName string = args[0] ) - cli.StartProgress("Loading components Catalog...") - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) - defer catalog.Persist() - - cli.StopProgress() + catalog, err := LoadCatalog() if err != nil { return errors.Wrap(err, "unable to load component Catalog") } @@ -598,12 +574,7 @@ func updateComponent(args []string) (err error) { targetVersion *semver.Version ) - cli.StartProgress("Loading components Catalog...") - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) - defer catalog.Persist() - - cli.StopProgress() + catalog, err := LoadCatalog() if err != nil { return errors.Wrap(err, "unable to load component Catalog") } @@ -724,12 +695,7 @@ func deleteComponent(args []string) (err error) { componentName string = args[0] ) - cli.StartProgress("Loading components Catalog...") - - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) - defer catalog.Persist() - - cli.StopProgress() + catalog, err := LoadCatalog() if err != nil { return errors.Wrap(err, "unable to load component Catalog") } @@ -1215,3 +1181,37 @@ func downloadProgress(complete chan int8, path string, sizeB int64) { time.Sleep(time.Second) } } + +func LoadCatalog() (*lwcomponent.Catalog, error) { + cli.StartProgress("Loading component catalog...") + defer cli.StopProgress() + + var componentsApiInfo map[string]*lwcomponent.ApiInfo + + // try to load components Catalog from cache + if !cli.noCache { + expired := cli.ReadCachedAsset(componentsCacheKey, &componentsApiInfo) + if !expired { + cli.Log.Infow("loaded components from cache", "components", componentsApiInfo) + return lwcomponent.NewCachedCatalog(cli.LwApi, lwcomponent.NewStageTarGz, componentsApiInfo) + } + } + + // load components Catalog from API + catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, true) + if err != nil { + return nil, err + } + + componentsApiInfo = make(map[string]*lwcomponent.ApiInfo, len(catalog.Components)) + + for _, c := range catalog.Components { + if c.ApiInfo != nil { + componentsApiInfo[c.Name] = c.ApiInfo + } + } + + cli.WriteAssetToCache(componentsCacheKey, time.Now().Add(time.Hour*12), componentsApiInfo) + + return catalog, nil +} diff --git a/cli/cmd/component_dev.go b/cli/cmd/component_dev.go index 018624555..f6e149235 100644 --- a/cli/cmd/component_dev.go +++ b/cli/cmd/component_dev.go @@ -92,8 +92,7 @@ func devModeComponent(args []string) error { cli.StartProgress("Loading components Catalog...") - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz, false) - defer catalog.Persist() + catalog, err := LoadCatalog() cli.StopProgress() if err != nil { diff --git a/integration/container_vulnerability_test.go b/integration/container_vulnerability_test.go index 13ea6e5b3..f7ab1e620 100644 --- a/integration/container_vulnerability_test.go +++ b/integration/container_vulnerability_test.go @@ -205,7 +205,9 @@ func TestContainerVulnerabilityCommandScanErrorContainerImageNotFound(t *testing "EXITCODE is not the expected one") } -func TestContainerVulnerabilityCommandScanFailOnSeverity(t *testing.T) { +// Disabled due to the api timeout issue +// TODO: re-enable when timeout is fixed +func _TestContainerVulnerabilityCommandScanFailOnSeverity(t *testing.T) { home := createTOMLConfigFromCIvars() defer os.RemoveAll(home) _, err, exitcode := LaceworkCLIWithHome(home, diff --git a/lwcomponent/api_info.go b/lwcomponent/api_info.go index 0b2ea847d..728b1d690 100644 --- a/lwcomponent/api_info.go +++ b/lwcomponent/api_info.go @@ -4,27 +4,15 @@ import ( "github.com/Masterminds/semver" ) -type ApiInfo interface { - Id() int32 - - LatestVersion() *semver.Version - - AllVersions() []*semver.Version - - Deprecated() bool - - Size() int64 -} - -type apiInfo struct { - id int32 - name string - version semver.Version - allVersions []*semver.Version - desc string - sizeKB int64 - deprecated bool - componentType Type +type ApiInfo struct { + Id int32 `json:"id"` + Name string `json:"name"` + Version *semver.Version `json:"version"` + AllVersions []*semver.Version `json:"allVersions"` + Desc string `json:"desc"` + SizeKB int64 `json:"sizeKB"` + Deprecated bool `json:"deprecated"` + ComponentType Type `json:"componentType"` } func NewAPIInfo( @@ -36,39 +24,15 @@ func NewAPIInfo( size int64, deprecated bool, componentType Type, -) ApiInfo { - return &apiInfo{ - id: id, - name: name, - version: *version, - allVersions: allVersions, - desc: desc, - sizeKB: size, - deprecated: deprecated, - componentType: componentType, +) *ApiInfo { + return &ApiInfo{ + Id: id, + Name: name, + Version: version, + AllVersions: allVersions, + Desc: desc, + SizeKB: size, + Deprecated: deprecated, + ComponentType: componentType, } } - -func (a *apiInfo) Id() int32 { - return a.id -} - -// AllVersions implements ApiInfo. -func (a *apiInfo) AllVersions() []*semver.Version { - return a.allVersions -} - -// LatestVersion implements ApiInfo. -func (a *apiInfo) LatestVersion() *semver.Version { - return &a.version -} - -// Deprecated implements ApiInfo. -func (a *apiInfo) Deprecated() bool { - return a.deprecated -} - -// Size implements ApiInfo. -func (a *apiInfo) Size() int64 { - return a.sizeKB -} diff --git a/lwcomponent/api_info_test.go b/lwcomponent/api_info_test.go index 15d1f4ee9..c90b3d99d 100644 --- a/lwcomponent/api_info_test.go +++ b/lwcomponent/api_info_test.go @@ -19,7 +19,7 @@ func TestApiInfoId(t *testing.T) { info := lwcomponent.NewAPIInfo(id, "test", version, allVersions, "", 0, false, lwcomponent.BinaryType) - result := info.Id() + result := info.Id assert.Equal(t, id, result) } @@ -34,6 +34,6 @@ func TestApiInfoLatestVersion(t *testing.T) { info := lwcomponent.NewAPIInfo(1, "test", version, allVersions, "", 0, false, lwcomponent.BinaryType) - result := info.LatestVersion() + result := info.Version assert.Equal(t, expectedVer, result.String()) } diff --git a/lwcomponent/catalog.go b/lwcomponent/catalog.go index 836d54799..ce8bdea37 100644 --- a/lwcomponent/catalog.go +++ b/lwcomponent/catalog.go @@ -15,6 +15,7 @@ import ( const ( componentCacheDir string = "components" + cdkCacheName string = "cdk_cache" featureFlag string = "PUBLIC.cdk.v1" operatingSystem string = runtime.GOOS architecture string = runtime.GOARCH @@ -58,10 +59,6 @@ func (c *Catalog) ComponentCount() int { return len(c.Components) } -func (c *Catalog) Persist() { - // @jon-stewart: TODO: store catalog on disk -} - // Return a CDKComponent that is present on the host. func (c *Catalog) GetComponent(name string) (*CDKComponent, error) { component, exists := c.Components[name] @@ -74,16 +71,16 @@ func (c *Catalog) GetComponent(name string) (*CDKComponent, error) { func (c *Catalog) ListComponentVersions(component *CDKComponent) (versions []*semver.Version, err error) { if component.ApiInfo == nil { - err = errors.Errorf("component '%s' api info already installed", component.Name) + err = errors.Errorf("component '%s' api info not available", component.Name) return } - versions = component.ApiInfo.AllVersions() + versions = component.ApiInfo.AllVersions if versions != nil { - return versions, nil + return } - return listComponentVersions(c.client, component.ApiInfo.Id()) + return listComponentVersions(c.client, component.ApiInfo.Id) } func (c *Catalog) PrintComponents() [][]string { @@ -107,7 +104,7 @@ func (c *Catalog) Stage( stageClose = func() {} if version == "" { - semv = component.ApiInfo.LatestVersion() + semv = component.ApiInfo.Version } else { semv, err = semver.NewVersion(version) if err != nil { @@ -130,7 +127,7 @@ func (c *Catalog) Stage( } response, err := c.client.V2.Components.FetchComponentArtifact( - component.ApiInfo.Id(), + component.ApiInfo.Id, operatingSystem, architecture, semv.String()) @@ -213,6 +210,13 @@ func (c *Catalog) Install(component *CDKComponent) (err error) { return } + if component.ApiInfo != nil && + (component.ApiInfo.ComponentType == BinaryType || component.ApiInfo.ComponentType == CommandType) { + if err := os.Chmod(filepath.Join(componentDir, component.Name), 0744); err != nil { + return errors.Wrap(err, "unable to make component executable") + } + } + component.HostInfo = NewHostInfo(componentDir) return @@ -244,12 +248,7 @@ func NewCatalog( includeComponentVersions bool, ) (*Catalog, error) { if stageConstructor == nil { - return nil, errors.New("nil Catalog StageConstructor") - } - - localComponents, err := loadLocalComponents() - if err != nil { - return nil, err + return nil, errors.New("StageConstructor is not specified to create new catalog") } response, err := client.V2.Components.ListComponents(operatingSystem, architecture) @@ -263,7 +262,7 @@ func NewCatalog( rawComponents = response.Data[0].Components } - cdkComponents := make(map[string]CDKComponent, len(rawComponents)+len(localComponents)) + cdkComponents := make(map[string]CDKComponent, len(rawComponents)) for _, c := range rawComponents { ver, err := semver.NewVersion(c.Version) @@ -279,63 +278,68 @@ func NewCatalog( } } - api := NewAPIInfo(c.Id, c.Name, ver, allVersions, c.Description, c.Size, c.Deprecated, Type(c.ComponentType)) + apiInfo := NewAPIInfo(c.Id, c.Name, ver, allVersions, c.Description, c.Size, c.Deprecated, Type(c.ComponentType)) + cdkComponents[c.Name] = NewCDKComponent(c.Name, c.Description, Type(c.ComponentType), apiInfo, nil) + } - host, found := localComponents[c.Name] - if found { - delete(localComponents, c.Name) - } + components, err := mergeComponents(cdkComponents) + if err != nil { + return nil, err + } - component := NewCDKComponent(c.Name, c.Description, Type(c.ComponentType), api, host) + return &Catalog{client, components, stageConstructor}, nil +} - cdkComponents[c.Name] = component +func NewCachedCatalog( + client *api.Client, + stageConstructor StageConstructor, + cachedComponentsApiInfo map[string]*ApiInfo, +) (*Catalog, error) { + if stageConstructor == nil { + return nil, errors.New("StageConstructor is not specified to create new catalog") } - for _, localHost := range localComponents { - if localHost.Development() { - devInfo, err := NewDevInfo(localHost.Dir()) - if err != nil { - return nil, err - } + cachedComponents := make(map[string]CDKComponent, len(cachedComponentsApiInfo)) - cdkComponents[localHost.Name()] = NewCDKComponent( - localHost.Name(), - devInfo.Desc, - devInfo.ComponentType, - nil, - localHost) - } else { - // @jon-stewart: persisted API info - cdkComponents[localHost.Name()] = NewCDKComponent(localHost.Name(), "", BinaryType, nil, localHost) - } + for _, a := range cachedComponentsApiInfo { + cachedComponents[a.Name] = NewCDKComponent(a.Name, a.Desc, a.ComponentType, a, nil) + } + + components, err := mergeComponents(cachedComponents) + if err != nil { + return nil, err } - return &Catalog{client, cdkComponents, stageConstructor}, nil + return &Catalog{client, components, stageConstructor}, nil } -func LocalComponents() (components []CDKComponent, err error) { - var localHost map[string]HostInfo +// mergeComponents combines the passed in components with the local components +func mergeComponents(components map[string]CDKComponent) (allComponents map[string]CDKComponent, err error) { + localComponents, err := LoadLocalComponents() + if err != nil { + return + } - localHost, err = loadLocalComponents() + allComponents = make(map[string]CDKComponent, len(localComponents)+len(components)) - for _, l := range localHost { - if l.Development() { - devInfo, err := NewDevInfo(l.Dir()) - if err != nil { - return nil, err - } - - components = append(components, NewCDKComponent(l.Name(), devInfo.Desc, devInfo.ComponentType, nil, l)) - } else { - // @jon-stewart: persisted API info - components = append(components, NewCDKComponent(l.Name(), "", BinaryType, nil, l)) + for _, c := range components { + var hostInfo *HostInfo + component, ok := localComponents[c.Name] + if ok { + hostInfo = component.HostInfo + delete(localComponents, c.Name) } + allComponents[c.Name] = NewCDKComponent(c.Name, c.Description, c.Type, c.ApiInfo, hostInfo) + } + + for _, c := range localComponents { + allComponents[c.Name] = c } return } -func loadLocalComponents() (local map[string]HostInfo, err error) { +func LoadLocalComponents() (components map[string]CDKComponent, err error) { cacheDir, err := CatalogCacheDir() if err != nil { return @@ -346,14 +350,25 @@ func loadLocalComponents() (local map[string]HostInfo, err error) { return } - local = make(map[string]HostInfo, len(subDir)) + components = make(map[string]CDKComponent, len(subDir)) for _, file := range subDir { if !file.IsDir() { continue } - local[file.Name()] = NewHostInfo(filepath.Join(cacheDir, file.Name())) + hostInfo := NewHostInfo(filepath.Join(cacheDir, file.Name())) + + if hostInfo.Development() { + devInfo, err := newDevInfo(hostInfo.Dir) + if err != nil { + return nil, err + } + components[hostInfo.Name()] = NewCDKComponent(hostInfo.Name(), devInfo.Desc, devInfo.ComponentType, nil, hostInfo) + } else { + // Unknown components + components[hostInfo.Name()] = NewCDKComponent(hostInfo.Name(), "", EmptyType, nil, hostInfo) + } } return diff --git a/lwcomponent/catalog_test.go b/lwcomponent/catalog_test.go index 38f470e85..f7156359f 100644 --- a/lwcomponent/catalog_test.go +++ b/lwcomponent/catalog_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "testing" + "github.com/Masterminds/semver" "github.com/lacework/go-sdk/api" "github.com/lacework/go-sdk/internal/file" "github.com/lacework/go-sdk/internal/lacework" @@ -162,6 +163,81 @@ func TestCatalogNewCatalog(t *testing.T) { } +func TestCatalogNewCachedCatalog(t *testing.T) { + var ( + prefix = "testComponentWithApiInfo" + cachedComponentsCount = 4 + ) + + t.Run("return new catalog with correct components", func(t *testing.T) { + _, home := FakeHome() + defer ResetHome(home) + + fakeServer := lacework.MockServer() + defer fakeServer.Close() + + client, _ := api.NewClient("catalog_test", + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + + allVersions := []*semver.Version{} + versionStrings := []string{"1.0.0", "1.1.1", "3.0.1", "5.4.3"} + for _, ver := range versionStrings { + version, _ := semver.NewVersion(ver) + allVersions = append(allVersions, version) + } + latestVersion := allVersions[len(allVersions)-1] + + cachedComponentsApiInfo := make(map[string]*lwcomponent.ApiInfo, cachedComponentsCount) + for i := 0; i < cachedComponentsCount; i++ { + name := fmt.Sprintf("%s-%d", prefix, i) + cachedComponentsApiInfo[name] = lwcomponent.NewAPIInfo(1, name, latestVersion, allVersions, "", 1, false, lwcomponent.BinaryType) + } + + CreateLocalComponent("testComponentWithApiInfo-0", "5.4.3", false) + CreateLocalComponent("testComponentWithApiInfo-1", "1.0.0", false) + CreateLocalComponent("testComponentWithApiInfo-2", "2.0.1", false) + CreateLocalComponent("testComponentWithApiInfo-3", "3.0.1", true) + CreateLocalComponent("testComponent", "0.0.1-dev", true) + + catalog, err := lwcomponent.NewCachedCatalog(client, newTestStage, cachedComponentsApiInfo) + assert.NotNil(t, catalog) + assert.Equal(t, 5, catalog.ComponentCount()) + assert.Nil(t, err) + + // `Installed` component should be returned + component, err := catalog.GetComponent("testComponentWithApiInfo-0") + assert.NotNil(t, component) + assert.Nil(t, err) + assert.Equal(t, lwcomponent.Installed, component.Status) + + // `UpdateAvailable` component should be returned + component, err = catalog.GetComponent("testComponentWithApiInfo-1") + assert.NotNil(t, component) + assert.Nil(t, err) + assert.Equal(t, lwcomponent.UpdateAvailable, component.Status) + + // `Tainted` component should be returned + component, err = catalog.GetComponent("testComponentWithApiInfo-2") + assert.NotNil(t, component) + assert.Nil(t, err) + assert.Equal(t, lwcomponent.Tainted, component.Status) + + // `Development` component should be returned + component, err = catalog.GetComponent("testComponentWithApiInfo-3") + assert.NotNil(t, component) + assert.Nil(t, err) + assert.Equal(t, lwcomponent.Development, component.Status) + + // `Development` local component should be returned + component, err = catalog.GetComponent("testComponent") + assert.NotNil(t, component) + assert.Nil(t, err) + assert.Equal(t, lwcomponent.Development, component.Status) + }) +} + func TestCatalogComponentCount(t *testing.T) { var ( prefix = "testCount" diff --git a/lwcomponent/cdk_component.go b/lwcomponent/cdk_component.go index 92cd55ac0..aeadfdcc6 100644 --- a/lwcomponent/cdk_component.go +++ b/lwcomponent/cdk_component.go @@ -18,21 +18,21 @@ const ( ) type CDKComponent struct { - Name string - Description string - Type Type - Status Status - InstallMessage string - UpdateMessage string + Name string `json:"name"` + Description string `json:"description"` + Type Type `json:"type"` + Status Status `json:"-"` + InstallMessage string `json:"-"` + UpdateMessage string `json:"-"` - Exec Executer + Exec Executer `json:"-"` - ApiInfo ApiInfo - HostInfo HostInfo + ApiInfo *ApiInfo `json:"apiInfo,omitempty"` + HostInfo *HostInfo `json:"-"` stage Stager } -func NewCDKComponent(name string, desc string, componentType Type, apiInfo ApiInfo, hostInfo HostInfo) CDKComponent { +func NewCDKComponent(name string, desc string, componentType Type, apiInfo *ApiInfo, hostInfo *HostInfo) CDKComponent { var ( exec Executer = &nonExecutable{} ) @@ -42,7 +42,7 @@ func NewCDKComponent(name string, desc string, componentType Type, apiInfo ApiIn switch status { case Installed, UpdateAvailable, InstalledDeprecated, Development: { - dir := hostInfo.Dir() + dir := hostInfo.Dir if componentType == BinaryType || componentType == CommandType { exec = NewExecuable(name, dir) @@ -116,6 +116,15 @@ func (c *CDKComponent) InstalledVersion() (version *semver.Version) { if err == nil { return } + + if componentDir, err := c.Dir(); err == nil { + if devInfo, err := newDevInfo(componentDir); err == nil { + version, err = semver.NewVersion(devInfo.Version) + if err == nil { + return + } + } + } } return @@ -123,7 +132,7 @@ func (c *CDKComponent) InstalledVersion() (version *semver.Version) { func (c *CDKComponent) LatestVersion() (version *semver.Version) { if c.ApiInfo != nil { - version = c.ApiInfo.LatestVersion() + version = c.ApiInfo.Version } return @@ -133,26 +142,13 @@ func (c *CDKComponent) PrintSummary() []string { var ( colorize *color.Color version *semver.Version - err error ) switch c.Status { - case Installed, InstalledDeprecated, NotInstalledDeprecated, UpdateAvailable, Tainted: - version, err = c.HostInfo.Version() - if err != nil { - panic(err) - } - case NotInstalled: - version = c.ApiInfo.LatestVersion() - case Development: - devInfo, err := NewDevInfo(c.HostInfo.Dir()) - if err != nil { - panic(err) - } - version, err = semver.NewVersion(devInfo.Version) - if err != nil { - panic(err) - } + case Installed, InstalledDeprecated, UpdateAvailable, Development, Tainted: + version = c.InstalledVersion() + case NotInstalled, NotInstalledDeprecated: + version = c.ApiInfo.Version default: version = &semver.Version{} } @@ -167,7 +163,7 @@ func (c *CDKComponent) PrintSummary() []string { } } -func status(apiInfo ApiInfo, hostInfo HostInfo) Status { +func status(apiInfo *ApiInfo, hostInfo *HostInfo) Status { status := UnknownStatus if hostInfo != nil { @@ -189,11 +185,11 @@ func status(apiInfo ApiInfo, hostInfo HostInfo) Status { return Tainted } - if apiInfo.Deprecated() { + if apiInfo.Deprecated { return InstalledDeprecated } - latestVer := apiInfo.LatestVersion() + latestVer := apiInfo.Version if latestVer.GreaterThan(installedVer) { return UpdateAvailable } else { @@ -205,7 +201,7 @@ func status(apiInfo ApiInfo, hostInfo HostInfo) Status { } if apiInfo != nil && hostInfo == nil { - if apiInfo.Deprecated() { + if apiInfo.Deprecated { return NotInstalledDeprecated } @@ -215,8 +211,8 @@ func status(apiInfo ApiInfo, hostInfo HostInfo) Status { return status } -func isTainted(apiInfo ApiInfo, installedVer *semver.Version) bool { - for _, ver := range apiInfo.AllVersions() { +func isTainted(apiInfo *ApiInfo, installedVer *semver.Version) bool { + for _, ver := range apiInfo.AllVersions { if ver.Equal(installedVer) { return false } diff --git a/lwcomponent/cdk_executable.go b/lwcomponent/cdk_executable.go index ad9fc7472..01ef2a3e6 100644 --- a/lwcomponent/cdk_executable.go +++ b/lwcomponent/cdk_executable.go @@ -21,6 +21,8 @@ type Executer interface { Execute(args []string, envs ...string) (stdout string, stderr string, err error) ExecuteInline(args []string, envs ...string) (err error) + + Path() string } type executable struct { @@ -36,6 +38,10 @@ func NewExecuable(name string, dir string) Executer { return &executable{path: path} } +func (e *executable) Path() string { + return e.path +} + func (e *executable) Executable() bool { return true } @@ -107,3 +113,7 @@ func (e *nonExecutable) Execute(args []string, envs ...string) (stdout string, s func (e *nonExecutable) ExecuteInline(args []string, envs ...string) (err error) { return ErrNonExecutable } + +func (e *nonExecutable) Path() string { + return "" +} diff --git a/lwcomponent/dev_info.go b/lwcomponent/dev_info.go index e5841260b..2b05c7846 100644 --- a/lwcomponent/dev_info.go +++ b/lwcomponent/dev_info.go @@ -17,7 +17,7 @@ type DevInfo struct { Version string } -func NewDevInfo(dir string) (*DevInfo, error) { +func newDevInfo(dir string) (*DevInfo, error) { path := filepath.Join(dir, DevelopmentFile) data, err := os.ReadFile(path) diff --git a/lwcomponent/host_info.go b/lwcomponent/host_info.go index 3e0d78aa2..3c2cb4556 100644 --- a/lwcomponent/host_info.go +++ b/lwcomponent/host_info.go @@ -17,56 +17,36 @@ var ( DevelopmentFile = ".dev" ) -type HostInfo interface { - Delete() error - - Development() bool - - Dir() string - - Name() string - - Signature() (sig []byte, err error) - - Validate() error - - Version() (*semver.Version, error) +type HostInfo struct { + Dir string } -type hostInfo struct { - dir string +func NewHostInfo(dir string) *HostInfo { + return &HostInfo{dir} } -func NewHostInfo(dir string) HostInfo { - return &hostInfo{dir} +func (h *HostInfo) Delete() error { + return os.RemoveAll(h.Dir) } -func (h *hostInfo) Delete() error { - return os.RemoveAll(h.dir) -} - -func (h *hostInfo) Development() bool { - return file.FileExists(filepath.Join(h.dir, DevelopmentFile)) -} - -func (h *hostInfo) Dir() string { - return h.dir +func (h *HostInfo) Development() bool { + return file.FileExists(filepath.Join(h.Dir, DevelopmentFile)) } // Returns the Component name // // The Component name is the same as the name of the base directory -func (h *hostInfo) Name() string { - return filepath.Base(h.dir) +func (h *HostInfo) Name() string { + return filepath.Base(h.Dir) } -func (h *hostInfo) Signature() (sig []byte, err error) { - _, err = os.Stat(h.dir) +func (h *HostInfo) Signature() (sig []byte, err error) { + _, err = os.Stat(h.Dir) if os.IsNotExist(err) { return } - path := filepath.Join(h.dir, SignatureFile) + path := filepath.Join(h.Dir, SignatureFile) if !file.FileExists(path) { return } @@ -79,15 +59,15 @@ func (h *hostInfo) Signature() (sig []byte, err error) { return } -func (h *hostInfo) Version() (version *semver.Version, err error) { - _, err = os.Stat(h.dir) +func (h *HostInfo) Version() (version *semver.Version, err error) { + _, err = os.Stat(h.Dir) if os.IsNotExist(err) { return } - path := filepath.Join(h.dir, VersionFile) + path := filepath.Join(h.Dir, VersionFile) if !file.FileExists(path) { - return + return nil, errors.New("missing .version file") } data, err := os.ReadFile(path) @@ -98,8 +78,8 @@ func (h *hostInfo) Version() (version *semver.Version, err error) { return semver.NewVersion(strings.TrimSpace(string(data))) } -func (h *hostInfo) Validate() (err error) { - data, err := os.ReadFile(filepath.Join(h.dir, VersionFile)) +func (h *HostInfo) Validate() (err error) { + data, err := os.ReadFile(filepath.Join(h.Dir, VersionFile)) if err != nil { return } @@ -113,11 +93,11 @@ func (h *hostInfo) Validate() (err error) { componentName := h.Name() - if !file.FileExists(filepath.Join(h.dir, SignatureFile)) { + if !file.FileExists(filepath.Join(h.Dir, SignatureFile)) { return errors.New(fmt.Sprintf("missing file '%s'", componentName)) } - if !file.FileExists(filepath.Join(h.dir, componentName)) { + if !file.FileExists(filepath.Join(h.Dir, componentName)) { return errors.New(fmt.Sprintf("missing file '%s'", componentName)) }