Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: self-hosted agent get/list command #217

Merged
merged 3 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions api/client/agent_v1_alpha.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package client

import (
"fmt"
"net/url"

models "github.com/semaphoreci/cli/api/models"
)

type AgentApiV1AlphaApi struct {
BaseClient BaseClient
ResourceNameSingular string
ResourceNamePlural string
}

func NewAgentApiV1AlphaApi() AgentApiV1AlphaApi {
baseClient := NewBaseClientFromConfig()
baseClient.SetApiVersion("v1alpha")

return AgentApiV1AlphaApi{
BaseClient: baseClient,
ResourceNamePlural: "agents",
ResourceNameSingular: "agent",
}
}

func (c *AgentApiV1AlphaApi) ListAgents(agentType string, cursor string) (*models.AgentListV1Alpha, error) {
query := url.Values{}
query.Add("page_size", "200")

if agentType != "" {
query.Add("agent_type", agentType)
}

if cursor != "" {
query.Add("cursor", cursor)
}

body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query)

if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != 200 {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewAgentListV1AlphaFromJson(body)
}

func (c *AgentApiV1AlphaApi) GetAgent(name string) (*models.AgentV1Alpha, error) {
body, status, err := c.BaseClient.Get(c.ResourceNamePlural, name)

if err != nil {
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)
}

if status != 200 {
return nil, fmt.Errorf("http status %d with message \"%s\" received from upstream", status, body)
}

return models.NewAgentV1AlphaFromJson(body)
}
29 changes: 29 additions & 0 deletions api/models/agent_list_v1_alpha.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package models

import "encoding/json"

type AgentListV1Alpha struct {
Agents []AgentV1Alpha `json:"agents" yaml:"agents"`
Cursor string `json:"cursor" yaml:"cursor"`
}

func NewAgentListV1AlphaFromJson(data []byte) (*AgentListV1Alpha, error) {
list := AgentListV1Alpha{}

err := json.Unmarshal(data, &list)
if err != nil {
return nil, err
}

for _, s := range list.Agents {
if s.ApiVersion == "" {
s.ApiVersion = "v1alpha"
}

if s.Kind == "" {
s.Kind = "SelfHostedAgent"
}
}

return &list, nil
}
80 changes: 80 additions & 0 deletions api/models/agent_v1_alpha.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package models

import (
"encoding/json"
"fmt"

yaml "gopkg.in/yaml.v2"
)

type AgentV1Alpha struct {
ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"`
Kind string `json:"kind,omitempty" yaml:"kind"`
Metadata AgentV1AlphaMetadata `json:"metadata" yaml:"metadata"`
Status AgentV1AlphaStatus `json:"status" yaml:"status"`
}

type AgentV1AlphaMetadata struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
ConnectedAt json.Number `json:"connected_at,omitempty" yaml:"connected_at,omitempty"`
DisabledAt json.Number `json:"disabled_at,omitempty" yaml:"disabled_at,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
OS string `json:"os,omitempty" yaml:"os,omitempty"`
Arch string `json:"arch,omitempty" yaml:"arch,omitempty"`
Hostname string `json:"hostname,omitempty" yaml:"hostname,omitempty"`
IPAddress string `json:"ip_address,omitempty" yaml:"ip_address,omitempty"`
PID json.Number `json:"pid,omitempty" yaml:"pid,omitempty"`
}

type AgentV1AlphaStatus struct {
State string `json:"state,omitempty" yaml:"state,omitempty"`
}

func NewAgentV1Alpha(name string) AgentV1Alpha {
a := AgentV1Alpha{}
a.Metadata.Name = name
a.setApiVersionAndKind()
return a
}

func NewAgentV1AlphaFromJson(data []byte) (*AgentV1Alpha, error) {
a := AgentV1Alpha{}

err := json.Unmarshal(data, &a)
if err != nil {
return nil, err
}

a.setApiVersionAndKind()
return &a, nil
}

func NewAgentV1AlphaFromYaml(data []byte) (*AgentV1Alpha, error) {
a := AgentV1Alpha{}

err := yaml.UnmarshalStrict(data, &a)
if err != nil {
return nil, err
}

a.setApiVersionAndKind()
return &a, nil
}

func (s *AgentV1Alpha) setApiVersionAndKind() {
s.ApiVersion = "v1alpha"
s.Kind = "SelfHostedAgent"
}

func (s *AgentV1Alpha) ObjectName() string {
return fmt.Sprintf("SelfHostedAgent/%s", s.Metadata.Name)
}

func (s *AgentV1Alpha) ToJson() ([]byte, error) {
return json.Marshal(s)
}

func (s *AgentV1Alpha) ToYaml() ([]byte, error) {
return yaml.Marshal(s)
}
79 changes: 79 additions & 0 deletions cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"text/tabwriter"

client "github.com/semaphoreci/cli/api/client"
"github.com/semaphoreci/cli/api/models"
"github.com/semaphoreci/cli/cmd/pipelines"
"github.com/semaphoreci/cli/cmd/utils"
"github.com/semaphoreci/cli/cmd/workflows"
Expand Down Expand Up @@ -191,6 +192,80 @@ var GetAgentTypeCmd = &cobra.Command{
},
}

var GetAgentsCmd = &cobra.Command{
Use: "agents",
Short: "Get self-hosted agents.",
Long: ``,
Aliases: []string{"agent"},
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
c := client.NewAgentApiV1AlphaApi()

if len(args) == 0 {
agentType, err := cmd.Flags().GetString("agent-type")
utils.Check(err)

agents, err := getAllAgents(c, agentType)
utils.Check(err)

if len(agents) == 0 {
fmt.Fprintln(os.Stdout, "No agents found")
return
}

const padding = 3
w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)

fmt.Fprintln(w, "NAME\tTYPE\tSTATE\tAGE")
for _, a := range agents {
connectedAt, err := a.Metadata.ConnectedAt.Int64()
utils.Check(err)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
a.Metadata.Name,
a.Metadata.Type,
a.Status.State,
utils.RelativeAgeForHumans(connectedAt),
)
}

w.Flush()
} else {
name := args[0]

agent, err := c.GetAgent(name)

utils.Check(err)

y, err := agent.ToYaml()

utils.Check(err)

fmt.Printf("%s", y)
}
},
}

func getAllAgents(client client.AgentApiV1AlphaApi, agentType string) ([]models.AgentV1Alpha, error) {
agents := []models.AgentV1Alpha{}
cursor := ""

for {
agentList, err := client.ListAgents(agentType, cursor)
if err != nil {
return nil, err
}

agents = append(agents, agentList.Agents...)
if agentList.Cursor == "" {
break
}

cursor = agentList.Cursor
}

return agents, nil
}

var GetProjectCmd = &cobra.Command{
Use: "projects [name]",
Short: "Get projects.",
Expand Down Expand Up @@ -384,6 +459,10 @@ func init() {
getCmd.AddCommand(GetProjectCmd)
getCmd.AddCommand(GetAgentTypeCmd)

GetAgentsCmd.Flags().StringP("agent-type", "t", "",
"agent type; if specified, returns only agents for this agent type")
getCmd.AddCommand(GetAgentsCmd)

GetSecretCmd.Flags().StringP("project-name", "p", "",
"project name; if specified will get secret from project level, otherwise organization secret")
GetSecretCmd.Flags().StringP("project-id", "i", "",
Expand Down
34 changes: 34 additions & 0 deletions cmd/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,40 @@ func Test__GetAgentType__Response200(t *testing.T) {
}
}

func Test__GetAgent__Response200(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

received := false

httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/agents/asdfasdfadsf",
func(req *http.Request) (*http.Response, error) {
received = true

a1 := `{
"metadata":{
"name":"s1-testing",
"type":"asdfasdfadsf",
"connected_at":1536673464,
"disabled_at":1536674946
},
"status":{
"state":"waiting_for_job"
}
}`

return httpmock.NewStringResponse(200, a1), nil
},
)

RootCmd.SetArgs([]string{"get", "agent", "asdfasdfadsf"})
RootCmd.Execute()

if received == false {
t.Error("Expected the API to receive GET agents/asdfasdfadsf")
}
}

func Test__GetPipeline__Response200(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
Expand Down