Skip to content

Commit

Permalink
add agent data source (#456)
Browse files Browse the repository at this point in the history
Add new agent data source to the Client
  • Loading branch information
laurenolivia committed Aug 11, 2022
1 parent 18329dc commit 3835095
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -63,6 +63,7 @@ For complete usage of the API client, see the [full package docs](https://pkg.go
This API client covers most of the existing Terraform Cloud API calls and is updated regularly to add new or missing endpoints.

- [x] Account
- [x] Agents
- [x] Agent Pools
- [x] Agent Tokens
- [x] Applies
Expand Down
91 changes: 91 additions & 0 deletions agent.go
@@ -0,0 +1,91 @@
package tfe

import (
"context"
"fmt"
"net/url"
"time"
)

// Compile-time proof of interface implementation.
var _ Agents = (*agents)(nil)

// Agents describes all the agent-related methods that the
// Terraform Cloud API supports.
// TFE API docs: https://www.terraform.io/docs/cloud/api/agents.html
type Agents interface {
// Read an agent by its ID.
Read(ctx context.Context, agentID string) (*Agent, error)

// List all the agents of the given pool.
List(ctx context.Context, agentPoolID string, options *AgentListOptions) (*AgentList, error)
}

// agents implements Agents.
type agents struct {
client *Client
}

// AgentList represents a list of agents.
type AgentList struct {
*Pagination
Items []*Agent
}

// Agent represents a Terraform Cloud agent.
type Agent struct {
ID string `jsonapi:"primary,agents"`
Name string `jsonapi:"attr,name"`
IP string `jsonapi:"attr,ip-address"`
Status string `jsonapi:"attr,status"`
LastPingAt string `jsonapi:"attr,last-ping-at"`
}

type AgentListOptions struct {
ListOptions

//Optional:
LastPingSince time.Time `url:"filter[last-ping-since],omitempty,iso8601"`
}

// Read a single agent by its ID
func (s *agents) Read(ctx context.Context, agentID string) (*Agent, error) {
if !validStringID(&agentID) {
return nil, ErrInvalidAgentID
}

u := fmt.Sprintf("agents/%s", url.QueryEscape(agentID))
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}

agent := &Agent{}
err = req.Do(ctx, agent)
if err != nil {
return nil, err
}

return agent, nil
}

// List all the agents of the given organization.
func (s *agents) List(ctx context.Context, agentPoolID string, options *AgentListOptions) (*AgentList, error) {
if !validStringID(&agentPoolID) {
return nil, ErrInvalidOrg
}

u := fmt.Sprintf("agent-pools/%s/agents", url.QueryEscape(agentPoolID))
req, err := s.client.NewRequest("GET", u, options)
if err != nil {
return nil, err
}

agentList := &AgentList{}
err = req.Do(ctx, agentList)
if err != nil {
return nil, err
}

return agentList, nil
}
75 changes: 75 additions & 0 deletions agent_integration_test.go
@@ -0,0 +1,75 @@
//go:build integration
// +build integration

package tfe

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAgentsRead(t *testing.T) {
skipIfNotLinuxAmd64(t)

client := testClient(t)
ctx := context.Background()

org, orgCleanup := createOrganization(t, client)
t.Cleanup(orgCleanup)

upgradeOrganizationSubscription(t, client, org)

agent, _, agentCleanup := createAgent(t, client, org)

t.Cleanup(agentCleanup)

t.Run("when the agent exists", func(t *testing.T) {
k, err := client.Agents.Read(ctx, agent.ID)
require.NoError(t, err)
assert.Equal(t, agent, k)
})

t.Run("when the agent does not exist", func(t *testing.T) {
k, err := client.Agents.Read(ctx, "nonexistent")
assert.Nil(t, k)
assert.Equal(t, err, ErrResourceNotFound)
})

t.Run("without a valid agent ID", func(t *testing.T) {
k, err := client.Agents.Read(ctx, badIdentifier)
assert.Nil(t, k)
assert.EqualError(t, err, ErrInvalidAgentID.Error())
})
}

func TestAgentsList(t *testing.T) {
skipIfNotLinuxAmd64(t)

client := testClient(t)
ctx := context.Background()

org, orgCleanup := createOrganization(t, client)
t.Cleanup(orgCleanup)

upgradeOrganizationSubscription(t, client, org)

_, agentPool, agentCleanup := createAgent(t, client, org)
t.Cleanup(agentCleanup)

t.Run("expect an agent to exist", func(t *testing.T) {
agent, err := client.Agents.List(ctx, agentPool.ID, nil)

require.NoError(t, err)
require.NotEmpty(t, agent.Items)
assert.NotEmpty(t, agent.Items[0].ID)
})

t.Run("without a valid agent pool ID", func(t *testing.T) {
agent, err := client.Agents.List(ctx, badIdentifier, nil)
assert.Nil(t, agent)
assert.EqualError(t, err, ErrInvalidOrg.Error())
})
}
4 changes: 2 additions & 2 deletions agent_pool.go
Expand Up @@ -20,10 +20,10 @@ type AgentPools interface {
// Create a new agent pool with the given options.
Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error)

// Read a agent pool by its ID.
// Read an agent pool by its ID.
Read(ctx context.Context, agentPoolID string) (*AgentPool, error)

// Read a agent pool by its ID with the given options.
// Read an agent pool by its ID with the given options.
ReadWithOptions(ctx context.Context, agentPoolID string, options *AgentPoolReadOptions) (*AgentPool, error)

// Update an agent pool by its ID.
Expand Down
2 changes: 2 additions & 0 deletions errors.go
Expand Up @@ -176,6 +176,8 @@ var (

ErrInvalidArch = errors.New("invalid value for arch")

ErrInvalidAgentID = errors.New("invalid value for Agent ID")

ErrInvalidRegistryName = errors.New(`invalid value for registry-name. It must be either "private" or "public"`)
)

Expand Down
1 change: 1 addition & 0 deletions generate_mocks.sh
Expand Up @@ -60,3 +60,4 @@ mockgen -source=variable_set.go -destination=mocks/variable_set_mocks.go -packag
mockgen -source=variable_set_variable.go -destination=mocks/variable_set_variable_mocks.go -package=mocks
mockgen -source=workspace.go -destination=mocks/workspace_mocks.go -package=mocks
mockgen -source=workspace_run_task.go -destination=mocks/workspace_run_tasks_mocks.go -package=mocks
mockgen -source=agent.go -destination=mocks/agents.go -package=mocks

0 comments on commit 3835095

Please sign in to comment.