diff --git a/cmd/dbc/install.go b/cmd/dbc/install.go index e15e1e0..85f8b1e 100644 --- a/cmd/dbc/install.go +++ b/cmd/dbc/install.go @@ -119,6 +119,8 @@ type writeDriverManifestMsg struct { DriverInfo config.DriverInfo } +type localInstallMsg struct{} + type installState int const ( @@ -162,9 +164,16 @@ type progressiveInstallModel struct { p dbc.FileProgressModel width, height int + isLocal bool } func (m progressiveInstallModel) Init() tea.Cmd { + if strings.HasSuffix(m.Driver, ".tar.gz") || strings.HasSuffix(m.Driver, ".tgz") { + return tea.Batch(m.spinner.Tick, func() tea.Msg { + return localInstallMsg{} + }) + } + return tea.Batch(m.spinner.Tick, func() tea.Msg { drivers, err := m.getDriverRegistry() if err != nil { @@ -281,6 +290,17 @@ func (m progressiveInstallModel) startDownloading() (tea.Model, tea.Cmd) { func (m progressiveInstallModel) startInstalling(downloaded *os.File) (tea.Model, tea.Cmd) { m.state = stInstalling + if m.isLocal { + driverName := strings.TrimSuffix( + strings.TrimSuffix(filepath.Base(m.Driver), ".tar.gz"), ".tgz") + parts := strings.Split(driverName, "_"+config.PlatformTuple()+"_") + if len(parts) < 2 { + m.Driver = driverName + } else { + m.Driver = parts[0] // drivername_platform_arch_version grab drivername + } + } + return m, func() tea.Msg { if m.conflictingInfo.ID != "" { if err := config.UninstallDriver(m.cfg, m.conflictingInfo); err != nil { @@ -313,6 +333,17 @@ func (m progressiveInstallModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd case []dbc.Driver: return m.searchForDriver(msg) + case localInstallMsg: + m.isLocal = true + return m, tea.Sequence( + tea.Printf("Installing from local package: %s\n", m.Driver), + func() tea.Msg { + localDrv, err := os.Open(m.Driver) + if err != nil { + return err + } + return localDrv + }) case dbc.PkgInfo: m.DriverPackage = msg di, err := config.GetDriver(m.cfg, m.Driver) @@ -324,6 +355,10 @@ func (m progressiveInstallModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case *os.File: return m.startInstalling(msg) case config.Manifest: + if m.DriverPackage.Version == nil { + m.DriverPackage = msg.ToPackageInfo() + } + m.state = stVerifying m.postInstallMessage = strings.Join(msg.PostInstall.Messages, "\n") return m, func() tea.Msg { @@ -372,6 +407,10 @@ func (m progressiveInstallModel) View() string { var b strings.Builder for s := range stDone { + if m.isLocal && (s == stSearching || s == stDownloading) { + continue + } + if s == m.state { fmt.Fprintf(&b, "[%s] %s...", m.spinner.View(), s.String()) if s == stDownloading { diff --git a/cmd/dbc/install_test.go b/cmd/dbc/install_test.go index f4c0ac7..e8568a3 100644 --- a/cmd/dbc/install_test.go +++ b/cmd/dbc/install_test.go @@ -262,3 +262,62 @@ func (suite *SubcommandTestSuite) TestInstallCreatesSymlinks() { suite.NoError(err) suite.Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink, "Expected test-driver-1.toml to be a symlink") } + +func (suite *SubcommandTestSuite) TestInstallLocalPackage() { + packagePath := filepath.Join("testdata", "test-driver-1.tar.gz") + m := InstallCmd{Driver: packagePath, Level: suite.configLevel}. + GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg}) + out := suite.runCmd(m) + + suite.validateOutput("Installing from local package: "+packagePath+"\r\n\r\n\r"+ + "[✓] installing\r\n[✓] verifying signature\r\n", + "\nInstalled test-driver-1 1.0.0 to "+suite.Dir()+"\n", out) + suite.driverIsInstalled("test-driver-1", true) +} + +func (suite *SubcommandTestSuite) TestInstallLocalPackageNotFound() { + packagePath := filepath.Join("testdata", "test-driver-2.tar.gz") + m := InstallCmd{Driver: packagePath, Level: suite.configLevel}. + GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg}) + out := suite.runCmdErr(m) + + errmsg := "no such file or directory" + if runtime.GOOS == "windows" { + errmsg = "The system cannot find the file specified." + } + suite.validateOutput("Installing from local package: "+packagePath+"\r\n\r\nError: open "+packagePath+ + ": "+errmsg+"\r\n\r ", "", out) + suite.driverIsNotInstalled("test-driver-2") +} + +func (suite *SubcommandTestSuite) TestInstallLocalPackageNoSignature() { + packagePath := filepath.Join("testdata", "test-driver-no-sig.tar.gz") + m := InstallCmd{Driver: packagePath}. + GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg}) + out := suite.runCmdErr(m) + suite.Contains(out, "signature file 'test-driver-1-not-valid.so.sig' for driver is missing") + + suite.Empty(suite.getFilesInTempDir()) + suite.NoDirExists(filepath.Join(suite.tempdir, "test-driver-no-sig")) + + m = InstallCmd{Driver: packagePath, NoVerify: true}. + GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg}) + suite.validateOutput("Installing from local package: "+packagePath+"\r\n\r\n\r"+ + "[✓] installing\r\n[✓] verifying signature\r\n", + "\nInstalled test-driver-no-sig 1.1.0 to "+suite.tempdir+"\n", suite.runCmd(m)) +} + +func (suite *SubcommandTestSuite) TestInstallLocalPackageFixUpName() { + origPackagePath, err := filepath.Abs(filepath.Join("testdata", "test-driver-1.tar.gz")) + suite.Require().NoError(err) + packagePath := filepath.Join(suite.tempdir, "test-driver-1_"+config.PlatformTuple()+"_v1.0.0.tgz") + suite.Require().NoError(os.Symlink(origPackagePath, packagePath)) + m := InstallCmd{Driver: packagePath, Level: suite.configLevel}. + GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg}) + out := suite.runCmd(m) + + suite.validateOutput("Installing from local package: "+packagePath+"\r\n\r\n\r"+ + "[✓] installing\r\n[✓] verifying signature\r\n", + "\nInstalled test-driver-1 1.0.0 to "+suite.Dir()+"\n", out) + suite.driverIsInstalled("test-driver-1", true) +} diff --git a/config/config.go b/config/config.go index 63efd29..e9dded5 100644 --- a/config/config.go +++ b/config/config.go @@ -220,7 +220,7 @@ func InstallDriver(cfg Config, shortName string, downloaded *os.File) (Manifest, if loc, err = EnsureLocation(cfg); err != nil { return Manifest{}, fmt.Errorf("could not ensure config location: %w", err) } - base := strings.TrimSuffix(filepath.Base(downloaded.Name()), ".tar.gz") + base := strings.TrimSuffix(strings.TrimSuffix(filepath.Base(downloaded.Name()), ".tar.gz"), ".tgz") finalDir := filepath.Join(loc, base) if err := os.MkdirAll(finalDir, 0o755); err != nil { diff --git a/config/driver.go b/config/driver.go index a78d767..f74b74b 100644 --- a/config/driver.go +++ b/config/driver.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/Masterminds/semver/v3" + "github.com/columnar-tech/dbc" "github.com/pelletier/go-toml/v2" ) @@ -42,6 +43,17 @@ type Manifest struct { } `toml:"PostInstall,omitempty"` } +func (m Manifest) ToPackageInfo() dbc.PkgInfo { + return dbc.PkgInfo{ + Driver: dbc.Driver{ + Title: m.Name, + Path: m.ID, + License: m.License, + }, + Version: m.Version, + } +} + type DriverInfo struct { ID string FilePath string