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

Update Run Tasks for global config and stages #865

Merged
merged 4 commits into from
Apr 8, 2024
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

## Enhancements
* Adds Bitbucket Data Center as a new `ServiceProviderType` and ensures similar validation as Bitbucket Server by @zainq11 [#879](https://github.com/hashicorp/go-tfe/pull/879)
* Add `GlobalRunTasks` field to `Entitlements`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)
* Add `Global` field to `RunTask`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)
* Add `Stages` field to `WorkspaceRunTask`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)

# v1.49.0
## Deprecations
* The `Stage` field has been deprecated on `WorkspaceRunTask`. Instead, use `Stages`. by @glennsarti [#865](https://github.com/hashicorp/go-tfe/pull/865)

# v1.49.0
## Enhancements
* Adds `post_apply` to list of possible `stages` for Run Tasks by @glennsarti [#878](https://github.com/hashicorp/go-tfe/pull/878)

Expand Down
74 changes: 4 additions & 70 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"io"
"math/rand"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
Expand All @@ -38,31 +37,6 @@ const agentVersion = "1.3.0"

var _testAccountDetails *TestAccountDetails

type featureSet struct {
ID string `jsonapi:"primary,feature-sets"`
}

type featureSetList struct {
Items []*featureSet
*Pagination
}

type featureSetListOptions struct {
Q string `url:"q,omitempty"`
}

type retryableFn func() (interface{}, error)

type updateFeatureSetOptions struct {
Type string `jsonapi:"primary,subscription"`
RunsCeiling int `jsonapi:"attr,runs-ceiling"`
ContractStartAt time.Time `jsonapi:"attr,contract-start-at,iso8601"`
ContractUserLimit int `jsonapi:"attr,contract-user-limit"`
ContractApplyLimit int `jsonapi:"attr,contract-apply-limit"`

FeatureSet *featureSet `jsonapi:"relation,feature-set"`
}

func testClient(t *testing.T) *Client {
client, err := NewClient(&Config{
RetryServerErrors: true,
Expand Down Expand Up @@ -959,7 +933,7 @@ func createOrganizationWithOptions(t *testing.T, client *Client, options Organiz
ctx := context.Background()
org, err := client.Organizations.Create(ctx, options)
if err != nil {
t.Fatal(err)
t.Fatalf("Failed to create organization: %s", err)
}

return org, func() {
Expand Down Expand Up @@ -2526,49 +2500,9 @@ func createVariableSetVariable(t *testing.T, client *Client, vs *VariableSet, op
}

// Attempts to upgrade an organization to the business plan. Requires a user token with admin access.
func upgradeOrganizationSubscription(t *testing.T, client *Client, organization *Organization) {
if enterpriseEnabled() {
t.Skip("Cannot upgrade an organization's subscription when enterprise is enabled. Set ENABLE_TFE=0 to run.")
}

adminClient := testAdminClient(t, provisionLicensesAdmin)
req, err := adminClient.NewRequest("GET", "admin/feature-sets", featureSetListOptions{
Q: "Business",
})
if err != nil {
t.Fatal(err)
return
}

fsl := &featureSetList{}
err = req.Do(context.Background(), fsl)
if err != nil {
t.Fatalf("failed to enumerate feature sets: %v", err)
return
} else if len(fsl.Items) == 0 {
t.Fatalf("feature set response was empty")
return
}

opts := updateFeatureSetOptions{
RunsCeiling: 10,
ContractStartAt: time.Now(),
ContractUserLimit: 1000,
ContractApplyLimit: 5000,
FeatureSet: fsl.Items[0],
}

u := fmt.Sprintf("admin/organizations/%s/subscription", url.QueryEscape(organization.Name))
req, err = adminClient.NewRequest("POST", u, &opts)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
return
}

err = req.Do(context.Background(), nil)
if err != nil {
t.Fatalf("Failed to upgrade subscription: %v", err)
}
// DEPRECATED : Please use the newSubscriptionUpdater instead.
func upgradeOrganizationSubscription(t *testing.T, _ *Client, organization *Organization) {
newSubscriptionUpdater(organization).WithBusinessPlan().Update(t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!! 👍

}

func createProject(t *testing.T, client *Client, org *Organization) (*Project, func()) {
Expand Down
103 changes: 103 additions & 0 deletions internal_run_task.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tfe

// A private struct we need for unmarshalling
type internalRunTask struct {
ID string `jsonapi:"primary,tasks"`
Name string `jsonapi:"attr,name"`
URL string `jsonapi:"attr,url"`
Description string `jsonapi:"attr,description"`
Category string `jsonapi:"attr,category"`
HMACKey *string `jsonapi:"attr,hmac-key,omitempty"`
Enabled bool `jsonapi:"attr,enabled"`
RawGlobal map[string]interface{} `jsonapi:"attr,global-configuration,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL that jsonapi doesn't automatically decode json annotated attribute object types. It's certainly in spec to have an object within an attribute. I will take a closer look after I finish this review.

Copy link
Contributor Author

@glennsarti glennsarti Apr 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, jsonapi is effectively a dead project :-( It had a "No Maintenance Intended" badge added 4 years ago and the PR/Issues list is wasteland. We'd have to fork it if we wanted to fix any issues with it.

DERP ME ... we are using a fork


Organization *Organization `jsonapi:"relation,organization"`
WorkspaceRunTasks []*internalWorkspaceRunTask `jsonapi:"relation,workspace-tasks"`
}

// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using map[string]interface{}
// and then perform our own conversion from the map into a GlobalRunTask struct
func (irt internalRunTask) ToRunTask() *RunTask {
obj := RunTask{
ID: irt.ID,
Name: irt.Name,
URL: irt.URL,
Description: irt.Description,
Category: irt.Category,
HMACKey: irt.HMACKey,
Enabled: irt.Enabled,

Organization: irt.Organization,
}

// Convert the WorkspaceRunTasks
workspaceTasks := make([]*WorkspaceRunTask, len(irt.WorkspaceRunTasks))
for idx, rawTask := range irt.WorkspaceRunTasks {
if rawTask != nil {
workspaceTasks[idx] = rawTask.ToWorkspaceRunTask()
}
}
obj.WorkspaceRunTasks = workspaceTasks

// Check if the global configuration exists
if val, ok := irt.RawGlobal["enabled"]; !ok {
// The enabled property is required so we can assume now that the
// global configuration was not supplied
return &obj
} else if boolVal, ok := val.(bool); !ok {
// The enabled property exists but it is invalid (Couldn't cast to boolean)
// so assume the global configuration was not supplied
return &obj
} else {
obj.Global = &GlobalRunTask{
Enabled: boolVal,
}
}

// Global Enforcement Level
if val, ok := irt.RawGlobal["enforcement-level"]; ok {
if stringVal, ok := val.(string); ok {
obj.Global.EnforcementLevel = TaskEnforcementLevel(stringVal)
}
}

// Global Stages
if val, ok := irt.RawGlobal["stages"]; ok {
if stringsVal, ok := val.([]interface{}); ok {
obj.Global.Stages = make([]Stage, len(stringsVal))
for idx, stageName := range stringsVal {
if stringVal, ok := stageName.(string); ok {
obj.Global.Stages[idx] = Stage(stringVal)
}
}
}
}

return &obj
}

// A private struct we need for unmarshalling
type internalRunTaskList struct {
*Pagination
Items []*internalRunTask
}

// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using
// the internal RunTask struct and convert that a RunTask
func (irt internalRunTaskList) ToRunTaskList() *RunTaskList {
obj := RunTaskList{
Pagination: irt.Pagination,
Items: make([]*RunTask, len(irt.Items)),
}

for idx, src := range irt.Items {
if src != nil {
obj.Items[idx] = src.ToRunTask()
}
}

return &obj
}
57 changes: 57 additions & 0 deletions internal_workspace_run_task.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tfe

// A private struct we need for unmarshalling
type internalWorkspaceRunTask struct {
ID string `jsonapi:"primary,workspace-tasks"`
EnforcementLevel TaskEnforcementLevel `jsonapi:"attr,enforcement-level"`
Stage Stage `jsonapi:"attr,stage"`
Stages []string `jsonapi:"attr,stages"`

RunTask *RunTask `jsonapi:"relation,task"`
Workspace *Workspace `jsonapi:"relation,workspace"`
}

// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using map[string]interface{}
// and then perform our own conversion for the Stages
func (irt internalWorkspaceRunTask) ToWorkspaceRunTask() *WorkspaceRunTask {
obj := WorkspaceRunTask{
ID: irt.ID,
EnforcementLevel: irt.EnforcementLevel,
Stage: irt.Stage,
Stages: make([]Stage, len(irt.Stages)),
RunTask: irt.RunTask,
Workspace: irt.Workspace,
}

for idx, val := range irt.Stages {
obj.Stages[idx] = Stage(val)
}

return &obj
}

// A private struct we need for unmarshalling
type internalWorkspaceRunTaskList struct {
*Pagination
Items []*internalWorkspaceRunTask
}

// Due to https://github.com/google/jsonapi/issues/74 we must first unmarshall using
// the internal WorkspaceRunTask struct and convert that a WorkspaceRunTask
func (irt internalWorkspaceRunTaskList) ToWorkspaceRunTaskList() *WorkspaceRunTaskList {
obj := WorkspaceRunTaskList{
Pagination: irt.Pagination,
Items: make([]*WorkspaceRunTask, len(irt.Items)),
}

for idx, src := range irt.Items {
if src != nil {
obj.Items[idx] = src.ToWorkspaceRunTask()
}
}

return &obj
}
1 change: 1 addition & 0 deletions organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type Entitlements struct {
Agents bool `jsonapi:"attr,agents"`
AuditLogging bool `jsonapi:"attr,audit-logging"`
CostEstimation bool `jsonapi:"attr,cost-estimation"`
GlobalRunTasks bool `jsonapi:"attr,global-run-tasks"`
Operations bool `jsonapi:"attr,operations"`
PrivateModuleRegistry bool `jsonapi:"attr,private-module-registry"`
RunTasks bool `jsonapi:"attr,run-tasks"`
Expand Down
Loading
Loading