diff --git a/cmd/cardano-up/test.go b/cmd/cardano-up/install.go similarity index 51% rename from cmd/cardano-up/test.go rename to cmd/cardano-up/install.go index d6e81ba..168593b 100644 --- a/cmd/cardano-up/test.go +++ b/cmd/cardano-up/install.go @@ -1,4 +1,4 @@ -// Copyright 2023 Blink Labs Software +// Copyright 2024 Blink Labs Software // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,41 +20,41 @@ import ( "os" "github.com/blinklabs-io/cardano-up/pkgmgr" - "github.com/blinklabs-io/cardano-up/pkgmgr/service" "github.com/spf13/cobra" ) -func testCommand() *cobra.Command { +func installCommand() *cobra.Command { return &cobra.Command{ - Use: "test", - Short: "Test subcommand for development work (TODO: remove me)", + Use: "install", + Short: "Install package", Run: func(cmd *cobra.Command, args []string) { - pm, err := pkgmgr.NewDefaultPackageManager() - if err != nil { - slog.Error(fmt.Sprintf("failed to create package manager: %s", err)) - os.Exit(1) - } - slog.Debug(fmt.Sprintf("pm = %#v", pm)) - svc, err := service.NewDockerServiceFromContainerName("cadvisor", slog.Default()) - if err != nil { - slog.Error(fmt.Sprintf("failed to get Docker service: %s", err)) + if len(args) == 0 { + slog.Error("no package provided") os.Exit(1) } - slog.Debug(fmt.Sprintf("svc = %#v", svc)) - if err := svc.Stop(); err != nil { - slog.Error(fmt.Sprintf("failed to stop service: %s", err)) + if len(args) > 1 { + slog.Error("only one package may be specified a a time") os.Exit(1) } - if err := svc.Remove(); err != nil { - slog.Error(fmt.Sprintf("failed to remove service: %s", err)) + pm, err := pkgmgr.NewDefaultPackageManager() + if err != nil { + slog.Error(fmt.Sprintf("failed to create package manager: %s", err)) os.Exit(1) } - if err := svc.Create(); err != nil { - slog.Error(fmt.Sprintf("failed to create service: %s", err)) - os.Exit(1) + packages := pm.AvailablePackages() + foundPackage := false + for _, tmpPackage := range packages { + if tmpPackage.Name == args[0] { + foundPackage = true + if err := pm.Install(tmpPackage); err != nil { + slog.Error(err.Error()) + os.Exit(1) + } + break + } } - if err := svc.Start(); err != nil { - slog.Error(fmt.Sprintf("failed to start service: %s", err)) + if !foundPackage { + slog.Error(fmt.Sprintf("no such package: %s", args[0])) os.Exit(1) } }, diff --git a/cmd/cardano-up/list.go b/cmd/cardano-up/list.go new file mode 100644 index 0000000..e946f45 --- /dev/null +++ b/cmd/cardano-up/list.go @@ -0,0 +1,44 @@ +// Copyright 2024 Blink Labs Software +// +// 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 main + +import ( + "fmt" + "log/slog" + "os" + + "github.com/blinklabs-io/cardano-up/pkgmgr" + "github.com/spf13/cobra" +) + +func listAvailableCommand() *cobra.Command { + return &cobra.Command{ + Use: "list-available", + Short: "List available packages", + Run: func(cmd *cobra.Command, args []string) { + pm, err := pkgmgr.NewDefaultPackageManager() + if err != nil { + slog.Error(fmt.Sprintf("failed to create package manager: %s", err)) + os.Exit(1) + } + packages := pm.AvailablePackages() + pkgOutput := "Available packages:\n\n" + for _, tmpPackage := range packages { + pkgOutput += fmt.Sprintf("%s (%s) %s\n", tmpPackage.Name, tmpPackage.Version, tmpPackage.Description) + } + slog.Info(pkgOutput) + }, + } +} diff --git a/cmd/cardano-up/main.go b/cmd/cardano-up/main.go index cf89723..c1e9b3c 100644 --- a/cmd/cardano-up/main.go +++ b/cmd/cardano-up/main.go @@ -60,8 +60,8 @@ func main() { // Add subcommands rootCmd.AddCommand( versionCommand(), - // TODO: remove me - testCommand(), + listAvailableCommand(), + installCommand(), ) if err := rootCmd.Execute(); err != nil { diff --git a/cmd/cardano-up/version.go b/cmd/cardano-up/version.go index 48e791d..abf9482 100644 --- a/cmd/cardano-up/version.go +++ b/cmd/cardano-up/version.go @@ -1,4 +1,4 @@ -// Copyright 2023 Blink Labs Software +// Copyright 2024 Blink Labs Software // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/internal/version/version.go b/internal/version/version.go index 5a10b82..6aaab6d 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,4 +1,4 @@ -// Copyright 2023 Blink Labs Software +// Copyright 2024 Blink Labs Software // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkgmgr/service/docker.go b/pkgmgr/docker.go similarity index 99% rename from pkgmgr/service/docker.go rename to pkgmgr/docker.go index 25f9de9..b134132 100644 --- a/pkgmgr/service/docker.go +++ b/pkgmgr/docker.go @@ -1,4 +1,4 @@ -// Copyright 2023 Blink Labs Software +// Copyright 2024 Blink Labs Software // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package service +package pkgmgr import ( "context" diff --git a/pkgmgr/error.go b/pkgmgr/error.go new file mode 100644 index 0000000..3ade97e --- /dev/null +++ b/pkgmgr/error.go @@ -0,0 +1,30 @@ +// Copyright 2024 Blink Labs Software +// +// 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 pkgmgr + +import "errors" + +// ErrOperationFailed is a placeholder error for operations that directly log errors. +// It's used to signify when an operation has failed when the actual error message is +// sent through the provided logger +var ErrOperationFailed = errors.New("the operation has failed") + +// ErrMultipleInstallMethods is returned when a package's install steps specify more than one install method +// on a single install step +var ErrMultipleInstallMethods = errors.New("only one install method may be specified in an install step") + +// ErrNoInstallMethods is returned when a package's install steps include an install step which has no +// recognized install method specified +var ErrNoInstallMethods = errors.New("no supported install method specified on install step") diff --git a/pkgmgr/package.go b/pkgmgr/package.go new file mode 100644 index 0000000..50e6319 --- /dev/null +++ b/pkgmgr/package.go @@ -0,0 +1,118 @@ +// Copyright 2024 Blink Labs Software +// +// 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 pkgmgr + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" +) + +type Package struct { + Name string + Version string + Description string + InstallSteps []PackageInstallStep +} + +func (p Package) install(cfg Config) error { + pkgName := fmt.Sprintf("%s-%s", p.Name, p.Version) + for _, installStep := range p.InstallSteps { + // Make sure only one install method is specified per install step + if installStep.Docker != nil && + installStep.File != nil { + return ErrMultipleInstallMethods + } + if installStep.Docker != nil { + if err := installStep.Docker.install(cfg, pkgName); err != nil { + return err + } + } else if installStep.File != nil { + if err := installStep.File.install(cfg, pkgName); err != nil { + return err + } + } else { + return ErrNoInstallMethods + } + } + return nil +} + +type PackageInstallStep struct { + Docker *PackageInstallStepDocker + File *PackageInstallStepFile +} + +type PackageInstallStepDocker struct { + ContainerName string + Image string + Env map[string]string + Command []string + Args []string + Binds []string + Ports []string +} + +func (p *PackageInstallStepDocker) install(cfg Config, pkgName string) error { + containerName := fmt.Sprintf("%s-%s", pkgName, p.ContainerName) + svc := DockerService{ + logger: cfg.Logger, + ContainerName: containerName, + Image: p.Image, + Env: p.Env, + Command: p.Command, + Args: p.Args, + Binds: p.Binds, + Ports: p.Ports, + } + if err := svc.Create(); err != nil { + return err + } + if err := svc.Start(); err != nil { + return err + } + return nil +} + +type PackageInstallStepFile struct { + Filename string + Content string + Template bool + Mode fs.FileMode +} + +func (p *PackageInstallStepFile) install(cfg Config, pkgName string) error { + // TODO: add templating support + filePath := filepath.Join( + cfg.ConfigDir, + "data", + pkgName, + p.Filename, + ) + parentDir := filepath.Dir(filePath) + if err := os.MkdirAll(parentDir, fs.ModePerm); err != nil { + return err + } + fileMode := fs.ModePerm + if p.Mode > 0 { + fileMode = p.Mode + } + if err := os.WriteFile(filePath, []byte(p.Content), fileMode); err != nil { + return err + } + cfg.Logger.Debug(fmt.Sprintf("wrote file %s", filePath)) + return nil +} diff --git a/pkgmgr/pkgmgr.go b/pkgmgr/pkgmgr.go index e64f999..a2fa4c7 100644 --- a/pkgmgr/pkgmgr.go +++ b/pkgmgr/pkgmgr.go @@ -19,14 +19,10 @@ import ( "fmt" ) -// ErrOperationFailed is a placeholder error for operations that directly log errors. -// It's used to signify when an operation has failed when the actual error message is -// sent through the provided logger -var ErrOperationFailed = errors.New("the operation has failed") - type PackageManager struct { - config Config - state *State + config Config + state *State + availablePackages []Package // TODO } @@ -58,9 +54,19 @@ func (p *PackageManager) init() error { if err := p.state.Load(); err != nil { return fmt.Errorf("failed to load state: %s", err) } + // TODO: replace this with syncing a repo and reading from disk + p.availablePackages = append(p.availablePackages, RegistryPackages...) // TODO: remove me if err := p.state.Save(); err != nil { return err } return nil } + +func (p *PackageManager) AvailablePackages() []Package { + return p.availablePackages[:] +} + +func (p *PackageManager) Install(pkg Package) error { + return pkg.install(p.config) +} diff --git a/pkgmgr/registry.go b/pkgmgr/registry.go new file mode 100644 index 0000000..7ac2a90 --- /dev/null +++ b/pkgmgr/registry.go @@ -0,0 +1,46 @@ +// Copyright 2024 Blink Labs Software +// +// 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 pkgmgr + +var RegistryPackages = []Package{ + { + Name: "cardano-node", + Version: "8.7.3", + Description: "Cardano node software by Input Output Global", + InstallSteps: []PackageInstallStep{ + { + Docker: &PackageInstallStepDocker{ + ContainerName: "cardano-node", + Image: "ghcr.io/blinklabs-io/cardano-node:8.7.3", + Env: map[string]string{ + "NETWORK": "preview", + }, + Ports: []string{ + "3001:3001", + }, + }, + }, + { + // TODO: turn this into a template + File: &PackageInstallStepFile{ + Filename: "test-cardano-cli", + Content: `#!/bin/bash +docker exec -ti cardano-node-8.7.3-cardano-node cardano-cli $@ +`, + }, + }, + }, + }, +}