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

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions