Table of Contents
Projects & Policies — ZEDCloud Terraform
A practical guide to how projects, deployments, and policies fit together in this
Terraform config, written against the zededa/zedcloud provider.
💡 TL;DR — A project groups devices and applies tenant-level policies. A deployment is a tag-driven template that auto-provisions config (networks, volumes, apps, attestation) onto every device carrying a matching tag. The matching is: devicetagskey/value == policypolicy_target_conditionkey/value.
1. Mental model
Project (TAG_TYPE_PROJECT | TAG_TYPE_DEPLOYMENT)
│
├── tenant-level policies → edgeview, attestation, configuration-lock
│
└── Deployment (zedcloud_deployment)
├── deployment_tag → deployment-wide device selector (a single string)
└── policies (per-policy device selector via policy_target_condition):
├── device_policies → attestation (accept / enforce)
├── network_inst_policies → auto-create a Network Instance per matching device
├── volume_inst_policies → auto-create a Volume Instance per matching device
├── app_inst_policies → auto-create an App Instance per matching device
├── cluster_policy
├── edgeview_policy
└── integration_policy
The key difference from a standalone resource (e.g. a single zedcloud_volume_instance pinned to
one node via device_id): a policy is a template. ZEDCloud stamps out a copy on every device
that matches the policy's condition, instead of one explicit instance.
2. Projects (zedcloud_project)
A project is the top-level container for devices and the policies applied to them.
Project types
| type | Purpose |
|---|---|
TAG_TYPE_PROJECT | Standard project that owns edge nodes, apps, networks, etc. |
TAG_TYPE_DEPLOYMENT | Project used as the home for zedcloud_deployment resources. |
In this repo:
demo_zededa_project_1(TF-ZEDEDA-DEMO) —TAG_TYPE_PROJECT, owns the edge nodes.demo_zededa_project_2(TF-DEPLOY-PROJ-1) —TAG_TYPE_DEPLOYMENT, home of the deployment.
Project-level policy blocks
| Block | What it controls |
|---|---|
tag_level_settings | Interface ordering, flow-log transmission. |
edgeview_policy | Whether EdgeView remote access is allowed, session limits, JWT settings. |
attestation_policy | Tenant-level TPM attestation acceptance. |
configuration_lock_policy | Whether device config can be locked. |
Example (deployment-type project)
resource "zedcloud_project" "demo_zededa_project_2" {
name = "TF-DEPLOY-PROJ-1"
title = "TF-DEPLOY-PROJ-1"
type = "TAG_TYPE_DEPLOYMENT"
tag_level_settings {
interface_ordering = "INTERFACE_ORDERING_ENABLED"
flow_log_transmission = "NETWORK_INSTANCE_FLOW_LOG_TRANSMISSION_ENABLED"
}
configuration_lock_policy {
type = "POLICY_TYPE_CONFIGURATION_LOCK"
configuration_lock_policy {
config_lock = "CONFIGURATION_LOCK_DISABLED"
}
}
attestation_policy {
type = "POLICY_TYPE_ATTESTATION"
attestation_policy {
type = "ATTEST_POLICY_TYPE_ACCEPT"
}
}
edgeview_policy {
type = "POLICY_TYPE_EDGEVIEW"
edgeview_policy {
access_allow_change = true
edgeview_allow = true
edgeviewcfg {
app_policy { allow_app = true }
dev_policy { allow_dev = true }
jwt_info {
disp_url = "zedcloud.gmwtus.zededa.net/api/v1/edge-view"
allow_sec = 18000
num_inst = 1
encrypt = false # see gotcha below
}
ext_policy { allow_ext = true }
}
max_expire_sec = 2592000
max_inst = 3
}
}
}
⚠️ EdgeView encryption gotcha. Settingencrypt = trueinjwt_infofails with:policy validation failed: edgeview policy: Edgeview Encryption is not supported and cannot be enabled(HTTP 400). EdgeView encryption is not supported on this tenant — keepencrypt = false.
3. Attestation: ACCEPT vs ENFORCE
Attestation appears at two levels (project attestation_policy and deployment device_policies).
The semantics, straight from the provider enum docs:
| Value | Meaning |
|---|---|
…_ACCEPT | Do not enforce attestation. All devices are marked as successfully attested (bypass). |
…_ENFORCE | Enforce attestation. Devices failing TPM attestation are marked accordingly. |
Note: A device policy can only be attestation — thepolicy_sub_typeenum has justDEVICE_POLICY_TYPE_UNSPECIFIEDandDEVICE_POLICY_TYPE_ATTESTATION.
4. Deployments (zedcloud_deployment)
A deployment lives in a TAG_TYPE_DEPLOYMENT project and carries one or more policy blocks.
Each policy is a template applied to devices matching its condition.
The two selectors — don't confuse them
| Scope | Field | Type | Role |
|---|---|---|---|
| Whole deployment | deployment_tag | single string | Deployment-wide device selector / label. |
| Each policy | policy_target_condition | key/value map | Per-policy device selector — this is what gates each policy. |
They do not have to be equal. In this repo they are deliberately different
(deployment_tag = “mc-edge-dep-tag” vs policy_target_condition = { demo = “mc-app-auto-dep” }).
THE ONE RULE
🔑 device'stagskey/value == policy'spolicy_target_conditionkey/value
A policy fires on a device only if the device'stagsmap contains the exact key/value pair in that policy'spolicy_target_condition.
⚠️ Tag values are strings. A device tag written asdevice-tag-1 = truebecomes the string“true”, so the condition must be quoted:policy_target_condition = { device-tag-1 = “true” }.
Policy blocks
| Block | Auto-creates per matching device | Key config |
|---|---|---|
device_policies | (no object) sets attestation accept/enforce | attestation_policy.type |
network_inst_policies | a Network Instance | net_inst_config (kind, type, port) |
volume_inst_policies | a Volume Instance | vol_inst_config (type, size, accessmode) |
app_inst_policies | an App Instance | app id, drives, interfaces |
Network instance kinds (quick reference)
| kind | Behaviour |
|---|---|
NETWORK_INSTANCE_KIND_SWITCH | Transparent L2 bridge. No DHCP/NAT — apps get direct access to the port. |
NETWORK_INSTANCE_KIND_LOCAL | L3 with DHCP + NAT managed by EVE. |
5. Worked example — target a single device
Step 1 — tag the device
resource "zedcloud_edgenode" "demo_asus_nuc_zks_1" {
# ...
tags = {
cluster = "zed-cluster-1"
device-tag-1 = true # selector for the deployment policies
}
}
Step 2 — write policies whose condition matches that tag
resource "zedcloud_deployment" "demo_eve_deployment_1" {
name = "TF-MC-DEPLOYMENT-TESTING"
title = "TF-MC-DEPLOYMENT-TESTING"
project_id = zedcloud_project.demo_zededa_project_2.id
deployment_tag = "mc-edge-dep-tag" # deployment label — independent of the match below
device_policies {
policy_sub_type = "DEVICE_POLICY_TYPE_ATTESTATION"
attestation_policy { type = "DEVICE_ATTEST_POLICY_TYPE_ACCEPT" }
meta_data {
name = "TF-DEPLOY-PROJ-1"
policy_target_condition = { device-tag-1 = "true" }
}
}
network_inst_policies {
meta_data {
name = "TF-APP-NI"
policy_target_condition = { device-tag-1 = "true" }
}
net_inst_config {
kind = "NETWORK_INSTANCE_KIND_SWITCH"
type = "NETWORK_INSTANCE_DHCP_TYPE_UNSPECIFIED"
port = "eth1"
tags = { demo = "app-ni" } # label applied TO the created NI (not a selector)
}
}
volume_inst_policies {
meta_data {
name = "TF-AUTO-VOL-1"
policy_target_condition = { device-tag-1 = "true" }
}
vol_inst_config {
label = "TF-VOL-1-LABEL"
type = "VOLUME_INSTANCE_TYPE_BLOCKSTORAGE"
accessmode = "VOLUME_INSTANCE_ACCESS_MODE_READWRITE"
cleartext = false
size_bytes = 1073741824 # 1 GiB
multiattach = false
}
}
}
Result
On demo_asus_nuc_zks_1 (because it has device-tag-1 = true):
- attestation is bypassed (ACCEPT),
- a SWITCH network instance is created on
eth1, - a 1 GiB read-write block volume is created.
Devices without device-tag-1 = true get nothing from these policies.
Note: Networks + volumes alone don't run anything. Add anapp_inst_policiesblock (or a standalonezedcloud_application_instance) for an actual app to land on the device and consume the NI + volume.
6. Standalone instance vs policy — when to use which
| Standalone resource | Policy in a deployment | |
|---|---|---|
| Resource | zedcloud_volume_instance, zedcloud_network_instance, zedcloud_application_instance | *_inst_policies block inside zedcloud_deployment |
| Targeting | Explicit: device_id (one node) or edge_node_cluster { id } (one cluster) | Tag-based: every device matching policy_target_condition |
| Use when | You want one specific instance on a known node/cluster | You want the same config rolled out to a tagged fleet |
Targeting a standalone instance
# single node
device_id = zedcloud_edgenode.demo_en_advantech_1.id
# OR a cluster (mutually exclusive with device_id)
edge_node_cluster {
id = zedcloud_edgenode_cluster.demo_edgenode_cluster_1.id
}
7. Common errors & fixes
| Error | Cause | Fix |
|---|---|---|
Edgeview Encryption is not supported and cannot be enabled (HTTP 400) | encrypt = true in a project/deployment edgeview jwt_info | Set encrypt = false. |
Reference to undeclared resource … zedcloud_edgecluster | Wrong resource type/name for a cluster | Use zedcloud_edgenode_cluster.<name>.id. |
volume instance update error: [PUT …/instances/id/…][400] (empty message) | An immutable field (type, size_bytes, accessmode, placement target) was changed on an existing volume; provider issues a PUT the API rejects | terraform apply -replace='<resource>' (destroys volume data) or revert HCL to the original values. |
| Deployment policy provisions nothing | No device carries the policy_target_condition tag | Add the matching key = “value” to the target nodes' tags. |
Note: On volume instances, onlynameisForceNewin the provider schema — every other field is treated as updatable by Terraform even though ZEDCloud rejects changing it. That's why immutable changes surface as a runtime 400 rather than a plan-time “forces replacement”.
8. Glossary
| Term | Meaning |
|---|---|
| Project | Top-level container for devices and tenant policies. |
| Deployment | Tag-driven bundle of policies that auto-provision config onto matching devices. |
| deployment_tag | Deployment-wide device selector / label (single string). |
| policy_target_condition | Per-policy device selector (key/value map). The primary match. |
| device tags | Key/value pairs on a zedcloud_edgenode; what conditions match against. |
| Attestation ACCEPT | Bypass TPM attestation; mark all devices attested. |
| Attestation ENFORCE | Require TPM attestation; flag failures. |
| SWITCH network instance | Transparent L2 bridge (no DHCP/NAT). |
| Standalone instance | An explicitly-placed volume/network/app pinned to one node or cluster. |
