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

[WAYP-2819] Support creating no-code workspaces #927

Merged
merged 9 commits into from
Jul 26, 2024
1 change: 1 addition & 0 deletions .github/actions/test-go-tfe/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ runs:
TFC_RUN_TASK_URL: "http://testing-mocks.tfe:22180/runtasks/pass"
GITHUB_POLICY_SET_IDENTIFIER: "hashicorp/test-policy-set"
GITHUB_REGISTRY_MODULE_IDENTIFIER: "hashicorp/terraform-random-module"
GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER: "hashicorp/terraform-random-no-code-module"
Copy link
Contributor

Choose a reason for hiding this comment

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

Could my team be provided with an admin permissions to this repo? I'm just thinking of the future scenario where we may want to add an gh action secret or adjust the settings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added your team as admin!

OAUTH_CLIENT_GITHUB_TOKEN: "${{ inputs.oauth-client-github-token }}"
GO111MODULE: "on"
ENABLE_TFE: ${{ inputs.enterprise }}
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ There are instances where several new resources being added (i.e Workspace Run T

After opening a PR, our CI system will perform a series of code checks, one of which is linting. Linting is not strictly required for a change to be merged, but it helps smooth the review process and catch common mistakes early. If you'd like to run the linters manually, follow these steps:

1. Ensure you have [installed golangci-lint](https://golangci-lint.run/usage/install/#local-installation)
Copy link
Contributor

Choose a reason for hiding this comment

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

oh, "page not found". Thanks for this update!

1. Ensure you have [installed golangci-lint](https://golangci-lint.run/welcome/install/#local-installation)
2. Format your code by running `make fmt`
3. Run lint checks using `make lint`

Expand Down
1 change: 1 addition & 0 deletions docs/TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Tests are run against an actual backend so they require a valid backend address
```sh
$ GITHUB_APP_INSTALLATION_ID=ghain-xxxx TFE_ADDRESS= https://tfe.local TFE_TOKEN=xxx GITHUB_POLICY_SET_IDENTIFIER=username/repository GITHUB_REGISTRY_MODULE_IDENTIFIER=username/repository go test -run "(GHA|GithubApp)" -v ./...
```
8. `GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER` - Required for running tests for workspaces using no-code modules.

## 3. Make sure run queue settings are correct

Expand Down
15 changes: 15 additions & 0 deletions mocks/registry_no_code_module_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions registry_no_code_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,42 @@ type RegistryNoCodeModules interface {
// Delete a registry no-code module
// **Note: This API is still in BETA and subject to change.**
Delete(ctx context.Context, ID string) error

// CreateWorkspace creates a workspace using a no-code module.
CreateWorkspace(ctx context.Context, noCodeModuleID string, options *RegistryNoCodeModuleCreateWorkspaceOptions) (*RegistryNoCodeModuleWorkspace, error)
}

type RegistryNoCodeModuleCreateWorkspaceOptions struct {
Type string `jsonapi:"primary,no-code-module-workspace"`
// Add more create options here

// Name is the name of the workspace, which can only include letters,
// numbers, and _. This will be used as an identifier and must be unique in
// the organization.
Name string `jsonapi:"attr,name"`

// Description is a description for the workspace.
Description *string `jsonapi:"attr,description,omitempty"`

AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"`

// Project is the associated project with the workspace. If not provided,
// default project of the organization will be assigned to the workspace.
Project *Project `jsonapi:"relation,project,omitempty"`

// Variables is the slice of variables to be configured for the no-code
// workspace.
Variables []*Variable `jsonapi:"relation,vars,omitempty"`

// SourceName is the name of the source of the workspace.
SourceName *string `jsonapi:"attr,source-name,omitempty"`

// SourceUrl is the URL of the source of the workspace.
SourceURL *string `jsonapi:"attr,source-url,omitempty"`
}

type RegistryNoCodeModuleWorkspace struct {
Workspace
}

// registryNoCodeModules implements RegistryNoCodeModules.
Expand Down Expand Up @@ -223,6 +259,31 @@ func (r *registryNoCodeModules) Delete(ctx context.Context, noCodeModuleID strin
return req.Do(ctx, nil)
}

// CreateWorkspace creates a no-code workspace using a no-code module.
func (r *registryNoCodeModules) CreateWorkspace(
ctx context.Context,
noCodeModuleID string,
options *RegistryNoCodeModuleCreateWorkspaceOptions,
) (*RegistryNoCodeModuleWorkspace, error) {
if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("no-code-modules/%s/workspaces", url.QueryEscape(noCodeModuleID))
req, err := r.client.NewRequest("POST", u, options)
if err != nil {
return nil, err
}

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

return w, nil
}

func (o RegistryNoCodeModuleCreateOptions) valid() error {
if o.RegistryModule == nil || o.RegistryModule.ID == "" {
return ErrRequiredRegistryModule
Expand All @@ -246,3 +307,11 @@ func (o *RegistryNoCodeModuleUpdateOptions) valid() error {
func (o *RegistryNoCodeModuleReadOptions) valid() error {
return nil
}

func (o *RegistryNoCodeModuleCreateWorkspaceOptions) valid() error {
if !validString(&o.Name) {
return ErrRequiredName
}

return nil
}
87 changes: 87 additions & 0 deletions registry_no_code_module_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ package tfe

import (
"context"
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -298,3 +301,87 @@ func createNoCodeRegistryModule(t *testing.T, client *Client, orgName string, rm
}
}
}

func TestRegistryNoCodeModulesCreateWorkspace(t *testing.T) {
skipUnlessBeta(t)
client := testClient(t)
ctx := context.Background()
r := require.New(t)

// create an org that will be deleted later. the wskp will live here
orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

org, err := client.Organizations.Read(ctx, orgTest.Name)
r.NoError(err)
r.NotNil(org)

githubIdentifier := os.Getenv("GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER")
if githubIdentifier == "" {
t.Skip("Export a valid GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER before running this test")
}

token, cleanupToken := createOAuthToken(t, client, org)
defer cleanupToken()

rmOpts := RegistryModuleCreateWithVCSConnectionOptions{
VCSRepo: &RegistryModuleVCSRepoOptions{
OrganizationName: String(org.Name),
Identifier: String(githubIdentifier),
Tags: Bool(true),
OAuthTokenID: String(token.ID),
DisplayIdentifier: String(githubIdentifier),
},
}

rm, err := client.RegistryModules.CreateWithVCSConnection(ctx, rmOpts)
r.NoError(err)

// 1. create the registry module
// 2. create the no-code module, with the registry module
// 3. use the ID to create the workspace
ncm, err := client.RegistryNoCodeModules.Create(ctx, org.Name, RegistryNoCodeModuleCreateOptions{
RegistryModule: rm,
Enabled: Bool(true),
VariableOptions: nil,
})
r.NoError(err)
r.NotNil(ncm)

// We sleep for 10 seconds to let the module finish getting ready
time.Sleep(time.Second * 10)

t.Run("test creating a workspace via a no-code module", func(t *testing.T) {
wn := fmt.Sprintf("foo-%s", randomString(t))
sn := "my-app"
su := "http://my-app.com"
_, err = client.RegistryNoCodeModules.CreateWorkspace(
ctx,
ncm.ID,
&RegistryNoCodeModuleCreateWorkspaceOptions{
Name: wn,
SourceName: String(sn),
SourceURL: String(su),
},
)
r.NoError(err)

w, err := client.Workspaces.Read(ctx, org.Name, wn)
r.NoError(err)
r.Equal(wn, w.Name)
r.Equal(sn, w.SourceName)
r.Equal(su, w.SourceURL)
})

t.Run("fail to create a workspace with a bad module ID", func(t *testing.T) {
wn := fmt.Sprintf("foo-%s", randomString(t))
_, err = client.RegistryNoCodeModules.CreateWorkspace(
ctx,
"codeno-abc123XYZ",
&RegistryNoCodeModuleCreateWorkspaceOptions{
Name: wn,
},
)
r.Error(err)
})
}
Loading