diff --git a/api/workspace.go b/api/workspace.go index 3717c43..b306ac1 100644 --- a/api/workspace.go +++ b/api/workspace.go @@ -194,3 +194,59 @@ func (c Client) GitPull(workspaceId int, remote string, branch string) error { _, err := req.Execute() return errors.FormatAPIError(err) } + +type WorkspaceDomains struct { + DevDomain string + CustomDomains []string +} + +func (c *Client) GetWorkspaceDomains(workspaceId int) (*WorkspaceDomains, error) { + workspace, err := c.GetWorkspace(workspaceId) + if err != nil { + return nil, err + } + + dc, err := c.getDataCenterById(workspace.DataCenterId) + if err != nil { + return nil, err + } + + devDomain := fmt.Sprintf("%d-3000.%s.codesphere.com", workspaceId, dc.Name) + + domains, err := c.ListDomains(workspace.TeamId) + if err != nil { + return nil, err + } + + customDomains := []string{} + for _, domain := range domains { + for _, workspaceIds := range domain.Workspaces { + for _, wsId := range workspaceIds { + if wsId == workspaceId { + customDomains = append(customDomains, domain.Name) + break + } + } + } + } + + return &WorkspaceDomains{ + DevDomain: devDomain, + CustomDomains: customDomains, + }, nil +} + +func (c *Client) getDataCenterById(dcId int) (*DataCenter, error) { + datacenters, err := c.ListDataCenters() + if err != nil { + return nil, err + } + + for _, dc := range datacenters { + if dc.Id == dcId { + return &dc, nil + } + } + + return nil, errors.NotFound(fmt.Sprintf("datacenter with id %d not found", dcId)) +} diff --git a/cli/cmd/client.go b/cli/cmd/client.go index 5b68d57..f2ffa50 100644 --- a/cli/cmd/client.go +++ b/cli/cmd/client.go @@ -18,6 +18,7 @@ type Client interface { ListWorkspaces(teamId int) ([]api.Workspace, error) ListBaseimages() ([]api.Baseimage, error) GetWorkspace(workspaceId int) (api.Workspace, error) + GetWorkspaceDomains(workspaceId int) (*api.WorkspaceDomains, error) SetEnvVarOnWorkspace(workspaceId int, vars map[string]string) error ExecCommand(workspaceId int, command string, workdir string, env map[string]string) (string, string, error) ListWorkspacePlans() ([]api.WorkspacePlan, error) diff --git a/cli/cmd/get_domains.go b/cli/cmd/get_domains.go new file mode 100644 index 0000000..a903157 --- /dev/null +++ b/cli/cmd/get_domains.go @@ -0,0 +1,64 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "fmt" + + "github.com/codesphere-cloud/cs-go/pkg/io" + "github.com/jedib0t/go-pretty/v6/table" + + "github.com/spf13/cobra" +) + +type GetDomainsCmd struct { + Opts GlobalOptions + cmd *cobra.Command +} + +func addGetDomainsCmd(p *cobra.Command, opts GlobalOptions) { + g := GetDomainsCmd{ + cmd: &cobra.Command{ + Use: "domains", + Short: "Get domains for a workspace", + Long: `Get both the devDomain and any custom domains for a workspace`, + Example: io.FormatExampleCommands("list domains", []io.Example{ + {Cmd: "--workspace-id ", Desc: "Get domains for a specific workspace"}, + }), + }, + Opts: opts, + } + g.cmd.RunE = g.RunE + p.AddCommand(g.cmd) +} + +func (g *GetDomainsCmd) RunE(_ *cobra.Command, args []string) (err error) { + client, err := NewClient(g.Opts) + if err != nil { + return fmt.Errorf("failed to create Codesphere client: %w", err) + } + + workspaceId, err := g.Opts.GetWorkspaceId() + if err != nil { + return fmt.Errorf("failed to get workspace ID: %w", err) + } + + domains, err := client.GetWorkspaceDomains(workspaceId) + if err != nil { + return fmt.Errorf("failed to get workspace domains: %w", err) + } + + t := io.GetTableWriter() + t.AppendHeader(table.Row{"Type", "Domain"}) + + t.AppendRow(table.Row{"Dev Domain", domains.DevDomain}) + + for _, customDomain := range domains.CustomDomains { + t.AppendRow(table.Row{"Custom Domain", customDomain}) + } + + t.Render() + + return nil +} diff --git a/cli/cmd/get_domains_test.go b/cli/cmd/get_domains_test.go new file mode 100644 index 0000000..b912f6a --- /dev/null +++ b/cli/cmd/get_domains_test.go @@ -0,0 +1,67 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "errors" + + "github.com/codesphere-cloud/cs-go/api" + "github.com/codesphere-cloud/cs-go/cli/cmd" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetDomainsCmd", func() { + var ( + mockClient *cmd.MockClient + wsId int + domains *api.WorkspaceDomains + ) + + BeforeEach(func() { + mockClient = cmd.NewMockClient(GinkgoT()) + wsId = 123 + domains = &api.WorkspaceDomains{ + DevDomain: "123-3000.test.codesphere.com", + CustomDomains: []string{"custom1.example.com", "custom2.example.com"}, + } + }) + + Context("GetWorkspaceDomains", func() { + It("retrieves workspace domains successfully", func() { + mockClient.EXPECT().GetWorkspaceDomains(wsId).Return(domains, nil) + + result, err := mockClient.GetWorkspaceDomains(wsId) + + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(domains)) + Expect(result.DevDomain).To(Equal("123-3000.test.codesphere.com")) + Expect(result.CustomDomains).To(HaveLen(2)) + }) + + It("handles errors from API", func() { + expectedErr := errors.New("workspace not found") + mockClient.EXPECT().GetWorkspaceDomains(wsId).Return(nil, expectedErr) + + result, err := mockClient.GetWorkspaceDomains(wsId) + + Expect(err).To(HaveOccurred()) + Expect(result).To(BeNil()) + }) + + It("handles workspace with only devDomain", func() { + domainsNoCustm := &api.WorkspaceDomains{ + DevDomain: "123-3000.test.codesphere.com", + CustomDomains: []string{}, + } + mockClient.EXPECT().GetWorkspaceDomains(wsId).Return(domainsNoCustm, nil) + + result, err := mockClient.GetWorkspaceDomains(wsId) + + Expect(err).ToNot(HaveOccurred()) + Expect(result.DevDomain).To(Equal("123-3000.test.codesphere.com")) + Expect(result.CustomDomains).To(BeEmpty()) + }) + }) +}) diff --git a/cli/cmd/list.go b/cli/cmd/list.go index fec0a1d..10922c8 100644 --- a/cli/cmd/list.go +++ b/cli/cmd/list.go @@ -20,6 +20,7 @@ func AddListCmd(rootCmd *cobra.Command, opts GlobalOptions) { Long: `List resources available in Codesphere`, Example: io.FormatExampleCommands("list", []io.Example{ {Cmd: "workspaces", Desc: "List all workspaces"}, + {Cmd: "domains --workspace-id ", Desc: "List domains for a workspace"}, }), }, } @@ -28,4 +29,5 @@ func AddListCmd(rootCmd *cobra.Command, opts GlobalOptions) { AddListBaseimagesCmd(l.cmd, opts) addListTeamsCmd(l.cmd, opts) AddListPlansCmd(l.cmd, opts) + addGetDomainsCmd(l.cmd, opts) } diff --git a/cli/cmd/list_workspaces.go b/cli/cmd/list_workspaces.go index 6fd78ad..2c001b8 100644 --- a/cli/cmd/list_workspaces.go +++ b/cli/cmd/list_workspaces.go @@ -46,13 +46,20 @@ func (l *ListWorkspacesCmd) RunE(_ *cobra.Command, args []string) (err error) { } t := io.GetTableWriter() - t.AppendHeader(table.Row{"Team ID", "ID", "Name", "Repository"}) + t.AppendHeader(table.Row{"Team ID", "ID", "Name", "Repository", "Dev Domain"}) for _, w := range workspaces { gitUrl := "" if w.GitUrl.Get() != nil { gitUrl = *w.GitUrl.Get() } - t.AppendRow(table.Row{w.TeamId, w.Id, w.Name, gitUrl}) + + devDomain := "" + domains, err := client.GetWorkspaceDomains(w.Id) + if err == nil && domains != nil { + devDomain = domains.DevDomain + } + + t.AppendRow(table.Row{w.TeamId, w.Id, w.Name, gitUrl, devDomain}) } t.Render() diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 3b27fae..f1eba5a 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -357,6 +357,62 @@ func (_c *MockClient_GetWorkspace_Call) RunAndReturn(run func(workspaceId int) ( return _c } +// GetWorkspaceDomains provides a mock function for the type MockClient +func (_mock *MockClient) GetWorkspaceDomains(workspaceId int) (*api.WorkspaceDomains, error) { + ret := _mock.Called(workspaceId) + + if len(ret) == 0 { + panic("no return value specified for GetWorkspaceDomains") + } + + var r0 *api.WorkspaceDomains + var r1 error + if returnFunc, ok := ret.Get(0).(func(int) (*api.WorkspaceDomains, error)); ok { + return returnFunc(workspaceId) + } + if returnFunc, ok := ret.Get(0).(func(int) *api.WorkspaceDomains); ok { + r0 = returnFunc(workspaceId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.WorkspaceDomains) + } + } + if returnFunc, ok := ret.Get(1).(func(int) error); ok { + r1 = returnFunc(workspaceId) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GetWorkspaceDomains_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWorkspaceDomains' +type MockClient_GetWorkspaceDomains_Call struct { + *mock.Call +} + +// GetWorkspaceDomains is a helper method to define mock.On call +// - workspaceId +func (_e *MockClient_Expecter) GetWorkspaceDomains(workspaceId interface{}) *MockClient_GetWorkspaceDomains_Call { + return &MockClient_GetWorkspaceDomains_Call{Call: _e.mock.On("GetWorkspaceDomains", workspaceId)} +} + +func (_c *MockClient_GetWorkspaceDomains_Call) Run(run func(workspaceId int)) *MockClient_GetWorkspaceDomains_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int)) + }) + return _c +} + +func (_c *MockClient_GetWorkspaceDomains_Call) Return(workspaceDomains *api.WorkspaceDomains, err error) *MockClient_GetWorkspaceDomains_Call { + _c.Call.Return(workspaceDomains, err) + return _c +} + +func (_c *MockClient_GetWorkspaceDomains_Call) RunAndReturn(run func(workspaceId int) (*api.WorkspaceDomains, error)) *MockClient_GetWorkspaceDomains_Call { + _c.Call.Return(run) + return _c +} + // GitPull provides a mock function for the type MockClient func (_mock *MockClient) GitPull(wsId int, remote string, branch string) error { ret := _mock.Called(wsId, remote, branch)