Help us shape the future of Go! Take the 2026 Go Developer Survey

postprocess

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Quiet bool

Quiet suppresses progress messages.

Functions

func ExtractStringValue

func ExtractStringValue(attr *hclwrite.Attribute) string

ExtractStringValue extracts a string or numeric literal from an HCL attribute's expression tokens. Exported for use by provider packages.

func Process

func Process(outputDir, generatedFile string, reg *registry.Registry, opts *ProcessOptions) error

Process reads the generated HCL file, rewrites ID references, extracts script contents to files, and splits into per-type output files.

func ReadObjectAttrString added in v0.4.0

func ReadObjectAttrString(body *hclwrite.Body, attrPath []string, attrName string) string

ReadObjectAttrString reads attrName from the nested object-expression attribute path attrPath within body, returning "" if absent. Exported for provider label composers that derive names from a nested attribute (e.g. the federated jamfplatform_pro_* types whose display name lives at general.name).

func RemoveNestedAttrs added in v0.4.0

func RemoveNestedAttrs(body *hclwrite.Body, attrPath []string, names ...string) bool

RemoveNestedAttrs removes the named attributes from the object expression at attrPath within body, re-serializing the affected object expressions, and returns whether anything changed. Exported for provider post-processing that drops server-computed echo fields from a nested object (e.g. dropping a self_service_icon's uri/filename so only the rewritten id reference remains).

func RenameLabels

func RenameLabels(generatedFile string, nameAttrFn func(string) string) error

RenameLabels rewrites auto-generated labels (like "all_0") in the generated HCL file to friendly names derived from resource attributes. It also updates the corresponding import blocks' "to" attributes to match.

nameAttrFn returns the attribute name used to derive a friendly label for the given resource type (e.g. "name", "email", "title").

func RenameLabelsWithComposer added in v0.4.0

func RenameLabelsWithComposer(generatedFile string, compose LabelComposer) error

RenameLabelsWithComposer is the most general label-renaming entry point: it derives each resource's friendly name via the supplied LabelComposer, which can inspect the whole resource body (e.g. to fold device_type into a jamfplatform_device_group label so computer and mobile groups that share a name do not collide).

func RenameLabelsWithFallback added in v0.2.0

func RenameLabelsWithFallback(generatedFile string, nameAttrFn func(string) string, idToName map[string]map[string]string) error

RenameLabelsWithFallback is like RenameLabels but, when the resource block's name attribute is missing or empty, falls back to looking up the name by import-block ID via idToName[resourceType][id]. This is needed for resource types whose name field is computed/read-only and so doesn't appear in the HCL produced by terraform query / -generate-config-out (e.g. Jamf Protect's jamfprotect_analytic_managed).

func RewriteBlockForTest

func RewriteBlockForTest(body *hclwrite.Body, blockPath []string, rule ReferenceRule, reg *registry.Registry)

RewriteBlockForTest is an exported wrapper around rewriteBlock for use by external test packages. Not intended for production use.

func RewriteListElementField added in v0.4.0

func RewriteListElementField(body *hclwrite.Body, attrName, elemAttr string, resolve func(string) (string, bool)) bool

RewriteListElementField rewrites elemAttr on each object element of the list-of-objects attribute attrName in body. For each element it reads the current string value of elemAttr and calls resolve(value); when resolve returns (ref, true) the field is replaced with the raw ref token, otherwise the element is left untouched (no TODO marker — unmatched values are expected, e.g. built-in smart-group criteria). Returns true if any element changed.

Exported for provider-specific criteria rewriting where the resolution target depends on context the generic ReferenceRule engine cannot express from a single element (e.g. the owning resource's device_type selecting which extension-attribute name space to resolve against).

func ShouldSkipProfile added in v0.3.0

func ShouldSkipProfile(payload string) (bool, string)

ShouldSkipProfile reports whether a configuration profile should be excluded from the Terraform output. Returns (true, reason) for:

  • Jamf Protect profiles: any PayloadIdentifier starts with com.jamf.protect.*
  • Jamf Security Cloud / Jamf Trust profiles: any PayloadIdentifier starts with com.jamf.trust.*
  • Non-XML payloads: signed binary profiles stored as base64-encoded DER.

func StripBlueprintDrafts added in v0.6.0

func StripBlueprintDrafts(genPath string) (int, error)

StripBlueprintDrafts removes non-creatable blueprint drafts — and their import blocks — from a generated.tf before anything else reads it. Process makes the same call mid-pass for the single-env pipeline; this exists for the multi-env path, where the validation auto-fix runs against the raw generated.tf first and would otherwise reach a draft ahead of the skip.

It matters because the two treatments contradict each other. The draft's empty device_groups trips the provider's SizeAtLeast(1) validator, which the auto-fix reads as a Required empty collection and answers by turning the attribute into a variable for the operator to supply. That rewrite hides the emptiness the skip keys on, so the draft then survives into the module as a resource nobody can plan without inventing a device group for a blueprint that was never creatable in the first place.

Returns the number of drafts removed. A parse failure leaves the file alone.

Types

type ExtractSpec added in v0.4.0

type ExtractSpec struct {
	// ResourceType is the TF resource type this spec applies to.
	ResourceType string
	// BlockPath navigates nested HCL blocks to the container holding AttrName.
	BlockPath []string
	// AttrPath navigates nested object-expression attributes to the container.
	AttrPath []string
	// AttrName is the string attribute to extract (e.g. "script_contents", "payloads").
	AttrName string
	// OutputSubdir is the directory under support_files/ to write the file into.
	OutputSubdir string
	// FileKind selects the file extension.
	FileKind FileKind
	// Ext is the literal file extension (including leading dot) used when
	// FileKind is FileKindRaw; ignored for other kinds.
	Ext string
	// NameAttr names the attribute used to derive the filename (default "name").
	NameAttr string
	// NameAttrPath is the object-attribute path to the container holding NameAttr
	// (empty = resource root; e.g. ["general"] for jamfplatform profiles).
	NameAttrPath []string
	// SkipFn, when set, is consulted with the extracted content; a true result
	// means the whole resource should be dropped from output (handled by the caller).
	SkipFn func(content string) (bool, string)
}

ExtractSpec declaratively describes a string attribute whose value should be extracted to a support file and replaced with a file() reference. It is provider-agnostic: BlockPath navigates HCL nested blocks (jamfpro), AttrPath navigates plugin-framework object-expression attributes (jamfplatform), and they compose (BlockPath first, then AttrPath).

type FileKind added in v0.4.0

type FileKind int

FileKind selects the filename extension and derivation strategy for an extracted support file.

const (
	// FileKindScript guesses the extension from the script shebang (.sh/.py/...).
	FileKindScript FileKind = iota
	// FileKindMobileconfig writes a .mobileconfig file.
	FileKindMobileconfig
	// FileKindXML writes a .xml file.
	FileKindXML
	// FileKindRaw writes a file with the literal extension given by ExtractSpec.Ext
	// (e.g. ".ppd", ".mobileprovision"). Used for document-like content that is
	// neither a guessable script nor a profile/XML payload.
	FileKindRaw
)

type FixEdit added in v0.6.0

type FixEdit struct {
	Resource string // resource address, e.g. "jamfplatform_pro_policy.install_chrome"
	Attr     string // attribute path
	Action   string // "removed", "set" or "replaced with a variable"
	Reason   string // the diagnostic summary
}

FixEdit is one auto-fix: the resource and attribute changed, and the diagnostic summary that prompted it.

type FixResult

type FixResult struct {
	Fixed        int           // total attributes fixed (removed, set, or replaced)
	RequiredVars []RequiredVar // write-only variables that need user-supplied values
	// Edits records what was changed and why, one entry per fix. The auto-fix
	// edits generated configuration on the user's behalf, so it has to be able
	// to say what it touched — a bare count leaves no way to tell a correct
	// repair from a wrong one, or to notice when a provider fix upstream has
	// made a repair unnecessary.
	Edits []FixEdit
	// StillInvalid reports that the project did not pass terraform validate by
	// the time the fix loop gave up — either nothing left was auto-fixable or
	// the iteration cap was reached. FixValidationErrors returns a nil error in
	// both cases (an unfixable diagnostic is not a jamformer failure), so
	// without this the caller has no way to tell a repaired project from one
	// that still will not plan, and would print a success summary over it.
	StillInvalid bool
}

FixResult holds the outcome of FixValidationErrors.

func FixValidationErrors

func FixValidationErrors(outputDir string, schema *ProviderSchema) (*FixResult, error)

FixValidationErrors runs terraform validate in a loop, parsing error diagnostics to auto-fix attributes the provider says are invalid.

Supported fix strategies:

  • "remove it" errors: remove the offending attribute
  • "must be 'VALUE'" errors: set the attribute to the required value
  • "must be one of [...]" errors: remove the attribute with the invalid value
  • "conflicts with" errors: remove the attribute pointed to by the diagnostic
  • "required null" errors: sensitive attrs get variable references, non-sensitive get ""

The schema parameter enables sensitive-aware handling of required nulls. If nil, all required nulls are replaced with variable references.

Returns a FixResult with the count and any required variables, or an error.

type LabelComposer added in v0.4.0

type LabelComposer func(resourceType string, body *hclwrite.Body, idLookup func() string) string

LabelComposer derives the pre-sanitization friendly name for a resource block. resourceType is the TF type; body is the resource block body; idLookup returns the import-block ID for this resource (for name fallback). Returning "" skips renaming this resource. The returned name is sanitized and de-duplicated by the caller's naming.Tracker.

type ProcessOptions

type ProcessOptions struct {
	// TypeToFileMap maps TF resource type → output filename. Required.
	TypeToFileMap map[string]string
	// Rules defines the cross-resource reference rewriting rules.
	Rules []ReferenceRule
	// PackageFiles maps package filename -> relative path for downloaded packages.
	PackageFiles map[string]string
	// PackageInfo maps package_name -> filename from discovery.
	PackageInfo map[string]string
	// PlatformPackageFiles maps a jamfplatform_pro_package file_name -> relative
	// path for files downloaded from JCDS. When a package's file_name is present,
	// post-processing switches it to upload mode: package_file_source = file(...)
	// and the conflicting server-supplied hash attributes are removed.
	PlatformPackageFiles map[string]string
	// IconURLs maps icon Jamf ID -> CDN URL for icon_file_web_source.
	IconURLs map[string]string
	// EnrollmentCustomizationImageFiles maps enrollment customization Jamf ID -> relative path.
	EnrollmentCustomizationImageFiles map[string]string
	// ExtractSpecs declaratively drives string-attribute extraction to support
	// files (scripts, profile payloads, app configs). Used by providers whose
	// resources expose these as nested object attributes (e.g. jamfplatform).
	// The jamfpro pipeline uses dedicated inline extraction instead.
	ExtractSpecs []ExtractSpec
	// SupportDirs lists extra directories (relative to support_files/) to create
	// as recommended locations for user-supplied files (e.g. token directories).
	SupportDirs []string
	// SkipReferences disables cross-resource reference resolution, leaving raw ID values.
	SkipReferences bool
	// SplitByCategory splits categorised resource types into per-category output files.
	SplitByCategory bool
	// ProviderSchemas is the parsed provider schema from tfexec (for null stripping).
	ProviderSchemas *tfjson.ProviderSchemas
	// SupportFilesPrefix, when set, is inserted into support file paths:
	// support_files/<prefix>/scripts/ instead of support_files/scripts/.
	// Used by multi-env mode to separate files per environment.
	SupportFilesPrefix string
	// InjectRequiredWriteOnly wires Required WriteOnly attributes the server never
	// returns to sensitive Terraform variables (and seeds their _wo_version
	// companions with 1) so a generated config validates. Used by jamfplatform.
	InjectRequiredWriteOnly bool
}

ProcessOptions holds configuration for post-processing.

type ProviderSchema

type ProviderSchema struct {
	// contains filtered or unexported fields
}

ProviderSchema holds the parsed schema information for resource types.

func LoadProviderSchema

func LoadProviderSchema(schemas *tfjson.ProviderSchemas) *ProviderSchema

LoadProviderSchema builds a ProviderSchema from the typed tfjson.ProviderSchemas returned by tfexec's ProvidersSchema().

type ReferenceRule

type ReferenceRule struct {
	// ResourceType is the TF resource type containing the reference (e.g. "jamfpro_policy")
	ResourceType string

	// BlockPath is the HCL block nesting path to the attribute.
	// e.g. ["payloads", "scripts"] means the "id" attr lives inside payloads { scripts { id = ... } }
	BlockPath []string

	// AttrPath is the nested object-attribute path to the container holding the
	// target attribute, e.g. ["scope", "targets"] for scope = { targets = { computer_group_ids = ... } }.
	// Walked after BlockPath. Empty for root-level attributes.
	AttrPath []string

	// AttrName is the attribute within the container that holds the raw ID(s)
	// (e.g. "id", "computer_group_ids"). When ElementAttr is set, AttrName names
	// the list-of-objects attribute and ElementAttr names the per-element field.
	AttrName string

	// TargetTypes are the TF resource types to look up in the registry.
	// Tried in order; first match wins.
	TargetTypes []string

	// TargetAttr is the attribute on the target resource to reference (e.g. "id")
	TargetAttr string

	// Numeric indicates the source attribute is number-typed (e.g. an Int64
	// profile_id) while the target attribute (TargetAttr) is a string. The
	// resolved reference is wrapped in tonumber() so the assignment type-checks.
	// Only honoured for single-attribute (non-list, non-element) rules.
	Numeric bool

	// EmbeddedIDs indicates AttrName is a free-form string that may contain one
	// or more target-type ids embedded in surrounding text (e.g. device-group
	// UUIDs inside a blueprint activation_conditions expression). Each resolvable
	// UUID is replaced in place with a ${addr.TargetAttr} interpolation; the rest
	// of the string and any unresolvable ids are left untouched.
	EmbeddedIDs bool

	// IsList indicates the attribute is a list of IDs rather than a single ID.
	IsList bool

	// ElementAttr, when set, indicates AttrName is a list-of-objects attribute
	// (e.g. scripts = [ { id = "44" }, ... ]) and ElementAttr (e.g. "id") is the
	// field rewritten on each element.
	ElementAttr string

	// DiscriminatorAttr, when set (with ElementAttr), names a sibling field on
	// each list element whose value selects the target type via DiscriminatorMap.
	// Used for polymorphic references where one ID field points at different
	// resource types depending on a type tag (e.g. macOS onboarding items whose
	// entity_id targets a policy, profile, or app per self_service_entity_type).
	DiscriminatorAttr string

	// DiscriminatorMap maps a DiscriminatorAttr value to the TF resource types to
	// resolve the element's ID against, tried in order. An unmapped value, or one
	// no listed type claims, leaves the ID untouched. More than one type is what
	// a wire field written in two forms needs: a user-group "member of" criterion
	// carries the target group's numeric ID on some Jamf Pro versions and its
	// name on others, so both the ID- and name-keyed registry types are listed.
	//
	// With PrefixedIDs the keys are literal value prefixes instead of sibling
	// field values, and the longest matching prefix wins.
	DiscriminatorMap map[string][]string

	// AllowUnresolved says a value no registry entry claims is expected here, so
	// it is left exactly as it was with no TODO marker. Use it where the field
	// legitimately accepts IDs from outside the export — a Jamf Security Cloud
	// gateway reference takes either the tenant's own gateway or one of Jamf's
	// shared gateways, and the shared ones are a Jamf-maintained catalogue with
	// no resource to point at. Marking those TODO sends the reader looking for a
	// broken reference that was never broken.
	AllowUnresolved bool

	// PrefixedIDs indicates the value is an ID carried behind a literal prefix
	// that also selects the target type, e.g. UEM Connect's uem_group_id, which
	// is written "computer_12" or "mobile_7" for a Jamf Pro device group.
	// DiscriminatorMap maps each prefix to the resource type its tail resolves
	// against; the prefix is preserved and the tail becomes a ${...} reference,
	// so "computer_12" becomes "computer_${jamfplatform_device_group.x.jamf_pro_id}".
	//
	// Used with ElementAttr (per list element) or on its own (a single
	// attribute). An unmatched prefix, or a tail that resolves to nothing,
	// leaves the value untouched: unlike a bare ID reference, a prefixed value
	// is still valid on its own and is not evidence of a broken export.
	PrefixedIDs bool
}

ReferenceRule defines how to resolve a literal ID in a generated resource to a cross-resource Terraform reference.

Two traversal mechanisms are supported and may be combined (BlockPath is walked first, then AttrPath):

  • BlockPath descends through HCL nested *blocks* (e.g. the jamfpro provider's scope { ... } / payloads { scripts { ... } }), navigating live hclwrite blocks.
  • AttrPath descends through plugin-framework nested *attributes* (object expressions, e.g. the jamfplatform provider's scope = { targets = { ... } }), re-parsing object-expression bytes at each level.

type RequiredVar

type RequiredVar struct {
	VarName  string // Terraform variable name (e.g. "smtp_server_password")
	AttrPath string // original attribute path (e.g. "basic_auth_credentials.0.password")
	Resource string // resource address (e.g. "jamfpro_smtp_server.settings")
	Filename string // source file (e.g. "smtp_server.tf")
	// VarType is the declared Terraform type. Empty means string, which is
	// what a write-only secret wants. A collection attribute has to declare
	// its own type or the config will not accept a list value.
	VarType string
	// NotSensitive suppresses `sensitive = true`. A list of approver email
	// addresses is a required value but not a secret, and marking it sensitive
	// only makes it harder to see what a plan is doing.
	NotSensitive bool
	// Cause names why the value has to be supplied here and becomes the
	// variable's description. Empty falls back to the write-only wording,
	// which is what an unreadable Required secret means. An attribute the
	// server returned empty is a different problem, and a description that
	// calls it a write-only secret sends the operator looking for a value the
	// API is hiding rather than one the tenant never set.
	Cause string
}

RequiredVar describes a write-only variable that was created to replace a required null attribute. The caller should flag these to the user.

type TokenVar

type TokenVar struct {
	VarName     string // e.g. "dep_token_path_my_enrollment"
	Description string // e.g. "Path to DEP token file (.p7m) for 'My Enrollment'"
}

extractTokenToFile extracts a token attribute (e.g. encoded_token, service_token) from a resource block, writes it to a file in the given directory, and replaces the attribute with a file() function call. If the token value is empty/null, a placeholder file() reference is created for the user to fill in. TokenVar describes a variable generated for a token file path.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL