Skip to content

Commit

Permalink
feat: add registry and global storage
Browse files Browse the repository at this point in the history
Signed-off-by: Kasper J. Hermansen <[email protected]>
  • Loading branch information
kjuulh committed Jan 25, 2024
1 parent 353eec9 commit dfde6f5
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 20 deletions.
65 changes: 52 additions & 13 deletions cmd/ext.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,94 @@
package cmd

import (
"errors"
"os"

"github.com/lunarway/shuttle/internal/extensions"
"github.com/lunarway/shuttle/internal/global"
"github.com/spf13/cobra"
)

type extGlobalConfig struct {
registry string
}

func (c *extGlobalConfig) Registry() (string, bool) {
if c.registry != "" {
return c.registry, true
}

if registryEnv := os.Getenv("SHUTTLE_EXTENSIONS_REGISTRY"); registryEnv != "" {
return registryEnv, true
}

return "", false
}

func newExtCmd() *cobra.Command {
extManager := extensions.NewExtensionsManager("some registry")
globalConfig := &extGlobalConfig{}

cmd := &cobra.Command{
Use: "ext",
Long: "helps you manage shuttle extensions",
}

cmd.AddCommand(
newExtInstallCmd(extManager),
newExtUpdateCmd(extManager),
newExtInitCmd(extManager),
newExtInstallCmd(globalConfig),
newExtUpdateCmd(globalConfig),
newExtInitCmd(globalConfig),
)

cmd.PersistentFlags().StringVar(&globalConfig.registry, "registry", "", "the given registry, if not set will default to SHUTTLE_EXTENSIONS_REGISTRY")

return cmd
}

func newExtInstallCmd(extManager *extensions.ExtensionsManager) *cobra.Command {
func newExtInstallCmd(globalConfig *extGlobalConfig) *cobra.Command {
cmd := &cobra.Command{
Use: "install",
Long: "Install ensures that extensions already known about are downloaded and available",
Use: "install",
Short: "Install ensures that extensions already known about are downloaded and available",
RunE: func(cmd *cobra.Command, args []string) error {
extManager := extensions.NewExtensionsManager(global.NewGlobalStore())

if err := extManager.Install(cmd.Context()); err != nil {
return err
}

return nil
},
}

return cmd
}

func newExtUpdateCmd(extManager *extensions.ExtensionsManager) *cobra.Command {
func newExtUpdateCmd(globalConfig *extGlobalConfig) *cobra.Command {
cmd := &cobra.Command{
Use: "update",
Long: "Update will fetch the latest version of the extensions from the given registry",
Use: "update",
Short: "Update will fetch the latest version of the extensions from the given registry",
RunE: func(cmd *cobra.Command, args []string) error {
extManager := extensions.NewExtensionsManager(global.NewGlobalStore())

registry, ok := globalConfig.Registry()
if !ok {
return errors.New("registry is not set")
}

if err := extManager.Update(cmd.Context(), registry); err != nil {
return err
}

return nil
},
}

return cmd
}

func newExtInitCmd(extManager *extensions.ExtensionsManager) *cobra.Command {
func newExtInitCmd(globalConfig *extGlobalConfig) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Long: "init will create an initial extensions repository",
Use: "init",
Short: "init will create an initial extensions repository",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
Expand Down
4 changes: 4 additions & 0 deletions internal/extensions/extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package extensions

// Extension is the descriptor of a single extension, it is used to add description to the cli, as well as calling the specific extension in question
type Extension struct{}
33 changes: 26 additions & 7 deletions internal/extensions/extensions.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
package extensions

import "context"
import (
"context"
"fmt"

"github.com/lunarway/shuttle/internal/global"
)

// ExtensionsManager is the entry into installing, updating and using extensions. It is the orchestrator of all the parts that consist of extensions
type ExtensionsManager struct {
registry string
globalStore *global.GlobalStore
}

func NewExtensionsManager(registry string) *ExtensionsManager {
func NewExtensionsManager(globalStore *global.GlobalStore) *ExtensionsManager {
return &ExtensionsManager{
registry: registry,
globalStore: globalStore,
}
}

// Init will initialize a repository with a sample extension package
func (e *ExtensionsManager) Init(ctx context.Context) error {
return nil
}

// GetAll will return all known and installed extensions
func (e *ExtensionsManager) GetAll(ctx context.Context) ([]Extension, error) {
return nil, nil
}

// Install will ensure that all known extensions are installed and ready for use
func (e *ExtensionsManager) Install(ctx context.Context) error {
return nil
}

func (e *ExtensionsManager) Update(ctx context.Context) error {
// Update will fetch the latest extensions from a registry and install them afterwards so that they're ready for use
func (e *ExtensionsManager) Update(ctx context.Context, registry string) error {
reg, err := NewRegistry(registry, e.globalStore)
if err != nil {
return fmt.Errorf("failed to update extensions: %w", err)
}

if err := reg.Update(ctx); err != nil {
return err
}

// 3. Initiate install

return nil
}

type Extension struct{}
53 changes: 53 additions & 0 deletions internal/extensions/git_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package extensions

import (
"context"
"fmt"

"github.com/lunarway/shuttle/internal/global"
)

// gitRegistry represents a type of registry backed by a remote git registry, whether folder or url based. it is denoted by the variable git=github.com/lunarway/shuttle-extensions.git as an example
type gitRegistry struct {
url string
globalStore *global.GlobalStore
}

func (*gitRegistry) Get(ctx context.Context) error {
panic("unimplemented")
}

func (g *gitRegistry) Update(ctx context.Context) error {
registry := getRegistryPath(g.globalStore)

if exists(registry) {
if err := g.fetchGitRepository(ctx); err != nil {
return fmt.Errorf("failed to update registry: %w", err)
}
} else {
if err := ensureExists(registry); err != nil {
return fmt.Errorf("failed to create registry path: %w", err)
}

if err := g.cloneGitRepository(ctx); err != nil {
return fmt.Errorf("failed to clone registry: %w", err)
}
}

return nil
}

func newGitRegistry(url string, globalStore *global.GlobalStore) Registry {
return &gitRegistry{
url: url,
globalStore: globalStore,
}
}

func (g *gitRegistry) fetchGitRepository(ctx context.Context) error {
panic("unimplemented")
}

func (g *gitRegistry) cloneGitRepository(ctx context.Context) error {
panic("unimplemented")
}
39 changes: 39 additions & 0 deletions internal/extensions/global_store_paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package extensions

import (
"errors"
"os"
"path"

"github.com/lunarway/shuttle/internal/global"
)

func getRegistryPath(globalStore *global.GlobalStore) string {
return path.Join(globalStore.Root(), "registry")
}

func getExtensionsPath(globalStore *global.GlobalStore) string {
return path.Join(globalStore.Root(), "extensions")
}

func getExtensionsCachePath(globalStore *global.GlobalStore) string {
return path.Join(getExtensionsPath(globalStore), "cache")
}

func ensureExists(dirPath string) error {
return os.MkdirAll(dirPath, 0o666)
}

func exists(dirPath string) bool {
_, err := os.Stat(dirPath)

if errors.Is(err, os.ErrNotExist) {
return false
}

if err != nil {
return false
}

return true
}
30 changes: 30 additions & 0 deletions internal/extensions/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package extensions

import (
"context"
"fmt"
"strings"

"github.com/lunarway/shuttle/internal/global"
)

// Registry represents some kind of upstream registry where extension metadata lives, such as which ones should be downloaded, which versions they're on and how to download them
type Registry interface {
Get(ctx context.Context) error
Update(ctx context.Context) error
}

// NewRegistry is a shim for concrete implementations of the registries, such as gitRegistry
func NewRegistry(registry string, globalStore *global.GlobalStore) (Registry, error) {
registryType, registryUrl, ok := strings.Cut(registry, "=")
if !ok {
return nil, fmt.Errorf("registry was not a valid url: %s", registry)
}

switch registryType {
case "git":
return newGitRegistry(registryUrl, globalStore), nil
default:
return nil, fmt.Errorf("registry type was not valid: %s", registryType)
}
}
41 changes: 41 additions & 0 deletions internal/global/global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package global

import "os"

// GlobalStore represents the ~/.shuttle folder it acts as an abstraction for said folder and ensures operations against it are consistent and controlled
type GlobalStore struct {
options *GlobalStoreOptions
}

type GlobalStoreOption func(options *GlobalStoreOptions)

func WithShuttleConfig(shuttleConfig string) GlobalStoreOption {
return func(options *GlobalStoreOptions) {
options.ShuttleConfig = shuttleConfig
}
}

type GlobalStoreOptions struct {
ShuttleConfig string
}

func newDefaultGlobalStoreOptions() *GlobalStoreOptions {
return &GlobalStoreOptions{
ShuttleConfig: "$HOME/.shuttle",
}
}

func NewGlobalStore(options ...GlobalStoreOption) *GlobalStore {
defaultOptions := newDefaultGlobalStoreOptions()
for _, opt := range options {
opt(defaultOptions)
}

return &GlobalStore{
options: defaultOptions,
}
}

func (gs *GlobalStore) Root() string {
return os.ExpandEnv(gs.options.ShuttleConfig)
}

0 comments on commit dfde6f5

Please sign in to comment.