Skip to content

Commit

Permalink
feat: pass go-tfe organization tests (#495)
Browse files Browse the repository at this point in the history
...and ensure `tfe_organization` returns a clean plan.
  • Loading branch information
leg100 authored Jul 2, 2023
1 parent 77a9c7e commit 5cb3cb1
Show file tree
Hide file tree
Showing 34 changed files with 597 additions and 355 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ sql: install-pggen
--query-glob 'internal/sql/queries/*.sql' \
--output-dir ./internal/sql/pggen \
--go-type 'text=github.com/jackc/pgtype.Text' \
--go-type 'int4=int' \
--go-type 'int8=int' \
--go-type 'int4=github.com/jackc/pgtype.Int4' \
--go-type 'int8=github.com/jackc/pgtype.Int8' \
--go-type 'bool=bool' \
--go-type 'bytea=[]byte' \
--acronym url \
Expand Down
1 change: 1 addition & 0 deletions hack/go-tfe-tests-upstream.bash
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export GO_TFE_REPO=github.com/hashicorp/go-tfe@latest
export OAUTH_CLIENT_GITHUB_TOKEN="my-secret-github-token"

tests=()
tests+=('TestOrganizations')
tests+=('TestStateVersionOutputsRead')
tests+=('TestOrganizationTagsList/with_no_query_params')
tests+=('TestOrganizationTagsList/with_no_param_Filter')
Expand Down
2 changes: 1 addition & 1 deletion hack/go-tfe-tests.bash
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
set -e

GO_TFE_REPO="${GO_TFE_REPO:-github.com/leg100/go-tfe@otf}"
TESTS="${@:-Test(Variables|Workspaces(Create|List|Update|Delete|Lock|Unlock|ForceUnlock|Read\$|ReadByID)|Organizations(Create|List|Read|Update)|StateVersion|Runs|Plans|Applies(Read|Logs)|ConfigurationVersions|TeamsRead)}"
TESTS="${@:-Test(Variables|Workspaces(Create|List|Update|Delete|Lock|Unlock|ForceUnlock|Read\$|ReadByID)|StateVersion|Runs|Plans|Applies(Read|Logs)|ConfigurationVersions)}"

export TFE_ADDRESS="${TFE_ADDRESS:-https://localhost:8080}"
# go-tfe tests perform privileged operations (e.g. creating organizations), so
Expand Down
17 changes: 11 additions & 6 deletions internal/api/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ func (a *api) createOrganization(w http.ResponseWriter, r *http.Request) {
}

org, err := a.CreateOrganization(r.Context(), orgcreator.OrganizationCreateOptions{
Name: opts.Name,
SessionRemember: opts.SessionRemember,
SessionTimeout: opts.SessionTimeout,
Name: opts.Name,
Email: opts.Email,
CollaboratorAuthPolicy: (*string)(opts.CollaboratorAuthPolicy),
SessionRemember: opts.SessionRemember,
SessionTimeout: opts.SessionTimeout,
AllowForceDeleteWorkspaces: opts.AllowForceDeleteWorkspaces,
})
if err != nil {
Error(w, err)
Expand Down Expand Up @@ -90,9 +93,11 @@ func (a *api) updateOrganization(w http.ResponseWriter, r *http.Request) {
}

org, err := a.UpdateOrganization(r.Context(), name, organization.OrganizationUpdateOptions{
Name: opts.Name,
SessionRemember: opts.SessionRemember,
SessionTimeout: opts.SessionTimeout,
Name: opts.Name,
Email: opts.Email,
CollaboratorAuthPolicy: (*string)(opts.CollaboratorAuthPolicy),
SessionRemember: opts.SessionRemember,
SessionTimeout: opts.SessionTimeout,
})
if err != nil {
Error(w, err)
Expand Down
24 changes: 16 additions & 8 deletions internal/api/organization_marshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import (
"github.com/leg100/otf/internal/organization"
)

func (m *jsonapiMarshaler) toOrganization(org *organization.Organization) *types.Organization {
return &types.Organization{
Name: org.Name,
CreatedAt: org.CreatedAt,
ExternalID: org.ID,
Permissions: &types.DefaultOrganizationPermissions,
SessionRemember: org.SessionRemember,
SessionTimeout: org.SessionTimeout,
func (m *jsonapiMarshaler) toOrganization(from *organization.Organization) *types.Organization {
to := &types.Organization{
Name: from.Name,
CreatedAt: from.CreatedAt,
ExternalID: from.ID,
Permissions: &types.DefaultOrganizationPermissions,
SessionRemember: from.SessionRemember,
SessionTimeout: from.SessionTimeout,
AllowForceDeleteWorkspaces: from.AllowForceDeleteWorkspaces,
}
if from.Email != nil {
to.Email = *from.Email
}
if from.CollaboratorAuthPolicy != nil {
to.CollaboratorAuthPolicy = types.AuthPolicyType(*from.CollaboratorAuthPolicy)
}
return to
}

func (m *jsonapiMarshaler) toOrganizationList(from *organization.OrganizationList) (to []*types.Organization, opts []jsonapi.MarshalOption) {
Expand Down
99 changes: 81 additions & 18 deletions internal/api/types/organization.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package types

import "time"
Expand All @@ -8,19 +11,29 @@ var DefaultOrganizationPermissions = OrganizationPermissions{
CanDestroy: true,
}

// Organization JSON-API representation
// Organization represents a Terraform Enterprise organization.
type Organization struct {
Name string `jsonapi:"primary,organizations"`
CostEstimationEnabled bool `jsonapi:"attribute" json:"cost-estimation-enabled"`
CreatedAt time.Time `jsonapi:"attribute" json:"created-at"`
ExternalID string `jsonapi:"attribute" json:"external-id"`
OwnersTeamSAMLRoleID string `jsonapi:"attribute" json:"owners-team-saml-role-id"`
Permissions *OrganizationPermissions `jsonapi:"attribute" json:"permissions"`
SAMLEnabled bool `jsonapi:"attribute" json:"saml-enabled"`
SessionRemember int `jsonapi:"attribute" json:"session-remember"`
SessionTimeout int `jsonapi:"attribute" json:"session-timeout"`
TrialExpiresAt time.Time `jsonapi:"attribute" json:"trial-expires-at"`
TwoFactorConformant bool `jsonapi:"attribute" json:"two-factor-conformant"`
Name string `jsonapi:"primary,organizations"`
AssessmentsEnforced bool `jsonapi:"attribute" json:"assessments-enforced"`
CollaboratorAuthPolicy AuthPolicyType `jsonapi:"attribute" json:"collaborator-auth-policy"`
CostEstimationEnabled bool `jsonapi:"attribute" json:"cost-estimation-enabled"`
CreatedAt time.Time `jsonapi:"attribute" json:"created-at"`
Email string `jsonapi:"attribute" json:"email"`
ExternalID string `jsonapi:"attribute" json:"external-id"`
OwnersTeamSAMLRoleID string `jsonapi:"attribute" json:"owners-team-saml-role-id"`
Permissions *OrganizationPermissions `jsonapi:"attribute" json:"permissions"`
SAMLEnabled bool `jsonapi:"attribute" json:"saml-enabled"`
SessionRemember *int `jsonapi:"attribute" json:"session-remember"`
SessionTimeout *int `jsonapi:"attribute" json:"session-timeout"`
TrialExpiresAt time.Time `jsonapi:"attribute" json:"trial-expires-at"`
TwoFactorConformant bool `jsonapi:"attribute" json:"two-factor-conformant"`
SendPassingStatusesForUntriggeredSpeculativePlans bool `jsonapi:"attribute" json:"send-passing-statuses-for-untriggered-speculative-plans"`
// Note: This will be false for TFE versions older than v202211, where the setting was introduced.
// On those TFE versions, safe delete does not exist, so ALL deletes will be force deletes.
AllowForceDeleteWorkspaces bool `jsonapi:"attribute" json:"allow-force-delete-workspaces"`

// Relations
// DefaultProject *Project `jsonapi:"relation,default-project"`
}

// OrganizationList JSON-API representation
Expand All @@ -42,26 +55,46 @@ type OrganizationPermissions struct {
CanUpdateSentinel bool `json:"can-update-sentinel"`
}

// OrganizationCreateOptions represents the options for creating an
// organization.
// OrganizationCreateOptions represents the options for creating an organization.
type OrganizationCreateOptions struct {
// Type is a public field utilized by JSON:API to
// set the resource type via the field tag.
// It is not a user-defined value and does not need to be set.
// https://jsonapi.org/format/#crud-creating
Type string `jsonapi:"primary,organizations"`

// Name of the organization.
// Required: Name of the organization.
Name *string `jsonapi:"attribute" json:"name"`

// Optional: AssessmentsEnforced toggles whether health assessment enablement is enforced across all assessable workspaces (those with a minimum terraform versio of 0.15.4 and not running in local execution mode) or if the decision to enabled health assessments is delegated to the workspace setting AssessmentsEnabled.
AssessmentsEnforced *bool `jsonapi:"attribute" json:"assessments-enforced,omitempty"`

// Required: Admin email address.
Email *string `jsonapi:"attribute" json:"email"`

// Optional: Session expiration (minutes).
SessionRemember *int `jsonapi:"attribute" json:"session-remember,omitempty"`

// Session timeout after inactivity (minutes).
// Optional: Session timeout after inactivity (minutes).
SessionTimeout *int `jsonapi:"attribute" json:"session-timeout,omitempty"`

// Optional: Authentication policy.
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attribute" json:"collaborator-auth-policy,omitempty"`

// Optional: Enable Cost Estimation
CostEstimationEnabled *bool `jsonapi:"attribute" json:"cost-estimation-enabled,omitempty"`

// Optional: The name of the "owners" team
OwnersTeamSAMLRoleID *string `jsonapi:"attribute" json:"owners-team-saml-role-id,omitempty"`

// Optional: SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub.
SendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:"attribute" json:"send-passing-statuses-for-untriggered-speculative-plans,omitempty"`

// Optional: AllowForceDeleteWorkspaces toggles behavior of allowing workspace admins to delete workspaces with resources under management.
AllowForceDeleteWorkspaces *bool `jsonapi:"attribute" json:"allow-force-delete-workspaces,omitempty"`
}

// OrganizationUpdateOptions represents the options for updating an
// organization.
// OrganizationUpdateOptions represents the options for updating an organization.
type OrganizationUpdateOptions struct {
// Type is a public field utilized by JSON:API to
// set the resource type via the field tag.
Expand All @@ -72,11 +105,32 @@ type OrganizationUpdateOptions struct {
// New name for the organization.
Name *string `jsonapi:"attribute" json:"name,omitempty"`

// Optional: AssessmentsEnforced toggles whether health assessment enablement is enforced across all assessable workspaces (those with a minimum terraform versio of 0.15.4 and not running in local execution mode) or if the decision to enabled health assessments is delegated to the workspace setting AssessmentsEnabled.
AssessmentsEnforced *bool `jsonapi:"attribute" json:"assessments-enforced,omitempty"`

// New admin email address.
Email *string `jsonapi:"attribute" json:"email,omitempty"`

// Session expiration (minutes).
SessionRemember *int `jsonapi:"attribute" json:"session-remember,omitempty"`

// Session timeout after inactivity (minutes).
SessionTimeout *int `jsonapi:"attribute" json:"session-timeout,omitempty"`

// Authentication policy.
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attribute" json:"collaborator-auth-policy,omitempty"`

// Enable Cost Estimation
CostEstimationEnabled *bool `jsonapi:"attribute" json:"cost-estimation-enabled,omitempty"`

// The name of the "owners" team
OwnersTeamSAMLRoleID *string `jsonapi:"attribute" json:"owners-team-saml-role-id,omitempty"`

// SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub.
SendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:"attribute" json:"send-passing-statuses-for-untriggered-speculative-plans,omitempty"`

// Optional: AllowForceDeleteWorkspaces toggles behavior of allowing workspace admins to delete workspaces with resources under management.
AllowForceDeleteWorkspaces *bool `jsonapi:"attribute" json:"allow-force-delete-workspaces,omitempty"`
}

// Entitlements represents the entitlements of an organization. Unlike TFE/TFC,
Expand All @@ -95,3 +149,12 @@ type Entitlements struct {
Teams bool `jsonapi:"attribute" json:"teams"`
VCSIntegrations bool `jsonapi:"attribute" json:"vcs-integrations"`
}

// AuthPolicyType represents an authentication policy type.
type AuthPolicyType string

// List of available authentication policies.
const (
AuthPolicyPassword AuthPolicyType = "password"
AuthPolicyTwoFactor AuthPolicyType = "two_factor_mandatory"
)
20 changes: 12 additions & 8 deletions internal/auth/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,20 @@ type (
CreateTeamOptions struct {
// Name of team to create
Name *string `schema:"name,required"`

OrganizationAccessOptions

// TFE fields that OTF does not support but persists merely to pass the
// go-tfe integration tests
SSOTeamID *string
Visibility *string

// Database transaction within which to create team. Optional.
Tx internal.DB
}

UpdateTeamOptions struct {
Name *string

OrganizationAccessOptions

Expand Down Expand Up @@ -62,14 +74,6 @@ type (
ManagePolicies *bool
ManagePolicyOverrides *bool
}

UpdateTeamOptions struct {
Name *string
SSOTeamID *string
Visibility *string

OrganizationAccessOptions
}
)

func newTeam(organization string, opts CreateTeamOptions) (*Team, error) {
Expand Down
6 changes: 3 additions & 3 deletions internal/configversion/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func (db *pgdb) ListConfigurationVersions(ctx context.Context, workspaceID strin
batch := &pgx.Batch{}
db.FindConfigurationVersionsByWorkspaceIDBatch(batch, pggen.FindConfigurationVersionsByWorkspaceIDParams{
WorkspaceID: sql.String(workspaceID),
Limit: opts.GetLimit(),
Offset: opts.GetOffset(),
Limit: sql.Int8(opts.GetLimit()),
Offset: sql.Int8(opts.GetOffset()),
})
db.CountConfigurationVersionsByWorkspaceIDBatch(batch, sql.String(workspaceID))
results := db.SendBatch(ctx, batch)
Expand All @@ -96,7 +96,7 @@ func (db *pgdb) ListConfigurationVersions(ctx context.Context, workspaceID strin

return &ConfigurationVersionList{
Items: items,
Pagination: internal.NewPagination(opts.ListOptions, count),
Pagination: internal.NewPagination(opts.ListOptions, int(count.Int)),
}, nil
}

Expand Down
8 changes: 4 additions & 4 deletions internal/logs/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ func (db *pgdb) put(ctx context.Context, opts internal.PutChunkOptions) (string,
RunID: sql.String(opts.RunID),
Phase: sql.String(string(opts.Phase)),
Chunk: opts.Data,
Offset: opts.Offset,
Offset: sql.Int4(opts.Offset),
})
if err != nil {
return "", sql.Error(err)
}
return strconv.Itoa(id), nil
return strconv.Itoa(int(id.Int)), nil
}

// GetByID implements pubsub.Getter
Expand All @@ -45,7 +45,7 @@ func (db *pgdb) GetByID(ctx context.Context, chunkID string, action pubsub.DBAct
if err != nil {
return nil, err
}
chunk, err := db.FindLogChunkByID(ctx, id)
chunk, err := db.FindLogChunkByID(ctx, sql.Int4(id))
if err != nil {
return nil, sql.Error(err)
}
Expand All @@ -54,6 +54,6 @@ func (db *pgdb) GetByID(ctx context.Context, chunkID string, action pubsub.DBAct
RunID: chunk.RunID.String,
Phase: internal.PhaseType(chunk.Phase.String),
Data: chunk.Chunk,
Offset: chunk.Offset,
Offset: int(chunk.Offset.Int),
}, nil
}
Loading

0 comments on commit 5cb3cb1

Please sign in to comment.