diff --git a/environment/handler/env.go b/environment/handler/env.go new file mode 100644 index 0000000..0b2af11 --- /dev/null +++ b/environment/handler/env.go @@ -0,0 +1,36 @@ +// Package handler provides the CMD handler logic for managing environments. +package handler + +import ( + "fmt" + + "gofr.dev/pkg/gofr" +) + +// Handler is responsible for managing environment-related operations. +type Handler struct { + envSvc EnvAdder +} + +// New creates a new Handler with the given EnvAdder service. +func New(envSvc EnvAdder) *Handler { + return &Handler{envSvc: envSvc} +} + +// Add handles the HTTP request to add environments. It delegates the task +// to the EnvAdder service and returns a success message or an error. +// +// Parameters: +// - ctx: The GoFR context containing request data. +// +// Returns: +// - A success message indicating how many environments were added, or an error +// if the operation failed. +func (h *Handler) Add(ctx *gofr.Context) (any, error) { + n, err := h.envSvc.Add(ctx) + if err != nil { + return nil, err + } + + return fmt.Sprintf("%d environments added", n), nil +} diff --git a/environment/handler/interface.go b/environment/handler/interface.go new file mode 100644 index 0000000..bb958c5 --- /dev/null +++ b/environment/handler/interface.go @@ -0,0 +1,7 @@ +package handler + +import "gofr.dev/pkg/gofr" + +type EnvAdder interface { + Add(ctx *gofr.Context) (int, error) +} diff --git a/environment/service/app_selector.go b/environment/service/app_selector.go new file mode 100644 index 0000000..ea9fb5f --- /dev/null +++ b/environment/service/app_selector.go @@ -0,0 +1,132 @@ +// Package service provides functionalities for interacting with applications and environments. +// It supports selecting an application and adding environments to it by communicating with an external API. +// it gives users a text-based user interface (TUI) for displaying and selecting items +// using the Charmbracelet bubbletea and list packages. +package service + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +const ( + // listWidth defines the width of the list. + listWidth = 20 + // listHeight defines the height of the list. + listHeight = 14 + // listPaddingLeft defines the left padding of the list items. + listPaddingLeft = 2 + // paginationPadding defines the padding for pagination controls. + paginationPadding = 4 +) + +//nolint:gochecknoglobals //required TUI styles for displaying the list +var ( + // itemStyle defines the default style for list items. + itemStyle = lipgloss.NewStyle().PaddingLeft(listPaddingLeft) + // selectedItemStyle defines the style for the selected list item. + selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) + // paginationStyle defines the style for pagination controls. + paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(paginationPadding) + // helpStyle defines the style for the help text. + helpStyle = list.DefaultStyles().HelpStyle +) + +// item represents a single item in the list. +type item struct { + id int // ID is the unique identifier for the item. + name string // Name is the display name of the item. +} + +// FilterValue returns the value to be used for filtering list items. +// In this case, it's the name of the item. +func (i *item) FilterValue() string { + return i.name +} + +// itemDelegate is a struct responsible for rendering and interacting with list items. +type itemDelegate struct{} + +// Height returns the height of the item (always 1). +func (itemDelegate) Height() int { return 1 } + +// Spacing returns the spacing between items (always 0). +func (itemDelegate) Spacing() int { return 0 } + +// Update is used to handle updates to the item model. It doesn't do anything in this case. +func (itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { + return nil +} + +// Render renders a single list item, applying the selected item style if it's the currently selected item. +// +//nolint:gocritic //required for rendering list items and implementing ItemDelegate interface +func (itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(*item) + if !ok { + return + } + + str := fmt.Sprintf("%3d. %s", index+1, i.name) + + fn := itemStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return selectedItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} + +// model represents the state of the TUI interface, including the list and selected item. +type model struct { + choice *item // choice is the selected item. + quitting bool // quitting indicates if the application is quitting. + list list.Model // list holds the list of items displayed in the TUI. +} + +// Init initializes the model, returning nil for no commands. +func (*model) Init() tea.Cmd { + return nil +} + +// Update handles updates from messages, such as key presses or window resizing. +// It updates the list and handles quitting or selecting an item. +func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.list.SetWidth(msg.Width) + return m, nil + + case tea.KeyMsg: + switch keypress := msg.String(); keypress { + case "q", "ctrl+c": + m.quitting = true // Set quitting to true when 'q' or 'ctrl+c' is pressed. + return m, tea.Quit + + case "enter": + i, ok := m.list.SelectedItem().(*item) + if ok { + m.choice = i + } + + return m, tea.Quit + } + } + + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + + return m, cmd +} + +// View renders the view of the current model, displaying the list to the user. +func (m *model) View() string { + return "\n" + m.list.View() +} diff --git a/environment/service/env.go b/environment/service/env.go new file mode 100644 index 0000000..9ff8d9a --- /dev/null +++ b/environment/service/env.go @@ -0,0 +1,160 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "gofr.dev/pkg/gofr" +) + +var ( + // ErrUnableToRenderApps is returned when the application list cannot be rendered. + ErrUnableToRenderApps = errors.New("unable to render the list of applications") + // ErrConnectingZopAPI is returned when there is an error connecting to the Zop API. + ErrConnectingZopAPI = errors.New("unable to connect to Zop API") + // ErrorAddingEnv is returned when there is an error adding an environment. + ErrorAddingEnv = errors.New("unable to add environment") + // ErrNoApplicationSelected is returned when no application is selected. + ErrNoApplicationSelected = errors.New("no application selected") +) + +// Service represents the application service that handles application and environment operations. +type Service struct { + appGet ApplicationGetter // appGet is responsible for fetching the list of applications. +} + +// New creates a new Service instance with the provided ApplicationGetter. +func New(appGet ApplicationGetter) *Service { + return &Service{appGet: appGet} +} + +// Add prompts the user to add environments to a selected application. +// It returns the number of environments added and an error, if any. +func (s *Service) Add(ctx *gofr.Context) (int, error) { + app, err := s.getSelectedApplication(ctx) + if err != nil { + return 0, err + } + + ctx.Out.Println("Selected application: ", app.name) + ctx.Out.Println("Please provide names of environments to be added...") + + var ( + input string + level = 1 + ) + + // Loop to gather environment names from the user and add them to the application. + for { + ctx.Out.Print("Enter environment name: ") + + _, _ = fmt.Scanf("%s", &input) + + err = postEnvironment(ctx, &Environment{Name: input, Level: level, ApplicationID: int64(app.id)}) + if err != nil { + return level, err + } + + level++ + + // Ask the user if they want to add more environments. + ctx.Out.Print("Do you wish to add more? (y/n) ") + + _, _ = fmt.Scanf("%s", &input) + + if input == "n" { + break + } + } + + return level, nil +} + +// getSelectedApplication renders a list of applications for the user to select from. +// It returns the selected application or an error if no selection is made. +func (s *Service) getSelectedApplication(ctx *gofr.Context) (*item, error) { + apps, err := s.appGet.List(ctx) + if err != nil { + return nil, err + } + + // Prepare a list of items for the user to select from. + items := make([]list.Item, 0) + for _, app := range apps { + items = append(items, &item{app.ID, app.Name}) + } + + // Initialize the list component for application selection. + l := list.New(items, itemDelegate{}, listWidth, listHeight) + l.Title = "Select the application where you want to add the environment!" + l.SetShowStatusBar(false) + l.SetFilteringEnabled(true) + l.Styles.PaginationStyle = paginationStyle + l.Styles.HelpStyle = helpStyle + l.SetShowStatusBar(false) + + m := model{list: l} + + // Render the list using the bubbletea program. + if _, er := tea.NewProgram(&m).Run(); er != nil { + ctx.Logger.Errorf("unable to render the list of applications! %v", er) + return nil, ErrUnableToRenderApps + } + + if m.choice == nil { + return nil, ErrNoApplicationSelected + } + + return m.choice, nil +} + +// postEnvironment sends a POST request to the API to add the provided environment to the application. +// It returns an error if the request fails or the response status code is not created (201). +func postEnvironment(ctx *gofr.Context, env *Environment) error { + body, _ := json.Marshal(env) + + resp, err := ctx.GetHTTPService("api-service"). + PostWithHeaders(ctx, fmt.Sprintf("application/%d/environments", env.ApplicationID), nil, body, map[string]string{ + "Content-Type": "application/json", + }) + if err != nil { + ctx.Logger.Errorf("unable to connect to Zop API! %v", err) + return ErrConnectingZopAPI + } + + if resp.StatusCode != http.StatusCreated { + var errResp struct { + Errors any `json:"errors,omitempty"` + } + + err = getResponse(resp, &errResp) + if err != nil { + ctx.Logger.Errorf("unable to add environment!, could not decode error message %v", err) + } + + ctx.Logger.Errorf("unable to add environment! %v", resp) + + return ErrorAddingEnv + } + + return nil +} + +// getResponse reads the HTTP response body and unmarshals it into the provided interface. +func getResponse(resp *http.Response, i any) error { + defer resp.Body.Close() + + b, _ := io.ReadAll(resp.Body) + + err := json.Unmarshal(b, i) + if err != nil { + return err + } + + return nil +} diff --git a/environment/service/interface.go b/environment/service/interface.go new file mode 100644 index 0000000..aaf7d29 --- /dev/null +++ b/environment/service/interface.go @@ -0,0 +1,14 @@ +package service + +import ( + "gofr.dev/pkg/gofr" + appSvc "zop.dev/cli/zop/application/service" +) + +// ApplicationGetter interface is used to abstract the process of fetching application data, +// which can be implemented by any service that has access to application-related data. +type ApplicationGetter interface { + // List fetches a list of applications from the service. + // It returns a slice of Application objects and an error if the request fails. + List(ctx *gofr.Context) ([]appSvc.Application, error) +} diff --git a/environment/service/mock_interface.go b/environment/service/mock_interface.go new file mode 100644 index 0000000..9fd52b4 --- /dev/null +++ b/environment/service/mock_interface.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go +// +// Generated by this command: +// +// mockgen -source=interface.go -destination=mock_interface.go -package=service +// + +// Package service is a generated GoMock package. +package service + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + gofr "gofr.dev/pkg/gofr" + service "zop.dev/cli/zop/application/service" +) + +// MockApplicationGetter is a mock of ApplicationGetter interface. +type MockApplicationGetter struct { + ctrl *gomock.Controller + recorder *MockApplicationGetterMockRecorder + isgomock struct{} +} + +// MockApplicationGetterMockRecorder is the mock recorder for MockApplicationGetter. +type MockApplicationGetterMockRecorder struct { + mock *MockApplicationGetter +} + +// NewMockApplicationGetter creates a new mock instance. +func NewMockApplicationGetter(ctrl *gomock.Controller) *MockApplicationGetter { + mock := &MockApplicationGetter{ctrl: ctrl} + mock.recorder = &MockApplicationGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockApplicationGetter) EXPECT() *MockApplicationGetterMockRecorder { + return m.recorder +} + +// GetApplications mocks base method. +func (m *MockApplicationGetter) GetApplications(ctx *gofr.Context) ([]service.Application, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetApplications", ctx) + ret0, _ := ret[0].([]service.Application) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetApplications indicates an expected call of GetApplications. +func (mr *MockApplicationGetterMockRecorder) GetApplications(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplications", reflect.TypeOf((*MockApplicationGetter)(nil).GetApplications), ctx) +} diff --git a/environment/service/models.go b/environment/service/models.go new file mode 100644 index 0000000..41010ad --- /dev/null +++ b/environment/service/models.go @@ -0,0 +1,18 @@ +package service + +// Environment represents an environment within an application. +// It holds details about the environment, such as its ID, associated application ID, +// level, name, and timestamps for when it was created, updated, and optionally deleted. +type Environment struct { + // ID is the unique identifier of the environment. + ID int64 `json:"id"` + + // ApplicationID is the identifier of the application to which this environment belongs. + ApplicationID int64 `json:"applicationId"` + + // Level indicates the environment's level, which might be used to denote the hierarchy or order of environments. + Level int `json:"level"` + + // Name is the name of the environment. + Name string `json:"name"` +} diff --git a/go.mod b/go.mod index 92e7d9d..31668c7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module zop.dev/cli/zop go 1.22.8 require ( + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.2.4 + github.com/charmbracelet/lipgloss v1.0.0 github.com/mattn/go-sqlite3 v1.14.24 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.10.0 @@ -22,13 +25,18 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/XSAM/otelsql v0.34.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/x/ansi v0.4.5 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -48,7 +56,13 @@ require ( github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect @@ -62,6 +76,8 @@ require ( github.com/redis/go-redis/extra/redisotel/v9 v9.7.0 // indirect github.com/redis/go-redis/v9 v9.7.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect github.com/segmentio/kafka-go v0.4.47 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect diff --git a/go.sum b/go.sum index 4a0283a..46bc929 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,10 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZp github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -38,6 +42,16 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= +github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= +github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -53,6 +67,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -135,10 +151,22 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -171,8 +199,13 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/segmentio/kafka-go v0.4.47 h1:IqziR4pA3vrZq7YdRxaT3w1/5fvIH5qpCwstUanQQB0= github.com/segmentio/kafka-go v0.4.47/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -295,6 +328,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 010aa3a..fc90b54 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( _ "github.com/mattn/go-sqlite3" "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/service" + _ "modernc.org/sqlite" applicationHandler "zop.dev/cli/zop/application/handler" applicationSvc "zop.dev/cli/zop/application/service" @@ -16,6 +17,8 @@ import ( impService "zop.dev/cli/zop/cloud/service/gcp" listSvc "zop.dev/cli/zop/cloud/service/list" impStore "zop.dev/cli/zop/cloud/store/gcp" + envHandler "zop.dev/cli/zop/environment/handler" + envService "zop.dev/cli/zop/environment/service" ) const ( @@ -58,5 +61,10 @@ func main() { app.SubCommand("application add", appH.Add) app.SubCommand("application list", appH.List) + envSvc := envService.New(appSvc) + envH := envHandler.New(envSvc) + + app.SubCommand("environment add", envH.Add) + app.Run() }