{"id":20561454,"url":"https://github.com/brabster/crucible","last_synced_at":"2025-04-09T18:24:05.622Z","repository":{"id":62435366,"uuid":"55720814","full_name":"brabster/crucible","owner":"brabster","description":"AWS CloudFormation templates built with Clojure","archived":false,"fork":false,"pushed_at":"2023-04-18T14:58:38.000Z","size":351,"stargazers_count":73,"open_issues_count":8,"forks_count":18,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-02T12:18:58.562Z","etag":null,"topics":["clojure","clojure-specs","cloudformation","crucible"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brabster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-07T19:08:32.000Z","updated_at":"2025-02-14T00:50:20.000Z","dependencies_parsed_at":"2022-11-01T21:00:51.783Z","dependency_job_id":null,"html_url":"https://github.com/brabster/crucible","commit_stats":null,"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brabster%2Fcrucible","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brabster%2Fcrucible/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brabster%2Fcrucible/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brabster%2Fcrucible/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brabster","download_url":"https://codeload.github.com/brabster/crucible/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248086193,"owners_count":21045293,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojure-specs","cloudformation","crucible"],"created_at":"2024-11-16T03:58:08.941Z","updated_at":"2025-04-09T18:24:05.606Z","avatar_url":"https://github.com/brabster.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# crucible\n\nCreate better cloudformation templates with Clojure\n\n[![Travis Build](https://travis-ci.org/brabster/crucible.svg?branch=master)](https://travis-ci.org/brabster/crucible) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/290/badge)](https://bestpractices.coreinfrastructure.org/projects/290)\n\n## Installation\n\nCrucible depends on clojure.spec, currently available in Clojure 1.9 alpha 10+ (breaking changes in spec around alpha 9)\n\n![Clojars Latest Version](https://clojars.org/crucible/latest-version.svg)\n\n## Examples\n\n```clojure\n(ns crucible.examples-test\n  (:require [crucible.core :refer [template parameter resource output xref encode join]]\n            [crucible.aws.ec2 :as ec2]))\n\n(def simple (template \"A simple sample template\"\n                      :my-vpc-cidr (parameter)\n                      :my-vpc (ec2/vpc {::ec2/cidr-block (xref :my-vpc-cidr)})\n                      :vpc (output (join \"/\" [\"foo\" (xref :my-vpc)]))))\n\n```\n\n```clojure\nrepl\u003e (clojure.pprint/pprint (encode simple))\n{\"AWSTemplateFormatVersion\" \"2010-09-09\"\n \"Description\" \"A simple sample template\"\n \"Parameters\" {\"MyVpcCidr\" {\"Type\" \"String\"}}\n \"Resources\" {\"MyVpc\"\n              {\"Type\" \"AWS::EC2::VPC\",\n               \"Properties\" {\"CidrBlock\" {\"Ref\" \"MyVpcCidr\"}}}},\n \"Outputs\" {\"Vpc\" {\"Value\" {\"Fn::Join\" [\"/\" [\"foo\" {\"Ref\" \"MyVpc\"}]]}}}}\n```\n\nAlternative template construction function accepts map and string arguments for building a template from partials, for example:\n\n```clojure\n(def simple (-\u003e {:my-vpc-cidr (parameter)}\n                (assoc :igw (ec2/internet-gateway {}))\n                (assoc :my-vpc (ec2/vpc {::ec2/cidr-block (xref :my-vpc-cidr)}))\n                (assoc :vpc (output (join \"/\" [\"foo\" (xref :my-vpc)])))\n                (template \"A simple sample template\")))\n```\n\n### Parameter Options\n\nSee `crucible.parameters` namespace, required as `param` in this example:\n\n```clojure\n:my-vpc-cidr (parameter ::param/type ::param/number\n                        ::param/description \"A demonstration of parameter options\"\n                        ::param/allowed-values [1 2 3]\n                        ::param/no-echo true)\n```\n\n### Resource Policies\n\nSee `crucible.policies` namespace, required as `policies` in this example:\n\n```clojure\n:my-vpc (ec2/vpc {::ec2/cidr-block (xref :my-vpc-cidr)}\n                 (policies/deletion ::policies/retain)\n                 (policies/depends-on :my-vpc-cidr))\n```\n\n## Resource Types\n\nStandard AWS resource types can be found as children of the [crucible.aws](src/crucible/aws) namespace.\n\nExamples of resource type usage can be found in the tests.\n\n* AWS::EC2::* partial coverage\n* AWS::ElasticLoadBalancingV2::*\n* AWS::ApiGateway::*\n* AWS::DynamoDB::Table\n* AWS::CloudWatch::Alarm\n* AWS::Lambda::Function\n* AWS::Lambda::EventSourceMapping\n* AWS::IAM::Role (basic support for Lambda applications)\n* AWS::ECR::Repository\n* AWS::S3::Bucket\n* AWS::CloudFormation::Stack\n* AWS::Kinesis::Stream\n* AWS::KinesisFirehose::DeliveryStream\n* AWS::Route53::RecordSet\n* AWS::SNS::Topic\n* AWS::SNS::TopicPolicy\n* AWS::SQS::Queue\n* AWS::Events::Rule\n* AWS::AutoScaling::AutoScalingGroup/LaunchConfiguration\n* Custom::* custom resources\n\n## Writing your own resource type\n\nThe easiest way is to use `defresource` and `spec-or-ref` from the `crucible.resources` namespace, eg.\n\n```clojure\n(ns crucible.aws.ec2\n  \"Resources in AWS::EC2::*\"\n  (:require [crucible.resources :refer [spec-or-ref defresource] :as res]\n            [clojure.spec :as s]))\n\n;; spec-or-ref applies your spec if a literal value is given,\n;; but also allows a parameter or function to be given instead of a literal.\n(s/def ::cidr-block (spec-or-ref string?))\n\n;; ::res/tags reuses the tags spec defined in the crucible/resources namespace\n(s/def ::vpc (s/keys :req [::cidr-block]\n                     :opt [::enable-dns-support\n                           ::enable-dns-hostnames\n                           ::instance-tenancy\n                           ::res/tags]))\n\n;; creates resource factory crucible.aws.ec2/vpc with type \"AWS::EC2::VPC\" \n;; and validates the data structure using the ::vpc spec\n(defresource  vpc \"AWS::EC2::VPC\" ::vpc)\n```\n\nPull requests to add or enhance resource types available in Crucible will be welcomed. If it's a standard AWS type please place it in the `crucible.aws` namespace and use `defresource` as it documents the resource type for you. At lest one test for the update would be great!\n\n### Testing a Resource Type\n\nAlthough you can test a resource by testing the data structure and validity directly, testing the conversion from the crucible code to a map ready to encode as CloudFormation-valid JSON is good minimal coverage. The default `clojure.test` behaviour does not pretty-print the `ex-data` map on validation exceptions, which makes testing and debugging validation failures painful. A custom assertion `crucible.assertion/resource=` is available to ensure any failure map is pretty-printed. See [the resource tests](/test/crucible/aws) for examples.\n\n## Overriding JSON Keys\n\nCrucible uses camel-snake-kebab's `-\u003ePascalCase` function to convert\nClojure map keys into JSON map keys. That takes care of most\ntranslations between Clojure-style `:keyword-key` and\nJSON/CloudFormation-style `KeywordKey`. To handle the occasional\nmistranslation, typically due to capitalisation,\n`clojure.encoding.keys` exposes a `-\u003ekey` multimethod, allowing\noverriding of the translation. For example, this problem occurs in\nAWS::CloudFormation::Stack, where a required key is `TemplateURL`. The\nfollowing overrides the natural translation of `:template-url` to\n`TemplateUrl`.\n\n```clojure\n\n(ns crucible.aws.cloudformation\n  (:require [crucible.encoding.keys :refer [-\u003ekey]]))\n\n(defmethod -\u003ekey :template-url [_] \"TemplateURL\")\n```\n\nNote, these translations take place during the final JSON encoding step and do not see keyword namespacing.\n\n## Severless Application Model templates\n\nThe [AWS Serverless Application Model](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md) (SAM) is supported by using [SAM resources](/src/crucible/aws/serverless) and the [SAM encoder](/src/crucible/encoding/serverless.clj). All CloudFormation resources are valid SAM resources and they can be combined in the same template. The `crucible.core/template` function can be used to generate a template data structure for SAM resources.\n\n### Globals\n\nAWS SAM supports [global properties](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#globals-section) for functions and APIs. There is a two-arity version of `build` and `encode` in the SAM encoder that accepts a template and a globals object.\n\n### Example\n\nIn the example below, the second argument to `encoding.sam/build` can be omitted if globals are not used.\n\n```clojure\n(ns crucible.sam-example\n  (:require [crucible.aws.kinesis :as k]\n            [crucible.aws.serverless.function :as f]\n            [crucible.aws.serverless.function.event-source :as es]\n            [crucible.aws.serverless.function.event-source.kinesis :as es.k]\n            [crucible.aws.serverless.globals :as g]\n            [crucible.core :refer [template xref]]\n            [crucible.encoding.serverless :as encoding.sam]))\n\n(-\u003e {:stream-processor (f/function\n                        {::f/handler \"index.handler\"\n                         ::f/runtime \"nodejs6.10\"\n                         ::f/code-uri \"src/\"\n                         ::f/events {:stream {::es/type \"Kinesis\"\n                                              ::es.k/properties {::es.k/stream (xref :stream :arn)\n                                                                 ::es.k/starting-position \"TRIM_HORIZON\"}}}})\n     :stream (k/stream {::k/shard-count 1})}\n    (template \"A function that processes data from a Kinesis stream.\")\n    (encoding.sam/build (g/globals {::g/function {::f/memory-size 1024\n                                                  ::f/timeout 15}})))\n```\n\n\n## CLI Support\n\nBasic CLI support, intended for use with Leiningen, is provided in the `crucible.encoding.main/-main` function. Running this function will reload the namespaces available in the project, then enumerate any vars that have a metadata tag provided by the `crucible.core/template` function. These vars are then encoded into CloudFormation templates and exported to the local filesystem. They can then be used directly or uploaded to S3 for use with CloudFormation.\n\nFlag `-h` for help. Templates are exported to `target/templates` by default, override with `-o output-dir`. Namespaces are converted to filesystem locations by replacing `.` characters with `/` characters.\n\nI create a templates directory within my project and then add it as a source-path and crucible as a dependency to the dev profile. Then I can work at the repl, write tests for my templates and use this tooling without having my template code or crucible mixed with my source code.\n\n[demo-project](demo-project) is an example of setting up a project with crucible templates defined alongside the code. The templates can see the code to verify any references they might have, but not the other way round. Run `lein templates` in the demo project to generate templates.\n\n```clojure\n:aliases {\"templates\" [\"run\" \"-m\" crucible.encoding.main]} \n:profiles {:dev {:source-paths [\"templates\"]\n                 :dependencies [[crucible \"0.10.0-SNAPSHOT\"]]}}\n```\n\n## Helping Out\n\nAny help appreciated! Happy to receive any issues and pull requests. See [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nDistributed under the [Eclipse Public License](http://opensource.org/licenses/eclipse-1.0.php)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrabster%2Fcrucible","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrabster%2Fcrucible","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrabster%2Fcrucible/lists"}