Skip to content

cloudstack_project cannot be imported or read by admin accounts that are not project members — read path never sets listall #303

Description

@Daniel-Alfasi

Summary

terraform import cloudstack_project.<name> <uuid> fails with a not-found error whenever the
account behind the provider credentials is not a member of the project — even when that
account is a Root Admin or Domain Admin that can see the project via listProjects with
listall=true.

The root cause is that the provider's project lookup never sets the listall parameter, and the
CloudStack API default (listall=false) makes listProjects participant-scoped: it only
returns projects the calling account owns or is a member of, regardless of the caller's role.

Steps to reproduce

  1. Have a project P owned by account team-a in domain D.
  2. Configure the provider with credentials of a Root Admin (or Domain Admin over D) whose
    account is not a member of P.
  3. Run terraform import cloudstack_project.p <uuid-of-P>.

The API-level asymmetry is easy to demonstrate with CloudMonkey using the same key pair:

$ cmk list projects id=<uuid-of-P>                # count: 0  (participant-scoped default)
$ cmk list projects id=<uuid-of-P> listall=true   # returns the project

Expected behavior

The import succeeds: an admin whose role entitles it to see the project via listall=true
should be able to import it.

Actual behavior

The import fails with a not-found error ("No match found for " from the lookup /
"Cannot import non-existent remote object"), because the read path queries with the
participant-scoped default.

Root cause

resourceCloudStackProjectRead (cloudstack/resource_cloudstack_project.go) resolves the project
via the getProjectByID helper, which builds the params with only the id (and optionally the
domain):

p := cs.Project.NewListProjectsParams()
p.SetId(id)
...
l, err := cs.Project.ListProjects(p)

listall is never set, and neither the helper nor the SDK's NewListProjectsParams applies a
default, so the request goes out with the API default listall=false.

Proposed fix

Set listall=true on the lookup — the SDK already exposes it:

p := cs.Project.NewListProjectsParams()
p.SetId(id)
p.SetListall(true)

This does not widen anyone's privileges: listall=true only disables the "own/participating
projects only" default filter, and the management server still enforces the caller's role and
domain-subtree entitlements. Callers who could not see the project before will still get
count: 0.

Workaround and why it is insufficient

The current workaround is to add the provider's account as a member of every project
(addAccountToProject). This is impractical at scale (we hit this with ~90 projects) and,
more importantly, impossible across domains: CloudStack requires a project member account
to belong to the project's exact domain (ProjectManagerImpl looks the account up with
project.getDomainId()), and an account cannot exist in more than one domain. A Root Admin
managing projects across several domains therefore has no workaround at all.

Environment

  • Provider version: latest
  • CloudStack version: latest
  • Terraform version: 1.5.7

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions