====== 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: **device ''tags'' key/value == policy ''policy_target_condition'' key/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.** Setting ''encrypt = true'' in ''jwt_info'' fails 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 — keep ''encrypt = 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 — the ''policy_sub_type'' enum has just ''DEVICE_POLICY_TYPE_UNSPECIFIED'' and ''DEVICE_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's ''tags'' key/value == policy's ''policy_target_condition'' key/value**
> \\ A policy fires on a device only if the device's ''tags'' map contains the exact key/value pair in that policy's ''policy_target_condition''.
> **⚠️ Tag values are strings.** A device tag written as ''device-tag-1 = true'' becomes 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 an ''app_inst_policies'' block (or a standalone ''zedcloud_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..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='''' (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, **only ''name'' is ''ForceNew''** in 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. |