Skip to content

Commit

Permalink
Filter workspaces by current run status
Browse files Browse the repository at this point in the history
  • Loading branch information
arybolovlev committed Jun 5, 2024
1 parent 29665a7 commit 432d807
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 101 deletions.
153 changes: 53 additions & 100 deletions controllers/agentpool_controller_autoscaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,115 +12,71 @@ import (
tfc "github.com/hashicorp/go-tfe"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

appv1alpha2 "github.com/hashicorp/terraform-cloud-operator/api/v1alpha2"
)

func computeRequiredAgentsForWorkspace(ctx context.Context, ap *agentPoolInstance, workspaceID string) (int, error) {
statuses := []string{
func computeRequiredAgents(ctx context.Context, ap *agentPoolInstance) (int32, error) {
required := 0
runStatuses := []string{
string(tfc.RunPlanQueued),
string(tfc.RunApplyQueued),
string(tfc.RunApplying),
string(tfc.RunPlanning),
}
runs, err := ap.tfClient.Client.Runs.List(ctx, workspaceID, &tfc.RunListOptions{
Status: strings.Join(statuses, ","),
})
if err != nil {
return 0, err
}
return len(runs.Items), nil
}

func getAllAgentPoolWorkspaceIDs(ctx context.Context, ap *agentPoolInstance) ([]string, error) {
agentPool, err := ap.tfClient.Client.AgentPools.Read(ctx, ap.instance.Status.AgentPoolID)
if err != nil {
return []string{}, nil
}
ids := []string{}
for _, w := range agentPool.Workspaces {
ids = append(ids, w.ID)
}
return ids, nil
}
workspaceNames := map[string]struct{}{}
workspaceIDs := map[string]struct{}{}

func getTargetWorkspaceIDs(ctx context.Context, ap *agentPoolInstance) ([]string, error) {
workspaces := ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces
if workspaces == nil {
return getAllAgentPoolWorkspaceIDs(ctx, ap)
}
workspaceIDs := map[string]struct{}{} // NOTE: this is a map so we avoid duplicates when using wildcards
for _, w := range *workspaces {
if w.WildcardName != "" {
ids, err := getTargetWorkspaceIDsByWildcardName(ctx, ap, w)
if err != nil {
return []string{}, err
}
for _, id := range ids {
workspaceIDs[id] = struct{}{}
}
continue
}
id, err := getTargetWorkspaceID(ctx, ap, w)
nextPage := 1
for nextPage > 0 {
workspaceList, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{
CurrentRunStatus: strings.Join(runStatuses, ","),
ListOptions: tfc.ListOptions{
PageSize: 100,
PageNumber: nextPage,
},
})
if err != nil {
return []string{}, err
return 0, err
}
workspaceIDs[id] = struct{}{}
}
ids := []string{}
for v := range workspaceIDs {
ids = append(ids, v)
}
return ids, nil
}

func getTargetWorkspaceID(ctx context.Context, ap *agentPoolInstance, targetWorkspace appv1alpha2.TargetWorkspace) (string, error) {
if targetWorkspace.ID != "" {
return targetWorkspace.ID, nil
}
list, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{
Search: targetWorkspace.Name,
})
if err != nil {
return "", err
}
for _, w := range list.Items {
if w.Name == targetWorkspace.Name {
return w.ID, nil
nextPage = workspaceList.NextPage
for _, ws := range workspaceList.Items {
if ws.AgentPool.ID == ap.instance.Status.AgentPoolID {
workspaceNames[ws.Name] = struct{}{}
workspaceIDs[ws.ID] = struct{}{}
}
}
}
return "", fmt.Errorf("no such workspace found %q", targetWorkspace.Name)
}

func getTargetWorkspaceIDsByWildcardName(ctx context.Context, ap *agentPoolInstance, targetWorkspace appv1alpha2.TargetWorkspace) ([]string, error) {
list, err := ap.tfClient.Client.Workspaces.List(ctx, ap.instance.Spec.Organization, &tfc.WorkspaceListOptions{
WildcardName: targetWorkspace.WildcardName,
})
if err != nil {
return []string{}, err
}
workspaceIDs := []string{}
for _, w := range list.Items {
workspaceIDs = append(workspaceIDs, w.ID)
}
return workspaceIDs, nil
}

func computeRequiredAgents(ctx context.Context, ap *agentPoolInstance) (int32, error) {
required := 0
workspaceIDs, err := getTargetWorkspaceIDs(ctx, ap)
if err != nil {
return 0, err
if ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces == nil {
return int32(len(workspaceNames)), nil
}
for _, workspaceID := range workspaceIDs {
r, err := computeRequiredAgentsForWorkspace(ctx, ap, workspaceID)
if err != nil {
return 0, err

for _, t := range *ap.instance.Spec.AgentDeploymentAutoscaling.TargetWorkspaces {
switch {
case t.Name != "":
if _, ok := workspaceNames[t.Name]; ok {
required++
}
// info message?
case t.ID != "":
if _, ok := workspaceIDs[t.ID]; ok {
required++
}
case t.WildcardName != "":
nn := strings.Trim(t.WildcardName, "*")
for w := range workspaceNames {
if strings.Contains(w, nn) {
required++
delete(workspaceNames, w)
}
}
}
required += r
}

return int32(required), nil
}

Expand Down Expand Up @@ -166,16 +122,12 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap

ap.log.Info("Reconcile Agent Autoscaling", "msg", "new reconciliation event")

status := ap.instance.Status.AgentDeploymentAutoscalingStatus
if status != nil {
lastScalingEvent := status.LastScalingEvent
if lastScalingEvent != nil {
lastScalingEventSeconds := int(time.Since(lastScalingEvent.Time).Seconds())
cooldownPeriodSeconds := ap.instance.Spec.AgentDeploymentAutoscaling.CooldownPeriodSeconds
if lastScalingEventSeconds <= int(*cooldownPeriodSeconds) {
ap.log.Info("Reconcile Agent Autoscaling", "msg", "autoscaler is within the cooldown period, skipping")
return nil
}
if s := ap.instance.Status.AgentDeploymentAutoscalingStatus; s != nil && s.LastScalingEvent != nil {
lastScalingEventSeconds := int(time.Since(s.LastScalingEvent.Time).Seconds())
cooldownPeriodSeconds := int(*ap.instance.Spec.AgentDeploymentAutoscaling.CooldownPeriodSeconds)
if lastScalingEventSeconds <= cooldownPeriodSeconds {
ap.log.Info("Reconcile Agent Autoscaling", "msg", "autoscaler is within the cooldown period, skipping")
return nil
}
}

Expand All @@ -185,6 +137,7 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap
r.Recorder.Eventf(&ap.instance, corev1.EventTypeWarning, "AutoscaleAgentPoolDeployment", "Autoscaling failed: %v", err.Error())
return err
}
ap.log.Info("Reconcile Agent Autoscaling", "msg", fmt.Sprintf("%d workspaces have pending runs", requiredAgents))

currentReplicas, err := r.getAgentDeploymentReplicas(ctx, ap)
if err != nil {
Expand All @@ -208,7 +161,7 @@ func (r *AgentPoolReconciler) reconcileAgentAutoscaling(ctx context.Context, ap
}
ap.instance.Status.AgentDeploymentAutoscalingStatus = &appv1alpha2.AgentDeploymentAutoscalingStatus{
DesiredReplicas: &desiredReplicas,
LastScalingEvent: &v1.Time{
LastScalingEvent: &metav1.Time{
Time: time.Now(),
},
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/hashicorp/terraform-cloud-operator

go 1.22
go 1.22.4

require (
github.com/go-logr/logr v1.4.1
Expand Down

0 comments on commit 432d807

Please sign in to comment.