diff --git a/website/assets/css/custom.css b/website/assets/css/custom.css index 09c33481..748bde7d 100644 --- a/website/assets/css/custom.css +++ b/website/assets/css/custom.css @@ -97,9 +97,24 @@ code, kbd, pre { padding: 1em !important; } +.hljs-red { + color: #ff5874; +} +.hljs-yellow { + color: #ecc48d; +} +.hljs-green { + color: #addb67; +} +.hljs-bash-prompt { + color: #dcdcaa; + font-weight: bold; +} + .doc pre.highlightjs { font-size: .9rem; - padding-top: 0.75em; + padding-top: 0.3em; + padding-bottom: 0.5em; } .doc pre.highlightjs code { diff --git a/website/assets/js/vendor/highlight.pack.js b/website/assets/js/vendor/highlight.pack.js index 88104b6f..d0a6ad16 100644 --- a/website/assets/js/vendor/highlight.pack.js +++ b/website/assets/js/vendor/highlight.pack.js @@ -488,4 +488,31 @@ className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1 },e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func", end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params", begin:/\(/,end:/\)/,endsParent:!0,keywords:n,illegal:/["']/}]}]}}})() -;hljs.registerLanguage("go",e)})(); \ No newline at end of file +;hljs.registerLanguage("go",e)})();/*! `rego` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const r=e.regex;return{case_insensitive:!1,keywords:{ +keyword:["as","contains","default","else","every","if","in","import","package","not","some","with"], +literal:["false","true","null"],symbol:["data","input"], +built_in:["abs","all","and","any","array.concat","array.reverse","array.slice","assign","base64.decode","base64.encode","base64.is_valid","base64url.decode","base64url.encode","base64url.encode_no_pad","bits.and","bits.lsh","bits.negate","bits.or","bits.rsh","bits.xor","cast_array","cast_boolean","cast_null","cast_object","cast_set","cast_string","ceil","concat","count","crypto.hmac.equal","crypto.hmac.md5","crypto.hmac.sha1","crypto.hmac.sha256","crypto.hmac.sha512","crypto.md5","crypto.parse_private_keys","crypto.sha1","crypto.sha256","crypto.x509.parse_and_verify_certificates","crypto.x509.parse_and_verify_certificates_with_options","crypto.x509.parse_certificate_request","crypto.x509.parse_certificates","crypto.x509.parse_keypair","crypto.x509.parse_rsa_private_key","div","endswith","eq","equal","floor","format_int","glob.match","glob.quote_meta","graph.reachable","graph.reachable_paths","graphql.is_valid","graphql.parse","graphql.parse_and_verify","graphql.parse_query","graphql.parse_schema","graphql.schema_is_valid","gt","gte","hex.decode","hex.encode","http.send","indexof","indexof_n","internal.member_2","internal.member_3","internal.print","intersection","io.jwt.decode","io.jwt.decode_verify","io.jwt.encode_sign","io.jwt.encode_sign_raw","io.jwt.verify_es256","io.jwt.verify_es384","io.jwt.verify_es512","io.jwt.verify_hs256","io.jwt.verify_hs384","io.jwt.verify_hs512","io.jwt.verify_ps256","io.jwt.verify_ps384","io.jwt.verify_ps512","io.jwt.verify_rs256","io.jwt.verify_rs384","io.jwt.verify_rs512","is_array","is_boolean","is_null","is_number","is_object","is_set","is_string","json.filter","json.is_valid","json.marshal","json.marshal_with_options","json.match_schema","json.patch","json.remove","json.unmarshal","json.verify_schema","lower","lt","lte","max","min","minus","mul","neq","net.cidr_contains","net.cidr_contains_matches","net.cidr_expand","net.cidr_intersects","net.cidr_is_valid","net.cidr_merge","net.cidr_overlap","net.lookup_ip_addr","numbers.range","numbers.range_step","object.filter","object.get","object.keys","object.remove","object.subset","object.union","object.union_n","opa.runtime","or","plus","print","product","providers.aws.sign_req","rand.intn","re_match","regex.find_all_string_submatch_n","regex.find_n","regex.globs_match","regex.is_valid","regex.match","regex.replace","regex.split","regex.template_match","rego.metadata.chain","rego.metadata.rule","rego.parse_module","rem","replace","round","semver.compare","semver.is_valid","set_diff","sort","split","sprintf","startswith","strings.any_prefix_match","strings.any_suffix_match","strings.count","strings.render_template","strings.replace_n","strings.reverse","substring","sum","time.add_date","time.clock","time.date","time.diff","time.format","time.now_ns","time.parse_duration_ns","time.parse_ns","time.parse_rfc3339_ns","time.weekday","to_number","trace","trim","trim_left","trim_prefix","trim_right","trim_space","trim_suffix","type_name","union","units.parse","units.parse_bytes","upper","urlquery.decode","urlquery.decode_object","urlquery.encode","urlquery.encode_object","uuid.parse","uuid.rfc4122","walk","yaml.is_valid","yaml.marshal","yaml.unmarshal"] +},contains:[e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,{match:/[{}[\]\\(\\),\\.]/, +className:"punctuation",relevance:0},{scope:"operator", +match:r.either("==","=","\\+","-","<=","<",">=",">","!=","\\|","\\*")},{ +scope:"string",begin:"`",end:"`"},e.HASH_COMMENT_MODE,{begin:/import rego\.v1/, +keywords:"import",relevance:10}]}}})();hljs.registerLanguage("rego",e)})(); + +/*! Custom highlight Pack for Conforma output. Simulate + * how it looks in the terminal with the ansi text color. */ +(function () { + 'use strict'; + hljs.registerLanguage('ec', function statusHighlight() { + return { + name: 'Conforma output', + case_insensitive: false, + contains: [ + { begin: /✕ \[Violation\]/, className: 'red' }, + { begin: /› \[Warning\]/, className: 'yellow' }, + { begin: /✓ \[Success\]/, className: 'green' }, + { begin: /^\$/, className: 'bash-prompt' }, + ] + }; + }); +})(); diff --git a/website/content/posts/01-validate-input-basics.md b/website/content/posts/01-validate-input-basics.md new file mode 100644 index 00000000..d7eb97f3 --- /dev/null +++ b/website/content/posts/01-validate-input-basics.md @@ -0,0 +1,330 @@ +--- +title: Validating arbitary data +date: 2026-01-13T00:30:43-05:00 +author: Simon Baird +--- +In this tutorial we'll introduce some basic Conforma concepts +and work through some examples of applying policies against some arbitrary +input data. + +## ec validate input + +Conforma can perform policy checks on arbitrary data with `ec +validate input`. Let's try an example. + + +A simple data file: + +```yaml +# file: input.yaml + +animals: + - name: Charlie + species: dog + - name: Luna + species: cat +``` + + +A minimal Conforma policy defined in Rego: + +```rego +# file: no-cats/main.rego + +package main + +# METADATA +# title: No cats +# description: Disallow felines. +# custom: +# short_name: no_cats +# solution: Please ensure no cats are +# present in the animal list. +# +deny contains result if { + some animal in input.animals + animal.species == "cat" + result := {"code": "main.no_cats", "msg": "No cats allowed!"} +} +``` + +To use that, Conforma needs a policy file specifying a policy source: + +```yaml +# file: policy.yaml + +sources: + - policy: + - ./no-cats +``` + + +Now we can run Conforma like this: + +```ec +$ ec validate input --file input.yaml --policy policy.yaml +Success: false +Result: FAILURE +Violations: 1, Warnings: 0, Successes: 0 +Input File: input.yaml + +Results: +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: No cats allowed! + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ +Error: success criteria not met + +``` + + +## Using --info for more detailed output + +The metadata associated with the policy rule is important for +Conforma. Adding the `--info` flag will use the metadata to show more +verbose/user-friendly output: + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --info +Success: false +Result: FAILURE +Violations: 1, Warnings: 0, Successes: 0 +Input File: input.yaml + +Results: +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: No cats allowed! + Title: No cats + Description: Disallow felines. To exclude this rule add "main.no_cats" to the `exclude` section of the policy configuration. + Solution: Please ensure no cats are present in the animal list. + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ +Error: success criteria not met + +``` + + +## Using --show-successes to show passing checks + +Let's "fix" the violation and run it again: + +```ec +$ sed -i "s/cat/rabbit/" input.yaml + +``` + +```yaml +# file: input.yaml + +animals: + - name: Charlie + species: dog + - name: Luna + species: rabbit +``` + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --info +Success: true +Result: SUCCESS +Violations: 0, Warnings: 0, Successes: 1 +Input File: input.yaml + + +``` + + +By default there's not much output on success, but we can add +the `--show-successes` flag to change that: + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --info --show-successes +Success: true +Result: SUCCESS +Violations: 0, Warnings: 0, Successes: 1 +Input File: input.yaml + +Results: +✓ [Success] main.no_cats + FilePath: input.yaml + Title: No cats + Description: Disallow felines. + + +``` + + +(Turn rabbits back into cats for the next step): + +```ec +$ sed -i "s/rabbit/cat/" input.yaml + +``` + + +## Warnings + +We can use "warn" to produce a warning instead of a violation: + +(Append to the existing file...) + +```rego +# file: no-cats/main.rego + +# METADATA +# title: Charlie warning +# description: Charlie is a troublemaker! +# custom: +# short_name: charlie_watch +# solution: Keep a close eye on Charlie. +# +warn contains result if { + some animal in input.animals + animal.name == "Charlie" + result := {"code": "main.charlie_watch", "msg": "Charlie is here"} +} +``` + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --info --show-successes +Success: false +Result: FAILURE +Violations: 1, Warnings: 1, Successes: 0 +Input File: input.yaml + +Results: +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: No cats allowed! + Title: No cats + Description: Disallow felines. To exclude this rule add "main.no_cats" to the `exclude` section of the policy configuration. + Solution: Please ensure no cats are present in the animal list. + +› [Warning] main.charlie_watch + FilePath: input.yaml + Reason: Charlie is here + Title: Charlie warning + Description: Charlie is a troublemaker! + Solution: Keep a close eye on Charlie. + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ +Error: success criteria not met + +``` + +Warnings are considered non-blocking. + + +## Adding more detail to the violation reason + +```rego +# file: no-cats/main.rego + +deny contains result if { + some animal in input.animals + animal.species == "cat" + result := {"code": "main.no_cats", "msg": sprintf("A cat named %s was found!", [animal.name])} +} +``` +```ec +$ ec validate input --file input.yaml --policy policy.yaml +Success: false +Result: FAILURE +Violations: 1, Warnings: 1, Successes: 0 +Input File: input.yaml + +Results: +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: A cat named Luna was found! + +› [Warning] main.charlie_watch + FilePath: input.yaml + Reason: Charlie is here + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ +Error: success criteria not met + +``` + + +If there are multiple cats, we now get multiple different violations: + +```yaml +# file: input.yaml + +animals: + - name: Charlie + species: dog + - name: Luna + species: cat + - name: Fluffy + species: cat +``` + +```ec +$ ec validate input --file input.yaml --policy policy.yaml +Success: false +Result: FAILURE +Violations: 2, Warnings: 1, Successes: 0 +Input File: input.yaml + +Results: +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: A cat named Fluffy was found! + +✕ [Violation] main.no_cats + FilePath: input.yaml + Reason: A cat named Luna was found! + +› [Warning] main.charlie_watch + FilePath: input.yaml + Reason: Charlie is here + +For more information about policy issues, see the policy documentation: https://conforma.dev/docs/policy/ +Error: success criteria not met + +``` + + +## Machine readable output + +Text output is the default, but you can also output json or yaml, +which includes some additional information. + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --info --output json +{"success":false,"filepaths":[{"filepath":"input.yaml","violations":[{"msg":"A cat named Fluffy was found!","metadata":{"code":"main.no_cats","description":"Disallow felines. To exclude this rule add \"main.no_cats\" to the `exclude` section of the policy configuration.","solution":"Please ensure no cats are present in the animal list.","title":"No cats"}},{"msg":"A cat named Luna was found!","metadata":{"code":"main.no_cats","description":"Disallow felines. To exclude this rule add \"main.no_cats\" to the `exclude` section of the policy configuration.","solution":"Please ensure no cats are present in the animal list.","title":"No cats"}}],"warnings":[{"msg":"Charlie is here","metadata":{"code":"main.charlie_watch","description":"Charlie is a troublemaker!","solution":"Keep a close eye on Charlie.","title":"Charlie warning"}}],"successes":null,"success":false,"success-count":0}],"policy":{"sources":[{"policy":["./no-cats"]}]},"ec-version":"v0.8.79","effective-time":"2026-01-13T05:30:43.860003948Z"} +Error: success criteria not met + +``` + + +## Strict vs non-strict + +By default we produce a non-zero exit code if there are any +violations, which is useful to interrupt a script or a CI task. You can change +that behavior if you need to with --strict=false: + +```ec +$ ec validate input --file input.yaml --policy policy.yaml > output.txt; echo "Exit code: $?"; head -3 output.txt +Error: success criteria not met +Exit code: 1 +Success: false +Result: FAILURE +Violations: 2, Warnings: 1, Successes: 0 + +``` + +```ec +$ ec validate input --file input.yaml --policy policy.yaml --strict=false > output.txt; echo "Exit code: $?"; head -3 output.txt +Exit code: 0 +Success: false +Result: FAILURE +Violations: 2, Warnings: 1, Successes: 0 + +``` +