diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,14 @@
 # Changelog for dhall-fly
 
+## 0.2.0
+
+* Tested with dhall-concourse 0.5.0
+* Add `--file` command line option to render pipelines from files
+* Add support for grouping jobs
+* Fix: Add inputs to `PutSteps`
+* Fix: Add tags, timeouts and attempts to `ToJSON` instance of `PutStep`
+* Fix: Add vars, tags, timeouts and attempts to `TaskStep`
+
 ## 0.1.0
 
 * Support for dhall-concourse 0.2.2
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,6 +4,22 @@
 
 ## Installation
 
+### Using Homebrew
+
+`brew install akshaymankar/tap/dhall-fly`
+
+### Copy the binary (only MacOS)
+
+Go to [Releases Page](https://github.com/akshaymankar/dhall-fly/releases), download the `dhall-fly-<version>-darwin.tgz`, use the binary inside. 
+
+**Note:** This is only tested on Mojave, should work on Catalina.
+
+### From Hackage
+
+`cabal install dhall-fly`
+
+### From source
+
 1. Install stack: https://docs.haskellstack.org/en/stable/README/
 1. Clone this repository **recursively**
 1. Run `stack install` in the repository. This will install `dhall-fly` binary in `~/.local/bin`.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,16 +1,33 @@
 module Main where
 
-import Data.Aeson.Yaml (encode)
-import Dhall           (auto, input)
-import Dhall.JSON      (omitNull)
-import Fly.Types       (Job)
-import Fly.Yaml        (dhallToYaml)
+import Data.Aeson          (Value)
+import Data.Aeson.Yaml     (encode)
+import Dhall               (auto, input, inputFile)
+import Dhall.JSON          (omitNull)
+import Fly.Types           (GroupedJob, Job)
+import Fly.Yaml            (groupedJobsToValue, jobsToValue)
+import Fly.Options
 
 import qualified Data.ByteString.Lazy.Char8 as LBS
-import qualified Data.Text.IO               (getContents)
+import qualified Data.Text.IO               as T
+import qualified Options.Applicative        as O
 
 main :: IO ()
 main = do
-  stdin <- Data.Text.IO.getContents
-  jobs <- input auto stdin :: IO [Job]
-  LBS.putStrLn $ encode $ omitNull $ dhallToYaml jobs
+  opts <- O.execParser optsParserWithHelp
+  yaml <- dhallTextToValue opts
+  LBS.putStrLn $ encode $ omitNull yaml
+
+dhallTextToValue :: Opts -> IO Value
+dhallTextToValue (Opts Jobs (File f)) = do
+  jobs <- inputFile auto f :: IO [Job]
+  pure $ jobsToValue jobs
+dhallTextToValue (Opts Jobs Stdin) = do
+  jobs <- (input auto =<< T.getContents) :: IO [Job]
+  pure $ jobsToValue jobs
+dhallTextToValue (Opts GroupedJobs (File f)) = do
+  groupedJobs <- inputFile auto f :: IO [GroupedJob]
+  pure $ groupedJobsToValue groupedJobs
+dhallTextToValue (Opts GroupedJobs Stdin) = do
+  groupedJobs <- (input auto =<< T.getContents) :: IO [GroupedJob]
+  pure $ groupedJobsToValue groupedJobs
diff --git a/dhall-concourse/README.md b/dhall-concourse/README.md
--- a/dhall-concourse/README.md
+++ b/dhall-concourse/README.md
@@ -20,8 +20,55 @@
 
 ## Usage
 
+### Using dhall-fly
+
 To use dhall-concourse you need to install [dhall-fly](https://github.com/akshaymankar/dhall-fly#installation).
 
+### Using dhall-to-json and jq (Experimental)
+
+#### Jobs without Groups
+
+To use native rendering to render a list of jobs in a file called `jobs.dhall`, you'd have to write a dhall expression like this:
+
+```dhall
+let Concourse = 
+      https://raw.githubusercontent.com/akshaymankar/dhall-concourse/0.5.0/package.dhall
+
+let jobs = ./jobs.dhall
+
+in Concourse.render.pipeline jobs
+```
+
+Now you can render this using dhall-to-json and jq like this:
+
+```bash
+dhall-to-json <<< './pipeline.dhall' \
+  | jq '.resources = (.resources|unique)' \
+  | jq '.resource_types = (.resource_types|unique)'
+```
+
+#### Jobs with groups
+
+Similarly, to render a list of `GroupedJob`s in a filed called `grouped-jobs.dhall`, this would be the expression to render:
+
+```dhall
+let Concourse = 
+      https://raw.githubusercontent.com/akshaymankar/dhall-concourse/0.5.0/package.dhall
+
+let groupedJobs = ./grouped-jobs.dhall
+
+in Concourse.render.groupedJobs groupedJobs
+```
+
+Now you can render this using dhall-to-json and jq like this:
+
+```bash
+dhall-to-json <<< './pipeline.dhall' \
+  | jq '.resources = (.resources|unique)' \
+  | jq '.resource_types = (.resource_types|unique)' \
+  | jq '.groups = (.groups | group_by(.name) | map({name: .[0].name, jobs: (map(.jobs) | flatten) }))'
+```
+
 ## Defining a pipeline
 
 ### Example 1: Hello World
@@ -30,7 +77,7 @@
 
 ```dhall
 let Concourse =
-      https://raw.githubusercontent.com/akshaymankar/dhall-concourse/0.2.1/package.dhall sha256:afc1f4a27ac5a1f4746065ee2e318041698cc9bb57096aa4f0d4d665f44a6ef2
+      https://raw.githubusercontent.com/akshaymankar/dhall-concourse/0.5.0/package.dhall
 
 let Prelude =
       https://prelude.dhall-lang.org/v11.1.0/package.dhall sha256:99462c205117931c0919f155a6046aec140c70fb8876d208c7c77027ab19c2fa
@@ -68,10 +115,29 @@
 To set the pipeline, run this command:
 
 ```
-fly -t <TARGET> set-pipeline -p hello-dhall -c <(dhall-fly <pipeline.dhall)
+fly -t <TARGET> set-pipeline -p hello-dhall -c <(dhall-fly <example1.dhall)
 ```
 
-### Example 2 (Real World™️)
+### Example 2 (Hello World with groups)
+
+```dhall
+let Concourse =
+      https://raw.githubusercontent.com/akshaymankar/dhall-concourse/0.5.0/package.dhall
+
+let Prelude =
+      https://prelude.dhall-lang.org/v11.1.0/package.dhall sha256:99462c205117931c0919f155a6046aec140c70fb8876d208c7c77027ab19c2fa
+
+-- Assuming above file is here
+let jobs = ./example1.dhall
+
+in  Prelude.List.map
+      Concourse.Types.Job
+      Concourse.Types.GroupedJob
+      (λ(j : Concourse.Types.Job) → { job = j, groups = [ "hello-world" ] })
+      jobs
+```
+
+### Example 3 (Real World™)
 
 
 We in the Eirini team were facing issues with templating our pipeline YAMLs. Recently, we started converting our spruce/aviator based yaml templating into dhall. The work in progress can be seen in [our CI repo](https://github.com/cloudfoundry-incubator/eirini-ci/blob/47d2f229e33d9fcdb5641cec06fa68a0d82c0bff/pipelines/ci/pipeline.dhall).
diff --git a/dhall-concourse/defaults/CustomResourceType.dhall b/dhall-concourse/defaults/CustomResourceType.dhall
--- a/dhall-concourse/defaults/CustomResourceType.dhall
+++ b/dhall-concourse/defaults/CustomResourceType.dhall
@@ -1,12 +1,9 @@
 let Types = ../types/package.dhall
 
-in    { name = "CHANGEME"
-      , type = "CHANGEME"
-      , source = None Types.JSONObject
-      , privileged = None Bool
-      , params = None Types.JSONObject
-      , check_every = None Text
-      , tags = None Text
-      , unique_version_history = None Bool
-      }
-    : Types.CustomResourceType
+in  { source = None Types.JSONObject
+    , privileged = None Bool
+    , params = None Types.JSONObject
+    , check_every = None Text
+    , tags = None Text
+    , unique_version_history = None Bool
+    }
diff --git a/dhall-concourse/defaults/GetStep.dhall b/dhall-concourse/defaults/GetStep.dhall
--- a/dhall-concourse/defaults/GetStep.dhall
+++ b/dhall-concourse/defaults/GetStep.dhall
@@ -1,13 +1,11 @@
 let Types = ../types/package.dhall
 
-in    { get = None Text
-      , resource = ./Resource.dhall
-      , params = None Types.JSONObject
-      , version = None Types.GetVersion
-      , passed = None (List Text)
-      , trigger = None Bool
-      , tags = None (List Text)
-      , timeout = None Text
-      , attempts = None Natural
-      }
-    : Types.GetStep
+in  { get = None Text
+    , params = None Types.JSONObject
+    , version = None Types.GetVersion
+    , passed = None (List Text)
+    , trigger = None Bool
+    , tags = None (List Text)
+    , timeout = None Text
+    , attempts = None Natural
+    }
diff --git a/dhall-concourse/defaults/ImageResource.dhall b/dhall-concourse/defaults/ImageResource.dhall
--- a/dhall-concourse/defaults/ImageResource.dhall
+++ b/dhall-concourse/defaults/ImageResource.dhall
@@ -1,8 +1,6 @@
 let Types = ../types/package.dhall
 
-in    { type = "CHANGEME"
-      , source = None Types.JSONObject
-      , params = None Types.JSONObject
-      , version = None (List Types.TextTextPair)
-      }
-    : Types.ImageResource
+in  { source = None Types.JSONObject
+    , params = None Types.JSONObject
+    , version = None (List Types.TextTextPair)
+    }
diff --git a/dhall-concourse/defaults/Job.dhall b/dhall-concourse/defaults/Job.dhall
--- a/dhall-concourse/defaults/Job.dhall
+++ b/dhall-concourse/defaults/Job.dhall
@@ -1,8 +1,6 @@
 let Types = ../types/package.dhall
 
-in    { name = "CHANGEME"
-      , old_name = None Text
-      , plan = [] : List Types.Step
+in    { old_name = None Text
       , serial = None Bool
       , build_log_retention = None Types.JobBuildLogRetention
       , build_logs_to_retain = None Natural
@@ -16,4 +14,3 @@
       , on_abort = None Types.Step
       , ensure = None Types.Step
       }
-    : Types.Job
diff --git a/dhall-concourse/defaults/PutStep.dhall b/dhall-concourse/defaults/PutStep.dhall
--- a/dhall-concourse/defaults/PutStep.dhall
+++ b/dhall-concourse/defaults/PutStep.dhall
@@ -1,12 +1,10 @@
 let Types = ../types/package.dhall
 
-in    { put = None Text
-      , resource = ./Resource.dhall
-      , inputs = None (List Text)
-      , params = None Types.JSONObject
-      , get_params = None Types.JSONObject
-      , tags = None (List Text)
-      , timeout = None Text
-      , attempts = None Natural
-      }
-    : Types.PutStep
+in  { put = None Text
+    , inputs = None (List Text)
+    , params = None Types.JSONObject
+    , get_params = None Types.JSONObject
+    , tags = None (List Text)
+    , timeout = None Text
+    , attempts = None Natural
+    }
diff --git a/dhall-concourse/defaults/Resource.dhall b/dhall-concourse/defaults/Resource.dhall
--- a/dhall-concourse/defaults/Resource.dhall
+++ b/dhall-concourse/defaults/Resource.dhall
@@ -1,13 +1,10 @@
 let Types = ../types/package.dhall
 
-in    { name = "CHANGEME"
-      , type = Types.ResourceType.InBuilt "CHANGEME"
-      , icon = None Text
-      , source = None Types.JSONObject
-      , version = None (List Types.TextTextPair)
-      , check_every = None Text
-      , tags = None (List Text)
-      , public = None Bool
-      , webhook_token = None Text
-      }
-    : Types.Resource
+in  { icon = None Text
+    , source = None Types.JSONObject
+    , version = None (List Types.TextTextPair)
+    , check_every = None Text
+    , tags = None (List Text)
+    , public = None Bool
+    , webhook_token = None Text
+    }
diff --git a/dhall-concourse/defaults/StepHooks.dhall b/dhall-concourse/defaults/StepHooks.dhall
--- a/dhall-concourse/defaults/StepHooks.dhall
+++ b/dhall-concourse/defaults/StepHooks.dhall
@@ -1,5 +1,6 @@
   λ(Step : Type)
 → let Types = ../types/package.dhall
+  
   in    { on_success = None Step
         , on_failure = None Step
         , on_abort = None Step
diff --git a/dhall-concourse/defaults/TaskConfig.dhall b/dhall-concourse/defaults/TaskConfig.dhall
--- a/dhall-concourse/defaults/TaskConfig.dhall
+++ b/dhall-concourse/defaults/TaskConfig.dhall
@@ -1,18 +1,11 @@
 let Types = ../types/package.dhall
 
-in    { platform = "linux"
-      , run =
-          { path = "CHANGEME"
-          , args = None (List Text)
-          , dir = None Text
-          , user = None Text
-          }
-	  , container_limits = None Types.TaskContainerLimits
-      , image_resource = None Types.ImageResource
-      , rootfs_uri = None Text
-      , inputs = None (List Types.TaskInput)
-      , outputs = None (List Types.TaskOutput)
-      , caches = None (List Types.TaskCache)
-      , params = None (List { mapKey : Text, mapValue : Optional Text })
-      }
-    : Types.TaskConfig
+in  { platform = "linux"
+    , container_limits = None Types.TaskContainerLimits
+    , image_resource = None Types.ImageResource
+    , rootfs_uri = None Text
+    , inputs = None (List Types.TaskInput)
+    , outputs = None (List Types.TaskOutput)
+    , caches = None (List Types.TaskCache)
+    , params = None (List { mapKey : Text, mapValue : Optional Text })
+    }
diff --git a/dhall-concourse/defaults/TaskInput.dhall b/dhall-concourse/defaults/TaskInput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/defaults/TaskInput.dhall
@@ -0,0 +1,1 @@
+{ path = None Text, optional = None Bool }
diff --git a/dhall-concourse/defaults/TaskOutput.dhall b/dhall-concourse/defaults/TaskOutput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/defaults/TaskOutput.dhall
@@ -0,0 +1,1 @@
+{ path = None Text }
diff --git a/dhall-concourse/defaults/TaskRunConfig.dhall b/dhall-concourse/defaults/TaskRunConfig.dhall
--- a/dhall-concourse/defaults/TaskRunConfig.dhall
+++ b/dhall-concourse/defaults/TaskRunConfig.dhall
@@ -1,8 +1,1 @@
-let Types = ../types/package.dhall
-
-in    { path = "CHANGEME"
-      , args = None (List Text)
-      , dir = None Text
-      , user = None Text
-      }
-    : Types.TaskRunConfig
+{ args = None (List Text), dir = None Text, user = None Text }
diff --git a/dhall-concourse/defaults/TaskStep.dhall b/dhall-concourse/defaults/TaskStep.dhall
--- a/dhall-concourse/defaults/TaskStep.dhall
+++ b/dhall-concourse/defaults/TaskStep.dhall
@@ -1,15 +1,12 @@
 let Types = ../types/package.dhall
 
-in    { task = "CHANGEME"
-      , config = Types.TaskSpec.Config ./TaskConfig.dhall
-      , privileged = None Bool
-      , params = None (List Types.TextTextPair)
-      , image = None Text
-      , input_mapping = None (List Types.TextTextPair)
-      , output_mapping = None (List Types.TextTextPair)
-	  , vars = None Types.JSONObject
-      , tags = None (List Text)
-      , timeout = None Text
-      , attempts = None Natural
-      }
-    : Types.TaskStep
+in  { privileged = None Bool
+    , params = None (List Types.TextTextPair)
+    , image = None Text
+    , input_mapping = None (List Types.TextTextPair)
+    , output_mapping = None (List Types.TextTextPair)
+    , vars = None Types.JSONObject
+    , tags = None (List Text)
+    , timeout = None Text
+    , attempts = None Natural
+    }
diff --git a/dhall-concourse/defaults/package.dhall b/dhall-concourse/defaults/package.dhall
--- a/dhall-concourse/defaults/package.dhall
+++ b/dhall-concourse/defaults/package.dhall
@@ -1,11 +1,37 @@
-{ CustomResourceType = ./CustomResourceType.dhall
-, Resource = ./Resource.dhall
-, Job = ./Job.dhall
-, GetStep = ./GetStep.dhall
-, ImageResource = ./ImageResource.dhall
-, StepHooks = ./StepHooks.dhall
-, TaskStep = ./TaskStep.dhall
-, TaskConfig = ./TaskConfig.dhall
-, TaskRunConfig = ./TaskRunConfig.dhall
-, PutStep = ./PutStep.dhall
+{ CustomResourceType =
+      ./CustomResourceType.dhall sha256:f0d92c575214d4f127c8e6dcb78dea83bee1f29e9bcb4759b7709d002d31cf37
+    ? ./CustomResourceType.dhall
+, Resource =
+      ./Resource.dhall sha256:ee4a7e9c3bb43c5d015c51b3c63144ac3622976d51adb107eb6f53eae5b7c611
+    ? ./Resource.dhall
+, Job =
+      ./Job.dhall sha256:9bc3aa7c8aaa9aacffdf0f3cb2d88250fbc01a314dd3d49a6945585d6f6fb591
+    ? ./Job.dhall
+, GetStep =
+      ./GetStep.dhall sha256:7d630688d5fb2f6a2a6df76509c4c09c2ee3e49b517923085210fe0647dede9b
+    ? ./GetStep.dhall
+, ImageResource =
+      ./ImageResource.dhall sha256:b7e80677324979d06a1b41c2ba2f5fbcba88788aa828a9849356dac8ae1b119d
+    ? ./ImageResource.dhall
+, StepHooks =
+      ./StepHooks.dhall sha256:8458d8a9c42674f3f618152098e57a1345958192acd6c1acc5338c0709dbbc7d
+    ? ./StepHooks.dhall
+, TaskStep =
+      ./TaskStep.dhall sha256:31bbdf420318f308ac9c6f39c24515a5b15bed5f562b7625c16a41830e9a38fc
+    ? ./TaskStep.dhall
+, TaskConfig =
+      ./TaskConfig.dhall sha256:8dffe9abb5e94cfa317f6b95444111ae51667b3518c044f85f95307a38f5bc9f
+    ? ./TaskConfig.dhall
+, TaskInput =
+      ./TaskInput.dhall sha256:5e3edf573190543a8f48d4385f47c5e8e01e384baa5c2c593ee16682daea50f2
+    ? ./TaskInput.dhall
+, TaskOutput =
+      ./TaskOutput.dhall sha256:c3720062b9d3e2f4e6da3f1ca38f284725a2f80cdbcf01bf4538449121714abf
+    ? ./TaskOutput.dhall
+, TaskRunConfig =
+      ./TaskRunConfig.dhall sha256:c177d3da26ff0f2efb3fe5b3f44e53e778d8ed42ed7a866d9cb820d234d0a7d0
+    ? ./TaskRunConfig.dhall
+, PutStep =
+      ./PutStep.dhall sha256:0ce9032829ec8e204993f577f5c27b408f439677088ef24f4db11c556a64253f
+    ? ./PutStep.dhall
 }
diff --git a/dhall-concourse/extractors/package.dhall b/dhall-concourse/extractors/package.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/extractors/package.dhall
@@ -0,0 +1,7 @@
+{ resourcesFromJobs =
+      ./resourcesFromJobs.dhall sha256:8a625908d0d5599b9b52b8800e183f2f1a8ee179ec541dfede9f59e6d2573b6b
+    ? ./resourcesFromJobs.dhall
+, resourceTypesFromResources =
+      ./resourcesTypesFromResources.dhall sha256:4979de9c830538c5af89a8a230d21131a85112c8401471df46ab89055f423476
+    ? ./resourcesTypesFromResources.dhall
+}
diff --git a/dhall-concourse/extractors/resourcesFromJobs.dhall b/dhall-concourse/extractors/resourcesFromJobs.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/extractors/resourcesFromJobs.dhall
@@ -0,0 +1,94 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+let Resource = Types.Resource
+
+let StepHooks = Types.StepHooks
+
+let catOptionals = ../utils/catOptionals.dhall
+
+let resourcesFromStepHooks
+    : StepHooks (List Resource) → List Resource
+    =   λ(h : StepHooks (List Resource))
+      → let listOfListOfResources =
+              catOptionals
+                (List Resource)
+                [ h.ensure, h.on_success, h.on_failure, h.on_abort ]
+        
+        in  Prelude.List.concat Resource listOfListOfResources
+
+let resourcesFromGetStep
+    : Types.GetStep → StepHooks (List Resource) → List Resource
+    =   λ(g : Types.GetStep)
+      → λ(h : StepHooks (List Resource))
+      → [ g.resource ] # resourcesFromStepHooks h
+
+let resourcesFromPutStep
+    : Types.PutStep → StepHooks (List Resource) → List Resource
+    =   λ(p : Types.PutStep)
+      → λ(h : StepHooks (List Resource))
+      → [ p.resource ] # resourcesFromStepHooks h
+
+let resourcesFromTaskStep
+    : Types.TaskStep → StepHooks (List Resource) → List Resource
+    =   λ(_ : Types.TaskStep)
+      → λ(h : StepHooks (List Resource))
+      → resourcesFromStepHooks h
+
+let resourcesFromAggregateOrDo
+    : List (List Resource) → StepHooks (List Resource) → List Resource
+    =   λ(rs : List (List Resource))
+      → λ(h : StepHooks (List Resource))
+      → Prelude.List.concat Resource rs # resourcesFromStepHooks h
+
+let resourcesFromInParallelSteps =
+      λ(rs : List (List Resource)) → Prelude.List.concat Resource rs
+
+let resourcesFromInParallelConfig =
+        λ(config : Types.InParallelConfig (List Resource))
+      → Prelude.List.concat Resource config.steps
+
+let resourcesFromInParallel
+    :   Types.InParallelStep (List Resource)
+      → StepHooks (List Resource)
+      → List Resource
+    =   λ(step : Types.InParallelStep (List Resource))
+      → λ(h : StepHooks (List Resource))
+      →   merge
+            { Steps = resourcesFromInParallelSteps
+            , Config = resourcesFromInParallelConfig
+            }
+            step
+        # resourcesFromStepHooks h
+
+let resourcesFromTry
+    : List Resource → StepHooks (List Resource) → List Resource
+    =   λ(rs : List Resource)
+      → λ(h : StepHooks (List Resource))
+      → rs # resourcesFromStepHooks h
+
+let resourcesFromStep =
+        λ(s : Types.Step)
+      → s
+          (List Resource)
+          { get = resourcesFromGetStep
+          , put = resourcesFromPutStep
+          , task = resourcesFromTaskStep
+          , aggregate = resourcesFromAggregateOrDo
+          , do = resourcesFromAggregateOrDo
+          , try = resourcesFromTry
+          , in_parallel = resourcesFromInParallel
+          }
+
+let resourcesFromJob =
+        λ(j : Types.Job)
+      → let hookSteps =
+              catOptionals
+                Types.Step
+                [ j.on_abort, j.on_failure, j.on_success, j.ensure ]
+        
+        in  Prelude.List.concatMap Types.Step Resource resourcesFromStep (j.plan # hookSteps)
+
+in    Prelude.List.concatMap Types.Job Resource resourcesFromJob
+    : List Types.Job → List Resource
diff --git a/dhall-concourse/extractors/resourcesTypesFromResources.dhall b/dhall-concourse/extractors/resourcesTypesFromResources.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/extractors/resourcesTypesFromResources.dhall
@@ -0,0 +1,10 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+let Resource = Types.Resource
+
+let ResourceType = Types.ResourceType
+
+in    Prelude.List.map Resource ResourceType (λ(r : Resource) → r.type)
+    : List Resource → List ResourceType
diff --git a/dhall-concourse/helpers/package.dhall b/dhall-concourse/helpers/package.dhall
--- a/dhall-concourse/helpers/package.dhall
+++ b/dhall-concourse/helpers/package.dhall
@@ -1,9 +1,25 @@
-{ taskStep = ./taskStep.dhall
-, putStep = ./putStep.dhall
-, getStep = ./getStep.dhall
-, aggregateStep = ./aggregateStep.dhall
-, inParallelStep = ./inParallelStep.dhall
-, inParallelStepSimple = ./inParallelStepSimple.dhall
-, doStep = ./doStep.dhall
-, tryStep = ./tryStep.dhall
+{ taskStep =
+      ./taskStep.dhall sha256:f7c597fd8d12cb09915aa2f69197425b70cdd02228f60204fc1fb6ebd7752ba8
+    ? ./taskStep.dhall
+, putStep =
+      ./putStep.dhall sha256:2acca50d8b5ddc509ecbe9f5e5cb3cb1c7b16afcafc3be19832dc83277c7ba29
+    ? ./putStep.dhall
+, getStep =
+      ./getStep.dhall sha256:6450e6470d5d5c0874ace985cd7fc6e68c30c6b2ee96627e407709bac3d444c5
+    ? ./getStep.dhall
+, aggregateStep =
+      ./aggregateStep.dhall sha256:627ed574a25f46d86ce17dcac7a9a1d3a7091d99615420933357c241a575274a
+    ? ./aggregateStep.dhall
+, inParallelStep =
+      ./inParallelStep.dhall sha256:4b06b441eef8f7596882276125c956ac371f544d9d162de118bd8909fc483103
+    ? ./inParallelStep.dhall
+, inParallelStepSimple =
+      ./inParallelStepSimple.dhall sha256:f2163a83a674d4dcffa87d15743a071624b17c6709fe467e67e432e527de97c2
+    ? ./inParallelStepSimple.dhall
+, doStep =
+      ./doStep.dhall sha256:644a08ced3e2bfd7d7ceb929e5067d3a73add378f7b07a6740b5791a791933b9
+    ? ./doStep.dhall
+, tryStep =
+      ./tryStep.dhall sha256:29da9af3ac4f2a0eb01e606f74f26955f2026b37dac45e6d94bb8b3c63d56973
+    ? ./tryStep.dhall
 }
diff --git a/dhall-concourse/package.dhall b/dhall-concourse/package.dhall
--- a/dhall-concourse/package.dhall
+++ b/dhall-concourse/package.dhall
@@ -1,5 +1,19 @@
-{ Types = ./types/package.dhall
-, defaults = ./defaults/package.dhall
-, schemas = ./schemas/package.dhall
-, helpers = ./helpers/package.dhall
+{ Types =
+      ./types/package.dhall sha256:4576b3998000a9e15b5e569913a36dcdee8607470d94e6b37f6d12e725588e51
+    ? ./types/package.dhall
+, defaults =
+      ./defaults/package.dhall sha256:f4cc4d0a87f5a474de645f4ec8320aa055fa391d2a97696acba2814b8f594eb8
+    ? ./defaults/package.dhall
+, schemas =
+      ./schemas/package.dhall sha256:6a1466287aa08d3911f9b07357079dfb58740747d66510367ad17290be695e6f
+    ? ./schemas/package.dhall
+, helpers =
+      ./helpers/package.dhall sha256:94d9aab795fe7049da8c7904d6faa7c7be60ed10a59565c33d7cf9d026a6968d
+    ? ./helpers/package.dhall
+, render =
+      ./render/package.dhall sha256:380eab61bcc75473589242aa13011d0912f0634745f84c185296594c557bacb7
+    ? ./render/package.dhall
+, extractors =
+      ./extractors/package.dhall sha256:761aabbb030ca0fca473273e7c8b45c29dcd848388bdb767db7322a1243a5dd6
+    ? ./extractors/package.dhall
 }
diff --git a/dhall-concourse/render/getStep.dhall b/dhall-concourse/render/getStep.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/getStep.dhall
@@ -0,0 +1,26 @@
+let Types = ../types/package.dhall
+
+let nameResource = ./helpers/name-resource.dhall
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.GetStep → Types.JSONObject
+    =   λ(g : Types.GetStep)
+      → toMap
+          { get = nameResource.getName g.get g.resource
+          , resource = nameResource.getResource g.get g.resource
+          , params = RenderOptional.jsonObject g.params
+          , version =
+              RenderOptional.generic
+                Types.GetVersion
+                ./getVersion.dhall
+                g.version
+          , passed = RenderOptional.lists.text g.passed
+          , trigger = RenderOptional.bool g.trigger
+          , tags = RenderOptional.lists.text g.tags
+          , timeout = RenderOptional.text g.timeout
+          , attempts = RenderOptional.natural g.attempts
+          }
+
+in  render
diff --git a/dhall-concourse/render/getVersion.dhall b/dhall-concourse/render/getVersion.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/getVersion.dhall
@@ -0,0 +1,26 @@
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let Types = ../types/package.dhall
+
+let renderLatest
+    : Text → JSON.Type
+    = λ(ignored : Text) → JSON.string "latest"
+
+let renderEvery
+    : Text → JSON.Type
+    = λ(ignored : Text) → JSON.string "every"
+
+let renderSpecific
+    : List Types.TextTextPair → JSON.Type
+    =   λ(version : List Types.TextTextPair)
+      → JSON.object (./textTextMap.dhall version)
+
+in    λ(version : Types.GetVersion)
+    → merge
+        { Latest = renderLatest
+        , Every = renderEvery
+        , SpecificVersion = renderSpecific
+        }
+        version
diff --git a/dhall-concourse/render/groupedJobs.dhall b/dhall-concourse/render/groupedJobs.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/groupedJobs.dhall
@@ -0,0 +1,29 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+in    λ(groupedJobs : List Types.GroupedJob)
+    → let jobs =
+            Prelude.List.map
+              Types.GroupedJob
+              Types.Job
+              (λ(groupedJob : Types.GroupedJob) → groupedJob.job)
+              groupedJobs
+
+      let RenderedGroup = { name : Text, jobs : List Text }
+
+      let jobGroups
+          : List RenderedGroup
+          = Prelude.List.concatMap
+              Types.GroupedJob
+              RenderedGroup
+              (   λ(groupedJob : Types.GroupedJob)
+                → Prelude.List.map
+                    Text
+                    RenderedGroup
+                    (λ(g : Text) → { name = g, jobs = [ groupedJob.job.name ] })
+                    groupedJob.groups
+              )
+              groupedJobs
+
+      in  ./pipeline.dhall jobs ⫽ { groups = jobGroups }
diff --git a/dhall-concourse/render/helpers/name-resource.dhall b/dhall-concourse/render/helpers/name-resource.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/helpers/name-resource.dhall
@@ -0,0 +1,23 @@
+let Types = ../../types/package.dhall
+
+let Prelude = ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let getName
+    : Optional Text → Types.Resource → JSON.Type
+    =   λ(name : Optional Text)
+      → λ(resource : Types.Resource)
+      → JSON.string (Prelude.Optional.default Text resource.name name)
+
+let getResource
+    : Optional Text → Types.Resource → JSON.Type
+    =   λ(name : Optional Text)
+      → λ(resource : Types.Resource)
+      →       if Prelude.Optional.null Text name
+        
+        then  JSON.null
+        
+        else  JSON.string resource.name
+
+in  { getName = getName, getResource = getResource }
diff --git a/dhall-concourse/render/imageResource.dhall b/dhall-concourse/render/imageResource.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/imageResource.dhall
@@ -0,0 +1,21 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.ImageResource → JSON.Type
+    =   λ(r : Types.ImageResource)
+      → JSON.object
+          ( toMap
+              { type = JSON.string r.type
+              , source = RenderOptional.jsonObject r.source
+              , params = RenderOptional.jsonObject r.params
+              , version = RenderOptional.textTextMap r.version
+              }
+          )
+
+in  RenderOptional.generic Types.ImageResource render
diff --git a/dhall-concourse/render/inParallelStep.dhall b/dhall-concourse/render/inParallelStep.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/inParallelStep.dhall
@@ -0,0 +1,28 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let renderSteps = λ(steps : List JSON.Type) → JSON.array steps
+
+let renderConfig =
+        λ(config : Types.InParallelConfig JSON.Type)
+      → JSON.object
+          ( toMap
+              { steps = JSON.array config.steps
+              , limit = RenderOptional.natural config.limit
+              , fail_fast = RenderOptional.bool config.fail_fast
+              }
+          )
+
+let render
+    : Types.InParallelStep JSON.Type → Types.JSONObject
+    =   λ(p : Types.InParallelStep JSON.Type)
+      → toMap
+          { in_parallel = merge { Steps = renderSteps, Config = renderConfig } p
+          }
+
+in  render
diff --git a/dhall-concourse/render/job.dhall b/dhall-concourse/render/job.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/job.dhall
@@ -0,0 +1,39 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let optionalStep = RenderOptional.generic Types.Step ./step.dhall
+
+let optionalLogRetention =
+      RenderOptional.generic
+        Types.JobBuildLogRetention
+        ./jobBuildLogRetention.dhall
+
+in    λ(j : Types.Job)
+    → JSON.object
+        ( toMap
+            { name = JSON.string j.name
+            , plan =
+                JSON.array
+                  (Prelude.List.map Types.Step JSON.Type ./step.dhall j.plan)
+            , old_name = RenderOptional.text j.old_name
+            , serial = RenderOptional.bool j.serial
+            , build_log_retention = optionalLogRetention j.build_log_retention
+            , build_logs_to_retain =
+                RenderOptional.natural j.build_logs_to_retain
+            , serial_groups = RenderOptional.lists.text j.serial_groups
+            , max_in_flight = RenderOptional.natural j.max_in_flight
+            , public = RenderOptional.bool j.public
+            , disable_manual_trigger =
+                RenderOptional.bool j.disable_manual_trigger
+            , interruptible = RenderOptional.bool j.interruptible
+            , on_success = optionalStep j.on_success
+            , on_failure = optionalStep j.on_failure
+            , on_abort = optionalStep j.on_abort
+            , ensure = optionalStep j.ensure
+            }
+        )
diff --git a/dhall-concourse/render/jobBuildLogRetention.dhall b/dhall-concourse/render/jobBuildLogRetention.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/jobBuildLogRetention.dhall
@@ -0,0 +1,18 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render =
+        λ(r : Types.JobBuildLogRetention)
+      → JSON.object
+          ( toMap
+              { days = RenderOptional.natural r.days
+              , builds = RenderOptional.natural r.builds
+              }
+          )
+
+in  render
diff --git a/dhall-concourse/render/jobs.dhall b/dhall-concourse/render/jobs.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/jobs.dhall
@@ -0,0 +1,7 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+in    λ(js : List Types.Job)
+    → Prelude.JSON.array
+        (Prelude.List.map Types.Job Prelude.JSON.Type ./job.dhall js)
diff --git a/dhall-concourse/render/optionals/bool.dhall b/dhall-concourse/render/optionals/bool.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/bool.dhall
@@ -0,0 +1,11 @@
+let Prelude = ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let optionalToJSON = ./generic.dhall
+
+let boolToJSON
+    : Optional Bool → JSON.Type
+    = optionalToJSON Bool JSON.bool
+
+in  boolToJSON
diff --git a/dhall-concourse/render/optionals/generic.dhall b/dhall-concourse/render/optionals/generic.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/generic.dhall
@@ -0,0 +1,12 @@
+let Prelude = ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let optionalToJSON
+    : ∀(T : Type) → (T → JSON.Type) → Optional T → JSON.Type
+    =   λ(T : Type)
+      → λ(toJSON : T → JSON.Type)
+      → λ(thing : Optional T)
+      → Prelude.Optional.fold T thing JSON.Type toJSON JSON.null
+
+in  optionalToJSON
diff --git a/dhall-concourse/render/optionals/json-object.dhall b/dhall-concourse/render/optionals/json-object.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/json-object.dhall
@@ -0,0 +1,9 @@
+let JSON = (../../lib/prelude.dhall).JSON
+
+let JSONObject = ../../types/JSONObject.dhall
+
+let paramsToJSON
+    : Optional JSONObject → JSON.Type
+    = ./generic.dhall JSONObject JSON.object
+
+in  paramsToJSON
diff --git a/dhall-concourse/render/optionals/lists/generic.dhall b/dhall-concourse/render/optionals/lists/generic.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/lists/generic.dhall
@@ -0,0 +1,13 @@
+let Prelude = ../../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let render
+    : ∀(T : Type) → (T → JSON.Type) → Optional (List T) → JSON.Type
+    =   λ(T : Type)
+      → λ(f : T → JSON.Type)
+      → ../generic.dhall
+          (List T)
+          (λ(xs : List T) → JSON.array (Prelude.List.map T JSON.Type f xs))
+
+in  render
diff --git a/dhall-concourse/render/optionals/lists/package.dhall b/dhall-concourse/render/optionals/lists/package.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/lists/package.dhall
@@ -0,0 +1,11 @@
+let Prelude =
+        ../../../lib/prelude.dhall sha256:99462c205117931c0919f155a6046aec140c70fb8876d208c7c77027ab19c2fa
+      ? ../../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let generic =
+        ./generic.dhall sha256:e6fe28f437ac7fc2b574381ac2935273e923643d22947ddba14d941d9879955c
+      ? ./generic.dhall
+
+in  { generic = generic, text = generic Text JSON.string }
diff --git a/dhall-concourse/render/optionals/natural.dhall b/dhall-concourse/render/optionals/natural.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/natural.dhall
@@ -0,0 +1,5 @@
+let JSON = (../../lib/prelude.dhall).JSON
+
+in  ./generic.dhall
+      Natural
+      (λ(n : Natural) → JSON.number (Integer/toDouble (Natural/toInteger n)))
diff --git a/dhall-concourse/render/optionals/package.dhall b/dhall-concourse/render/optionals/package.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/package.dhall
@@ -0,0 +1,30 @@
+let generic =
+        ./generic.dhall sha256:e87b80b1ddea0190e04f34886ec715b8248fdfd87d0f52b2d98c9db8c9bfa58e
+      ? ./generic.dhall
+
+let Prelude =
+        ../../lib/prelude.dhall sha256:99462c205117931c0919f155a6046aec140c70fb8876d208c7c77027ab19c2fa
+      ? ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+in  { generic =
+        generic
+    , bool = generic Bool JSON.bool
+    , jsonObject =
+          ./json-object.dhall sha256:a716dcfe232f9281dba641ec8667f649cd66ecfb49adc0d606f2c141b6806a7b
+        ? ./json-object.dhall
+    , text = generic Text JSON.string
+    , natural =
+          ./natural.dhall sha256:d905b04cbe71416d5792ce04f107e7ceadebb5ff6bb3170bebd2766cc3783e5a
+        ? ./natural.dhall
+    , lists =
+          ./lists/package.dhall sha256:ea18f4a4c82d3876a94db607d4afdc86342cace7368401c8911c0e3cb2ffc4b7
+        ? ./lists/package.dhall
+    , textTextMap =
+          ./text-text-map.dhall sha256:ee10a9fce1cf002a2ee6c6ca395e274d7e877ee7365646d927c8bb9f6181249b
+        ? ./text-text-map.dhall
+    , textOptionalJSONMap =
+          ./text-optional-json-map.dhall sha256:537bb2f57868dd144aa4c8b68de775557d235b87af4b03b40b8d9ce02f7d2a3b
+        ? ./text-optional-json-map.dhall
+    }
diff --git a/dhall-concourse/render/optionals/text-optional-json-map.dhall b/dhall-concourse/render/optionals/text-optional-json-map.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/text-optional-json-map.dhall
@@ -0,0 +1,25 @@
+let Prelude = ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let Types = ../../types/package.dhall
+
+let TextOptionalJSON = { mapKey : Text, mapValue : Optional JSON.Type }
+
+let TextJSON = { mapKey : Text, mapValue : JSON.Type }
+
+let id = λ(x : JSON.Type) → x
+
+let renderPair
+    : TextOptionalJSON → TextJSON
+    =   λ(p : TextOptionalJSON)
+      →   p
+        ⫽ { mapValue =
+              ./generic.dhall JSON.Type id (p.mapValue : Optional JSON.Type)
+          }
+
+let renderMap
+    : List TextOptionalJSON → Types.JSONObject
+    = Prelude.List.map TextOptionalJSON TextJSON renderPair
+
+in  renderMap
diff --git a/dhall-concourse/render/optionals/text-text-map.dhall b/dhall-concourse/render/optionals/text-text-map.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/text-text-map.dhall
@@ -0,0 +1,12 @@
+let Prelude = ../../lib/prelude.dhall
+
+let Types = ../../types/package.dhall
+
+in    λ(x : Optional (List Types.TextTextPair))
+    → ./json-object.dhall
+        ( Prelude.Optional.map
+            (List Types.TextTextPair)
+            Types.JSONObject
+            ../textTextMap.dhall
+            x
+        )
diff --git a/dhall-concourse/render/optionals/text.dhall b/dhall-concourse/render/optionals/text.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/optionals/text.dhall
@@ -0,0 +1,9 @@
+let Prelude = ../../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let textToJSON
+    : Optional Text → JSON.Type
+    = ./generic.dhall Text JSON.string
+
+in  textToJSON
diff --git a/dhall-concourse/render/package.dhall b/dhall-concourse/render/package.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/package.dhall
@@ -0,0 +1,28 @@
+{ job =
+      ./job.dhall sha256:87b1bddbee9fbad497e8e5f8fa900bdbb05f8a1b7741f04745ce89f2310a2301
+    ? ./job.dhall
+, jobs =
+      ./jobs.dhall sha256:82c5d958835b112234e985aff019e51b96c301cf93cd0e6767a48fb6663ca3aa
+    ? ./jobs.dhall
+, step =
+      ./step.dhall sha256:1f0320dab15822eed2bc394d912a26ef2ed950afcb5b6ece2dd16202fce91c1e
+    ? ./step.dhall
+, resource =
+      ./resource.dhall sha256:24287125309ae12501bc6e6d12b6a11a4b68ae1a77fd3f8b3fde5392e349f5be
+    ? ./resource.dhall
+, resources =
+      ./resources.dhall sha256:4ed6a34a4cccc01540e57ba1b495f374df5341ad3f62450ee9fad0e732a2e1cc
+    ? ./resources.dhall
+, resourceType =
+      ./resourceType.dhall sha256:f3e6205fae63b9d9aa88459cac3b72a1bb5881c501f3117aca391f76016fe300
+    ? ./resourceType.dhall
+, resourceTypes =
+      ./resourceTypes.dhall sha256:ef42b1e7ee294c054380a51c432680a8a573ef85bc2d872a312282770055f3f4
+    ? ./resourceTypes.dhall
+, pipeline =
+      ./pipeline.dhall sha256:d66fb1637db9cf4680213b2b6939a5d5cff64b2f4e5d4e24444d49d40f92853e
+    ? ./pipeline.dhall
+, groupedJobs =
+      ./groupedJobs.dhall sha256:9d56b1f88ec737fa02cd8eddc5fa239fd89cb6c509130ea83295899c2ac49c26
+    ? ./groupedJobs.dhall
+}
diff --git a/dhall-concourse/render/pipeline.dhall b/dhall-concourse/render/pipeline.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/pipeline.dhall
@@ -0,0 +1,19 @@
+let Types = ../types/package.dhall
+
+let Extract = ../extractors/package.dhall
+
+let renderResources = ./resources.dhall
+
+let renderResourceTypes = ./resourceTypes.dhall
+
+let renderJobs = ./jobs.dhall
+
+in    λ(jobs : List Types.Job)
+    → let resources = Extract.resourcesFromJobs jobs
+      
+      let resource_types = Extract.resourceTypesFromResources resources
+      
+      in  { jobs = renderJobs jobs
+          , resources = renderResources resources
+          , resource_types = renderResourceTypes resource_types
+          }
diff --git a/dhall-concourse/render/putStep.dhall b/dhall-concourse/render/putStep.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/putStep.dhall
@@ -0,0 +1,21 @@
+let Types = ../types/package.dhall
+
+let nameResource = ./helpers/name-resource.dhall
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.PutStep → Types.JSONObject
+    =   λ(p : Types.PutStep)
+      → toMap
+          { put = nameResource.getName p.put p.resource
+          , resource = nameResource.getResource p.put p.resource
+          , inputs = RenderOptional.lists.text p.inputs
+          , params = RenderOptional.jsonObject p.params
+          , get_params = RenderOptional.jsonObject p.get_params
+          , tags = RenderOptional.lists.text p.tags
+          , timeout = RenderOptional.text p.timeout
+          , attempts = RenderOptional.natural p.attempts
+          }
+
+in  render
diff --git a/dhall-concourse/render/resource.dhall b/dhall-concourse/render/resource.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/resource.dhall
@@ -0,0 +1,35 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let typeName
+    : Types.ResourceType → Text
+    =   λ(t : Types.ResourceType)
+      → merge
+          { InBuilt = λ(n : Text) → n
+          , Custom = λ(c : Types.CustomResourceType) → c.name
+          }
+          t
+
+let render
+    : Types.Resource → JSON.Type
+    =   λ(r : Types.Resource)
+      → JSON.object
+          ( toMap
+              { name = JSON.string r.name
+              , type = JSON.string (typeName r.type)
+              , icon = RenderOptional.text r.icon
+              , source = RenderOptional.jsonObject r.source
+              , version = RenderOptional.textTextMap r.version
+              , check_every = RenderOptional.text r.check_every
+              , tags = RenderOptional.lists.text r.tags
+              , public = RenderOptional.bool r.public
+              , webhook_token = RenderOptional.text r.webhook_token
+              }
+          )
+
+in  render
diff --git a/dhall-concourse/render/resourceType.dhall b/dhall-concourse/render/resourceType.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/resourceType.dhall
@@ -0,0 +1,30 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let renderInBuilt = λ(ignored : Text) → None JSON.Type
+
+let renderCustom =
+        λ(c : Types.CustomResourceType)
+      → Some
+          ( JSON.object
+              ( toMap
+                  { name = JSON.string c.name
+                  , type = JSON.string c.type
+                  , source = RenderOptional.jsonObject c.source
+                  , privileged = RenderOptional.bool c.privileged
+                  , params = RenderOptional.jsonObject c.params
+                  , check_every = RenderOptional.text c.check_every
+                  , tags = RenderOptional.text c.tags
+                  , unique_version_history =
+                      RenderOptional.bool c.unique_version_history
+                  }
+              )
+          )
+
+in    λ(r : Types.ResourceType)
+    → merge { InBuilt = renderInBuilt, Custom = renderCustom } r
diff --git a/dhall-concourse/render/resourceTypes.dhall b/dhall-concourse/render/resourceTypes.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/resourceTypes.dhall
@@ -0,0 +1,35 @@
+let Prelude = ../lib/prelude.dhall
+
+let Types = ../types/package.dhall
+
+let JSON = Prelude.JSON
+
+let catOptionals
+    : ∀(T : Type) → List (Optional T) → List T
+    =   λ(T : Type)
+      → λ(ts : List (Optional T))
+      → List/fold
+          (Optional T)
+          ts
+          (List T)
+          (   λ(t : Optional T)
+            → λ(acc : List T)
+            → Prelude.List.concat T [ Prelude.Optional.toList T t, acc ]
+          )
+          ([] : List T)
+
+let catOptionalJSONs =
+      λ(js : List (Optional JSON.Type)) → JSON.array (catOptionals JSON.Type js)
+
+let render
+    : List Types.ResourceType → JSON.Type
+    =   λ(rs : List Types.ResourceType)
+      → catOptionalJSONs
+          ( Prelude.List.map
+              Types.ResourceType
+              (Optional JSON.Type)
+              ./resourceType.dhall
+              rs
+          )
+
+in  render
diff --git a/dhall-concourse/render/resources.dhall b/dhall-concourse/render/resources.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/resources.dhall
@@ -0,0 +1,7 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+in  Prelude.List.map Types.Resource JSON.Type ./resource.dhall
diff --git a/dhall-concourse/render/step.dhall b/dhall-concourse/render/step.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/step.dhall
@@ -0,0 +1,63 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let TextJSONPair = { mapKey : Text, mapValue : JSON.Type }
+
+let concatJSONs =
+        λ(xs : List Types.JSONObject)
+      → JSON.object (Prelude.List.concat TextJSONPair xs)
+
+let renderGet =
+        λ(get : Types.GetStep)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ ./getStep.dhall get, ./stepHooks.dhall hooks ]
+
+let renderPut =
+        λ(put : Types.PutStep)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ ./putStep.dhall put, ./stepHooks.dhall hooks ]
+
+let renderTask =
+        λ(task : Types.TaskStep)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ ./taskStep.dhall task, ./stepHooks.dhall hooks ]
+
+let renderAggregate =
+        λ(steps : List JSON.Type)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs
+          [ toMap { aggregate = JSON.array steps }, ./stepHooks.dhall hooks ]
+
+let renderDo =
+        λ(steps : List JSON.Type)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ toMap { do = JSON.array steps }, ./stepHooks.dhall hooks ]
+
+let renderTry =
+        λ(step : JSON.Type)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ toMap { try = step }, ./stepHooks.dhall hooks ]
+
+let renderInParallel =
+        λ(config : Types.InParallelStep JSON.Type)
+      → λ(hooks : Types.StepHooks JSON.Type)
+      → concatJSONs [ ./inParallelStep.dhall config, ./stepHooks.dhall hooks ]
+
+let render
+    : Types.Step → JSON.Type
+    =   λ(step : Types.Step)
+      → step
+          JSON.Type
+          { get = renderGet
+          , put = renderPut
+          , task = renderTask
+          , aggregate = renderAggregate
+          , do = renderDo
+          , try = renderTry
+          , in_parallel = renderInParallel
+          }
+
+in  render
diff --git a/dhall-concourse/render/stepHooks.dhall b/dhall-concourse/render/stepHooks.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/stepHooks.dhall
@@ -0,0 +1,15 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.StepHooks JSON.Type → Types.JSONObject
+    =   λ(hooks : Types.StepHooks JSON.Type)
+      → RenderOptional.textOptionalJSONMap
+          (toMap hooks : List { mapKey : Text, mapValue : Optional JSON.Type })
+
+in  render
diff --git a/dhall-concourse/render/taskCache.dhall b/dhall-concourse/render/taskCache.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskCache.dhall
@@ -0,0 +1,11 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let render
+    : Types.TaskCache → JSON.Type
+    = λ(c : Types.TaskCache) → JSON.object (toMap { path = JSON.string c.path })
+
+in  render
diff --git a/dhall-concourse/render/taskConfig.dhall b/dhall-concourse/render/taskConfig.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskConfig.dhall
@@ -0,0 +1,48 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let TextOptionalTextPair = { mapKey : Text, mapValue : Optional Text }
+
+let render
+    : Types.TaskConfig → JSON.Type
+    =   λ(c : Types.TaskConfig)
+      → JSON.object
+          ( toMap
+              { platform = JSON.string c.platform
+              , run = ./taskRunConfig.dhall c.run
+              , image_resource = ./imageResource.dhall c.image_resource
+              , rootfs_uri = RenderOptional.text c.rootfs_uri
+              , container_limits =
+                  ./taskContainerLimits.dhall c.container_limits
+              , inputs =
+                  RenderOptional.lists.generic
+                    Types.TaskInput
+                    ./taskInput.dhall
+                    c.inputs
+              , outputs =
+                  RenderOptional.lists.generic
+                    Types.TaskOutput
+                    ./taskOutput.dhall
+                    c.outputs
+              , caches =
+                  RenderOptional.lists.generic
+                    Types.TaskCache
+                    ./taskCache.dhall
+                    c.caches
+              , params =
+                  RenderOptional.jsonObject
+                    ( Prelude.Optional.map
+                        (List TextOptionalTextPair)
+                        Types.JSONObject
+                        ./textOptionalTextMap.dhall
+                        c.params
+                    )
+              }
+          )
+
+in  render
diff --git a/dhall-concourse/render/taskContainerLimits.dhall b/dhall-concourse/render/taskContainerLimits.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskContainerLimits.dhall
@@ -0,0 +1,19 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.TaskContainerLimits → JSON.Type
+    =   λ(l : Types.TaskContainerLimits)
+      → JSON.object
+          ( toMap
+              { cpu = RenderOptional.natural l.cpu
+              , memory = RenderOptional.natural l.memory
+              }
+          )
+
+in  RenderOptional.generic Types.TaskContainerLimits render
diff --git a/dhall-concourse/render/taskInput.dhall b/dhall-concourse/render/taskInput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskInput.dhall
@@ -0,0 +1,20 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.TaskInput → JSON.Type
+    =   λ(i : Types.TaskInput)
+      → JSON.object
+          ( toMap
+              { name = JSON.string i.name
+              , path = RenderOptional.text i.path
+              , optional = RenderOptional.bool i.optional
+              }
+          )
+
+in  render
diff --git a/dhall-concourse/render/taskOutput.dhall b/dhall-concourse/render/taskOutput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskOutput.dhall
@@ -0,0 +1,17 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.TaskOutput → JSON.Type
+    =   λ(o : Types.TaskOutput)
+      → JSON.object
+          ( toMap
+              { name = JSON.string o.name, path = RenderOptional.text o.path }
+          )
+
+in  render
diff --git a/dhall-concourse/render/taskRunConfig.dhall b/dhall-concourse/render/taskRunConfig.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskRunConfig.dhall
@@ -0,0 +1,19 @@
+let Types = ../types/package.dhall
+
+let JSON = (../lib/prelude.dhall).JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let render
+    : Types.TaskRunConfig → JSON.Type
+    =   λ(c : Types.TaskRunConfig)
+      → JSON.object
+          ( toMap
+              { path = JSON.string c.path
+              , args = RenderOptional.lists.text c.args
+              , dir = RenderOptional.text c.dir
+              , user = RenderOptional.text c.user
+              }
+          )
+
+in  render
diff --git a/dhall-concourse/render/taskSpec.dhall b/dhall-concourse/render/taskSpec.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskSpec.dhall
@@ -0,0 +1,17 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let renderFile = λ(f : Text) → toMap { file = JSON.string f }
+
+let renderConfig =
+      λ(c : Types.TaskConfig) → toMap { config = ./taskConfig.dhall c }
+
+let render
+    : Types.TaskSpec → Types.JSONObject
+    =   λ(s : Types.TaskSpec)
+      → merge { File = renderFile, Config = renderConfig } s
+
+in  render
diff --git a/dhall-concourse/render/taskStep.dhall b/dhall-concourse/render/taskStep.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/taskStep.dhall
@@ -0,0 +1,31 @@
+let Types = ../types/package.dhall
+
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let RenderOptional = ./optionals/package.dhall
+
+let TextJSONPair = { mapKey : Text, mapValue : JSON.Type }
+
+let render
+    : Types.TaskStep → Types.JSONObject
+    =   λ(t : Types.TaskStep)
+      → Prelude.List.concat
+          TextJSONPair
+          [ toMap
+              { task = JSON.string t.task
+              , privileged = RenderOptional.bool t.privileged
+              , vars = RenderOptional.jsonObject t.vars
+              , params = RenderOptional.textTextMap t.params
+              , image = RenderOptional.text t.image
+              , input_mapping = RenderOptional.textTextMap t.input_mapping
+              , output_mapping = RenderOptional.textTextMap t.output_mapping
+              , tags = RenderOptional.lists.text t.tags
+              , timeout = RenderOptional.text t.timeout
+              , attempts = RenderOptional.natural t.attempts
+              }
+          , ./taskSpec.dhall t.config
+          ]
+
+in  render
diff --git a/dhall-concourse/render/textOptionalTextMap.dhall b/dhall-concourse/render/textOptionalTextMap.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/textOptionalTextMap.dhall
@@ -0,0 +1,22 @@
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let Types = ../types/package.dhall
+
+let TextJSONPair = { mapKey : Text, mapValue : JSON.Type }
+
+let TextOptionalTextPair = { mapKey : Text, mapValue : Optional Text }
+
+let RenderOptional = ./optionals/package.dhall
+
+let renderPair
+    : TextOptionalTextPair → TextJSONPair
+    =   λ(p : TextOptionalTextPair)
+      → p ⫽ { mapValue = RenderOptional.text p.mapValue }
+
+let renderTextTextMap
+    : List TextOptionalTextPair → Types.JSONObject
+    = Prelude.List.map TextOptionalTextPair TextJSONPair renderPair
+
+in  renderTextTextMap
diff --git a/dhall-concourse/render/textTextMap.dhall b/dhall-concourse/render/textTextMap.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/render/textTextMap.dhall
@@ -0,0 +1,17 @@
+let Prelude = ../lib/prelude.dhall
+
+let JSON = Prelude.JSON
+
+let Types = ../types/package.dhall
+
+let TextJSONPair = { mapKey : Text, mapValue : JSON.Type }
+
+let renderTextTextPair
+    : Types.TextTextPair → TextJSONPair
+    = λ(p : Types.TextTextPair) → p ⫽ { mapValue = JSON.string p.mapValue }
+
+let renderTextTextMap
+    : List Types.TextTextPair → Types.JSONObject
+    = Prelude.List.map Types.TextTextPair TextJSONPair renderTextTextPair
+
+in  renderTextTextMap
diff --git a/dhall-concourse/schemas/TaskInput.dhall b/dhall-concourse/schemas/TaskInput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/schemas/TaskInput.dhall
@@ -0,0 +1,1 @@
+{ Type = ../types/TaskInput.dhall, default = ../defaults/TaskInput.dhall }
diff --git a/dhall-concourse/schemas/TaskOutput.dhall b/dhall-concourse/schemas/TaskOutput.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/schemas/TaskOutput.dhall
@@ -0,0 +1,1 @@
+{ Type = ../types/TaskOutput.dhall, default = ../defaults/TaskOutput.dhall }
diff --git a/dhall-concourse/schemas/package.dhall b/dhall-concourse/schemas/package.dhall
--- a/dhall-concourse/schemas/package.dhall
+++ b/dhall-concourse/schemas/package.dhall
@@ -1,11 +1,37 @@
-{ CustomResourceType = ./CustomResourceType.dhall
-, Resource = ./Resource.dhall
-, Job = ./Job.dhall
-, GetStep = ./GetStep.dhall
-, ImageResource = ./ImageResource.dhall
-, StepHooks = ./StepHooks.dhall
-, TaskStep = ./TaskStep.dhall
-, TaskConfig = ./TaskConfig.dhall
-, TaskRunConfig = ./TaskRunConfig.dhall
-, PutStep = ./PutStep.dhall
+{ CustomResourceType =
+      ./CustomResourceType.dhall sha256:50318d0495c2d26d3d740ad9e4b49d8c10cade82b1c1d0d8d4ee3ede97bdcb58
+    ? ./CustomResourceType.dhall
+, Resource =
+      ./Resource.dhall sha256:debaad639d7d6a81f88ed0b5d6edea14858baf5b0cb6edf5bf26031807b7b580
+    ? ./Resource.dhall
+, Job =
+      ./Job.dhall sha256:7f8ba5824cfab95e6afae4f3fc13173addeb019097e3973b75533b9524d888ea
+    ? ./Job.dhall
+, GetStep =
+      ./GetStep.dhall sha256:4ca1616e51450d41120c1a0ba213f523202371c7ec838fb2d6917dc5d79aad6b
+    ? ./GetStep.dhall
+, ImageResource =
+      ./ImageResource.dhall sha256:810b19dad32928f1bb6d1a4909103d453b6794adfd670e032de57ceb505a5c04
+    ? ./ImageResource.dhall
+, StepHooks =
+      ./StepHooks.dhall sha256:a1bb15b4b98805ba74cef905565b64baa4ce8a42a99bb12f4811816158d35531
+    ? ./StepHooks.dhall
+, TaskStep =
+      ./TaskStep.dhall sha256:a5a00b46ec4eb333ac1451e53b6d710d286d662e771aa83d777dccbb6837f1cd
+    ? ./TaskStep.dhall
+, TaskConfig =
+      ./TaskConfig.dhall sha256:2e8c1033841a4206dbd60b4a7cea9d46f325af98f2dfb39b4a909415beb23d8b
+    ? ./TaskConfig.dhall
+, TaskInput =
+      ./TaskInput.dhall sha256:754c52fb8a8e6bb1b06fc764685643820f843ad4bb9ba79b06e6bbdbcec8b205
+    ? ./TaskInput.dhall
+, TaskOutput =
+      ./TaskOutput.dhall sha256:f973b27504cbd54edcf13306aa4136aa649fdcc201aac9d98b4e49cd125eb699
+    ? ./TaskOutput.dhall
+, TaskRunConfig =
+      ./TaskRunConfig.dhall sha256:1c2a1df1e39fa3c4c401cec7bf642649f2eb623d5999929000226cded037b045
+    ? ./TaskRunConfig.dhall
+, PutStep =
+      ./PutStep.dhall sha256:ed389e969476d75c926265aeb2e2901c30820ba20adbeca925472af95207f925
+    ? ./PutStep.dhall
 }
diff --git a/dhall-concourse/types/BasicStep.dhall b/dhall-concourse/types/BasicStep.dhall
deleted file mode 100644
--- a/dhall-concourse/types/BasicStep.dhall
+++ /dev/null
@@ -1,9 +0,0 @@
-let GetStep = ./GetStep.dhall
-
-let PutStep = ./PutStep.dhall
-
-let TaskStep = ./TaskStep.dhall
-
-let BasicStep = < Get : GetStep | Put : PutStep | Task : TaskStep >
-
-in  BasicStep
diff --git a/dhall-concourse/types/GroupedJob.dhall b/dhall-concourse/types/GroupedJob.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/types/GroupedJob.dhall
@@ -0,0 +1,1 @@
+{ job : ./Job.dhall, groups : List Text }
diff --git a/dhall-concourse/types/package.dhall b/dhall-concourse/types/package.dhall
--- a/dhall-concourse/types/package.dhall
+++ b/dhall-concourse/types/package.dhall
@@ -1,26 +1,76 @@
-{ BasicStep = ./BasicStep.dhall
-, CustomResourceType = ./CustomResourceType.dhall
-, GetStep = ./GetStep.dhall
-, GetVersion = ./GetVersion.dhall
-, ImageResource = ./ImageResource.dhall
-, InParallelConfig = ./InParallelConfig.dhall
-, InParallelStep = ./InParallelStep.dhall
-, Job = ./Job.dhall
-, JobBuildLogRetention = ./JobBuildLogRetention.dhall
-, JSONObject = ./JSONObject.dhall
-, PutStep = ./PutStep.dhall
-, Resource = ./Resource.dhall
-, ResourceType = ./ResourceType.dhall
-, Step = ./Step.dhall
-, StepConstructors = ./StepConstructors.dhall
-, StepHooks = ./StepHooks.dhall
-, TaskConfig = ./TaskConfig.dhall
-, TaskInput = ./TaskInput.dhall
-, TaskOutput = ./TaskOutput.dhall
-, TaskCache = ./TaskCache.dhall
-, TaskContainerLimits = ./TaskContainerLimits.dhall
-, TaskRunConfig = ./TaskRunConfig.dhall
-, TaskStep = ./TaskStep.dhall
-, TaskSpec = ./TaskSpec.dhall
-, TextTextPair = ./TextTextPair.dhall
+{ CustomResourceType =
+      ./CustomResourceType.dhall sha256:38b2589726851995ee62762de23344cd5eefad1541643835456564e1d03cc7d1
+    ? ./CustomResourceType.dhall
+, GetStep =
+      ./GetStep.dhall sha256:83950c8fa16da1151fed433aac40309d4e80b796767cefbea489c1f43b091e7b
+    ? ./GetStep.dhall
+, GetVersion =
+      ./GetVersion.dhall sha256:cd79d8dea7662306078754278d9351a80d49ef71d64f3c37a01b9e1da69c3194
+    ? ./GetVersion.dhall
+, ImageResource =
+      ./ImageResource.dhall sha256:ed9b1a485ed6c8b7ac1cf81c42125b8a6d1e5a12bfabc51c11a64abb327f8f8c
+    ? ./ImageResource.dhall
+, InParallelConfig =
+      ./InParallelConfig.dhall sha256:020d595b34b31147883a2b846ddc53ca8c898f11b4ea0b01f6c5c6e25ff718fb
+    ? ./InParallelConfig.dhall
+, InParallelStep =
+      ./InParallelStep.dhall sha256:92395de51f548251bc0caae8e3aadafba89e4ed6c743ea86c80b4651fa0cd678
+    ? ./InParallelStep.dhall
+, Job =
+      ./Job.dhall sha256:218e1f17265e0bad39f094d24ccc4976bec4621d6255b66a7190deba20f43d6e
+    ? ./Job.dhall
+, JobBuildLogRetention =
+      ./JobBuildLogRetention.dhall sha256:130df5b466a3822ac49495046e68dc58b46b0366ac31f05520e44949010349b6
+    ? ./JobBuildLogRetention.dhall
+, JSONObject =
+      ./JSONObject.dhall sha256:dbfb548d60ec6b2a0fe3e43d1bf94bf4e9675a02656b813a2a2e0f43816f81ba
+    ? ./JSONObject.dhall
+, PutStep =
+      ./PutStep.dhall sha256:df4a0d60791b0cc1e05281c9a8e55c68a3ec470f277078e09dac2067e718fb29
+    ? ./PutStep.dhall
+, Resource =
+      ./Resource.dhall sha256:6ce8e29712c4260b1ec7caa83eb9ef2be71ef5878d3d309ce99ab1004cde2e32
+    ? ./Resource.dhall
+, ResourceType =
+      ./ResourceType.dhall sha256:0daa7a033cfc34de4cc0fd44581f37506bb0bd0b478be10a21aeb05f98610392
+    ? ./ResourceType.dhall
+, Step =
+      ./Step.dhall sha256:3476bbdb663cf72c72ccec357376f39ba8fad8280a3da35af9913dd98e314b94
+    ? ./Step.dhall
+, StepConstructors =
+      ./StepConstructors.dhall sha256:8e638e4e72d245d7ecddec74f5ecf4eebf20c2a6ea9125d8a9e0fb7f1dd15138
+    ? ./StepConstructors.dhall
+, StepHooks =
+      ./StepHooks.dhall sha256:4894aa205d000a93f04b62b2dbceff77cdf3fcda634bcc2fa6d3777e10c2734a
+    ? ./StepHooks.dhall
+, TaskConfig =
+      ./TaskConfig.dhall sha256:f2b2316b1f28912032630a9fed542ce0a00761edfbc33e3ea78a380ddc6b4eba
+    ? ./TaskConfig.dhall
+, TaskInput =
+      ./TaskInput.dhall sha256:1d2673731453da3d08fdf28e121be928603069720470ea8c9d75040e90bcf925
+    ? ./TaskInput.dhall
+, TaskOutput =
+      ./TaskOutput.dhall sha256:953faacc42bdde14b44016c52a79309480f965a006bf9651374ff9c10e51c95a
+    ? ./TaskOutput.dhall
+, TaskCache =
+      ./TaskCache.dhall sha256:3c8a50c4e75f718e4ddff4e627cb41f7876843293a25ffa323384e3bbe98d71b
+    ? ./TaskCache.dhall
+, TaskContainerLimits =
+      ./TaskContainerLimits.dhall sha256:1ec1a02eb15bfe577aa0f23bad7d3e255fc021affb53448ce1438492fa1ae01d
+    ? ./TaskContainerLimits.dhall
+, TaskRunConfig =
+      ./TaskRunConfig.dhall sha256:ac211950cd1c3aa3580ce3e9efec30cb987b31ad7a63b34db47d332a22c43b48
+    ? ./TaskRunConfig.dhall
+, TaskStep =
+      ./TaskStep.dhall sha256:24d9b5afb1f9ec6374432f1d54fdd1ab1532d967bc1f4a32f1cca5efa98aa0eb
+    ? ./TaskStep.dhall
+, TaskSpec =
+      ./TaskSpec.dhall sha256:abf13fba189dda70af996bc2aa530c49cfd6004e8b591a3b52e67dc3ed483725
+    ? ./TaskSpec.dhall
+, TextTextPair =
+      ./TextTextPair.dhall sha256:641845344ce6be001271e0052808b6c8ca10ce5939efefea3560c68c37538454
+    ? ./TextTextPair.dhall
+, GroupedJob =
+      ./GroupedJob.dhall sha256:85073c70ee204dc1be2fc6eae2639315b17f8737a36dc649677d137e4c583678
+    ? ./GroupedJob.dhall
 }
diff --git a/dhall-concourse/utils/catOptionals.dhall b/dhall-concourse/utils/catOptionals.dhall
new file mode 100644
--- /dev/null
+++ b/dhall-concourse/utils/catOptionals.dhall
@@ -0,0 +1,17 @@
+let Prelude = ../lib/prelude.dhall
+
+let catOptionals
+    : ∀(T : Type) → List (Optional T) → List T
+    =   λ(T : Type)
+      → λ(ts : List (Optional T))
+      → List/fold
+          (Optional T)
+          ts
+          (List T)
+          (   λ(t : Optional T)
+            → λ(acc : List T)
+            → Prelude.List.concat T [ Prelude.Optional.toList T t, acc ]
+          )
+          ([] : List T)
+
+in  catOptionals
diff --git a/dhall-concourse/utils/resourcesFromJobs.dhall b/dhall-concourse/utils/resourcesFromJobs.dhall
deleted file mode 100644
--- a/dhall-concourse/utils/resourcesFromJobs.dhall
+++ /dev/null
@@ -1,76 +0,0 @@
-let Prelude = ../lib/prelude.dhall
-
-let Types = ../types/package.dhall
-
-let Resource = Types.Resource
-
-let StepHooks = Types.StepHooks
-
-let catOptionals
-    : ∀(x : Type) → List (Optional x) → List x
-    =   λ(x : Type)
-      → λ(os : List (Optional x))
-      → Prelude.List.fold
-          (Optional x)
-          os
-          (List x)
-          (λ(a : Optional x) → λ(l : List x) → Prelude.Optional.toList x a # l)
-          ([] : List x)
-
-let resourcesFromStepHooks
-    : StepHooks (List Resource) → List Resource
-    =   λ(h : StepHooks (List Resource))
-      → let listOfListOfResources =
-              catOptionals
-                (List Resource)
-                [ h.ensure, h.on_success, h.on_failure, h.on_abort ]
-        
-        in  Prelude.List.concat Resource listOfListOfResources
-
-let resourcesFromGetStep
-    : Types.GetStep → StepHooks (List Resource) → List Resource
-    =   λ(g : Types.GetStep)
-      → λ(h : StepHooks (List Resource))
-      → [ g.resource ] # resourcesFromStepHooks h
-
-let resourcesFromPutStep
-    : Types.PutStep → StepHooks (List Resource) → List Resource
-    =   λ(p : Types.PutStep)
-      → λ(h : StepHooks (List Resource))
-      → [ p.resource ] # resourcesFromStepHooks h
-
-let resourcesFromTaskStep
-    : Types.TaskStep → StepHooks (List Resource) → List Resource
-    =   λ(_ : Types.TaskStep)
-      → λ(h : StepHooks (List Resource))
-      → resourcesFromStepHooks h
-
-let resourcesFromAggregateOrDo
-    : List (List Resource) → StepHooks (List Resource) → List Resource
-    =   λ(rs : List (List Resource))
-      → λ(h : StepHooks (List Resource))
-      → Prelude.List.concat Resource rs # resourcesFromStepHooks h
-
-let resourcesFromTry
-    : List Resource → StepHooks (List Resource) → List Resource
-    =   λ(rs : List Resource)
-      → λ(h : StepHooks (List Resource))
-      → rs # resourcesFromStepHooks h
-
-let resourcesFromStep =
-        λ(s : Types.Step)
-      → s
-          (List Resource)
-          resourcesFromGetStep
-          resourcesFromPutStep
-          resourcesFromTaskStep
-          resourcesFromAggregateOrDo
-          resourcesFromAggregateOrDo
-          resourcesFromTry
-
-let resourcesFromJob =
-        λ(j : Types.Job)
-      → Prelude.List.concatMap Types.Step Resource resourcesFromStep j.plan
-
-in    Prelude.List.concatMap Types.Job Resource resourcesFromJob
-    : List Types.Job → List Resource
diff --git a/dhall-fly.cabal b/dhall-fly.cabal
--- a/dhall-fly.cabal
+++ b/dhall-fly.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 34a102125b5ca6677a5bc0feae708c3b5f1137b172bb562d0b770ad6367e6e12
+-- hash: fb42954e64638266e0514c8cb46537461e36d17e1295f1d60bc2f2b48dbbf23b
 
 name:           dhall-fly
-version:        0.1.0
+version:        0.2.0
 synopsis:       Translate concourse config from Dhall to YAML
 description:    Please see the README on GitHub at <https://github.com/akshaymankar/dhall-fly#readme>
 category:       Concourse, YAML, JSON, Dhall
@@ -31,8 +31,13 @@
     dhall-concourse/defaults/Resource.dhall
     dhall-concourse/defaults/StepHooks.dhall
     dhall-concourse/defaults/TaskConfig.dhall
+    dhall-concourse/defaults/TaskInput.dhall
+    dhall-concourse/defaults/TaskOutput.dhall
     dhall-concourse/defaults/TaskRunConfig.dhall
     dhall-concourse/defaults/TaskStep.dhall
+    dhall-concourse/extractors/package.dhall
+    dhall-concourse/extractors/resourcesFromJobs.dhall
+    dhall-concourse/extractors/resourcesTypesFromResources.dhall
     dhall-concourse/helpers/aggregateStep.dhall
     dhall-concourse/helpers/doStep.dhall
     dhall-concourse/helpers/getStep.dhall
@@ -45,6 +50,44 @@
     dhall-concourse/lib/prelude.dhall
     dhall-concourse/package.dhall
     dhall-concourse/README.md
+    dhall-concourse/render/getStep.dhall
+    dhall-concourse/render/getVersion.dhall
+    dhall-concourse/render/groupedJobs.dhall
+    dhall-concourse/render/helpers/name-resource.dhall
+    dhall-concourse/render/imageResource.dhall
+    dhall-concourse/render/inParallelStep.dhall
+    dhall-concourse/render/job.dhall
+    dhall-concourse/render/jobBuildLogRetention.dhall
+    dhall-concourse/render/jobs.dhall
+    dhall-concourse/render/optionals/bool.dhall
+    dhall-concourse/render/optionals/generic.dhall
+    dhall-concourse/render/optionals/json-object.dhall
+    dhall-concourse/render/optionals/lists/generic.dhall
+    dhall-concourse/render/optionals/lists/package.dhall
+    dhall-concourse/render/optionals/natural.dhall
+    dhall-concourse/render/optionals/package.dhall
+    dhall-concourse/render/optionals/text-optional-json-map.dhall
+    dhall-concourse/render/optionals/text-text-map.dhall
+    dhall-concourse/render/optionals/text.dhall
+    dhall-concourse/render/package.dhall
+    dhall-concourse/render/pipeline.dhall
+    dhall-concourse/render/putStep.dhall
+    dhall-concourse/render/resource.dhall
+    dhall-concourse/render/resources.dhall
+    dhall-concourse/render/resourceType.dhall
+    dhall-concourse/render/resourceTypes.dhall
+    dhall-concourse/render/step.dhall
+    dhall-concourse/render/stepHooks.dhall
+    dhall-concourse/render/taskCache.dhall
+    dhall-concourse/render/taskConfig.dhall
+    dhall-concourse/render/taskContainerLimits.dhall
+    dhall-concourse/render/taskInput.dhall
+    dhall-concourse/render/taskOutput.dhall
+    dhall-concourse/render/taskRunConfig.dhall
+    dhall-concourse/render/taskSpec.dhall
+    dhall-concourse/render/taskStep.dhall
+    dhall-concourse/render/textOptionalTextMap.dhall
+    dhall-concourse/render/textTextMap.dhall
     dhall-concourse/schemas/CustomResourceType.dhall
     dhall-concourse/schemas/GetStep.dhall
     dhall-concourse/schemas/ImageResource.dhall
@@ -54,12 +97,14 @@
     dhall-concourse/schemas/Resource.dhall
     dhall-concourse/schemas/StepHooks.dhall
     dhall-concourse/schemas/TaskConfig.dhall
+    dhall-concourse/schemas/TaskInput.dhall
+    dhall-concourse/schemas/TaskOutput.dhall
     dhall-concourse/schemas/TaskRunConfig.dhall
     dhall-concourse/schemas/TaskStep.dhall
-    dhall-concourse/types/BasicStep.dhall
     dhall-concourse/types/CustomResourceType.dhall
     dhall-concourse/types/GetStep.dhall
     dhall-concourse/types/GetVersion.dhall
+    dhall-concourse/types/GroupedJob.dhall
     dhall-concourse/types/ImageResource.dhall
     dhall-concourse/types/InParallelConfig.dhall
     dhall-concourse/types/InParallelStep.dhall
@@ -82,10 +127,14 @@
     dhall-concourse/types/TaskSpec.dhall
     dhall-concourse/types/TaskStep.dhall
     dhall-concourse/types/TextTextPair.dhall
-    dhall-concourse/utils/resourcesFromJobs.dhall
+    dhall-concourse/utils/catOptionals.dhall
     test/data/custom-resource-type.dhall
+    test/data/get.dhall
     test/data/in-built-resource-type.dhall
+    test/data/put.dhall
     test/data/resource.dhall
+    test/data/task-config.dhall
+    test/data/task-step.dhall
 
 source-repository head
   type: git
@@ -96,6 +145,7 @@
       Fly.Internal.AesonOrphans
       Fly.Internal.DhallOrphans
       Fly.Internal.DhallWithPrefix
+      Fly.Options
       Fly.Types
       Fly.Yaml
   other-modules:
@@ -108,6 +158,7 @@
     , aeson-casing >=0.2.0.0
     , base >=4.7 && <5
     , dhall >=1.27.0 && <1.28
+    , optparse-applicative
     , scientific >=0.3.6.2
     , text >=1.2.3.1
     , transformers >=0.5.6.2
@@ -131,6 +182,7 @@
     , dhall >=1.27.0 && <1.28
     , dhall-fly
     , dhall-json >=1.5.0 && <1.6
+    , optparse-applicative
     , scientific >=0.3.6.2
     , text >=1.2.3.1
     , transformers >=0.5.6.2
@@ -142,7 +194,8 @@
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
-      Fly.InterpretSpec
+      Fly.TypesSpec
+      Fly.YamlSpec
       Paths_dhall_fly
   hs-source-dirs:
       test
@@ -156,6 +209,7 @@
     , dhall >=1.27.0 && <1.28
     , dhall-fly
     , hspec
+    , optparse-applicative
     , scientific >=0.3.6.2
     , text >=1.2.3.1
     , transformers >=0.5.6.2
diff --git a/src/Fly/Options.hs b/src/Fly/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Fly/Options.hs
@@ -0,0 +1,35 @@
+module Fly.Options where
+
+import Options.Applicative
+
+data PipelineType = Jobs | GroupedJobs
+
+data InputType = File FilePath | Stdin
+
+data Opts = Opts { pipelineType :: PipelineType, inputType :: InputType }
+
+inputTypeParser :: Parser InputType
+inputTypeParser = (File
+                    <$> strOption (long "file"
+                                    <> help "Optional, defaults to stdin"))
+                  <|> pure Stdin
+
+parsePipelineType :: String -> Maybe PipelineType
+parsePipelineType "jobs"         = Just Jobs
+parsePipelineType "grouped-jobs" = Just GroupedJobs
+parsePipelineType _              = Nothing
+
+pipelineTypeParser :: Parser PipelineType
+pipelineTypeParser = option
+                     (maybeReader parsePipelineType)
+                     (long "pipeline-type"
+                      <> help "'jobs' or 'grouped-jobs'"
+                      <> value Jobs)
+
+optsParser :: Parser Opts
+optsParser = Opts <$> pipelineTypeParser <*> inputTypeParser
+
+optsParserWithHelp :: ParserInfo Opts
+optsParserWithHelp = info (optsParser <**> helper)
+                     (fullDesc
+                      <> progDesc "Translate dhall pipelines to yaml")
diff --git a/src/Fly/Types.hs b/src/Fly/Types.hs
--- a/src/Fly/Types.hs
+++ b/src/Fly/Types.hs
@@ -167,6 +167,7 @@
                        , putResource  :: Resource
                        , putParams    :: Maybe (HashMap Text Value)
                        , putGetParams :: Maybe (HashMap Text Value)
+                       , putInputs    :: Maybe [Text]
                        , putTags      :: Maybe [Text]
                        , putTimeout   :: Maybe Text
                        , putAttempts  :: Maybe Natural
@@ -179,6 +180,10 @@
                               , "resource"   .= toJSON (resourceName putResource <$ putPut)
                               , "params"     .= putParams
                               , "get_params" .= putGetParams
+                              , "tags"       .= putTags
+                              , "inputs"     .= putInputs
+                              , "timeout"    .= putTimeout
+                              , "attempts"   .= putAttempts
                               ]
 
 data TaskStep = TaskStep { taskTask          :: Text
@@ -188,6 +193,10 @@
                          , taskImage         :: Maybe Text
                          , taskInputMapping  :: Maybe (HashMap Text Text)
                          , taskOutputMapping :: Maybe (HashMap Text Text)
+                         , taskVars          :: Maybe (HashMap Text Value)
+                         , taskTags          :: Maybe [Text]
+                         , taskTimeout       :: Maybe Text
+                         , taskAttempts      :: Maybe Natural
                          }
               deriving (Show, Generic, Eq)
               deriving FromDhall via FromDhallWithPrefix TaskStep
@@ -301,6 +310,10 @@
                }
          deriving (Show, Generic, Eq)
          deriving FromDhall via FromDhallWithPrefix Job
+
+data GroupedJob = GroupedJob { gjJob :: Job, gjGroups :: [Text]}
+                deriving (Show, Generic, Eq)
+                deriving FromDhall via FromDhallWithPrefix GroupedJob
 
 $(deriveToJSON (aesonPrefix snakeCase) ''CustomResourceType)
 $(deriveToJSON (aesonPrefix snakeCase){sumEncoding = UntaggedValue} ''ResourceType)
diff --git a/src/Fly/Yaml.hs b/src/Fly/Yaml.hs
--- a/src/Fly/Yaml.hs
+++ b/src/Fly/Yaml.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards   #-}
 module Fly.Yaml where
@@ -5,8 +6,38 @@
 import Data.Aeson
 import Data.List  (nub)
 import Data.Maybe (catMaybes)
+import Data.Text  (Text)
 import Fly.Types
 
+import qualified Data.HashMap.Strict as HM
+
+jobsToValue :: [Job] -> Value
+jobsToValue = Object . jobsToMap
+
+groupedJobsToValue :: [GroupedJob] -> Value
+groupedJobsToValue groupedJobs =
+  let mapWithoutGroups = jobsToMap $ map gjJob groupedJobs
+      groupsAsMap = groupedJobsToMap groupedJobs
+      mkGroupValue group jobs acc = object [ "name" .= group, "jobs" .= jobs ] : acc
+      groupsAsValue = toJSON $ HM.foldrWithKey mkGroupValue [] groupsAsMap
+      groupsMap = HM.singleton "groups" groupsAsValue
+  in Object $ HM.union mapWithoutGroups groupsMap
+
+jobsToMap :: [Job] -> HM.HashMap Text Value
+jobsToMap jobs =
+  let resources = nub $ concatMap getResourcesFromJob jobs
+      resourceTypes = nub $ customResourceTypes $ map Fly.Types.resourceType resources
+  in HM.fromList [ ("resource_types", toJSON resourceTypes)
+                 , ("resources", toJSON resources)
+                 , ("jobs", toJSON jobs)
+                 ]
+
+groupedJobsToMap :: [GroupedJob] -> HM.HashMap Text [Text]
+groupedJobsToMap gjs =
+  let toGroupJobsPair (GroupedJob j groups) = map (, [jobName j]) groups
+      groupJobsPairs = concatMap toGroupJobsPair gjs
+  in HM.fromListWith (++) groupJobsPairs
+
 customResourceTypes :: [ResourceType] -> [ResourceType]
 customResourceTypes [] = []
 customResourceTypes (ResourceTypeInBuilt _ : rts) =  customResourceTypes rts
@@ -37,12 +68,3 @@
 getResourcesFromJob Job{..} =
   getResourcesFromSteps
   $ jobPlan ++ catMaybes [ jobOnSuccess, jobOnFailure, jobOnAbort, jobEnsure ]
-
-dhallToYaml :: [Job] -> Value
-dhallToYaml jobs =
-  let resources = nub $ concatMap getResourcesFromJob jobs
-      resourceTypes = nub $ customResourceTypes $ map Fly.Types.resourceType resources
-  in object [ "resource_types" .= resourceTypes
-            , "resources" .= resources
-            , "jobs" .= jobs
-            ]
diff --git a/test/Fly/InterpretSpec.hs b/test/Fly/InterpretSpec.hs
deleted file mode 100644
--- a/test/Fly/InterpretSpec.hs
+++ /dev/null
@@ -1,77 +0,0 @@
-{-# LANGUAGE ExtendedDefaultRules #-}
-{-# LANGUAGE OverloadedStrings    #-}
-{-# LANGUAGE RecordWildCards      #-}
-module Fly.InterpretSpec where
-
-import Test.Hspec
-
-import Data.Aeson
-import Data.HashMap.Strict
-import Dhall
-import Fly.Types
-
-spec :: Spec
-spec = do
-  describe "Fly.Interpret" $ do
-    describe "Interpret ResourceType" $ do
-      it "should interpret in built resource type" $ do
-        resourceType <- input auto "./test/data/in-built-resource-type.dhall"
-        resourceType `shouldBe` ResourceTypeInBuilt "git"
-
-      it "should interpret custom resource type" $ do
-        resourceType <- input auto "./test/data/custom-resource-type.dhall"
-        resourceType `shouldBe` boshDeploymentResourceType
-
-    describe "Interpret Resource" $ do
-      it "should interpret a git resource" $ do
-        resource <- input auto "./test/data/resource.dhall"
-        resource `shouldBe` gitKuboCI
-
-    describe "Interpret TaskConfig" $ do
-      it "should interpret a File task" $ do
-        task <- input auto "./dhall-concourse/defaults/TaskConfig.dhall"
-        task `shouldBe` defaultTaskConfig
-
-boshDeploymentResourceType = ResourceTypeCustom (CustomResourceType{..}) where
-  crtName = "bosh-deployment"
-  crtType = "docker-image"
-  crtSource =  pure $ fromList [ ("repository", String "cloudfoundry/bosh-deployment-resource")
-                               , ("tag", String "latest")
-                               ]
-  crtPrivileged = Nothing
-  crtParams = Nothing
-  crtCheckEvery = Nothing
-  crtTags = Nothing
-  crtUniqueVersionHistory = Nothing
-
-gitKuboCI = Resource{..} where
-  resourceName =  "git-kubo-ci"
-  resourceType = ResourceTypeInBuilt "git"
-  resourceCheckEvery = Nothing
-  resourceParams = Nothing
-  resourceSource = Just $ fromList [ "privateKey" .= "((git-ssh-key.private_key))"
-                                   , "uri" .= "git@github.com:cloudfoundry-incubator/kubo-ci"
-                                   , "branch" .= "master"
-                                   ]
-  resourceTags = Nothing
-  resourceVersion = Nothing
-  resourceWebhookToken = Nothing
-  resourceIcon = Nothing
-  resourcePublic = Nothing
-
-defaultTaskRunConfig = TaskRunConfig{..} where
-  trcPath = "CHANGEME"
-  trcArgs = Nothing
-  trcDir = Nothing
-  trcUser = Nothing
-
-defaultTaskConfig = TaskConfig{..} where
-  tcPlatform = "linux"
-  tcRun = defaultTaskRunConfig
-  tcCaches = Nothing
-  tcImageResource = Nothing
-  tcInputs = Nothing
-  tcOutputs = Nothing
-  tcParams = Nothing
-  tcRootfsUri = Nothing
-  tcContainerLimits = Nothing
diff --git a/test/Fly/TypesSpec.hs b/test/Fly/TypesSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Fly/TypesSpec.hs
@@ -0,0 +1,167 @@
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE OverloadedStrings    #-}
+{-# LANGUAGE RecordWildCards      #-}
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+module Fly.TypesSpec where
+
+import Test.Hspec
+
+import Data.Aeson
+import Data.HashMap.Strict
+import Dhall
+import Fly.Types
+
+{-# ANN module "HLint: ignore Redundant do" #-}
+
+spec :: Spec
+spec = do
+  describe "Fly.Types" $ do
+    describe "Interpret ResourceType" $ do
+      it "should interpret in built resource type" $ do
+        resourceType <- input auto "./test/data/in-built-resource-type.dhall"
+        resourceType `shouldBe` ResourceTypeInBuilt "git"
+
+      it "should interpret custom resource type" $ do
+        resourceType <- input auto "./test/data/custom-resource-type.dhall"
+        resourceType `shouldBe` boshDeploymentResourceType
+
+    describe "Interpret Resource" $ do
+      it "should interpret a git resource" $ do
+        resource <- input auto "./test/data/resource.dhall"
+        resource `shouldBe` gitKuboCI
+
+    describe "Interpret TaskConfig" $ do
+      it "should interpret a File task" $ do
+        task <- input auto "./test/data/task-config.dhall"
+        task `shouldBe` defaultTaskConfig
+
+    describe "GetStep" $ do
+      let testGet = GetStep{..} where
+            getGet = Nothing
+            getResource = gitKuboCI
+            getParams = Nothing
+            getVersion = Nothing
+            getPassed = Nothing
+            getTrigger = Nothing
+            getTags = Nothing
+            getTimeout = Nothing
+            getAttempts = Nothing
+      describe "Interpret" $ do
+        it "should interpret a get step" $ do
+          get <- input auto "./test/data/get.dhall"
+          get `shouldBe` testGet
+      describe "ToJSON" $ do
+        it "should translate a get step" $ do
+          toJSON testGet `shouldBe` object [ "passed"   .= Null
+                                           , "get"      .= "git-kubo-ci"
+                                           , "attempts" .= Null
+                                           , "params"   .= Null
+                                           , "version"  .= Null
+                                           , "resource" .= Null
+                                           , "trigger"  .= Null
+                                           , "timeout"  .= Null
+                                           , "tags"     .= Null ]
+
+    describe "PutStep" $ do
+      let testPut = PutStep{..} where
+            putPut = Nothing
+            putResource = gitKuboCI
+            putParams = Nothing
+            putGetParams = Nothing
+            putInputs = Nothing
+            putTags = Nothing
+            putTimeout = Nothing
+            putAttempts = Nothing
+      describe "Interpret" $ do
+        it "should interpret a put step" $ do
+          put <- input auto "./test/data/put.dhall"
+          put `shouldBe` testPut
+      describe "ToJSON" $ do
+        it "should translate a put step" $ do
+          toJSON testPut `shouldBe` object [ "put"        .= "git-kubo-ci"
+                                           , "attempts"   .= Null
+                                           , "params"     .= Null
+                                           , "get_params" .= Null
+                                           , "resource"   .= Null
+                                           , "timeout"    .= Null
+                                           , "inputs"     .= Null
+                                           , "tags"       .= Null ]
+
+    describe "TaskStep" $ do
+      let testTask = TaskStep{..} where
+            taskTask = "test-task"
+            taskConfig = TaskSpecFile "some-file"
+            taskPrivileged = Nothing
+            taskParams = Nothing
+            taskImage = Nothing
+            taskInputMapping = Nothing
+            taskOutputMapping = Nothing
+            taskVars = Nothing
+            taskTags = Nothing
+            taskTimeout = Nothing
+            taskAttempts = Nothing
+
+      describe "Interpret" $ do
+        it "should interpret a task step" $ do
+          task <- input auto "./test/data/task-step.dhall"
+          task `shouldBe` testTask
+      describe "ToJSON" $ do
+        it "should translate a task step" $ do
+          toJSON testTask `shouldBe` object [ "task"          .= "test-task"
+                                            , "file"          .= "some-file"
+                                            , "params"        .= Null
+                                            , "privileged"    .= Null
+                                            , "image"         .= Null
+                                            , "input_mapping" .= Null
+                                            , "output_mapping".= Null
+                                            , "attempts"      .= Null
+                                            , "timeout"       .= Null
+                                            , "vars"          .= Null
+                                            , "tags"          .= Null ]
+
+boshDeploymentResourceType :: ResourceType
+boshDeploymentResourceType = ResourceTypeCustom (CustomResourceType{..}) where
+  crtName = "bosh-deployment"
+  crtType = "docker-image"
+  crtSource =  pure $ fromList [ ("repository", String "cloudfoundry/bosh-deployment-resource")
+                               , ("tag", String "latest")
+                               ]
+  crtPrivileged = Nothing
+  crtParams = Nothing
+  crtCheckEvery = Nothing
+  crtTags = Nothing
+  crtUniqueVersionHistory = Nothing
+
+gitKuboCI :: Resource
+gitKuboCI = Resource{..} where
+  resourceName =  "git-kubo-ci"
+  resourceType = ResourceTypeInBuilt "git"
+  resourceCheckEvery = Nothing
+  resourceSource = Just $ fromList [ "privateKey" .= "((git-ssh-key.private_key))"
+                                   , "uri" .= "git@github.com:cloudfoundry-incubator/kubo-ci"
+                                   , "branch" .= "master"
+                                   ]
+  resourceTags = Nothing
+  resourceVersion = Nothing
+  resourceWebhookToken = Nothing
+  resourceIcon = Nothing
+  resourcePublic = Nothing
+
+defaultTaskRunConfig :: TaskRunConfig
+defaultTaskRunConfig = TaskRunConfig{..} where
+  trcPath = "true"
+  trcArgs = Nothing
+  trcDir = Nothing
+  trcUser = Nothing
+
+defaultTaskConfig :: TaskConfig
+defaultTaskConfig = TaskConfig{..} where
+  tcPlatform = "linux"
+  tcRun = defaultTaskRunConfig
+  tcCaches = Nothing
+  tcImageResource = Nothing
+  tcInputs = Nothing
+  tcOutputs = Nothing
+  tcParams = Nothing
+  tcRootfsUri = Nothing
+  tcContainerLimits = Nothing
diff --git a/test/Fly/YamlSpec.hs b/test/Fly/YamlSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Fly/YamlSpec.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE OverloadedStrings    #-}
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+module Fly.YamlSpec where
+
+import Test.Hspec
+
+import Data.Aeson
+import Fly.Types
+import Fly.Yaml
+
+{-# ANN module "HLint: ignore Redundant do" #-}
+
+spec :: Spec
+spec = do
+  describe "Fly.Yaml" $ do
+    let gitResource = Resource { resourceName ="some-git"
+                               , resourceType = ResourceTypeInBuilt "git"
+                               , resourcePublic = Nothing
+                               , resourceSource = Nothing
+                               , resourceVersion = Nothing
+                               , resourceIcon = Nothing
+                               , resourceTags = Nothing
+                               , resourceCheckEvery = Nothing
+                               , resourceWebhookToken = Nothing
+                               }
+
+        slackResourceType = CustomResourceType { crtName = "slack"
+                                               , crtType = "docker-image"
+                                               , crtSource = Nothing
+                                               , crtPrivileged = Nothing
+                                               , crtParams = Nothing
+                                               , crtCheckEvery = Nothing
+                                               , crtTags = Nothing
+                                               , crtUniqueVersionHistory = Nothing
+                                               }
+        stepSlack = Resource { resourceName ="step-slack"
+                             , resourceType = ResourceTypeCustom slackResourceType
+                             , resourcePublic = Nothing
+                             , resourceSource = Nothing
+                             , resourceVersion = Nothing
+                             , resourceIcon = Nothing
+                             , resourceTags = Nothing
+                             , resourceCheckEvery = Nothing
+                             , resourceWebhookToken = Nothing
+                             }
+        jobSlack = stepSlack { resourceName ="job-slack" }
+        getGit = GetStep { getGet = Nothing
+                         , getResource = gitResource
+                         , getTags = Nothing
+                         , getPassed = Nothing
+                         , getTrigger = Nothing
+                         , getTimeout = Nothing
+                         , getAttempts = Nothing
+                         , getParams = Nothing
+                         , getVersion = Nothing
+                         }
+        putStepSlack = PutStep { putPut = Nothing
+                               , putResource = stepSlack
+                               , putTags = Nothing
+                               , putTimeout = Nothing
+                               , putAttempts = Nothing
+                               , putParams = Nothing
+                               , putGetParams = Nothing
+                               , putInputs = Nothing
+                               }
+        putJobSlack = putStepSlack { putResource = jobSlack }
+        emptyStepHooks = StepHooks Nothing Nothing Nothing Nothing
+        hooks = emptyStepHooks{ hookOnFailure = Just (Put putStepSlack emptyStepHooks) }
+        testJob = Job{ jobName = "test-job"
+                     , jobPlan = [Get getGit hooks]
+                     , jobOldName = Nothing
+                     , jobSerial = Nothing
+                     , jobBuildLogRetention = Nothing
+                     , jobBuildLogsToRetain = Nothing
+                     , jobSerialGroups = Nothing
+                     , jobMaxInFlight = Nothing
+                     , jobPublic = Nothing
+                     , jobDisableManualTrigger = Nothing
+                     , jobInterruptible = Nothing
+                     , jobOnSuccess = Just (Put putJobSlack emptyStepHooks)
+                     , jobOnFailure = Nothing
+                     , jobOnAbort = Nothing
+                     , jobEnsure = Nothing
+                     }
+
+    -- TODO: Make unordered assertions
+    describe "jobsToValue (without groups)" $ do
+      it "should translate jobs to Value as concourse expects it" $ do
+        jobsToValue [testJob]
+          `shouldBe` object [ "resources"      .= [gitResource, stepSlack, jobSlack]
+                            , "resource_types" .= [slackResourceType]
+                            , "jobs"           .= [testJob]
+                            ]
+
+    describe "groupedJobsToValue" $ do
+      it "should translate grouped jobs to list of groups as concourse expects" $ do
+        let groupedJob = GroupedJob testJob ["group1", "group2"]
+        groupedJobsToValue [groupedJob]
+          `shouldBe` object [ "resources"      .= [gitResource, stepSlack, jobSlack]
+                            , "resource_types" .= [slackResourceType]
+                            , "jobs"           .= [testJob]
+                            , "groups"         .= toJSON [ object [ "name" .= "group2"
+                                                                  , "jobs" .= [ jobName testJob ] ]
+                                                         , object [ "name" .= "group1"
+                                                                  , "jobs" .= [ jobName testJob ] ]
+                                                         ]
+                            ]
diff --git a/test/data/get.dhall b/test/data/get.dhall
new file mode 100644
--- /dev/null
+++ b/test/data/get.dhall
@@ -0,0 +1,3 @@
+let Concourse = ../../dhall-concourse/package.dhall
+
+in Concourse.schemas.GetStep::{ resource = ./resource.dhall }
diff --git a/test/data/put.dhall b/test/data/put.dhall
new file mode 100644
--- /dev/null
+++ b/test/data/put.dhall
@@ -0,0 +1,3 @@
+let Concourse = ../../dhall-concourse/package.dhall
+
+in Concourse.schemas.PutStep::{ resource = ./resource.dhall }
diff --git a/test/data/task-config.dhall b/test/data/task-config.dhall
new file mode 100644
--- /dev/null
+++ b/test/data/task-config.dhall
@@ -0,0 +1,5 @@
+let Concourse = ../../dhall-concourse/package.dhall
+
+in  Concourse.schemas.TaskConfig::{
+    , run = Concourse.schemas.TaskRunConfig::{ path = "true" }
+    }
diff --git a/test/data/task-step.dhall b/test/data/task-step.dhall
new file mode 100644
--- /dev/null
+++ b/test/data/task-step.dhall
@@ -0,0 +1,6 @@
+let Concourse = ../../dhall-concourse/package.dhall
+
+in  Concourse.schemas.TaskStep::{
+    , task = "test-task"
+    , config = Concourse.Types.TaskSpec.File "some-file"
+    }
