diff --git a/cmd/cardano-up/context.go b/cmd/cardano-up/context.go new file mode 100644 index 0000000..7eb0124 --- /dev/null +++ b/cmd/cardano-up/context.go @@ -0,0 +1,201 @@ +// 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 ( + "errors" + "fmt" + "log/slog" + "os" + "sort" + + "github.com/blinklabs-io/cardano-up/pkgmgr" + "github.com/spf13/cobra" +) + +var contextFlags = struct { + description string + network string +}{} + +func contextCommand() *cobra.Command { + contextCommand := &cobra.Command{ + Use: "context", + Short: "Manage the current context", + } + contextCommand.AddCommand( + contextListCommand(), + contextSelectCommand(), + contextCreateCommand(), + contextDeleteCommand(), + ) + + return contextCommand +} + +func contextListCommand() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List available contexts", + 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) + } + activeContext, _ := pm.ActiveContext() + contexts := pm.Contexts() + slog.Info("Contexts (* is active):\n") + slog.Info( + fmt.Sprintf( + " %-15s %-15s %s", + "Name", + "Network", + "Description", + ), + ) + var tmpContextNames []string + for contextName := range contexts { + tmpContextNames = append(tmpContextNames, contextName) + } + sort.Strings(tmpContextNames) + //for contextName, context := range contexts { + for _, contextName := range tmpContextNames { + context := contexts[contextName] + activeMarker := " " + if contextName == activeContext { + activeMarker = "*" + } + slog.Info( + fmt.Sprintf( + "%s %-15s %-15s %s", + activeMarker, + contextName, + context.Network, + context.Description, + ), + ) + } + }, + } +} + +func contextSelectCommand() *cobra.Command { + return &cobra.Command{ + Use: "select ", + Short: "Select the active context", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no context name provided") + } + if len(args) > 1 { + return errors.New("only one context name may be specified") + } + return nil + }, + 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) + } + if err := pm.SetActiveContext(args[0]); err != nil { + slog.Error(fmt.Sprintf("failed to set active context: %s", err)) + os.Exit(1) + } + slog.Info( + fmt.Sprintf( + "Selected context %q", + args[0], + ), + ) + }, + } +} + +func contextCreateCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "create ", + Short: "Create a new context", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no context name provided") + } + if len(args) > 1 { + return errors.New("only one context name may be specified") + } + return nil + }, + 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) + } + tmpContextName := args[0] + tmpContext := pkgmgr.Context{ + Description: contextFlags.description, + Network: contextFlags.network, + } + if err := pm.AddContext(tmpContextName, tmpContext); err != nil { + slog.Error(fmt.Sprintf("failed to add context: %s", err)) + os.Exit(1) + } + }, + } + cmd.Flags().StringVarP(&contextFlags.description, "description", "d", "", "specifies description for context") + cmd.Flags().StringVarP(&contextFlags.network, "network", "n", "", "specifies network for context. if not specified, it's set automatically on the first package install") + return cmd +} + +func contextDeleteCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete ", + Short: "Delete a context", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no context name provided") + } + if len(args) > 1 { + return errors.New("only one context name may be specified") + } + return nil + }, + 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) + } + activeContext, _ := pm.ActiveContext() + if args[0] == activeContext { + slog.Error("cannot delete active context") + os.Exit(1) + } + if err := pm.DeleteContext(args[0]); err != nil { + slog.Error(fmt.Sprintf("failed to delete context: %s", err)) + os.Exit(1) + } + slog.Info( + fmt.Sprintf( + "Deleted context %q", + args[0], + ), + ) + }, + } + cmd.Flags().StringVarP(&contextFlags.description, "description", "d", "", "specifies description for context") + return cmd +} diff --git a/cmd/cardano-up/main.go b/cmd/cardano-up/main.go index bbfebe0..b00838e 100644 --- a/cmd/cardano-up/main.go +++ b/cmd/cardano-up/main.go @@ -63,6 +63,7 @@ func main() { // Add subcommands rootCmd.AddCommand( + contextCommand(), versionCommand(), listAvailableCommand(), installCommand(), diff --git a/pkgmgr/context.go b/pkgmgr/context.go new file mode 100644 index 0000000..f0e4dd5 --- /dev/null +++ b/pkgmgr/context.go @@ -0,0 +1,29 @@ +// 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 + +const ( + defaultContextName = "default" +) + +var defaultContext = Context{ + Network: "preprod", + Description: "Default context", +} + +type Context struct { + Description string `yaml:"description"` + Network string `yaml:"network"` +} diff --git a/pkgmgr/env.go b/pkgmgr/env.go deleted file mode 100644 index 85fd6d4..0000000 --- a/pkgmgr/env.go +++ /dev/null @@ -1,5 +0,0 @@ -package pkgmgr - -type Environment struct { - // TODO -} diff --git a/pkgmgr/error.go b/pkgmgr/error.go index 3ade97e..d8aa4e1 100644 --- a/pkgmgr/error.go +++ b/pkgmgr/error.go @@ -28,3 +28,9 @@ var ErrMultipleInstallMethods = errors.New("only one install method may be speci // 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") + +// ErrContextNotExist is returned when trying to selecting/managing a context that does not exist +var ErrContextNotExist = errors.New("context does not exist") + +// ErrContextAlreadyExists is returned when creating a context with a name that is already in use +var ErrContextAlreadyExists = errors.New("specified context already exists") diff --git a/pkgmgr/installed_package.go b/pkgmgr/installed_package.go index 8d48095..c99bcb9 100644 --- a/pkgmgr/installed_package.go +++ b/pkgmgr/installed_package.go @@ -1,3 +1,17 @@ +// 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 ( diff --git a/pkgmgr/pkgmgr.go b/pkgmgr/pkgmgr.go index d85347c..9472ea2 100644 --- a/pkgmgr/pkgmgr.go +++ b/pkgmgr/pkgmgr.go @@ -103,3 +103,44 @@ func (p *PackageManager) Uninstall(installedPkg InstalledPackage) error { } return nil } + +func (p *PackageManager) Contexts() map[string]Context { + return p.state.Contexts +} + +func (p *PackageManager) ActiveContext() (string, Context) { + return p.state.ActiveContext, p.state.Contexts[p.state.ActiveContext] +} + +func (p *PackageManager) AddContext(name string, context Context) error { + if _, ok := p.state.Contexts[name]; ok { + return ErrContextAlreadyExists + } + p.state.Contexts[name] = context + if err := p.state.Save(); err != nil { + return err + } + return nil +} + +func (p *PackageManager) DeleteContext(name string) error { + if _, ok := p.state.Contexts[name]; !ok { + return ErrContextNotExist + } + delete(p.state.Contexts, name) + if err := p.state.Save(); err != nil { + return err + } + return nil +} + +func (p *PackageManager) SetActiveContext(name string) error { + if _, ok := p.state.Contexts[name]; !ok { + return ErrContextNotExist + } + p.state.ActiveContext = name + if err := p.state.Save(); err != nil { + return err + } + return nil +} diff --git a/pkgmgr/state.go b/pkgmgr/state.go index f590cf9..de15073 100644 --- a/pkgmgr/state.go +++ b/pkgmgr/state.go @@ -1,3 +1,17 @@ +// 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 ( @@ -8,9 +22,7 @@ import ( ) const ( - defaultContext = "default" - - environmentsFilename = "environments.yaml" + contextsFilename = "contexts.yaml" activeContextFilename = "active_context.yaml" installedPackagesFilename = "installed_packages.yaml" ) @@ -18,19 +30,19 @@ const ( type State struct { config Config ActiveContext string - Environments []Environment + Contexts map[string]Context InstalledPackages []InstalledPackage } func NewState(cfg Config) *State { return &State{ - config: cfg, - Environments: make([]Environment, 0), + config: cfg, + Contexts: make(map[string]Context), } } func (s *State) Load() error { - if err := s.loadEnvironments(); err != nil { + if err := s.loadContexts(); err != nil { return err } if err := s.loadActiveContext(); err != nil { @@ -43,7 +55,7 @@ func (s *State) Load() error { } func (s *State) Save() error { - if err := s.saveEnvironments(); err != nil { + if err := s.saveContexts(); err != nil { return err } if err := s.saveActiveContext(); err != nil { @@ -101,12 +113,18 @@ func (s *State) saveFile(filename string, src any) error { return nil } -func (s *State) loadEnvironments() error { - return s.loadFile(environmentsFilename, &(s.Environments)) +func (s *State) loadContexts() error { + if err := s.loadFile(contextsFilename, &(s.Contexts)); err != nil { + return err + } + if len(s.Contexts) == 0 { + s.Contexts[defaultContextName] = defaultContext + } + return nil } -func (s *State) saveEnvironments() error { - return s.saveFile(environmentsFilename, &(s.Environments)) +func (s *State) saveContexts() error { + return s.saveFile(contextsFilename, &(s.Contexts)) } func (s *State) loadActiveContext() error { @@ -114,7 +132,7 @@ func (s *State) loadActiveContext() error { return err } if s.ActiveContext == "" { - s.ActiveContext = defaultContext + s.ActiveContext = defaultContextName } return nil }