====== 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. |